Compare commits

...
Author SHA1 Message Date
Keith Zantow dd09e0362e chore: update quill to the latest version (#2065)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-25 20:45:04 +00:00
Keith Zantow 4ae94c37eb fix: duplicate entries in cyclonedx dependency list (#2063)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-25 12:19:01 -04:00
William Murphy d08e2be768 Fix panic in pom parsing (#2064)
A recent update to gopom changed many fields to be omitted when empty,
resulting in a number of nil pointers inside the nested structure of the
pom that previously didn't exist. Defend against these in the search for
the property value.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-25 12:04:57 -04:00
William Murphy faa902209e Fix: don't validate pom declared group (#2054)
Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-24 13:28:40 -04:00
William Murphy 9a2a988e7f chore: trace log pom property reflect usage (#2059)
This reflect code occasionally throws an obscure panic, but not enough
information is logged before the panic to know why it panicked. Log
enough to tell what property and package are being analyzed when the
panic occurs.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-24 11:28:44 -04:00
Chris SelzoandJoseph Palermo 5ceef48949 fix: do not double-prefix symlink paths that already contain volume names (#2051)
Signed-off-by: Joseph Palermo <jpalermo@vmware.com>
Signed-off-by: Chris Selzo <cselzo@vmware.com>
Co-authored-by: Joseph Palermo <jpalermo@vmware.com>
2023-08-24 10:45:22 -04:00
witchcraze 1848aa22cf feat: add bash classifier (#2055)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2023-08-24 10:13:59 -04:00
Sirish Bathina 62f689824c Detect golang boring crypto and fipsonly modules (#2021)
* Extending build info to include crypto settings

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* Use kasten fork for goversion module

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* go mod tidy

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* change key to GoCryptoSettings and lint fix

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* Addressing feedback

Signed-off-by: Sirish Bathina <sirish@kasten.io>

---------

Signed-off-by: Sirish Bathina <sirish@kasten.io>
2023-08-24 09:49:59 -04:00
Stefan ProfanterandAlex Goodman 07ac640ac5 fix: properly parse conan ref and include user and channel (#2034)
* fix: properly parse conan ref and include user and channel

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>

* unexport the conanRef type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-23 17:51:07 +00:00
dependabot[bot] a2b389523d chore(deps): bump github.com/charmbracelet/lipgloss from 0.7.1 to 0.8.0 (#2053)
Bumps [github.com/charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) from 0.7.1 to 0.8.0.
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v0.7.1...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/lipgloss
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 13:41:17 -04:00
Alex Goodman 17d4203bbb Enable reading non-utf-8 encodings for java pom.xml files (#2047)
* fix reading non utf8 encodings

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* in cases where we cant tell the encoding use the UTF8 replacement char

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* decompose the xml decoding func to get a valid utf8 reader first and test unknown encoding

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-23 10:06:34 -04:00
Christopher Angelo PhillipsandKeith Zantow ee121cff21 feat: 1944 - update purl generation to use a consistent groupID (#2033)
Separate the logic for CPE and PURL generation. 

PURL generation needs a single answer for groupID based on a priority of discovering the field. 
CPE generation still uses multiple potential groupID to populate the candidate cpe.

Improve GroupID detection. 

Currently syft does not use any hierarchy for GroupID detection and treats all sources as equal. 
It treats fields from the manifest file with priority. This change adds a hierarchy to the fields and returns a single answer based on that hierarchy.
---------
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-08-22 10:47:07 -04:00
dependabot[bot] cf37b17869 chore(deps): bump github.com/google/uuid from 1.3.0 to 1.3.1 (#2049)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 10:42:19 -04:00
anchore-actions-token-generator[bot]andspiffcs ee656fe088 chore(deps): update bootstrap tools to latest versions (#2048)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-08-22 08:48:42 -04:00
dependabot[bot] f58425a305 chore(deps): bump github.com/jinzhu/copier from 0.3.5 to 0.4.0 (#2045)
Bumps [github.com/jinzhu/copier](https://github.com/jinzhu/copier) from 0.3.5 to 0.4.0.
- [Commits](https://github.com/jinzhu/copier/compare/v0.3.5...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/jinzhu/copier
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 10:37:11 -04:00
anchore-actions-token-generator[bot]andwagoodman 01c7709e0d chore(deps): update CPE dictionary index (#2043)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-08-21 09:33:41 -04:00
Alex Goodman cb0214ec1d fill out new version notice (#2042)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-18 16:03:11 -04:00
38 changed files with 2965 additions and 346 deletions
@@ -29,6 +29,7 @@ jobs:
GOSIMPORTS_LATEST_VERSION=$(go list -m -json github.com/rinchsan/gosimports@latest 2>/dev/null | jq -r '.Version') GOSIMPORTS_LATEST_VERSION=$(go list -m -json github.com/rinchsan/gosimports@latest 2>/dev/null | jq -r '.Version')
YAJSV_LATEST_VERSION=$(go list -m -json github.com/neilpa/yajsv@latest 2>/dev/null | jq -r '.Version') YAJSV_LATEST_VERSION=$(go list -m -json github.com/neilpa/yajsv@latest 2>/dev/null | jq -r '.Version')
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign/v2@latest 2>/dev/null | jq -r '.Version') COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign/v2@latest 2>/dev/null | jq -r '.Version')
QUILL_LATEST_VERSION=$(go list -m -json github.com/anchore/quill@latest 2>/dev/null | jq -r '.Version')
GLOW_LATEST_VERSION=$(go list -m -json github.com/charmbracelet/glow@latest 2>/dev/null | jq -r '.Version') GLOW_LATEST_VERSION=$(go list -m -json github.com/charmbracelet/glow@latest 2>/dev/null | jq -r '.Version')
# update version variables in the Makefile # update version variables in the Makefile
@@ -39,6 +40,7 @@ jobs:
sed -r -i -e 's/^(GOSIMPORTS_VERSION := ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile sed -r -i -e 's/^(GOSIMPORTS_VERSION := ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(YAJSV_VERSION := ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile sed -r -i -e 's/^(YAJSV_VERSION := ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(COSIGN_VERSION := ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile sed -r -i -e 's/^(COSIGN_VERSION := ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(QUILL_VERSION := ).*/\1'${QUILL_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(GLOW_VERSION := ).*/\1'${GLOW_LATEST_VERSION}'/' Makefile sed -r -i -e 's/^(GLOW_VERSION := ).*/\1'${GLOW_LATEST_VERSION}'/' Makefile
# export the versions for use with create-pull-request # export the versions for use with create-pull-request
@@ -49,6 +51,7 @@ jobs:
echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "YAJSV=$YAJSV_LATEST_VERSION" >> $GITHUB_OUTPUT echo "YAJSV=$YAJSV_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "COSIGN=$COSIGN_LATEST_VERSION" >> $GITHUB_OUTPUT echo "COSIGN=$COSIGN_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "QUILL=$QUILL_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "GLOW=GLOW_LATEST_VERSION" >> $GITHUB_OUTPUT echo "GLOW=GLOW_LATEST_VERSION" >> $GITHUB_OUTPUT
id: latest-versions id: latest-versions
@@ -74,6 +77,7 @@ jobs:
- [gosimports ${{ steps.latest-versions.outputs.GOSIMPORTS }}](https://github.com/rinchsan/gosimports/releases/tag/${{ steps.latest-versions.outputs.GOSIMPORTS }}) - [gosimports ${{ steps.latest-versions.outputs.GOSIMPORTS }}](https://github.com/rinchsan/gosimports/releases/tag/${{ steps.latest-versions.outputs.GOSIMPORTS }})
- [yajsv ${{ steps.latest-versions.outputs.YAJSV }}](https://github.com/neilpa/yajsv/releases/tag/${{ steps.latest-versions.outputs.YAJSV }}) - [yajsv ${{ steps.latest-versions.outputs.YAJSV }}](https://github.com/neilpa/yajsv/releases/tag/${{ steps.latest-versions.outputs.YAJSV }})
- [cosign ${{ steps.latest-versions.outputs.COSIGN }}](https://github.com/sigstore/cosign/releases/tag/${{ steps.latest-versions.outputs.COSIGN }}) - [cosign ${{ steps.latest-versions.outputs.COSIGN }}](https://github.com/sigstore/cosign/releases/tag/${{ steps.latest-versions.outputs.COSIGN }})
- [quill ${{ steps.latest-versions.outputs.QUILL }}](https://github.com/anchore/quill/releases/tag/${{ steps.latest-versions.outputs.QUILL }})
- [glow ${{ steps.latest-versions.outputs.GLOW }}](https://github.com/charmbracelet/glow/releases/tag/${{ steps.latest-versions.outputs.GLOW }}) - [glow ${{ steps.latest-versions.outputs.GLOW }}](https://github.com/charmbracelet/glow/releases/tag/${{ steps.latest-versions.outputs.GLOW }})
This is an auto-generated pull request to update all of the bootstrap tools to the latest versions. This is an auto-generated pull request to update all of the bootstrap tools to the latest versions.
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
+2 -2
View File
@@ -10,14 +10,14 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
GLOW_CMD = $(TEMP_DIR)/glow GLOW_CMD = $(TEMP_DIR)/glow
# Tool versions ################################# # Tool versions #################################
GOLANGCILINT_VERSION := v1.54.1 GOLANGCILINT_VERSION := v1.54.2
GOSIMPORTS_VERSION := v0.3.8 GOSIMPORTS_VERSION := v0.3.8
BOUNCER_VERSION := v0.4.0 BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION := v0.7.0 CHRONICLE_VERSION := v0.7.0
GORELEASER_VERSION := v1.20.0 GORELEASER_VERSION := v1.20.0
YAJSV_VERSION := v1.4.1 YAJSV_VERSION := v1.4.1
COSIGN_VERSION := v2.1.1 COSIGN_VERSION := v2.1.1
QUILL_VERSION := v0.2.0 QUILL_VERSION := v0.4.1
GLOW_VERSION := v1.5.1 GLOW_VERSION := v1.5.1
# Formatting variables ################################# # Formatting variables #################################
@@ -27,12 +27,7 @@ report 1!!>
<notification 2> <notification 2>
<notification 3> <notification 3>
A newer version of syft is available for download: v0.33.0 (installed version is [not provided])
<my app can be updated!!
...to this version>
--- ---
+4 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/parsers" "github.com/anchore/syft/syft/event/parsers"
) )
@@ -118,12 +119,14 @@ func writeAppUpdate(writer io.Writer, events ...partybus.Event) error {
style := lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Italic(true) style := lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Italic(true)
for _, e := range events { for _, e := range events {
notice, err := parsers.ParseCLIAppUpdateAvailable(e) newVersion, err := parsers.ParseCLIAppUpdateAvailable(e)
if err != nil { if err != nil {
log.WithFields("error", err).Warn("failed to parse app update notification") log.WithFields("error", err).Warn("failed to parse app update notification")
continue continue
} }
notice := fmt.Sprintf("A newer version of syft is available for download: %s (installed version is %s)", newVersion, version.FromBuild().Version)
if _, err := fmt.Fprintln(writer, style.Render(notice)); err != nil { if _, err := fmt.Fprintln(writer, style.Render(notice)); err != nil {
// don't let this be fatal // don't let this be fatal
log.WithFields("error", err).Warn("failed to write app update notification") log.WithFields("error", err).Warn("failed to write app update notification")
@@ -35,7 +35,7 @@ func Test_postUIEventWriter_write(t *testing.T) {
}, },
{ {
Type: event.CLIAppUpdateAvailable, Type: event.CLIAppUpdateAvailable,
Value: "\n\n<my app can be updated!!\n...to this version>\n\n", Value: "v0.33.0",
}, },
{ {
Type: event.CLINotification, Type: event.CLINotification,
+6 -4
View File
@@ -15,10 +15,10 @@ require (
github.com/facebookincubator/nvdtools v0.1.5 github.com/facebookincubator/nvdtools v0.1.5
github.com/go-test/deep v1.1.0 github.com/go-test/deep v1.1.0
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.1
github.com/gookit/color v1.5.4 github.com/gookit/color v1.5.4
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/jinzhu/copier v0.3.5 github.com/jinzhu/copier v0.4.0
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5
@@ -57,7 +57,7 @@ require (
github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.7.1 github.com/charmbracelet/lipgloss v0.8.0
github.com/dave/jennifer v1.7.0 github.com/dave/jennifer v1.7.0
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/docker/distribution v2.8.2+incompatible github.com/docker/distribution v2.8.2+incompatible
@@ -69,9 +69,11 @@ require (
github.com/google/go-containerregistry v0.16.1 github.com/google/go-containerregistry v0.16.1
github.com/google/licensecheck v0.3.1 github.com/google/licensecheck v0.3.1
github.com/invopop/jsonschema v0.7.0 github.com/invopop/jsonschema v0.7.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/saferwall/pe v1.4.4 github.com/saferwall/pe v1.4.4
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
github.com/sassoftware/go-rpmutils v0.2.0 github.com/sassoftware/go-rpmutils v0.2.0
github.com/vbatts/go-mtree v0.5.3 github.com/vbatts/go-mtree v0.5.3
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
@@ -145,7 +147,7 @@ require (
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect github.com/nwaples/rardecode v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pborman/indent v1.2.1 // indirect github.com/pborman/indent v1.2.1 // indirect
+12 -8
View File
@@ -147,8 +147,8 @@ github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -355,8 +355,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 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/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.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@@ -419,8 +419,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/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.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -428,6 +428,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y=
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@@ -529,8 +531,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
@@ -596,6 +598,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/saferwall/pe v1.4.4 h1:Ml++7/2/Z1iKwV4zCsd1nIqTEAdUQKAetwbbcCarhOg= github.com/saferwall/pe v1.4.4 h1:Ml++7/2/Z1iKwV4zCsd1nIqTEAdUQKAetwbbcCarhOg=
github.com/saferwall/pe v1.4.4/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= github.com/saferwall/pe v1.4.4/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE=
github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI= github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI=
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=
+1 -1
View File
@@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "10.0.0" JSONSchemaVersion = "10.0.1"
) )
File diff suppressed because it is too large Load Diff
+26 -8
View File
@@ -5,6 +5,7 @@ import (
"github.com/CycloneDX/cyclonedx-go" "github.com/CycloneDX/cyclonedx-go"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/exp/slices"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
@@ -139,7 +140,7 @@ func isExpressiblePackageRelationship(ty artifact.RelationshipType) bool {
} }
func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependency { func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependency {
result := make([]cyclonedx.Dependency, 0) dependencies := map[string]*cyclonedx.Dependency{}
for _, r := range relationships { for _, r := range relationships {
exists := isExpressiblePackageRelationship(r.Type) exists := isExpressiblePackageRelationship(r.Type)
if !exists { if !exists {
@@ -160,15 +161,32 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
continue continue
} }
// ind dep toRef := deriveBomRef(toPkg)
dep := dependencies[toRef]
if dep == nil {
dep = &cyclonedx.Dependency{
Ref: toRef,
Dependencies: &[]string{},
}
dependencies[toRef] = dep
}
innerDeps := []string{} fromRef := deriveBomRef(fromPkg)
innerDeps = append(innerDeps, deriveBomRef(fromPkg)) if !slices.Contains(*dep.Dependencies, fromRef) {
result = append(result, cyclonedx.Dependency{ *dep.Dependencies = append(*dep.Dependencies, fromRef)
Ref: deriveBomRef(toPkg), }
Dependencies: &innerDeps,
})
} }
result := make([]cyclonedx.Dependency, 0, len(dependencies))
for _, dep := range dependencies {
slices.Sort(*dep.Dependencies)
result = append(result, *dep)
}
slices.SortFunc(result, func(a, b cyclonedx.Dependency) bool {
return a.Ref < b.Ref
})
return result return result
} }
@@ -1,6 +1,7 @@
package cyclonedxhelpers package cyclonedxhelpers
import ( import (
"fmt"
"testing" "testing"
"github.com/CycloneDX/cyclonedx-go" "github.com/CycloneDX/cyclonedx-go"
@@ -43,28 +44,34 @@ func Test_relationships(t *testing.T) {
p1 := pkg.Package{ p1 := pkg.Package{
Name: "p1", Name: "p1",
} }
p1.SetID()
p2 := pkg.Package{ p2 := pkg.Package{
Name: "p2", Name: "p2",
} }
p2.SetID()
p3 := pkg.Package{ p3 := pkg.Package{
Name: "p3", Name: "p3",
} }
p3.SetID()
p4 := pkg.Package{
Name: "p4",
}
for _, p := range []*pkg.Package{&p1, &p2, &p3, &p4} {
p.PURL = fmt.Sprintf("pkg:generic/%s@%s", p.Name, p.Name)
p.SetID()
}
tests := []struct { tests := []struct {
name string name string
sbom sbom.SBOM sbom sbom.SBOM
expected []string expected *[]cyclonedx.Dependency
}{ }{
{ {
name: "package dependencyOf relationships output as dependencies", name: "package dependencyOf relationships output as dependencies",
sbom: sbom.SBOM{ sbom: sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
Packages: pkg.NewCollection(p1, p2, p3), Packages: pkg.NewCollection(p1, p2, p3, p4),
}, },
Relationships: []artifact.Relationship{ Relationships: []artifact.Relationship{
{ {
@@ -77,9 +84,28 @@ func Test_relationships(t *testing.T) {
To: p1, To: p1,
Type: artifact.DependencyOfRelationship, Type: artifact.DependencyOfRelationship,
}, },
{
From: p4,
To: p2,
Type: artifact.DependencyOfRelationship,
},
},
},
expected: &[]cyclonedx.Dependency{
{
Ref: deriveBomRef(p1),
Dependencies: &[]string{
deriveBomRef(p2),
deriveBomRef(p3),
},
},
{
Ref: deriveBomRef(p2),
Dependencies: &[]string{
deriveBomRef(p4),
},
}, },
}, },
expected: []string{p2.Name, p3.Name},
}, },
{ {
name: "package contains relationships not output", name: "package contains relationships not output",
@@ -108,28 +134,7 @@ func Test_relationships(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
cdx := ToFormatModel(test.sbom) cdx := ToFormatModel(test.sbom)
got := cdx.Dependencies got := cdx.Dependencies
require.Equal(t, test.expected, got)
var deps []string
if got != nil {
for _, r := range *got {
for _, d := range *r.Dependencies {
c := findComponent(cdx, d)
require.NotNil(t, c)
deps = append(deps, c.Name)
}
}
}
require.Equal(t, test.expected, deps)
}) })
} }
} }
func findComponent(cdx *cyclonedx.BOM, bomRef string) *cyclonedx.Component {
for _, c := range *cdx.Components {
if c.BOMRef == bomRef {
return &c
}
}
return nil
}
@@ -349,9 +349,13 @@ func (r directoryIndexer) addSymlinkToIndex(p string, info os.FileInfo) (string,
} }
if filepath.IsAbs(linkTarget) { if filepath.IsAbs(linkTarget) {
linkTarget = filepath.Clean(linkTarget)
// if the link is absolute (e.g, /bin/ls -> /bin/busybox) we need to // if the link is absolute (e.g, /bin/ls -> /bin/busybox) we need to
// resolve relative to the root of the base directory // resolve relative to the root of the base directory, if it is not already
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget)) // prefixed with a volume name
if filepath.VolumeName(linkTarget) == "" {
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget))
}
} else { } else {
// if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to // if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to
// resolve it relative to the directory in question (e.g. resolve to // resolve it relative to the directory in question (e.g. resolve to
@@ -667,6 +667,18 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Metadata: metadata("nginx-binary"), Metadata: metadata("nginx-binary"),
}, },
}, },
{
name: "positive-bash-5.2.15",
fixtureDir: "test-fixtures/classifiers/positive/bash-5.2.15",
expected: pkg.Package{
Name: "bash",
Version: "5.2.15",
Type: "binary",
PURL: "pkg:generic/bash@5.2.15",
Locations: locations("bash"),
Metadata: metadata("bash-binary"),
},
},
} }
for _, test := range tests { for _, test := range tests {
@@ -269,6 +269,20 @@ var defaultClassifiers = []classifier{
cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"),
}, },
}, },
{
Class: "bash-binary",
FileGlob: "**/bash",
EvidenceMatcher: fileContentsVersionMatcher(
// @(#)Bash version 5.2.15(1) release GNU
// @(#)Bash version 5.2.0(1) alpha GNU
// @(#)Bash version 5.2.0(1) beta GNU
// @(#)Bash version 5.2.0(1) rc4 GNU
`(?m)@\(#\)Bash version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\([0-9]\) [a-z0-9]+ GNU`,
),
Package: "bash",
PURL: mustPURL("pkg:generic/bash@version"),
CPEs: singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*"),
},
} }
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL] // in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
@@ -0,0 +1 @@
@(#)Bash version 5.2.15(1) release GNU
@@ -283,6 +283,7 @@
"360class.jansenhm": "cpe:2.3:a:360class.jansenhm_project:360class.jansenhm:*:*:*:*:*:node.js:*:*", "360class.jansenhm": "cpe:2.3:a:360class.jansenhm_project:360class.jansenhm:*:*:*:*:*:node.js:*:*",
"626": "cpe:2.3:a:626_project:626:*:*:*:*:*:node.js:*:*", "626": "cpe:2.3:a:626_project:626:*:*:*:*:*:node.js:*:*",
"@actions/core": "cpe:2.3:a:toolkit_project:toolkit:*:*:*:*:*:node.js:*:*", "@actions/core": "cpe:2.3:a:toolkit_project:toolkit:*:*:*:*:*:node.js:*:*",
"@antfu/utils": "cpe:2.3:a:antfu:utils:*:*:*:*:*:node.js:*:*",
"@awsui/components-react": "cpe:2.3:a:amazon:awsui\\/components-react:*:*:*:*:*:node.js:*:*", "@awsui/components-react": "cpe:2.3:a:amazon:awsui\\/components-react:*:*:*:*:*:node.js:*:*",
"@azure/ms-rest-nodeauth": "cpe:2.3:a:microsoft:ms-rest-nodeauth:*:*:*:*:*:node.js:*:*", "@azure/ms-rest-nodeauth": "cpe:2.3:a:microsoft:ms-rest-nodeauth:*:*:*:*:*:node.js:*:*",
"@backstage/plugin-auth-backend": "cpe:2.3:a:linuxfoundation:auth_backend:*:*:*:*:*:node.js:*:*", "@backstage/plugin-auth-backend": "cpe:2.3:a:linuxfoundation:auth_backend:*:*:*:*:*:node.js:*:*",
@@ -1081,6 +1082,7 @@
"spacy": "cpe:2.3:a:explosion:spacy:*:*:*:*:*:python:*:*", "spacy": "cpe:2.3:a:explosion:spacy:*:*:*:*:*:python:*:*",
"sqlparse": "cpe:2.3:a:sqlparse_project:sqlparse:*:*:*:*:*:python:*:*", "sqlparse": "cpe:2.3:a:sqlparse_project:sqlparse:*:*:*:*:*:python:*:*",
"tkvideoplayer": "cpe:2.3:a:python:tkvideoplayer:*:*:*:*:*:*:*:*", "tkvideoplayer": "cpe:2.3:a:python:tkvideoplayer:*:*:*:*:*:*:*:*",
"togglee": "cpe:2.3:a:togglee:togglee:*:*:*:*:*:pypi:*:*",
"urllib3": "cpe:2.3:a:python:urllib3:*:*:*:*:*:*:*:*", "urllib3": "cpe:2.3:a:python:urllib3:*:*:*:*:*:*:*:*",
"validators": "cpe:2.3:a:validators_project:validators:*:*:*:*:*:python:*:*", "validators": "cpe:2.3:a:validators_project:validators:*:*:*:*:*:python:*:*",
"vault-cli": "cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*", "vault-cli": "cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*",
+15 -8
View File
@@ -21,16 +21,16 @@ var (
"be", "be",
} }
primaryJavaManifestGroupIDFields = []string{ PrimaryJavaManifestGroupIDFields = []string{
"Bundle-SymbolicName",
"Extension-Name", "Extension-Name",
"Specification-Vendor", "Specification-Vendor",
"Implementation-Vendor", "Implementation-Vendor",
"Bundle-SymbolicName",
"Implementation-Vendor-Id", "Implementation-Vendor-Id",
"Implementation-Title", "Implementation-Title",
"Bundle-Activator", "Bundle-Activator",
} }
secondaryJavaManifestGroupIDFields = []string{ SecondaryJavaManifestGroupIDFields = []string{
"Automatic-Module-Name", "Automatic-Module-Name",
"Main-Class", "Main-Class",
"Package", "Package",
@@ -168,7 +168,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
} }
artifactID := strings.TrimSpace(metadata.PomProperties.ArtifactID) artifactID := strings.TrimSpace(metadata.PomProperties.ArtifactID)
if startsWithTopLevelDomain(artifactID) && len(strings.Split(artifactID, ".")) > 1 { if looksLikeGroupID(artifactID) && len(strings.Split(artifactID, ".")) > 1 {
// there is a strong indication that the artifact ID is really a group ID, don't use it // there is a strong indication that the artifact ID is really a group ID, don't use it
return "" return ""
} }
@@ -184,6 +184,9 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
return GroupIDsFromJavaMetadata(p.Name, metadata) return GroupIDsFromJavaMetadata(p.Name, metadata)
} }
// GroupIDsFromJavaMetadata returns the possible group IDs for a Java package
// This function is similar to GroupIDFromJavaPackage, but returns all possible group IDs and is less strict
// It is used as a way to generate possible candidates for CPE matching.
func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupIDs []string) { func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupIDs []string) {
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...) groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...) groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
@@ -242,7 +245,7 @@ func addGroupIDsFromGroupIDsAndArtifactID(groupID, artifactID string) (groupIDs
} }
func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []string { func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []string {
if groupID, ok := defaultArtifactIDToGroupID[pkgName]; ok { if groupID, ok := DefaultArtifactIDToGroupID[pkgName]; ok {
return []string{groupID} return []string{groupID}
} }
@@ -251,7 +254,7 @@ func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []stri
} }
// try the common manifest fields first for a set of candidates // try the common manifest fields first for a set of candidates
groupIDs := getManifestFieldGroupIDs(manifest, primaryJavaManifestGroupIDFields) groupIDs := GetManifestFieldGroupIDs(manifest, PrimaryJavaManifestGroupIDFields)
if len(groupIDs) != 0 { if len(groupIDs) != 0 {
return groupIDs return groupIDs
@@ -262,10 +265,10 @@ func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []stri
// for more info see pkg:maven/commons-io/commons-io@2.8.0 within cloudbees/cloudbees-core-mm:2.263.4.2 // for more info see pkg:maven/commons-io/commons-io@2.8.0 within cloudbees/cloudbees-core-mm:2.263.4.2
// at /usr/share/jenkins/jenkins.war:WEB-INF/plugins/analysis-model-api.hpi:WEB-INF/lib/commons-io-2.8.0.jar // at /usr/share/jenkins/jenkins.war:WEB-INF/plugins/analysis-model-api.hpi:WEB-INF/lib/commons-io-2.8.0.jar
// as well as the ant package from cloudbees/cloudbees-core-mm:2.277.2.4-ra. // as well as the ant package from cloudbees/cloudbees-core-mm:2.277.2.4-ra.
return getManifestFieldGroupIDs(manifest, secondaryJavaManifestGroupIDFields) return GetManifestFieldGroupIDs(manifest, SecondaryJavaManifestGroupIDFields)
} }
func getManifestFieldGroupIDs(manifest *pkg.JavaManifest, fields []string) (groupIDs []string) { func GetManifestFieldGroupIDs(manifest *pkg.JavaManifest, fields []string) (groupIDs []string) {
if manifest == nil { if manifest == nil {
return nil return nil
} }
@@ -302,3 +305,7 @@ func removeOSCIDirectives(groupID string) string {
func startsWithTopLevelDomain(value string) bool { func startsWithTopLevelDomain(value string) bool {
return internal.HasAnyOfPrefixes(value, domains...) return internal.HasAnyOfPrefixes(value, domains...)
} }
func looksLikeGroupID(value string) bool {
return strings.Contains(value, ".")
}
@@ -1,6 +1,6 @@
package cpe package cpe
var defaultArtifactIDToGroupID = map[string]string{ var DefaultArtifactIDToGroupID = map[string]string{
"ant": "org.apache.ant", "ant": "org.apache.ant",
"ant-antlr": "org.apache.ant", "ant-antlr": "org.apache.ant",
"ant-antunit": "org.apache.ant", "ant-antunit": "org.apache.ant",
+70 -25
View File
@@ -8,23 +8,67 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package { type conanRef struct {
fields := strings.Split(strings.TrimSpace(m.Ref), "/") Name string
if len(fields) < 2 { Version string
return nil User string
Channel string
Revision string
Timestamp string
}
func splitConanRef(ref string) *conanRef {
// Conan ref format is:
// pkg/0.1@user/channel#rrev%timestamp
// This method is based on conan's ref.loads method:
// https://github.com/conan-io/conan/blob/release/2.0/conans/model/recipe_ref.py#L93C21-L93C21
var cref conanRef
// timestamp
tokens := strings.Split(ref, "%")
text := tokens[0]
if len(tokens) == 2 {
cref.Timestamp = tokens[1]
} }
pkgName, pkgVersion := fields[0], fields[1] // revision
tokens = strings.Split(text, "#")
ref = tokens[0]
if len(tokens) == 2 {
cref.Revision = tokens[1]
}
if pkgName == "" || pkgVersion == "" { // name and version are always given
tokens = strings.Split(ref, "@")
nameAndVersion := strings.Split(tokens[0], "/")
if len(nameAndVersion) < 2 || nameAndVersion[0] == "" || nameAndVersion[1] == "" {
return nil
}
cref.Name = nameAndVersion[0]
cref.Version = nameAndVersion[1]
// user and channel
if len(tokens) == 2 && tokens[1] != "" {
tokens = strings.Split(tokens[1], "/")
if len(tokens) == 2 {
cref.User = tokens[0]
cref.Channel = tokens[1]
}
}
return &cref
}
func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package {
ref := splitConanRef(m.Ref)
if ref == nil {
return nil return nil
} }
p := pkg.Package{ p := pkg.Package{
Name: pkgName, Name: ref.Name,
Version: pkgVersion, Version: ref.Version,
Locations: file.NewLocationSet(locations...), Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion), PURL: packageURL(ref),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
@@ -37,22 +81,16 @@ func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.P
} }
func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package { func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package {
fields := strings.Split(strings.Split(m.Ref, "@")[0], "/") ref := splitConanRef(m.Ref)
if len(fields) < 2 { if ref == nil {
return nil
}
pkgName, pkgVersion := fields[0], fields[1]
if pkgName == "" || pkgVersion == "" {
return nil return nil
} }
p := pkg.Package{ p := pkg.Package{
Name: pkgName, Name: ref.Name,
Version: pkgVersion, Version: ref.Version,
Locations: file.NewLocationSet(locations...), Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion), PURL: packageURL(ref),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType, MetadataType: pkg.ConanLockMetadataType,
@@ -64,13 +102,20 @@ func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *p
return &p return &p
} }
func packageURL(name, version string) string { func packageURL(ref *conanRef) string {
qualifiers := packageurl.Qualifiers{}
if ref.Channel != "" {
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "channel",
Value: ref.Channel,
})
}
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
packageurl.TypeConan, packageurl.TypeConan,
"", ref.User,
name, ref.Name,
version, ref.Version,
nil, // TODO: no qualifiers (...yet) qualifiers,
"", "",
).ToString() ).ToString()
} }
+1 -1
View File
@@ -36,7 +36,7 @@ func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.Locatio
switch { switch {
case strings.Contains(line, "[requires]"): case strings.Contains(line, "[requires]"):
inRequirements = true inRequirements = true
case strings.ContainsAny(line, "[]#"): case strings.ContainsAny(line, "[]") || strings.HasPrefix(strings.TrimSpace(line), "#"):
inRequirements = false inRequirements = false
} }
@@ -52,13 +52,13 @@ func TestParseConanfile(t *testing.T) {
{ {
Name: "spdlog", Name: "spdlog",
Version: "1.9.2", Version: "1.9.2",
PURL: "pkg:conan/spdlog@1.9.2", PURL: "pkg:conan/my_user/spdlog@1.9.2?channel=my_channel",
Locations: fixtureLocationSet, Locations: fixtureLocationSet,
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "spdlog/1.9.2", Ref: "spdlog/1.9.2@my_user/my_channel#1234567%%987654",
}, },
}, },
{ {
@@ -70,19 +70,19 @@ func TestParseConanfile(t *testing.T) {
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "sdl/2.0.20", Ref: "sdl/2.0.20#1234567%%987654",
}, },
}, },
{ {
Name: "fltk", Name: "fltk",
Version: "1.3.8", Version: "1.3.8",
PURL: "pkg:conan/fltk@1.3.8", PURL: "pkg:conan/my_user/fltk@1.3.8?channel=my_channel",
Locations: fixtureLocationSet, Locations: fixtureLocationSet,
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "fltk/1.3.8", Ref: "fltk/1.3.8@my_user/my_channel",
}, },
}, },
} }
+36 -7
View File
@@ -13,20 +13,49 @@ func TestParseConanlock(t *testing.T) {
fixture := "test-fixtures/conan.lock" fixture := "test-fixtures/conan.lock"
expected := []pkg.Package{ expected := []pkg.Package{
{ {
Name: "zlib", Name: "spdlog",
Version: "1.2.12", Version: "1.11.0",
PURL: "pkg:conan/zlib@1.2.12", PURL: "pkg:conan/spdlog@1.11.0",
Locations: file.NewLocationSet(file.NewLocation(fixture)), Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType, MetadataType: pkg.ConanLockMetadataType,
Metadata: pkg.ConanLockMetadata{ Metadata: pkg.ConanLockMetadata{
Ref: "zlib/1.2.12", Ref: "spdlog/1.11.0",
Options: map[string]string{ Options: map[string]string{
"fPIC": "True", "fPIC": "True",
"shared": "False", "header_only": "False",
"no_exceptions": "False",
"shared": "False",
"wchar_filenames": "False",
"wchar_support": "False",
"fmt:fPIC": "True",
"fmt:header_only": "False",
"fmt:shared": "False",
"fmt:with_fmt_alias": "False",
"fmt:with_os_api": "True",
},
Path: "conanfile.py",
Context: "host",
},
},
{
Name: "fmt",
Version: "9.1.0",
PURL: "pkg:conan/my_user/fmt@9.1.0?channel=my_channel",
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType,
Metadata: pkg.ConanLockMetadata{
Ref: "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba",
Options: map[string]string{
"fPIC": "True",
"header_only": "False",
"shared": "False",
"with_fmt_alias": "False",
"with_os_api": "True",
}, },
Path: "all/conanfile.py",
Context: "host", Context: "host",
}, },
}, },
@@ -2,15 +2,24 @@
"graph_lock": { "graph_lock": {
"nodes": { "nodes": {
"0": { "0": {
"ref": "zlib/1.2.12", "ref": "spdlog/1.11.0",
"options": "fPIC=True\nshared=False", "options": "fPIC=True\nheader_only=False\nno_exceptions=False\nshared=False\nwchar_filenames=False\nwchar_support=False\nfmt:fPIC=True\nfmt:header_only=False\nfmt:shared=False\nfmt:with_fmt_alias=False\nfmt:with_os_api=True",
"requires": [], "requires": [
"path": "all/conanfile.py", "1"
],
"path": "conanfile.py",
"context": "host"
},
"1": {
"ref": "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba",
"options": "fPIC=True\nheader_only=False\nshared=False\nwith_fmt_alias=False\nwith_os_api=True",
"package_id": "2c09c8f84c016041549fcee94e4caae5d89424b6",
"prev": "9f5ab13fc7c73e4a9f87e4e213f2cfa4",
"context": "host" "context": "host"
} }
}, },
"revisions_enabled": false "revisions_enabled": true
}, },
"version": "0.4", "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" "profile_host": "[settings]\narch=x86_64\narch.march=None\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++11\ncompiler.version=11\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n"
} }
@@ -4,9 +4,9 @@
catch2/2.13.8 catch2/2.13.8
docopt.cpp/0.6.3 docopt.cpp/0.6.3
fmt/8.1.1 fmt/8.1.1
spdlog/1.9.2 spdlog/1.9.2@my_user/my_channel#1234567%%987654
sdl/2.0.20 sdl/2.0.20#1234567%%987654
fltk/1.3.8 fltk/1.3.8@my_user/my_channel
[generators] [generators]
cmake_find_package_multi cmake_find_package_multi
+2 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...file.Location) pkg.Package { func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, cryptoSettings []string, locations ...file.Location) pkg.Package {
if dep.Replace != nil { if dep.Replace != nil {
dep = dep.Replace dep = dep.Replace
} }
@@ -36,6 +36,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debu
Architecture: architecture, Architecture: architecture,
BuildSettings: buildSettings, BuildSettings: buildSettings,
MainModule: mainModule, MainModule: mainModule,
GoCryptoSettings: cryptoSettings,
}, },
} }
+5 -3
View File
@@ -66,7 +66,7 @@ func (c *goBinaryCataloger) parseGoBinary(resolver file.Resolver, _ *generic.Env
return pkgs, nil, nil return pkgs, nil, nil
} }
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *debug.BuildInfo, arch string, location file.Location) pkg.Package { func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location) pkg.Package {
gbs := getBuildSettings(mod.Settings) gbs := getBuildSettings(mod.Settings)
main := c.newGoBinaryPackage( main := c.newGoBinaryPackage(
resolver, resolver,
@@ -75,6 +75,7 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *debug
mod.GoVersion, mod.GoVersion,
arch, arch,
gbs, gbs,
mod.cryptoSettings,
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
) )
@@ -153,7 +154,7 @@ func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion
// getArchs finds a binary architecture by two ways: // getArchs finds a binary architecture by two ways:
// 1) reading build info from binaries compiled by go1.18+ // 1) reading build info from binaries compiled by go1.18+
// 2) reading file headers from binaries compiled by < go1.18 // 2) reading file headers from binaries compiled by < go1.18
func getArchs(readers []io.ReaderAt, builds []*debug.BuildInfo) []string { func getArchs(readers []io.ReaderAt, builds []*extendedBuildInfo) []string {
if len(readers) != len(builds) { if len(readers) != len(builds) {
log.Trace("golang cataloger: bin parsing: number of builds and readers doesn't match") log.Trace("golang cataloger: bin parsing: number of builds and readers doesn't match")
return nil return nil
@@ -255,7 +256,7 @@ func createMainModuleFromPath(path string) (mod debug.Module) {
return return
} }
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *debug.BuildInfo, arch string) []pkg.Package { func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string) []pkg.Package {
var pkgs []pkg.Package var pkgs []pkg.Package
if mod == nil { if mod == nil {
return pkgs return pkgs
@@ -277,6 +278,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
mod.GoVersion, mod.GoVersion,
arch, arch,
nil, nil,
mod.cryptoSettings,
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
) )
if pkg.IsValid(&p) { if pkg.IsValid(&p) {
+164 -139
View File
@@ -155,7 +155,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
mod *debug.BuildInfo mod *extendedBuildInfo
arch string arch string
expected []pkg.Package expected []pkg.Package
}{ }{
@@ -166,16 +166,18 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "package without name", name: "package without name",
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
Deps: []*debug.Module{ &debug.BuildInfo{
{ Deps: []*debug.Module{
Path: "github.com/adrg/xdg", {
Path: "github.com/adrg/xdg",
},
{
Path: "",
Version: "v0.2.1",
},
}, },
{ }, nil,
Path: "",
Version: "v0.2.1",
},
},
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -198,26 +200,28 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "buildGoPkgInfo parses a blank mod and returns no packages", name: "buildGoPkgInfo parses a blank mod and returns no packages",
mod: &debug.BuildInfo{}, mod: &extendedBuildInfo{&debug.BuildInfo{}, nil},
expected: []pkg.Package(nil), expected: []pkg.Package(nil),
}, },
{ {
name: "parse a mod without main module", name: "parse a mod without main module",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Settings: []debug.BuildSetting{ GoVersion: goCompiledVersion,
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
}, {Key: "GOAMD64", Value: "v1"},
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
}, },
}, Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -246,14 +250,16 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse a mod with path but no main module", name: "parse a mod with path but no main module",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Settings: []debug.BuildSetting{ GoVersion: goCompiledVersion,
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
}, {Key: "GOAMD64", Value: "v1"},
Path: "github.com/a/b/c", },
Path: "github.com/a/b/c",
}, []string{"boringcrypto + fips"},
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -280,7 +286,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
"GOARCH": "amd64", "GOARCH": "amd64",
"GOOS": "darwin", "GOOS": "darwin",
}, },
MainModule: "github.com/a/b/c", MainModule: "github.com/a/b/c",
GoCryptoSettings: []string{"boringcrypto + fips"},
}, },
}, },
}, },
@@ -288,31 +295,35 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse a mod without packages", name: "parse a mod without packages",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
}, {Key: "GOAMD64", Value: "v1"},
},
}, nil,
}, },
expected: []pkg.Package{unmodifiedMain}, expected: []pkg.Package{unmodifiedMain},
}, },
{ {
name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)", name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, {Key: "GOAMD64", Value: "v1"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -349,17 +360,19 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse main mod and replace devel version with one from ldflags with vcs. build settings", name: "parse main mod and replace devel version with one from ldflags with vcs. build settings",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, {Key: "GOAMD64", Value: "v1"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -396,15 +409,17 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse main mod and replace devel version with one from ldflags without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags without any vcs. build settings",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, {Key: "GOAMD64", Value: "v1"},
}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -439,15 +454,17 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse main mod and replace devel version with one from ldflags main.version without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags main.version without any vcs. build settings",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`}, {Key: "GOAMD64", Value: "v1"},
}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -482,15 +499,17 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse main mod and replace devel version with one from ldflags main.Version without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags main.Version without any vcs. build settings",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`}, {Key: "GOAMD64", Value: "v1"},
}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -525,16 +544,18 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse main mod and replace devel version with a pseudo version", name: "parse main mod and replace devel version with a pseudo version",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, {Key: "GOAMD64", Value: "v1"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
},
}, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -570,26 +591,28 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse a populated mod string and returns packages but no source info", name: "parse a populated mod string and returns packages but no source info",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
}, {Key: "GOAMD64", Value: "v1"},
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
}, },
{ Deps: []*debug.Module{
Path: "github.com/anchore/client-go", {
Version: "v0.0.0-20210222170800-9c70f9b80bcf", Path: "github.com/adrg/xdg",
Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=", Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
},
{
Path: "github.com/anchore/client-go",
Version: "v0.0.0-20210222170800-9c70f9b80bcf",
Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=",
},
}, },
}, }, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@@ -642,31 +665,33 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
name: "parse a populated mod string and returns packages when a replace directive exists", name: "parse a populated mod string and returns packages when a replace directive exists",
arch: archDetails, arch: archDetails,
mod: &debug.BuildInfo{ mod: &extendedBuildInfo{
GoVersion: goCompiledVersion, &debug.BuildInfo{
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
{Key: "GOARCH", Value: archDetails}, Settings: []debug.BuildSetting{
{Key: "GOOS", Value: "darwin"}, {Key: "GOARCH", Value: archDetails},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOOS", Value: "darwin"},
}, {Key: "GOAMD64", Value: "v1"},
Deps: []*debug.Module{
{
Path: "golang.org/x/sys",
Version: "v0.0.0-20211006194710-c8a6f5223071",
Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
}, },
{ Deps: []*debug.Module{
Path: "golang.org/x/term", {
Version: "v0.0.0-20210927222741-03fcf44c2211", Path: "golang.org/x/sys",
Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=", Version: "v0.0.0-20211006194710-c8a6f5223071",
Replace: &debug.Module{ Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
},
{
Path: "golang.org/x/term", Path: "golang.org/x/term",
Version: "v0.0.0-20210916214954-140adaaadfaf", Version: "v0.0.0-20210927222741-03fcf44c2211",
Sum: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=", Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
Replace: &debug.Module{
Path: "golang.org/x/term",
Version: "v0.0.0-20210916214954-140adaaadfaf",
Sum: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=",
},
}, },
}, },
}, }, nil,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
+40 -3
View File
@@ -6,12 +6,19 @@ import (
"io" "io"
"runtime/debug" "runtime/debug"
"github.com/kastenhq/goversion/version"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
) )
type extendedBuildInfo struct {
*debug.BuildInfo
cryptoSettings []string
}
// scanFile scans file to try to report the Go and module versions. // scanFile scans file to try to report the Go and module versions.
func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildInfo, []string) { func scanFile(reader unionreader.UnionReader, filename string) ([]*extendedBuildInfo, []string) {
// NOTE: multiple readers are returned to cover universal binaries, which are files // NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary // with more than one binary
readers, err := unionreader.GetReaders(reader) readers, err := unionreader.GetReaders(reader)
@@ -20,7 +27,7 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
return nil, nil return nil, nil
} }
var builds []*debug.BuildInfo var builds []*extendedBuildInfo
for _, r := range readers { for _, r := range readers {
bi, err := getBuildInfo(r) bi, err := getBuildInfo(r)
if err != nil { if err != nil {
@@ -30,7 +37,14 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
if bi == nil { if bi == nil {
continue continue
} }
builds = append(builds, bi)
v, err := getCryptoInformation(r)
if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang version info")
continue
}
builds = append(builds, &extendedBuildInfo{bi, v})
} }
archs := getArchs(readers, builds) archs := getArchs(readers, builds)
@@ -38,6 +52,29 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
return builds, archs return builds, archs
} }
func getCryptoInformation(reader io.ReaderAt) ([]string, error) {
v, err := version.ReadExeFromReader(reader)
if err != nil {
return nil, err
}
return getCryptoSettingsFromVersion(v), nil
}
func getCryptoSettingsFromVersion(v version.Version) []string {
cryptoSettings := []string{}
if v.StandardCrypto {
cryptoSettings = append(cryptoSettings, "standard-crypto")
}
if v.BoringCrypto {
cryptoSettings = append(cryptoSettings, "boring-crypto")
}
if v.FIPSOnly {
cryptoSettings = append(cryptoSettings, "crypto/tls/fipsonly")
}
return cryptoSettings
}
func getBuildInfo(r io.ReaderAt) (bi *debug.BuildInfo, err error) { func getBuildInfo(r io.ReaderAt) (bi *debug.BuildInfo, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -6,6 +6,7 @@ import (
"runtime/debug" "runtime/debug"
"testing" "testing"
"github.com/kastenhq/goversion/version"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -38,3 +39,72 @@ func Test_getBuildInfo(t *testing.T) {
}) })
} }
} }
func Test_getCryptoSettingsFromVersion(t *testing.T) {
for _, tt := range []struct {
name string
version version.Version
result []string
}{
{
name: "standard crypto",
version: version.Version{
StandardCrypto: true,
},
result: []string{"standard-crypto"},
},
{
name: "boring crypto",
version: version.Version{
BoringCrypto: true,
},
result: []string{"boring-crypto"},
},
{ // Should never see this. Boring crypto is required for fipsonly
name: "fipsonly",
version: version.Version{
FIPSOnly: true,
},
result: []string{"crypto/tls/fipsonly"},
},
{
name: "boring crypto and fipsonly",
version: version.Version{
BoringCrypto: true,
FIPSOnly: true,
},
result: []string{"boring-crypto", "crypto/tls/fipsonly"},
},
{ // Should never see this.
name: "boring and standard crypto!",
version: version.Version{
BoringCrypto: true,
StandardCrypto: true,
},
result: []string{"boring-crypto", "standard-crypto"},
},
{ // Should never see this. Boring crypto is required for fipsonly
name: "fipsonly and standard crypto!",
version: version.Version{
FIPSOnly: true,
StandardCrypto: true,
},
result: []string{"crypto/tls/fipsonly", "standard-crypto"},
},
{ // Should never see this. Boring crypto is required for fipsonly
name: "fipsonly boringcrypto and standard crypto!",
version: version.Version{
FIPSOnly: true,
StandardCrypto: true,
BoringCrypto: true,
},
result: []string{"crypto/tls/fipsonly", "standard-crypto", "boring-crypto"},
},
} {
t.Run(tt.name, func(t *testing.T) {
res := getCryptoSettingsFromVersion(tt.version)
assert.ElementsMatch(t, res, tt.result)
})
}
}
+43 -20
View File
@@ -62,11 +62,11 @@ func parseJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.Locat
} }
// uniquePkgKey creates a unique string to identify the given package. // uniquePkgKey creates a unique string to identify the given package.
func uniquePkgKey(p *pkg.Package) string { func uniquePkgKey(groupID string, p *pkg.Package) string {
if p == nil { if p == nil {
return "" return ""
} }
return fmt.Sprintf("%s|%s", p.Name, p.Version) return fmt.Sprintf("%s|%s|%s", groupID, p.Name, p.Version)
} }
// newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover // newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover
@@ -371,13 +371,27 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
return projectByParentPath, nil return projectByParentPath, nil
} }
// packagesFromPomProperties processes a single Maven POM properties for a given parent package, returning all listed Java packages found and // newPackageFromMavenData processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not. // associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package { func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package {
// keep the artifact name within the virtual path if this package does not match the parent package // keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := "" vPathSuffix := ""
if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { groupID := ""
vPathSuffix += ":" + pomProperties.ArtifactID if parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata); ok {
groupID = groupIDFromJavaMetadata(parentPkg.Name, parentMetadata)
}
parentKey := fmt.Sprintf("%s:%s:%s", groupID, parentPkg.Name, parentPkg.Version)
// Since we don't have a package yet, it's important to use the same `field: value` association that we used when creating the parent package
// See below where Name => pomProperties.ArtifactID and Version => pomProperties.Version. We want to check for potentially nested identical
// packages and create equal virtual paths so they are de duped in the future
pomProjectKey := fmt.Sprintf("%s:%s:%s", pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version)
if parentKey != pomProjectKey {
// build a new virtual path suffix for the package that is different from the parent package
// we want to use the GroupID and ArtifactID here to preserve uniqueness
// Some packages have the same name but different group IDs (e.g. "org.glassfish.jaxb/jaxb-core", "com.sun.xml.bind/jaxb-core")
// https://github.com/anchore/syft/issues/1944
vPathSuffix += ":" + pomProperties.GroupID + ":" + pomProperties.ArtifactID
} }
virtualPath := location.AccessPath() + vPathSuffix virtualPath := location.AccessPath() + vPathSuffix
@@ -408,21 +422,26 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po
} }
func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool { func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool {
// the name/version pair matches...
if uniquePkgKey(&p) == uniquePkgKey(parentPkg) {
return true
}
metadata, ok := p.Metadata.(pkg.JavaMetadata) metadata, ok := p.Metadata.(pkg.JavaMetadata)
if !ok { parentMetadata, parentOk := parentPkg.Metadata.(pkg.JavaMetadata)
log.WithFields("package", p.String()).Warn("unable to extract java metadata to check for matching package identity") if !ok || !parentOk {
return false switch {
case !ok:
log.WithFields("package", p.String()).Trace("unable to extract java metadata to check for matching package identity for package: %s", p.Name)
case !parentOk:
log.WithFields("package", parentPkg.String()).Trace("unable to extract java metadata to check for matching package identity for package: %s", parentPkg.Name)
}
// if we can't extract metadata, we can check for matching identities via the package name
// this is not ideal, but it's better than nothing - this should not be used if we have Metadata
return uniquePkgKey("", &p) == uniquePkgKey("", parentPkg)
} }
parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata) // try to determine identity with the metadata
if !ok { groupID := groupIDFromJavaMetadata(p.Name, metadata)
log.WithFields("package", p.String()).Warn("unable to extract java metadata from parent for verifying virtual path") parentGroupID := groupIDFromJavaMetadata(parentPkg.Name, parentMetadata)
return false if uniquePkgKey(groupID, &p) == uniquePkgKey(parentGroupID, parentPkg) {
return true
} }
// the virtual path matches... // the virtual path matches...
@@ -434,10 +453,14 @@ func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool {
// note: you CANNOT use name-is-subset-of-artifact-id or vice versa --this is too generic. Shaded jars are a good // note: you CANNOT use name-is-subset-of-artifact-id or vice versa --this is too generic. Shaded jars are a good
// example of this: where the package name is "cloudbees-analytics-segment-driver" and a child is "analytics", but // example of this: where the package name is "cloudbees-analytics-segment-driver" and a child is "analytics", but
// they do not indicate the same package. // they do not indicate the same package.
if metadata.PomProperties.ArtifactID != "" && parentPkg.Name == metadata.PomProperties.ArtifactID { // NOTE: artifactId might not be a good indicator of uniqueness since archives can contain forks with the same name
return true // from different groups (e.g. "org.glassfish.jaxb.jaxb-core" and "com.sun.xml.bind.jaxb-core")
// we will use this check as a last resort
if metadata.PomProperties != nil {
if metadata.PomProperties.ArtifactID != "" && parentPkg.Name == metadata.PomProperties.ArtifactID {
return true
}
} }
return false return false
} }
+6 -44
View File
@@ -174,8 +174,8 @@ func TestParseJar(t *testing.T) {
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as // ensure that nested packages with different names than that of the parent are appended as
// a suffix on the virtual path // a suffix on the virtual path with a colon separator between group name and artifact name
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:joda-time", VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:joda-time:joda-time",
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties", Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time", GroupID: "joda-time",
@@ -240,7 +240,7 @@ func TestParseJar(t *testing.T) {
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as // ensure that nested packages with different names than that of the parent are appended as
// a suffix on the virtual path // a suffix on the virtual path
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar:joda-time", VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar:joda-time:joda-time",
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties", Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time", GroupID: "joda-time",
@@ -659,7 +659,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id", VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
Name: "some-name", Name: "some-name",
GroupID: "some-group-id", GroupID: "some-group-id",
@@ -728,7 +728,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id", VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
Name: "some-name", Name: "some-name",
GroupID: "some-group-id", GroupID: "some-group-id",
@@ -797,7 +797,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id", VirtualPath: virtualPath + ":" + "com.cloudbees.jenkins.plugins" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
Name: "some-name", Name: "some-name",
GroupID: "com.cloudbees.jenkins.plugins", GroupID: "com.cloudbees.jenkins.plugins",
@@ -894,44 +894,6 @@ func Test_newPackageFromMavenData(t *testing.T) {
}, },
expectedPackage: nil, expectedPackage: nil,
}, },
{
name: "child matches parent by virtual path -- override name and version",
props: pkg.PomProperties{
Name: "some-name",
GroupID: "some-group-id",
ArtifactID: "some-parent-name", // note: DOES NOT match parent package
Version: "3.0", // note: DOES NOT match parent package
},
parent: &pkg.Package{
Name: "", // note: empty, so should not be matched on
Version: "", // note: empty, so should not be matched on
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath, // note: matching virtual path
Manifest: nil,
PomProperties: nil,
Parent: nil,
},
},
expectedParent: pkg.Package{
Name: "some-parent-name",
Version: "3.0",
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath,
Manifest: nil,
// note: we attach the discovered pom properties data
PomProperties: &pkg.PomProperties{
Name: "some-name",
GroupID: "some-group-id",
ArtifactID: "some-parent-name",
Version: "3.0",
},
Parent: nil,
},
},
expectedPackage: nil,
},
{ {
name: "child matches parent by artifact id", name: "child matches parent by artifact id",
props: pkg.PomProperties{ props: pkg.PomProperties{
+123 -3
View File
@@ -1,6 +1,8 @@
package java package java
import ( import (
"strings"
"github.com/anchore/packageurl-go" "github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common/cpe" "github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
@@ -9,9 +11,9 @@ import (
// PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec) // PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec)
func packageURL(name, version string, metadata pkg.JavaMetadata) string { func packageURL(name, version string, metadata pkg.JavaMetadata) string {
var groupID = name var groupID = name
groupIDs := cpe.GroupIDsFromJavaMetadata(name, metadata)
if len(groupIDs) > 0 { if gID := groupIDFromJavaMetadata(name, metadata); gID != "" {
groupID = groupIDs[0] groupID = gID
} }
pURL := packageurl.NewPackageURL( pURL := packageurl.NewPackageURL(
@@ -23,3 +25,121 @@ func packageURL(name, version string, metadata pkg.JavaMetadata) string {
"") "")
return pURL.ToString() return pURL.ToString()
} }
// GroupIDFromJavaPackage returns the authoritative group ID for a Java package.
// The order of precedence is:
// 1. The group ID from the POM properties
// 2. The group ID from the POM project
// 3. The group ID from a select map of known group IDs
// 4. The group ID from the Java manifest
func groupIDFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupID string) {
if groupID = groupIDFromPomProperties(metadata.PomProperties); groupID != "" {
return groupID
}
if groupID = groupIDFromPomProject(metadata.PomProject); groupID != "" {
return groupID
}
if groupID = groupIDFromKnownPackageList(pkgName); groupID != "" {
return groupID
}
if groupID = groupIDFromJavaManifest(metadata.Manifest); groupID != "" {
return groupID
}
return groupID
}
func groupIDFromKnownPackageList(pkgName string) (groupID string) {
if groupID, ok := cpe.DefaultArtifactIDToGroupID[pkgName]; ok {
return groupID
}
return groupID
}
func groupIDFromJavaManifest(manifest *pkg.JavaManifest) (groupID string) {
if manifest == nil {
return groupID
}
groupIDS := cpe.GetManifestFieldGroupIDs(manifest, cpe.PrimaryJavaManifestGroupIDFields)
// assumes that primaryJavaManifestNameFields are ordered by priority
if len(groupIDS) != 0 {
return groupIDS[0]
}
groupIDS = cpe.GetManifestFieldGroupIDs(manifest, cpe.SecondaryJavaManifestGroupIDFields)
if len(groupIDS) != 0 {
return groupIDS[0]
}
return groupID
}
func groupIDFromPomProperties(properties *pkg.PomProperties) (groupID string) {
if properties == nil {
return groupID
}
if properties.GroupID != "" {
return cleanGroupID(properties.GroupID)
}
// sometimes the publisher puts the group ID in the artifact ID field unintentionally
if looksLikeGroupID(properties.ArtifactID) {
// there is a strong indication that the artifact ID is really a group ID
return cleanGroupID(properties.ArtifactID)
}
return groupID
}
func groupIDFromPomProject(project *pkg.PomProject) (groupID string) {
if project == nil {
return groupID
}
// check the project details
if project.GroupID != "" {
return cleanGroupID(project.GroupID)
}
// sometimes the publisher puts the group ID in the artifact ID field unintentionally
if looksLikeGroupID(project.ArtifactID) {
// there is a strong indication that the artifact ID is really a group ID
return cleanGroupID(project.ArtifactID)
}
// let's check the parent details
// if the current project does not have a group ID, but the parent does, we'll use the parent's group ID
if project.Parent != nil {
if project.Parent.GroupID != "" {
return cleanGroupID(project.Parent.GroupID)
}
// sometimes the publisher puts the group ID in the artifact ID field unintentionally
if looksLikeGroupID(project.Parent.ArtifactID) {
// there is a strong indication that the artifact ID is really a group ID
return cleanGroupID(project.Parent.ArtifactID)
}
}
return groupID
}
func looksLikeGroupID(value string) bool {
return strings.Contains(value, ".")
}
func cleanGroupID(groupID string) string {
return strings.TrimSpace(removeOSCIDirectives(groupID))
}
func removeOSCIDirectives(groupID string) string {
// for example:
// org.bar;uses:=“org.foo” -> org.bar
// more about OSGI directives see https://spring.io/blog/2008/10/20/understanding-the-osgi-uses-directive/
return strings.Split(groupID, ";")[0]
}
+142
View File
@@ -10,10 +10,12 @@ import (
func Test_packageURL(t *testing.T) { func Test_packageURL(t *testing.T) {
tests := []struct { tests := []struct {
name string
pkg pkg.Package pkg pkg.Package
expect string expect string
}{ }{
{ {
name: "maven",
pkg: pkg.Package{ pkg: pkg.Package{
Name: "example-java-app-maven", Name: "example-java-app-maven",
Version: "0.1.0", Version: "0.1.0",
@@ -38,6 +40,90 @@ func Test_packageURL(t *testing.T) {
}, },
expect: "pkg:maven/org.anchore/example-java-app-maven@0.1.0", expect: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
}, },
{
name: "POM properties have explicit group ID without . in it",
pkg: pkg.Package{
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
},
},
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties",
GroupID: "commons",
ArtifactID: "example-java-app-maven",
Version: "0.1.0",
Extra: make(map[string]string),
},
},
},
expect: "pkg:maven/commons/example-java-app-maven@0.1.0",
},
{
name: "POM project has explicit group ID without . in it",
pkg: pkg.Package{
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
},
},
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties",
ArtifactID: "example-java-app-maven",
Version: "0.1.0",
Extra: make(map[string]string),
},
PomProject: &pkg.PomProject{
GroupID: "commons",
},
},
},
expect: "pkg:maven/commons/example-java-app-maven@0.1.0",
},
{
name: "POM project has explicit group ID without . in it",
pkg: pkg.Package{
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
},
},
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties",
ArtifactID: "example-java-app-maven",
Version: "0.1.0",
Extra: make(map[string]string),
},
PomProject: &pkg.PomProject{
Parent: &pkg.PomParent{
GroupID: "parent",
},
},
},
},
expect: "pkg:maven/parent/example-java-app-maven@0.1.0",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.expect, func(t *testing.T) { t.Run(tt.expect, func(t *testing.T) {
@@ -45,3 +131,59 @@ func Test_packageURL(t *testing.T) {
}) })
} }
} }
func Test_groupIDFromJavaMetadata(t *testing.T) {
tests := []struct {
name string
pkgName string
metadata pkg.JavaMetadata
expect string
}{
{
name: "pom properties",
metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: "org.anchore",
},
},
expect: "org.anchore",
},
{
name: "pom project",
metadata: pkg.JavaMetadata{
PomProject: &pkg.PomProject{
GroupID: "org.anchore",
},
},
expect: "org.anchore",
},
{
name: "known package list",
pkgName: "ant-antlr",
metadata: pkg.JavaMetadata{},
expect: "org.apache.ant",
},
{
name: "java manifest",
metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Implementation-Vendor": "org.anchore",
},
},
},
expect: "org.anchore",
},
{
name: "no group id",
metadata: pkg.JavaMetadata{},
expect: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expect, groupIDFromJavaMetadata(tt.pkgName, tt.metadata))
})
}
}
+64 -14
View File
@@ -1,6 +1,7 @@
package java package java
import ( import (
"bytes"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
@@ -8,9 +9,11 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/saintfish/chardet"
"github.com/vifraa/gopom" "github.com/vifraa/gopom"
"golang.org/x/net/html/charset" "golang.org/x/net/html/charset"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@@ -58,12 +61,13 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
artifactID := safeString(p.ArtifactID) artifactID := safeString(p.ArtifactID)
name := safeString(p.Name) name := safeString(p.Name)
projectURL := safeString(p.URL) projectURL := safeString(p.URL)
log.WithFields("path", path, "artifactID", artifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
return &pkg.PomProject{ return &pkg.PomProject{
Path: path, Path: path,
Parent: pomParent(p, p.Parent), Parent: pomParent(p, p.Parent),
GroupID: resolveProperty(p, p.GroupID), GroupID: resolveProperty(p, p.GroupID, "groupId"),
ArtifactID: artifactID, ArtifactID: artifactID,
Version: resolveProperty(p, p.Version), Version: resolveProperty(p, p.Version, "version"),
Name: name, Name: name,
Description: cleanDescription(p.Description), Description: cleanDescription(p.Description),
URL: projectURL, URL: projectURL,
@@ -73,14 +77,14 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...file.Location) pkg.Package { func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...file.Location) pkg.Package {
m := pkg.JavaMetadata{ m := pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.PomProperties{
GroupID: resolveProperty(pom, dep.GroupID), GroupID: resolveProperty(pom, dep.GroupID, "groupId"),
ArtifactID: resolveProperty(pom, dep.ArtifactID), ArtifactID: resolveProperty(pom, dep.ArtifactID, "artifactId"),
Scope: resolveProperty(pom, dep.Scope), Scope: resolveProperty(pom, dep.Scope, "scope"),
}, },
} }
name := safeString(dep.ArtifactID) name := safeString(dep.ArtifactID)
version := resolveProperty(pom, dep.Version) version := resolveProperty(pom, dep.Version, "version")
p := pkg.Package{ p := pkg.Package{
Name: name, Name: name,
@@ -99,9 +103,15 @@ func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...fil
} }
func decodePomXML(content io.Reader) (project gopom.Project, err error) { func decodePomXML(content io.Reader) (project gopom.Project, err error) {
decoder := xml.NewDecoder(content) inputReader, err := getUtf8Reader(content)
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil" if err != nil {
return project, fmt.Errorf("unable to read pom.xml: %w", err)
}
decoder := xml.NewDecoder(inputReader)
// when an xml file has a character set declaration (e.g. '<?xml version="1.0" encoding="ISO-8859-1"?>') read that and use the correct decoder
decoder.CharsetReader = charset.NewReaderLabel decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&project); err != nil { if err := decoder.Decode(&project); err != nil {
return project, fmt.Errorf("unable to unmarshal pom.xml: %w", err) return project, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
} }
@@ -109,6 +119,33 @@ func decodePomXML(content io.Reader) (project gopom.Project, err error) {
return project, nil return project, nil
} }
func getUtf8Reader(content io.Reader) (io.Reader, error) {
pomContents, err := io.ReadAll(content)
if err != nil {
return nil, err
}
detector := chardet.NewTextDetector()
detection, err := detector.DetectBest(pomContents)
var inputReader io.Reader
if err == nil && detection != nil {
if detection.Charset == "UTF-8" {
inputReader = bytes.NewReader(pomContents)
} else {
inputReader, err = charset.NewReaderLabel(detection.Charset, bytes.NewReader(pomContents))
if err != nil {
return nil, fmt.Errorf("unable to get encoding: %w", err)
}
}
} else {
// we could not detect the encoding, but we want a valid file to read. Replace unreadable
// characters with the UTF-8 replacement character.
inputReader = strings.NewReader(strings.ToValidUTF8(string(pomContents), "�"))
}
return inputReader, nil
}
func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.PomParent) { func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.PomParent) {
if parent == nil { if parent == nil {
return nil return nil
@@ -116,9 +153,9 @@ func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.PomParent)
artifactID := safeString(parent.ArtifactID) artifactID := safeString(parent.ArtifactID)
result = &pkg.PomParent{ result = &pkg.PomParent{
GroupID: resolveProperty(pom, parent.GroupID), GroupID: resolveProperty(pom, parent.GroupID, "groupId"),
ArtifactID: artifactID, ArtifactID: artifactID,
Version: resolveProperty(pom, parent.Version), Version: resolveProperty(pom, parent.Version, "version"),
} }
if result.GroupID == "" && result.ArtifactID == "" && result.Version == "" { if result.GroupID == "" && result.ArtifactID == "" && result.Version == "" {
@@ -147,10 +184,11 @@ func cleanDescription(original *string) (cleaned string) {
// If no match is found, the entire expression including ${} is returned // If no match is found, the entire expression including ${} is returned
// //
//nolint:gocognit //nolint:gocognit
func resolveProperty(pom gopom.Project, property *string) string { func resolveProperty(pom gopom.Project, property *string, propertyName string) string {
propertyCase := safeString(property) propertyCase := safeString(property)
log.WithFields("existingPropertyValue", propertyCase, "propertyName", propertyName).Trace("resolving property")
return propertyMatcher.ReplaceAllStringFunc(propertyCase, func(match string) string { return propertyMatcher.ReplaceAllStringFunc(propertyCase, func(match string) string {
propertyName := strings.TrimSpace(match[2 : len(match)-1]) propertyName := strings.TrimSpace(match[2 : len(match)-1]) // remove leading ${ and trailing }
entries := pomProperties(pom) entries := pomProperties(pom)
if value, ok := entries[propertyName]; ok { if value, ok := entries[propertyName]; ok {
return value return value
@@ -172,14 +210,26 @@ func resolveProperty(pom gopom.Project, property *string) string {
for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ { for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ {
f := pomValueType.Field(fieldNum) f := pomValueType.Field(fieldNum)
tag := f.Tag.Get("xml") tag := f.Tag.Get("xml")
tag = strings.TrimSuffix(tag, ",omitempty") tag = strings.Split(tag, ",")[0]
// a segment of the property name matches the xml tag for the field,
// so we need to recurse down the nested structs or return a match
// if we're done.
if part == tag { if part == tag {
pomValue = pomValue.Field(fieldNum) pomValue = pomValue.Field(fieldNum)
pomValueType = pomValue.Type() pomValueType = pomValue.Type()
if pomValueType.Kind() == reflect.Ptr { if pomValueType.Kind() == reflect.Ptr {
// we were recursing down the nested structs, but one of the steps
// we need to take is a nil pointer, so give up and return the original match
if pomValue.IsNil() {
return match
}
pomValue = pomValue.Elem() pomValue = pomValue.Elem()
pomValueType = pomValue.Type() if !pomValue.IsZero() {
// we found a non-zero value whose tag matches this part of the property name
pomValueType = pomValue.Type()
}
} }
// If this was the last part of the property name, return the value
if partNum == numParts-1 { if partNum == numParts-1 {
return fmt.Sprintf("%v", pomValue.Interface()) return fmt.Sprintf("%v", pomValue.Interface())
} }
+102 -1
View File
@@ -1,10 +1,14 @@
package java package java
import ( import (
"encoding/base64"
"io"
"os" "os"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vifraa/gopom" "github.com/vifraa/gopom"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
@@ -63,6 +67,60 @@ func Test_parserPomXML(t *testing.T) {
} }
} }
func Test_decodePomXML_surviveNonUtf8Encoding(t *testing.T) {
// regression for https://github.com/anchore/syft/issues/2044
// we are storing the base64 contents of the pom.xml file. We are doing this to prevent accidental changes to the
// file, which is extremely important for this test.
// for instance, even changing a single character in the file and saving in an IntelliJ IDE will automatically
// convert the file to UTF-8, which will break this test:
// xxd with the original pom.xml
// 00000780: 6964 3e0d 0a20 2020 2020 2020 2020 2020 id>..
// 00000790: 203c 6e61 6d65 3e4a e972 f46d 6520 4d69 <name>J.r.me Mi
// 000007a0: 7263 3c2f 6e61 6d65 3e0d 0a20 2020 2020 rc</name>..
// xxd with the pom.xml converted to UTF-8 (from a simple change with IntelliJ)
// 00000780: 6964 3e0d 0a20 2020 2020 2020 2020 2020 id>..
// 00000790: 203c 6e61 6d65 3e4a efbf bd72 efbf bd6d <name>J...r...m
// 000007a0: 6520 4d69 7263 3c2f 6e61 6d65 3e0d 0a20 e Mirc</name>..
// Note that the name "Jérôme Mirc" was originally interpreted as "J.r.me Mi" and after the save
// is now encoded as "J...r...m" which is not what we want (note the extra bytes for each non UTF-8 character.
// The original 0xe9 byte (é) was converted to 0xefbfbd (�) which is the UTF-8 replacement character.
// This is quite silly on the part of IntelliJ, but it is what it is.
cases := []struct {
name string
fixture string
}{
{
name: "undeclared encoding",
fixture: "test-fixtures/pom/undeclared-iso-8859-encoded-pom.xml.base64",
},
{
name: "declared encoding",
fixture: "test-fixtures/pom/declared-iso-8859-encoded-pom.xml.base64",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
fh, err := os.Open(c.fixture)
require.NoError(t, err)
decoder := base64.NewDecoder(base64.StdEncoding, fh)
proj, err := decodePomXML(decoder)
require.NoError(t, err)
require.NotEmpty(t, proj.Developers)
})
}
}
func Test_parseCommonsTextPomXMLProject(t *testing.T) { func Test_parseCommonsTextPomXMLProject(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
@@ -388,11 +446,29 @@ func Test_resolveProperty(t *testing.T) {
}, },
expected: "org.some.parent", expected: "org.some.parent",
}, },
{
name: "nil pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: nil,
},
expected: "${project.parent.groupId}",
},
{
name: "nil string pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: &gopom.Parent{
GroupID: nil,
},
},
expected: "${project.parent.groupId}",
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
resolved := resolveProperty(test.pom, stringPointer(test.property)) resolved := resolveProperty(test.pom, stringPointer(test.property), test.name)
assert.Equal(t, test.expected, resolved) assert.Equal(t, test.expected, resolved)
}) })
} }
@@ -401,3 +477,28 @@ func Test_resolveProperty(t *testing.T) {
func stringPointer(s string) *string { func stringPointer(s string) *string {
return &s return &s
} }
func Test_getUtf8Reader(t *testing.T) {
tests := []struct {
name string
contents string
}{
{
name: "unknown encoding",
// random binary contents
contents: "BkiJz02JyEWE0nXR6TH///9NicpJweEETIucJIgAAABJicxPjQwhTY1JCE05WQh0BU2J0eunTYshTIusJIAAAAAPHwBNOeV1BUUx2+tWTIlUJDhMiUwkSEyJRCQgSIl8JFBMiQ==",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(tt.contents))
got, err := getUtf8Reader(decoder)
require.NoError(t, err)
gotBytes, err := io.ReadAll(got)
require.NoError(t, err)
// if we couldn't decode the section as UTF-8, we should get a replacement character
assert.Contains(t, string(gotBytes), "�")
})
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@ type GolangBinMetadata struct {
Architecture string `json:"architecture" cyclonedx:"architecture"` Architecture string `json:"architecture" cyclonedx:"architecture"`
H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"` H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"`
MainModule string `json:"mainModule,omitempty" cyclonedx:"mainModule"` MainModule string `json:"mainModule,omitempty" cyclonedx:"mainModule"`
GoCryptoSettings []string `json:"goCryptoSettings,omitempty" cyclonedx:"goCryptoSettings"`
} }
// GolangModMetadata represents all captured data for a Golang source scan with go.mod/go.sum // GolangModMetadata represents all captured data for a Golang source scan with go.mod/go.sum