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')
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')
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')
# 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/^(YAJSV_VERSION := ).*/\1'${YAJSV_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
# export the versions for use with create-pull-request
@@ -49,6 +51,7 @@ jobs:
echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "YAJSV=$YAJSV_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
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 }})
- [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 }})
- [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 }})
This is an auto-generated pull request to update all of the bootstrap tools to the latest versions.
token: ${{ steps.generate-token.outputs.token }}
+2 -2
View File
@@ -10,14 +10,14 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
GLOW_CMD = $(TEMP_DIR)/glow
# Tool versions #################################
GOLANGCILINT_VERSION := v1.54.1
GOLANGCILINT_VERSION := v1.54.2
GOSIMPORTS_VERSION := v0.3.8
BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION := v0.7.0
GORELEASER_VERSION := v1.20.0
YAJSV_VERSION := v1.4.1
COSIGN_VERSION := v2.1.1
QUILL_VERSION := v0.2.0
QUILL_VERSION := v0.4.1
GLOW_VERSION := v1.5.1
# Formatting variables #################################
@@ -27,12 +27,7 @@ report 1!!>
<notification 2>
<notification 3>
<my app can be updated!!
...to this version>
A newer version of syft is available for download: v0.33.0 (installed version is [not provided])
---
+4 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/wagoodman/go-partybus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/event"
"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)
for _, e := range events {
notice, err := parsers.ParseCLIAppUpdateAvailable(e)
newVersion, err := parsers.ParseCLIAppUpdateAvailable(e)
if err != nil {
log.WithFields("error", err).Warn("failed to parse app update notification")
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 {
// don't let this be fatal
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,
Value: "\n\n<my app can be updated!!\n...to this version>\n\n",
Value: "v0.33.0",
},
{
Type: event.CLINotification,
+6 -4
View File
@@ -15,10 +15,10 @@ require (
github.com/facebookincubator/nvdtools v0.1.5
github.com/go-test/deep v1.1.0
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/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/mholt/archiver/v3 v3.5.1
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/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
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/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/docker/distribution v2.8.2+incompatible
@@ -69,9 +69,11 @@ require (
github.com/google/go-containerregistry v0.16.1
github.com/google/licensecheck v0.3.1
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/opencontainers/go-digest v1.0.0
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/vbatts/go-mtree v0.5.3
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/cancelreader v0.2.2 // 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/opencontainers/image-spec v1.1.0-rc3 // 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/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
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.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
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/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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/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/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
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.9/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.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
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/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
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.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s=
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/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI=
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
// 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/google/uuid"
"golang.org/x/exp/slices"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
@@ -139,7 +140,7 @@ func isExpressiblePackageRelationship(ty artifact.RelationshipType) bool {
}
func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependency {
result := make([]cyclonedx.Dependency, 0)
dependencies := map[string]*cyclonedx.Dependency{}
for _, r := range relationships {
exists := isExpressiblePackageRelationship(r.Type)
if !exists {
@@ -160,15 +161,32 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
continue
}
// ind dep
toRef := deriveBomRef(toPkg)
dep := dependencies[toRef]
if dep == nil {
dep = &cyclonedx.Dependency{
Ref: toRef,
Dependencies: &[]string{},
}
dependencies[toRef] = dep
}
innerDeps := []string{}
innerDeps = append(innerDeps, deriveBomRef(fromPkg))
result = append(result, cyclonedx.Dependency{
Ref: deriveBomRef(toPkg),
Dependencies: &innerDeps,
})
fromRef := deriveBomRef(fromPkg)
if !slices.Contains(*dep.Dependencies, fromRef) {
*dep.Dependencies = append(*dep.Dependencies, fromRef)
}
}
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
}
@@ -1,6 +1,7 @@
package cyclonedxhelpers
import (
"fmt"
"testing"
"github.com/CycloneDX/cyclonedx-go"
@@ -43,28 +44,34 @@ func Test_relationships(t *testing.T) {
p1 := pkg.Package{
Name: "p1",
}
p1.SetID()
p2 := pkg.Package{
Name: "p2",
}
p2.SetID()
p3 := pkg.Package{
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 {
name string
sbom sbom.SBOM
expected []string
expected *[]cyclonedx.Dependency
}{
{
name: "package dependencyOf relationships output as dependencies",
sbom: sbom.SBOM{
Artifacts: sbom.Artifacts{
Packages: pkg.NewCollection(p1, p2, p3),
Packages: pkg.NewCollection(p1, p2, p3, p4),
},
Relationships: []artifact.Relationship{
{
@@ -77,9 +84,28 @@ func Test_relationships(t *testing.T) {
To: p1,
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",
@@ -108,28 +134,7 @@ func Test_relationships(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
cdx := ToFormatModel(test.sbom)
got := cdx.Dependencies
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)
require.Equal(t, test.expected, got)
})
}
}
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) {
linkTarget = filepath.Clean(linkTarget)
// if the link is absolute (e.g, /bin/ls -> /bin/busybox) we need to
// resolve relative to the root of the base directory
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget))
// resolve relative to the root of the base directory, if it is not already
// prefixed with a volume name
if filepath.VolumeName(linkTarget) == "" {
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget))
}
} else {
// 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
@@ -667,6 +667,18 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
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 {
@@ -269,6 +269,20 @@ var defaultClassifiers = []classifier{
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]
@@ -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:*:*",
"626": "cpe:2.3:a:626_project:626:*:*:*:*:*: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:*:*",
"@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:*:*",
@@ -1081,6 +1082,7 @@
"spacy": "cpe:2.3:a:explosion:spacy:*:*:*:*:*:python:*:*",
"sqlparse": "cpe:2.3:a:sqlparse_project:sqlparse:*:*:*:*:*:python:*:*",
"tkvideoplayer": "cpe:2.3:a:python:tkvideoplayer:*:*:*:*:*:*:*:*",
"togglee": "cpe:2.3:a:togglee:togglee:*:*:*:*:*:pypi:*:*",
"urllib3": "cpe:2.3:a:python:urllib3:*:*:*:*:*:*:*:*",
"validators": "cpe:2.3:a:validators_project:validators:*:*:*:*:*:python:*:*",
"vault-cli": "cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*",
+15 -8
View File
@@ -21,16 +21,16 @@ var (
"be",
}
primaryJavaManifestGroupIDFields = []string{
PrimaryJavaManifestGroupIDFields = []string{
"Bundle-SymbolicName",
"Extension-Name",
"Specification-Vendor",
"Implementation-Vendor",
"Bundle-SymbolicName",
"Implementation-Vendor-Id",
"Implementation-Title",
"Bundle-Activator",
}
secondaryJavaManifestGroupIDFields = []string{
SecondaryJavaManifestGroupIDFields = []string{
"Automatic-Module-Name",
"Main-Class",
"Package",
@@ -168,7 +168,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
}
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
return ""
}
@@ -184,6 +184,9 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
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) {
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
@@ -242,7 +245,7 @@ func addGroupIDsFromGroupIDsAndArtifactID(groupID, artifactID string) (groupIDs
}
func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []string {
if groupID, ok := defaultArtifactIDToGroupID[pkgName]; ok {
if groupID, ok := DefaultArtifactIDToGroupID[pkgName]; ok {
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
groupIDs := getManifestFieldGroupIDs(manifest, primaryJavaManifestGroupIDFields)
groupIDs := GetManifestFieldGroupIDs(manifest, PrimaryJavaManifestGroupIDFields)
if len(groupIDs) != 0 {
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
// 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.
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 {
return nil
}
@@ -302,3 +305,7 @@ func removeOSCIDirectives(groupID string) string {
func startsWithTopLevelDomain(value string) bool {
return internal.HasAnyOfPrefixes(value, domains...)
}
func looksLikeGroupID(value string) bool {
return strings.Contains(value, ".")
}
@@ -1,6 +1,6 @@
package cpe
var defaultArtifactIDToGroupID = map[string]string{
var DefaultArtifactIDToGroupID = map[string]string{
"ant": "org.apache.ant",
"ant-antlr": "org.apache.ant",
"ant-antunit": "org.apache.ant",
+70 -25
View File
@@ -8,23 +8,67 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package {
fields := strings.Split(strings.TrimSpace(m.Ref), "/")
if len(fields) < 2 {
return nil
type conanRef struct {
Name string
Version string
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
}
p := pkg.Package{
Name: pkgName,
Version: pkgVersion,
Name: ref.Name,
Version: ref.Version,
Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion),
PURL: packageURL(ref),
Language: pkg.CPP,
Type: pkg.ConanPkg,
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 {
fields := strings.Split(strings.Split(m.Ref, "@")[0], "/")
if len(fields) < 2 {
return nil
}
pkgName, pkgVersion := fields[0], fields[1]
if pkgName == "" || pkgVersion == "" {
ref := splitConanRef(m.Ref)
if ref == nil {
return nil
}
p := pkg.Package{
Name: pkgName,
Version: pkgVersion,
Name: ref.Name,
Version: ref.Version,
Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion),
PURL: packageURL(ref),
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType,
@@ -64,13 +102,20 @@ func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *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(
packageurl.TypeConan,
"",
name,
version,
nil, // TODO: no qualifiers (...yet)
ref.User,
ref.Name,
ref.Version,
qualifiers,
"",
).ToString()
}
+1 -1
View File
@@ -36,7 +36,7 @@ func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.Locatio
switch {
case strings.Contains(line, "[requires]"):
inRequirements = true
case strings.ContainsAny(line, "[]#"):
case strings.ContainsAny(line, "[]") || strings.HasPrefix(strings.TrimSpace(line), "#"):
inRequirements = false
}
@@ -52,13 +52,13 @@ func TestParseConanfile(t *testing.T) {
{
Name: "spdlog",
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,
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType,
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,
MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{
Ref: "sdl/2.0.20",
Ref: "sdl/2.0.20#1234567%%987654",
},
},
{
Name: "fltk",
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,
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType,
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"
expected := []pkg.Package{
{
Name: "zlib",
Version: "1.2.12",
PURL: "pkg:conan/zlib@1.2.12",
Name: "spdlog",
Version: "1.11.0",
PURL: "pkg:conan/spdlog@1.11.0",
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType,
Metadata: pkg.ConanLockMetadata{
Ref: "zlib/1.2.12",
Ref: "spdlog/1.11.0",
Options: map[string]string{
"fPIC": "True",
"shared": "False",
"fPIC": "True",
"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",
},
},
@@ -2,15 +2,24 @@
"graph_lock": {
"nodes": {
"0": {
"ref": "zlib/1.2.12",
"options": "fPIC=True\nshared=False",
"requires": [],
"path": "all/conanfile.py",
"ref": "spdlog/1.11.0",
"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": [
"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"
}
},
"revisions_enabled": false
"revisions_enabled": true
},
"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
docopt.cpp/0.6.3
fmt/8.1.1
spdlog/1.9.2
sdl/2.0.20
fltk/1.3.8
spdlog/1.9.2@my_user/my_channel#1234567%%987654
sdl/2.0.20#1234567%%987654
fltk/1.3.8@my_user/my_channel
[generators]
cmake_find_package_multi
+2 -1
View File
@@ -11,7 +11,7 @@ import (
"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 {
dep = dep.Replace
}
@@ -36,6 +36,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debu
Architecture: architecture,
BuildSettings: buildSettings,
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
}
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)
main := c.newGoBinaryPackage(
resolver,
@@ -75,6 +75,7 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *debug
mod.GoVersion,
arch,
gbs,
mod.cryptoSettings,
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:
// 1) reading build info 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) {
log.Trace("golang cataloger: bin parsing: number of builds and readers doesn't match")
return nil
@@ -255,7 +256,7 @@ func createMainModuleFromPath(path string) (mod debug.Module) {
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
if mod == nil {
return pkgs
@@ -277,6 +278,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
mod.GoVersion,
arch,
nil,
mod.cryptoSettings,
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)
if pkg.IsValid(&p) {
+164 -139
View File
@@ -155,7 +155,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
tests := []struct {
name string
mod *debug.BuildInfo
mod *extendedBuildInfo
arch string
expected []pkg.Package
}{
@@ -166,16 +166,18 @@ func TestBuildGoPkgInfo(t *testing.T) {
},
{
name: "package without name",
mod: &debug.BuildInfo{
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
mod: &extendedBuildInfo{
&debug.BuildInfo{
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
},
{
Path: "",
Version: "v0.2.1",
},
},
{
Path: "",
Version: "v0.2.1",
},
},
}, nil,
},
expected: []pkg.Package{
{
@@ -198,26 +200,28 @@ func TestBuildGoPkgInfo(t *testing.T) {
},
{
name: "buildGoPkgInfo parses a blank mod and returns no packages",
mod: &debug.BuildInfo{},
mod: &extendedBuildInfo{&debug.BuildInfo{}, nil},
expected: []pkg.Package(nil),
},
{
name: "parse a mod without main module",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
},
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
},
},
}, nil,
},
expected: []pkg.Package{
{
@@ -246,14 +250,16 @@ func TestBuildGoPkgInfo(t *testing.T) {
{
name: "parse a mod with path but no main module",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
Path: "github.com/a/b/c",
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
Path: "github.com/a/b/c",
}, []string{"boringcrypto + fips"},
},
expected: []pkg.Package{
{
@@ -280,7 +286,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
"GOARCH": "amd64",
"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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
}, nil,
},
expected: []pkg.Package{unmodifiedMain},
},
{
name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{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{
{
@@ -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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{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`},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{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{
{
@@ -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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{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{
{
@@ -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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`},
},
}, nil,
},
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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`},
},
}, nil,
},
expected: []pkg.Package{
{
@@ -525,16 +544,18 @@ func TestBuildGoPkgInfo(t *testing.T) {
{
name: "parse main mod and replace devel version with a pseudo version",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
},
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
},
}, nil,
},
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",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
Version: "v0.2.1",
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
{
Path: "github.com/anchore/client-go",
Version: "v0.0.0-20210222170800-9c70f9b80bcf",
Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=",
Deps: []*debug.Module{
{
Path: "github.com/adrg/xdg",
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{
{
@@ -642,31 +665,33 @@ func TestBuildGoPkgInfo(t *testing.T) {
{
name: "parse a populated mod string and returns packages when a replace directive exists",
arch: archDetails,
mod: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{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=",
mod: &extendedBuildInfo{
&debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
{
Path: "golang.org/x/term",
Version: "v0.0.0-20210927222741-03fcf44c2211",
Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
Replace: &debug.Module{
Deps: []*debug.Module{
{
Path: "golang.org/x/sys",
Version: "v0.0.0-20211006194710-c8a6f5223071",
Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
},
{
Path: "golang.org/x/term",
Version: "v0.0.0-20210916214954-140adaaadfaf",
Sum: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=",
Version: "v0.0.0-20210927222741-03fcf44c2211",
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{
{
+40 -3
View File
@@ -6,12 +6,19 @@ import (
"io"
"runtime/debug"
"github.com/kastenhq/goversion/version"
"github.com/anchore/syft/internal/log"
"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.
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
// with more than one binary
readers, err := unionreader.GetReaders(reader)
@@ -20,7 +27,7 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
return nil, nil
}
var builds []*debug.BuildInfo
var builds []*extendedBuildInfo
for _, r := range readers {
bi, err := getBuildInfo(r)
if err != nil {
@@ -30,7 +37,14 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
if bi == nil {
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)
@@ -38,6 +52,29 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildIn
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) {
defer func() {
if r := recover(); r != nil {
@@ -6,6 +6,7 @@ import (
"runtime/debug"
"testing"
"github.com/kastenhq/goversion/version"
"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.
func uniquePkgKey(p *pkg.Package) string {
func uniquePkgKey(groupID string, p *pkg.Package) string {
if p == nil {
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
@@ -371,13 +371,27 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
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.
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
vPathSuffix := ""
if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) {
vPathSuffix += ":" + pomProperties.ArtifactID
groupID := ""
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
@@ -408,21 +422,26 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po
}
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)
if !ok {
log.WithFields("package", p.String()).Warn("unable to extract java metadata to check for matching package identity")
return false
parentMetadata, parentOk := parentPkg.Metadata.(pkg.JavaMetadata)
if !ok || !parentOk {
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)
if !ok {
log.WithFields("package", p.String()).Warn("unable to extract java metadata from parent for verifying virtual path")
return false
// try to determine identity with the metadata
groupID := groupIDFromJavaMetadata(p.Name, metadata)
parentGroupID := groupIDFromJavaMetadata(parentPkg.Name, parentMetadata)
if uniquePkgKey(groupID, &p) == uniquePkgKey(parentGroupID, parentPkg) {
return true
}
// 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
// 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.
if metadata.PomProperties.ArtifactID != "" && parentPkg.Name == metadata.PomProperties.ArtifactID {
return true
// NOTE: artifactId might not be a good indicator of uniqueness since archives can contain forks with the same name
// 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
}
+6 -44
View File
@@ -174,8 +174,8 @@ func TestParseJar(t *testing.T) {
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as
// a suffix on the virtual path
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:joda-time",
// 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:joda-time",
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time",
@@ -240,7 +240,7 @@ func TestParseJar(t *testing.T) {
Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as
// 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{
Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time",
@@ -659,7 +659,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id",
VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{
Name: "some-name",
GroupID: "some-group-id",
@@ -728,7 +728,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id",
VirtualPath: virtualPath + ":" + "some-group-id" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{
Name: "some-name",
GroupID: "some-group-id",
@@ -797,7 +797,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath + ":" + "some-artifact-id",
VirtualPath: virtualPath + ":" + "com.cloudbees.jenkins.plugins" + ":" + "some-artifact-id",
PomProperties: &pkg.PomProperties{
Name: "some-name",
GroupID: "com.cloudbees.jenkins.plugins",
@@ -894,44 +894,6 @@ func Test_newPackageFromMavenData(t *testing.T) {
},
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",
props: pkg.PomProperties{
+123 -3
View File
@@ -1,6 +1,8 @@
package java
import (
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg"
"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)
func packageURL(name, version string, metadata pkg.JavaMetadata) string {
var groupID = name
groupIDs := cpe.GroupIDsFromJavaMetadata(name, metadata)
if len(groupIDs) > 0 {
groupID = groupIDs[0]
if gID := groupIDFromJavaMetadata(name, metadata); gID != "" {
groupID = gID
}
pURL := packageurl.NewPackageURL(
@@ -23,3 +25,121 @@ func packageURL(name, version string, metadata pkg.JavaMetadata) string {
"")
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) {
tests := []struct {
name string
pkg pkg.Package
expect string
}{
{
name: "maven",
pkg: pkg.Package{
Name: "example-java-app-maven",
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",
},
{
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 {
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
import (
"bytes"
"encoding/xml"
"fmt"
"io"
@@ -8,9 +9,11 @@ import (
"regexp"
"strings"
"github.com/saintfish/chardet"
"github.com/vifraa/gopom"
"golang.org/x/net/html/charset"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@@ -58,12 +61,13 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
artifactID := safeString(p.ArtifactID)
name := safeString(p.Name)
projectURL := safeString(p.URL)
log.WithFields("path", path, "artifactID", artifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
return &pkg.PomProject{
Path: path,
Parent: pomParent(p, p.Parent),
GroupID: resolveProperty(p, p.GroupID),
GroupID: resolveProperty(p, p.GroupID, "groupId"),
ArtifactID: artifactID,
Version: resolveProperty(p, p.Version),
Version: resolveProperty(p, p.Version, "version"),
Name: name,
Description: cleanDescription(p.Description),
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 {
m := pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: resolveProperty(pom, dep.GroupID),
ArtifactID: resolveProperty(pom, dep.ArtifactID),
Scope: resolveProperty(pom, dep.Scope),
GroupID: resolveProperty(pom, dep.GroupID, "groupId"),
ArtifactID: resolveProperty(pom, dep.ArtifactID, "artifactId"),
Scope: resolveProperty(pom, dep.Scope, "scope"),
},
}
name := safeString(dep.ArtifactID)
version := resolveProperty(pom, dep.Version)
version := resolveProperty(pom, dep.Version, "version")
p := pkg.Package{
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) {
decoder := xml.NewDecoder(content)
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil"
inputReader, err := getUtf8Reader(content)
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
if err := decoder.Decode(&project); err != nil {
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
}
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) {
if parent == nil {
return nil
@@ -116,9 +153,9 @@ func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.PomParent)
artifactID := safeString(parent.ArtifactID)
result = &pkg.PomParent{
GroupID: resolveProperty(pom, parent.GroupID),
GroupID: resolveProperty(pom, parent.GroupID, "groupId"),
ArtifactID: artifactID,
Version: resolveProperty(pom, parent.Version),
Version: resolveProperty(pom, parent.Version, "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
//
//nolint:gocognit
func resolveProperty(pom gopom.Project, property *string) string {
func resolveProperty(pom gopom.Project, property *string, propertyName string) string {
propertyCase := safeString(property)
log.WithFields("existingPropertyValue", propertyCase, "propertyName", propertyName).Trace("resolving property")
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)
if value, ok := entries[propertyName]; ok {
return value
@@ -172,14 +210,26 @@ func resolveProperty(pom gopom.Project, property *string) string {
for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ {
f := pomValueType.Field(fieldNum)
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 {
pomValue = pomValue.Field(fieldNum)
pomValueType = pomValue.Type()
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()
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 {
return fmt.Sprintf("%v", pomValue.Interface())
}
+102 -1
View File
@@ -1,10 +1,14 @@
package java
import (
"encoding/base64"
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vifraa/gopom"
"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) {
tests := []struct {
input string
@@ -388,11 +446,29 @@ func Test_resolveProperty(t *testing.T) {
},
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 {
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)
})
}
@@ -401,3 +477,28 @@ func Test_resolveProperty(t *testing.T) {
func stringPointer(s string) *string {
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"`
H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"`
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