mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 07:56:36 +01:00
Merge remote-tracking branch 'upstream/main' into add-nested-archive-cataloging
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ tools:
|
|||||||
# used to parse JSON/YAML annotations on the fixture cache image
|
# used to parse JSON/YAML annotations on the fixture cache image
|
||||||
- name: yq
|
- name: yq
|
||||||
version:
|
version:
|
||||||
want: v4.53.6
|
want: v4.54.1
|
||||||
method: github-release
|
method: github-release
|
||||||
with:
|
with:
|
||||||
repo: mikefarah/yq
|
repo: mikefarah/yq
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/syft/internal/redact"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandCanRunMultipleTimes(t *testing.T) {
|
||||||
|
// https://github.com/anchore/syft/issues/2285
|
||||||
|
t.Cleanup(redact.Reset)
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
_, cmd := create(clio.Identification{Name: "syft"}, io.Discard)
|
||||||
|
cmd.SetArgs([]string{"cataloger", "list", "-o", "json"})
|
||||||
|
require.NoError(t, cmd.Execute())
|
||||||
|
|
||||||
|
// the store is released at the end of each run so the next one can set its own
|
||||||
|
require.Nil(t, redact.Get())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig
|
|||||||
stereoscope.SetBus(state.Bus)
|
stereoscope.SetBus(state.Bus)
|
||||||
bus.Set(state.Bus)
|
bus.Set(state.Bus)
|
||||||
|
|
||||||
|
// the redact store is process-global and only cleared when a run ends (see the post-run below), so
|
||||||
|
// setting it while another run is still in flight panics rather than splitting secrets across stores.
|
||||||
redact.Set(state.RedactStore)
|
redact.Set(state.RedactStore)
|
||||||
|
|
||||||
log.Set(state.Logger)
|
log.Set(state.Logger)
|
||||||
@@ -48,8 +50,14 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
).
|
).
|
||||||
WithPostRuns(func(_ *clio.State, _ error) {
|
WithPostRuns(func(state *clio.State, _ error) {
|
||||||
stereoscope.Cleanup() //nolint:staticcheck // we don't have access to the image object here
|
stereoscope.Cleanup() //nolint:staticcheck // we don't have access to the image object here
|
||||||
|
|
||||||
|
// release the redact store now that nothing in this run can report anymore, so a later run in the same
|
||||||
|
// process (e.g. syft embedded as a library) can set its own. Only release the store this run set.
|
||||||
|
if redact.Get() == state.RedactStore {
|
||||||
|
redact.Reset()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return clioCfg
|
return clioCfg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/go-logger/adapter/discard"
|
||||||
|
gologgerredact "github.com/anchore/go-logger/adapter/redact"
|
||||||
|
"github.com/anchore/syft/internal/redact"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppClioSetupConfigInitializerPanicsWhileRunInFlight(t *testing.T) {
|
||||||
|
t.Cleanup(redact.Reset)
|
||||||
|
|
||||||
|
cfg := AppClioSetupConfig(clio.Identification{Name: "syft"}, io.Discard)
|
||||||
|
require.Len(t, cfg.Initializers, 1)
|
||||||
|
|
||||||
|
newState := func() *clio.State {
|
||||||
|
return &clio.State{Logger: discard.New(), RedactStore: gologgerredact.NewStore()}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, cfg.Initializers[0](newState()))
|
||||||
|
|
||||||
|
// a second run starting before the first has finished must not silently replace the store, otherwise
|
||||||
|
// secrets added to the first store would no longer be redacted
|
||||||
|
require.PanicsWithValue(t, "replace existing redaction store (probably unintentional)", func() {
|
||||||
|
_ = cfg.Initializers[0](newState())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -18,11 +18,11 @@ require (
|
|||||||
github.com/anchore/go-homedir v0.1.1
|
github.com/anchore/go-homedir v0.1.1
|
||||||
github.com/anchore/go-logger v0.2.0
|
github.com/anchore/go-logger v0.2.0
|
||||||
github.com/anchore/go-macholibre v0.1.1
|
github.com/anchore/go-macholibre v0.1.1
|
||||||
github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87
|
github.com/anchore/go-rpmdb v0.2.2
|
||||||
github.com/anchore/go-sync v0.1.2
|
github.com/anchore/go-sync v0.1.2
|
||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||||
github.com/anchore/packageurl-go v0.2.0
|
github.com/anchore/packageurl-go v0.2.0
|
||||||
github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6
|
github.com/anchore/stereoscope v0.3.4
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||||
@@ -188,7 +188,7 @@ require (
|
|||||||
github.com/containers/ocicrypt v1.3.2 // indirect
|
github.com/containers/ocicrypt v1.3.2 // indirect
|
||||||
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
|
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.7.0 // indirect
|
github.com/cyphar/filepath-securejoin v0.7.0 // indirect
|
||||||
github.com/docker/cli v29.8.1+incompatible // indirect
|
github.com/docker/cli v29.8.2+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.9.8 // indirect
|
github.com/docker/docker-credential-helpers v0.9.8 // indirect
|
||||||
github.com/docker/go-connections v0.8.1 // indirect
|
github.com/docker/go-connections v0.8.1 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
@@ -281,11 +281,11 @@ require (
|
|||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect
|
github.com/secure-systems-lab/go-securesystemslib v0.11.1 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/sigstore/fulcio v1.8.7 // indirect
|
github.com/sigstore/fulcio v1.8.7 // indirect
|
||||||
github.com/sigstore/protobuf-specs v0.5.1 // indirect
|
github.com/sigstore/protobuf-specs v0.5.2 // indirect
|
||||||
github.com/sigstore/sigstore v1.10.8 // indirect
|
github.com/sigstore/sigstore v1.10.11 // indirect
|
||||||
github.com/sirupsen/logrus v1.10.2 // indirect
|
github.com/sirupsen/logrus v1.10.2 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||||
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
|
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
|
||||||
@@ -299,7 +299,7 @@ require (
|
|||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/sylabs/sif/v2 v2.24.1 // indirect
|
github.com/sylabs/sif/v2 v2.24.2 // indirect
|
||||||
github.com/sylabs/squashfs v1.0.6 // indirect
|
github.com/sylabs/squashfs v1.0.6 // indirect
|
||||||
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ github.com/anchore/go-lzo v0.1.1 h1:IwL/fvkdtlIrYIXck6WxZ3nb8WjjHziYYmGxlooyOnM=
|
|||||||
github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||||
github.com/anchore/go-macholibre v0.1.1 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M=
|
github.com/anchore/go-macholibre v0.1.1 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M=
|
||||||
github.com/anchore/go-macholibre v0.1.1/go.mod h1:YNq2610RlvGCK0Za+Klz/OCMtUaEnwwAGA/VfUAQaro=
|
github.com/anchore/go-macholibre v0.1.1/go.mod h1:YNq2610RlvGCK0Za+Klz/OCMtUaEnwwAGA/VfUAQaro=
|
||||||
github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87 h1:aY7DcEkjgzMRbCCJsGHu8eQeL5I88V8G9ISEWtfrlCU=
|
github.com/anchore/go-rpmdb v0.2.2 h1:44VC7mvFAxV9t5JUZYg3S9sqG+E94iZvyWACMDq9hSk=
|
||||||
github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87/go.mod h1:Y64EbetdqhryMjtk8IzXkdCQw/aNDx+feZCT9aj6rjs=
|
github.com/anchore/go-rpmdb v0.2.2/go.mod h1:Y64EbetdqhryMjtk8IzXkdCQw/aNDx+feZCT9aj6rjs=
|
||||||
github.com/anchore/go-struct-converter v0.2.0-rc2 h1:q+859fW2/jbHJHB2etbNfRlFwYpknyvbqqk1hUdamQ4=
|
github.com/anchore/go-struct-converter v0.2.0-rc2 h1:q+859fW2/jbHJHB2etbNfRlFwYpknyvbqqk1hUdamQ4=
|
||||||
github.com/anchore/go-struct-converter v0.2.0-rc2/go.mod h1:cDBA5vhcR62nXWo8QH9/Kk2807o65ISaHPNPX66L+Uw=
|
github.com/anchore/go-struct-converter v0.2.0-rc2/go.mod h1:cDBA5vhcR62nXWo8QH9/Kk2807o65ISaHPNPX66L+Uw=
|
||||||
github.com/anchore/go-sync v0.1.2 h1:RDD5RCanrWqhbtBulqPSZpERadUlgcYjpybYzYd3Uk8=
|
github.com/anchore/go-sync v0.1.2 h1:RDD5RCanrWqhbtBulqPSZpERadUlgcYjpybYzYd3Uk8=
|
||||||
@@ -150,8 +150,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
|||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||||
github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk=
|
github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk=
|
||||||
github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE=
|
github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE=
|
||||||
github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6 h1:6l3YwREGEXX/w4tBhRcPb7efYWo4pf8+fmAy0mND4EY=
|
github.com/anchore/stereoscope v0.3.4 h1:dT5AfjCTXUJowMiRIc+VBQVwvwnIEwnxxwA1GwAkMxw=
|
||||||
github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6/go.mod h1:LxL4iOFtB8XMbA1MykUUTkmtQmDuYdbr4+TkBkCfljU=
|
github.com/anchore/stereoscope v0.3.4/go.mod h1:+HxqHTt7dvH8mea0mf3ulqkf084VvHiT1grpAQf5uEs=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
@@ -353,8 +353,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/docker/cli v29.8.1+incompatible h1:qYL1bCp6cRw2SB1xmLlIOPyV171dilw9W2Jew38vy9c=
|
github.com/docker/cli v29.8.2+incompatible h1:2zgdFuoFst2T80oS42vhKGxkd0JRo305eIEKTsxR7pQ=
|
||||||
github.com/docker/cli v29.8.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.8.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q=
|
github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q=
|
||||||
@@ -893,8 +893,8 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7
|
|||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
|
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
|
||||||
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs=
|
github.com/secure-systems-lab/go-securesystemslib v0.11.1 h1:ayahDPjfSIqKegyt5YVGEvQ7SAi72GHXTy+b2YjUd5w=
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q=
|
github.com/secure-systems-lab/go-securesystemslib v0.11.1/go.mod h1:UyOjhoZLi76ir63u1ptubEevVe6dlNPmNxwgVvKqFFY=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
@@ -903,10 +903,10 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
|
|||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sigstore/fulcio v1.8.7 h1:d7QJQxukgXarqPCT9uDNeyzt1Bbt/biw58/VfOnwI/o=
|
github.com/sigstore/fulcio v1.8.7 h1:d7QJQxukgXarqPCT9uDNeyzt1Bbt/biw58/VfOnwI/o=
|
||||||
github.com/sigstore/fulcio v1.8.7/go.mod h1:7fX+QyigZxRGKTgHvTfOfxwwniCDxK02PVloHy5/vg4=
|
github.com/sigstore/fulcio v1.8.7/go.mod h1:7fX+QyigZxRGKTgHvTfOfxwwniCDxK02PVloHy5/vg4=
|
||||||
github.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U=
|
github.com/sigstore/protobuf-specs v0.5.2 h1:RSWWUY8QrVTxbYH00jY/jg2e7YnjzrpwP+PeHTMll0E=
|
||||||
github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats=
|
github.com/sigstore/protobuf-specs v0.5.2/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats=
|
||||||
github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4=
|
github.com/sigstore/sigstore v1.10.11 h1:fjrwDfR0AWmRONYV/hKGJyqbT0osMtaBz9FTZ6MH9XI=
|
||||||
github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M=
|
github.com/sigstore/sigstore v1.10.11/go.mod h1:ecPk+ftLqP8/TmzQFJmn0+QrL2yUQIJvyzVKNmDiw6U=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
@@ -970,8 +970,8 @@ github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUm
|
|||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/sylabs/sif/v2 v2.24.1 h1:OhTOfTwBaGXfbWYWXK9C6Pojma3A1bTaNJ6CEhyuKok=
|
github.com/sylabs/sif/v2 v2.24.2 h1:rdkFf6z3xLC6yKweU7S9marlKfkS91Ht0ruIri6YWFY=
|
||||||
github.com/sylabs/sif/v2 v2.24.1/go.mod h1:PRq9MoP0g+p0qE5ZRGrOhyVAUdHrPsAVmXHgoiW6VU0=
|
github.com/sylabs/sif/v2 v2.24.2/go.mod h1:bbrkk0JhTX+xNJcnUtmDkivoO2r4DGWM3LxNR0U2ooA=
|
||||||
github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI=
|
github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI=
|
||||||
github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE=
|
github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE=
|
||||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
|
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ func Set(s redact.Store) {
|
|||||||
store = s
|
store = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset clears the global redaction store. It must only be called once a command
|
||||||
|
// run has fully finished (nothing left to Add or Apply), so that a later run can
|
||||||
|
// Set its own store. Calling it while a run is in flight would let a second Set
|
||||||
|
// split secrets across two stores and leak the ones in the dropped store.
|
||||||
|
func Reset() {
|
||||||
|
store = nil
|
||||||
|
}
|
||||||
|
|
||||||
func Get() redact.Store {
|
func Get() redact.Store {
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ catalogers:
|
|||||||
method: glob # AUTO-GENERATED
|
method: glob # AUTO-GENERATED
|
||||||
criteria: # AUTO-GENERATED
|
criteria: # AUTO-GENERATED
|
||||||
- '**/*.deb'
|
- '**/*.deb'
|
||||||
|
- '**/*.ipk'
|
||||||
metadata_types: # AUTO-GENERATED
|
metadata_types: # AUTO-GENERATED
|
||||||
- pkg.DpkgArchiveEntry
|
- pkg.DpkgArchiveEntry
|
||||||
package_types: # AUTO-GENERATED
|
package_types: # AUTO-GENERATED
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func NewDBCataloger() pkg.Cataloger {
|
|||||||
WithProcessors(dependency.Processor(dbEntryDependencySpecifier))
|
WithProcessors(dependency.Processor(dbEntryDependencySpecifier))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewArchiveCataloger returns a new Debian package cataloger object capable of parsing .deb archive files
|
// NewArchiveCataloger returns a new Debian package cataloger object capable of parsing .deb and .ipk (opkg) archive files
|
||||||
func NewArchiveCataloger() pkg.Cataloger {
|
func NewArchiveCataloger() pkg.Cataloger {
|
||||||
return generic.NewCataloger("deb-archive-cataloger").
|
return generic.NewCataloger("deb-archive-cataloger").
|
||||||
WithParserByGlobs(parseDebArchive, "**/*.deb")
|
WithParserByGlobs(parseDebArchive, "**/*.deb", "**/*.ipk")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,6 +276,36 @@ func TestDpkgArchiveCataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
name: "image-single-ipk",
|
||||||
|
expected: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "libz1",
|
||||||
|
Version: "1.3.2-r0",
|
||||||
|
FoundBy: "deb-archive-cataloger",
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("/libz1_1.3.2-r0_x86-64-v3.ipk"),
|
||||||
|
),
|
||||||
|
Type: pkg.DebPkg,
|
||||||
|
Metadata: pkg.DpkgArchiveEntry{
|
||||||
|
Package: "libz1",
|
||||||
|
Source: "zlib",
|
||||||
|
SourceVersion: "1.3.2",
|
||||||
|
Version: "1.3.2-r0",
|
||||||
|
Architecture: "x86-64-v3",
|
||||||
|
Maintainer: "Poky Maintainers <poky@lists.yoctoproject.org>",
|
||||||
|
InstalledSize: 0,
|
||||||
|
Description: `Zlib Compression Library
|
||||||
|
Zlib is a general-purpose, patent-free, lossless data compression library
|
||||||
|
which is used by many different programs.`,
|
||||||
|
Provides: []string{"zlib"},
|
||||||
|
Depends: []string{"musl (>= 1.2.6+git0+9fa28ece75)"},
|
||||||
|
},
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocationsWithContext(ctx, "Zlib"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/scylladb/go-set/strset"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
@@ -61,32 +63,65 @@ func newDpkgPackage(ctx context.Context, d dpkgExtractedMetadata, dbLocation fil
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a declared License field holds a single expression, a few hundred bytes at most in practice. Anything
|
||||||
|
// much larger only feeds the expression parser (which recurses on parentheses) attacker-sized input.
|
||||||
|
const maxDeclaredLicenseLen = 4096
|
||||||
|
|
||||||
|
var (
|
||||||
|
// opkg/ipkg (Yocto via bitbake) declare licenses with "&" for AND and "|" for OR
|
||||||
|
bitbakeLicenseOperators = strings.NewReplacer("&", " AND ", "|", " OR ")
|
||||||
|
tightenParens = strings.NewReplacer("( ", "(", " )", ")")
|
||||||
|
licenseListSeparators = strings.NewReplacer("(", " ", ")", " ")
|
||||||
|
)
|
||||||
|
|
||||||
// extractDeclaredLicenses converts a License field from the status DB into a license set. Returns nil
|
// extractDeclaredLicenses converts a License field from the status DB into a license set. Returns nil
|
||||||
// for empty input so standard dpkg entries (which never declare License inline) incur no allocation.
|
// for empty input so standard dpkg entries (which never declare License inline) incur no allocation.
|
||||||
// Mirrors the alpine cataloger's approach: keep the value whole if it parses as a valid SPDX expression,
|
|
||||||
// otherwise split on whitespace to handle space-separated lists.
|
|
||||||
func extractDeclaredLicenses(ctx context.Context, raw string, dbLocation file.Location) []pkg.License {
|
func extractDeclaredLicenses(ctx context.Context, raw string, dbLocation file.Location) []pkg.License {
|
||||||
|
values := declaredLicenseValues(raw)
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pkg.NewLicensesFromLocationWithContext(ctx, dbLocation, values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// declaredLicenseValues converts an inline License field (opkg status DB or an archive control file) into
|
||||||
|
// license values. Bitbake operators are rewritten to SPDX ones and the value is kept whole if it then parses
|
||||||
|
// as a valid SPDX expression (mirroring the alpine cataloger), otherwise it is treated as a list of license
|
||||||
|
// names with operators and grouping dropped.
|
||||||
|
func declaredLicenseValues(raw string) []string {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
licenseStrings := []string{raw}
|
if len(raw) > maxDeclaredLicenseLen {
|
||||||
if _, err := license.ParseExpression(raw); err != nil {
|
log.WithFields("length", len(raw)).Debug("ignoring oversized declared license field")
|
||||||
licenseStrings = strings.Fields(raw)
|
return nil
|
||||||
}
|
}
|
||||||
return pkg.NewLicensesFromLocationWithContext(ctx, dbLocation, licenseStrings...)
|
|
||||||
|
expr := tightenParens.Replace(strings.Join(strings.Fields(bitbakeLicenseOperators.Replace(raw)), " "))
|
||||||
|
if _, err := license.ParseExpression(expr); err == nil {
|
||||||
|
return []string{expr}
|
||||||
|
}
|
||||||
|
|
||||||
|
var values []string
|
||||||
|
seen := strset.New()
|
||||||
|
for _, field := range strings.Fields(licenseListSeparators.Replace(expr)) {
|
||||||
|
if field == "AND" || field == "OR" || seen.Has(field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen.Add(field)
|
||||||
|
values = append(values, field)
|
||||||
|
}
|
||||||
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebArchivePackage(ctx context.Context, location file.Location, metadata pkg.DpkgArchiveEntry, licenseStrings []string) pkg.Package {
|
func newDebArchivePackage(ctx context.Context, location file.Location, metadata pkg.DpkgArchiveEntry, licenseStrings []string) pkg.Package {
|
||||||
p := pkg.Package{
|
p := pkg.Package{
|
||||||
Name: metadata.Package,
|
Name: metadata.Package,
|
||||||
Version: metadata.Version,
|
Version: metadata.Version,
|
||||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, licenseStrings...)...),
|
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, licenseStrings...)...),
|
||||||
Type: pkg.DebPkg,
|
Type: pkg.DebPkg,
|
||||||
PURL: packageURL(
|
PURL: packageURL(pkg.DpkgDBEntry(metadata), archiveDistro(location)),
|
||||||
pkg.DpkgDBEntry(metadata),
|
|
||||||
// we don't know the distro information, but since this is a deb file then we can reasonably assume it is a debian-based distro
|
|
||||||
&linux.Release{IDLike: []string{"debian"}},
|
|
||||||
),
|
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
}
|
}
|
||||||
@@ -95,6 +130,17 @@ func newDebArchivePackage(ctx context.Context, location file.Location, metadata
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// archiveDistro guesses the distro an archive was built for. We don't know the distro information, but a .deb
|
||||||
|
// can reasonably be assumed to target a debian-based distro. An .ipk targets opkg-based distros (Yocto,
|
||||||
|
// OpenWrt) which have no purl type, so it gets no distro and therefore no PURL, the same as packages found in
|
||||||
|
// an opkg status DB.
|
||||||
|
func archiveDistro(location file.Location) *linux.Release {
|
||||||
|
if strings.EqualFold(path.Ext(location.Path()), ".ipk") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &linux.Release{IDLike: []string{"debian"}}
|
||||||
|
}
|
||||||
|
|
||||||
// PackageURL returns the PURL for the specific Debian package (see https://github.com/package-url/purl-spec)
|
// PackageURL returns the PURL for the specific Debian package (see https://github.com/package-url/purl-spec)
|
||||||
func packageURL(m pkg.DpkgDBEntry, distro *linux.Release) string {
|
func packageURL(m pkg.DpkgDBEntry, distro *linux.Release) string {
|
||||||
if distro == nil {
|
if distro == nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package debian
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@@ -145,6 +146,36 @@ func Test_extractDeclaredLicenses(t *testing.T) {
|
|||||||
raw: "GPL-2.0 BSD-3-Clause",
|
raw: "GPL-2.0 BSD-3-Clause",
|
||||||
expected: []string{"GPL-2.0", "BSD-3-Clause"},
|
expected: []string{"GPL-2.0", "BSD-3-Clause"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bitbake operators become an SPDX expression",
|
||||||
|
raw: "MIT & (BSD-3-Clause | Apache-2.0) & ( GPL-2.0-or-later )",
|
||||||
|
expected: []string{"MIT AND (BSD-3-Clause OR Apache-2.0) AND (GPL-2.0-or-later)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bitbake grouping is preserved rather than split",
|
||||||
|
raw: "(MIT & BSD-3-Clause) | Apache-2.0",
|
||||||
|
expected: []string{"(MIT AND BSD-3-Clause) OR Apache-2.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "parenthesized alternatives are preserved",
|
||||||
|
raw: "(MIT) | (Apache-2.0)",
|
||||||
|
expected: []string{"(MIT) OR (Apache-2.0)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-SPDX names drop operators and grouping",
|
||||||
|
raw: "GPLv2+ & (LGPLv2.1 | BSD) & GPLv2+",
|
||||||
|
expected: []string{"GPLv2+", "LGPLv2.1", "BSD"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "oversized field is ignored",
|
||||||
|
raw: strings.Repeat("MIT & ", maxDeclaredLicenseLen),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deeply nested parens are ignored",
|
||||||
|
raw: strings.Repeat("(", maxDeclaredLicenseLen) + "MIT" + strings.Repeat(")", maxDeclaredLicenseLen),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package debian
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -23,18 +24,27 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseDebArchive parses a Debian package archive (.deb) file and returns the packages it contains.
|
// parseDebArchive parses a Debian package archive (.deb) or opkg package (.ipk) file and returns the packages it
|
||||||
// A .deb file is an ar archive containing three main files:
|
// contains. Both are ar archives containing three main files:
|
||||||
// - debian-binary: Version of the .deb format (usually "2.0")
|
// - debian-binary: Version of the archive format (usually "2.0")
|
||||||
// - control.tar.gz/xz/zst: Contains package metadata (control file, md5sums, conffiles)
|
// - control.tar.gz/xz/zst: Contains package metadata (control file, md5sums, conffiles)
|
||||||
// - data.tar.gz/xz/zst: Contains the actual files to be installed (not processed by this cataloger)
|
// - data.tar.gz/xz/zst: Contains the actual files to be installed (not processed by this cataloger)
|
||||||
//
|
//
|
||||||
// This function extracts and processes the control information to create package metadata.
|
// This function extracts and processes the control information to create package metadata. Licenses come
|
||||||
|
// from copyright files in the data tar, falling back to the control file's License field (common for .ipk).
|
||||||
|
// Only ar-framed .ipk files are supported; the older gzipped-tar framing (e.g. OpenWrt's ipkg-build) is
|
||||||
|
// rejected as an invalid archive.
|
||||||
func parseDebArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseDebArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
arReader := ar.NewReader(reader)
|
validatedReader, err := newValidatedArReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid ar archive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arReader := ar.NewReader(validatedReader)
|
||||||
|
|
||||||
var metadata *pkg.DpkgArchiveEntry
|
var metadata *pkg.DpkgArchiveEntry
|
||||||
var licenses []string
|
var licenses []string
|
||||||
|
var ctrlLicenses []string
|
||||||
var unknownErr error
|
var unknownErr error
|
||||||
var sawControl, sawData bool
|
var sawControl, sawData bool
|
||||||
for {
|
for {
|
||||||
@@ -56,7 +66,7 @@ func parseDebArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, unknown.New(reader.Location, fmt.Errorf("failed to decompress control.tar.* file: %w", err))
|
return nil, nil, unknown.New(reader.Location, fmt.Errorf("failed to decompress control.tar.* file: %w", err))
|
||||||
}
|
}
|
||||||
metadata, err = processControlTar(dcReader)
|
metadata, ctrlLicenses, err = processControlTar(dcReader)
|
||||||
switch {
|
switch {
|
||||||
case err != nil && metadata == nil:
|
case err != nil && metadata == nil:
|
||||||
return nil, nil, unknown.New(reader.Location, fmt.Errorf("failed to process control.tar.* file: %w", err))
|
return nil, nil, unknown.New(reader.Location, fmt.Errorf("failed to process control.tar.* file: %w", err))
|
||||||
@@ -87,6 +97,10 @@ func parseDebArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
|
|||||||
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no application found described in .dpkg archive"))
|
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no application found described in .dpkg archive"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(licenses) == 0 && len(ctrlLicenses) > 0 {
|
||||||
|
licenses = ctrlLicenses
|
||||||
|
}
|
||||||
|
|
||||||
// a partial parse still yields a usable package, so report what went wrong alongside it rather than
|
// a partial parse still yields a usable package, so report what went wrong alongside it rather than
|
||||||
// dropping either one
|
// dropping either one
|
||||||
return []pkg.Package{
|
return []pkg.Package{
|
||||||
@@ -128,10 +142,10 @@ func processDataTar(dcReader io.ReadCloser) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// processControlTar always returns whatever metadata it managed to parse, even alongside a non-nil
|
// processControlTar always returns whatever metadata it managed to parse, even alongside a non-nil
|
||||||
// error. A non-nil error with non-nil metadata means the package is usable but incomplete (e.g. a
|
// error, along with any licenses declared in the control file's License field. A non-nil error with
|
||||||
// clipped file listing); nil metadata means nothing usable was found and the caller should treat it
|
// non-nil metadata means the package is usable but incomplete (e.g. a clipped file listing); nil
|
||||||
// as fatal.
|
// metadata means nothing usable was found and the caller should treat it as fatal.
|
||||||
func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, []string, error) {
|
||||||
defer internal.CloseAndLogError(dcReader, "")
|
defer internal.CloseAndLogError(dcReader, "")
|
||||||
|
|
||||||
tarReader := tar.NewReader(dcReader)
|
tarReader := tar.NewReader(dcReader)
|
||||||
@@ -140,6 +154,7 @@ func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
|||||||
var files []pkg.DpkgFileRecord
|
var files []pkg.DpkgFileRecord
|
||||||
var confFileRecords []pkg.DpkgFileRecord
|
var confFileRecords []pkg.DpkgFileRecord
|
||||||
var listingErr error
|
var listingErr error
|
||||||
|
var licenses []string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
header, err := tarReader.Next()
|
header, err := tarReader.Next()
|
||||||
@@ -147,7 +162,7 @@ func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return metadata, fmt.Errorf("failed to read control tar: %w", err)
|
return metadata, nil, fmt.Errorf("failed to read control tar: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch filepath.Base(header.Name) {
|
switch filepath.Base(header.Name) {
|
||||||
@@ -155,12 +170,13 @@ func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
|||||||
// parseDpkgStatus already streams via bufio.Reader
|
// parseDpkgStatus already streams via bufio.Reader
|
||||||
entries, err := parseDpkgStatus(tarReader)
|
entries, err := parseDpkgStatus(tarReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse control file: %w", err)
|
return nil, nil, fmt.Errorf("failed to parse control file: %w", err)
|
||||||
}
|
}
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil, fmt.Errorf("no package entries found in control file")
|
return nil, nil, fmt.Errorf("no package entries found in control file")
|
||||||
}
|
}
|
||||||
entry := pkg.DpkgArchiveEntry(entries[0].toDpkgEntry())
|
entry := pkg.DpkgArchiveEntry(entries[0].toDpkgEntry())
|
||||||
|
licenses = declaredLicenseValues(entries[0].License)
|
||||||
metadata = &entry
|
metadata = &entry
|
||||||
case "md5sums":
|
case "md5sums":
|
||||||
// parseDpkgMD5Info streams via bufio.Scanner and reports its own clipping/scan errors
|
// parseDpkgMD5Info streams via bufio.Scanner and reports its own clipping/scan errors
|
||||||
@@ -176,7 +192,7 @@ func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
return nil, fmt.Errorf("control file not found in archive")
|
return nil, nil, fmt.Errorf("control file not found in archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(confFileRecords) > 0 && len(files) > 0 {
|
if len(confFileRecords) > 0 && len(files) > 0 {
|
||||||
@@ -195,7 +211,22 @@ func processControlTar(dcReader io.ReadCloser) (*pkg.DpkgArchiveEntry, error) {
|
|||||||
|
|
||||||
// a clipped or unreadable file listing leaves the package usable, so hand both back and let the
|
// a clipped or unreadable file listing leaves the package usable, so hand both back and let the
|
||||||
// caller decide
|
// caller decide
|
||||||
return metadata, listingErr
|
return metadata, licenses, listingErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValidatedArReader checks for the ar global header before handing the stream to the ar reader, which
|
||||||
|
// otherwise skips those bytes unchecked and reads garbage headers from non-ar input.
|
||||||
|
func newValidatedArReader(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||||
|
prefix := make([]byte, len(ar.GLOBAL_HEADER))
|
||||||
|
if _, err := io.ReadFull(reader, prefix); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read ar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(prefix, []byte(ar.GLOBAL_HEADER)) {
|
||||||
|
return nil, fmt.Errorf("expected ar header %q, got %q", string(ar.GLOBAL_HEADER), string(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.NopCloser(io.MultiReader(bytes.NewReader(prefix), reader)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// a .deb's member sizes are bounded by the archive, but what they decompress to is not: xz and zstd
|
// a .deb's member sizes are bounded by the archive, but what they decompress to is not: xz and zstd
|
||||||
|
|||||||
@@ -26,12 +26,13 @@ import (
|
|||||||
func TestProcessControlTar(t *testing.T) {
|
func TestProcessControlTar(t *testing.T) {
|
||||||
tarBytes := createTestTarWithControlFiles(t)
|
tarBytes := createTestTarWithControlFiles(t)
|
||||||
|
|
||||||
metadata, err := processControlTar(io.NopCloser(bytes.NewReader(tarBytes)))
|
metadata, licenses, err := processControlTar(io.NopCloser(bytes.NewReader(tarBytes)))
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, metadata)
|
require.NotNil(t, metadata)
|
||||||
|
|
||||||
assert.Equal(t, "test-package", metadata.Package)
|
assert.Equal(t, "test-package", metadata.Package)
|
||||||
|
assert.Equal(t, []string{"MIT"}, licenses)
|
||||||
assert.Equal(t, "1.0.0", metadata.Version)
|
assert.Equal(t, "1.0.0", metadata.Version)
|
||||||
|
|
||||||
// md5sums should have been parsed into file records
|
// md5sums should have been parsed into file records
|
||||||
@@ -43,6 +44,14 @@ func TestProcessControlTar(t *testing.T) {
|
|||||||
assert.True(t, metadata.Files[0].IsConfigFile, "file listed in conffiles should be marked as config")
|
assert.True(t, metadata.Files[0].IsConfigFile, "file listed in conffiles should be marked as config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseDebArchive_RejectsInvalidArchiveHeader(t *testing.T) {
|
||||||
|
reader := file.NewLocationReadCloser(file.NewLocation("test.deb"), io.NopCloser(bytes.NewReader([]byte("not-an-ar-archive"))))
|
||||||
|
|
||||||
|
_, _, err := parseDebArchive(context.Background(), nil, nil, reader)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "ar archive")
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessControlTar_ConfigFileMarking(t *testing.T) {
|
func TestProcessControlTar_ConfigFileMarking(t *testing.T) {
|
||||||
// Create a tar where conffiles lists paths that overlap with md5sums entries
|
// Create a tar where conffiles lists paths that overlap with md5sums entries
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@@ -61,9 +70,10 @@ func TestProcessControlTar_ConfigFileMarking(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, tw.Close())
|
require.NoError(t, tw.Close())
|
||||||
|
|
||||||
metadata, err := processControlTar(io.NopCloser(bytes.NewReader(buf.Bytes())))
|
metadata, licenses, err := processControlTar(io.NopCloser(bytes.NewReader(buf.Bytes())))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, metadata.Files, 3)
|
require.Len(t, metadata.Files, 3)
|
||||||
|
assert.Empty(t, licenses)
|
||||||
|
|
||||||
assert.True(t, metadata.Files[0].IsConfigFile, "first file should be marked as config file")
|
assert.True(t, metadata.Files[0].IsConfigFile, "first file should be marked as config file")
|
||||||
assert.True(t, metadata.Files[1].IsConfigFile, "second file should be marked as config file")
|
assert.True(t, metadata.Files[1].IsConfigFile, "second file should be marked as config file")
|
||||||
@@ -75,7 +85,7 @@ func createTestTarWithControlFiles(t *testing.T) []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
tw := tar.NewWriter(&buf)
|
tw := tar.NewWriter(&buf)
|
||||||
|
|
||||||
controlContent := "Package: test-package\nVersion: 1.0.0\nArchitecture: all\nMaintainer: Test <test@example.com>\nDescription: Test package\n"
|
controlContent := "Package: test-package\nVersion: 1.0.0\nArchitecture: all\nMaintainer: Test <test@example.com>\nDescription: Test package\nLicense: MIT\n"
|
||||||
writeTarEntry(t, tw, "control", controlContent)
|
writeTarEntry(t, tw, "control", controlContent)
|
||||||
|
|
||||||
md5Content := "d41d8cd98f00b204e9800998ecf8427e usr/bin/test-command\n"
|
md5Content := "d41d8cd98f00b204e9800998ecf8427e usr/bin/test-command\n"
|
||||||
@@ -165,7 +175,7 @@ func Test_processControlTar_rejectsABombedControlMember(t *testing.T) {
|
|||||||
writeTarEntry(t, tw, "md5sums", strings.Repeat("d41d8cd98f00b204e9800998ecf8427e usr/bin/x\n", 20000))
|
writeTarEntry(t, tw, "md5sums", strings.Repeat("d41d8cd98f00b204e9800998ecf8427e usr/bin/x\n", 20000))
|
||||||
require.NoError(t, tw.Close())
|
require.NoError(t, tw.Close())
|
||||||
|
|
||||||
metadata, err := processControlTar(newBoundedReadCloser(io.NopCloser(bytes.NewReader(tarBuf.Bytes())), 4096))
|
metadata, _, err := processControlTar(newBoundedReadCloser(io.NopCloser(bytes.NewReader(tarBuf.Bytes())), 4096))
|
||||||
|
|
||||||
require.ErrorIs(t, err, errDecompressedTooLarge)
|
require.ErrorIs(t, err, errDecompressedTooLarge)
|
||||||
require.NotNil(t, metadata, "a control tar read failure after \"control\" was parsed must not drop the package")
|
require.NotNil(t, metadata, "a control tar read failure after \"control\" was parsed must not drop the package")
|
||||||
@@ -181,7 +191,7 @@ func Test_processControlTar_clippedListingIsUsableButReported(t *testing.T) {
|
|||||||
writeTarEntry(t, tw, "md5sums", strings.Repeat("d41d8cd98f00b204e9800998ecf8427e usr/bin/x\n", maxDpkgFileRecords+10))
|
writeTarEntry(t, tw, "md5sums", strings.Repeat("d41d8cd98f00b204e9800998ecf8427e usr/bin/x\n", maxDpkgFileRecords+10))
|
||||||
require.NoError(t, tw.Close())
|
require.NoError(t, tw.Close())
|
||||||
|
|
||||||
metadata, err := processControlTar(io.NopCloser(bytes.NewReader(tarBuf.Bytes())))
|
metadata, _, err := processControlTar(io.NopCloser(bytes.NewReader(tarBuf.Bytes())))
|
||||||
|
|
||||||
require.Error(t, err, "clipping must be reported")
|
require.Error(t, err, "clipping must be reported")
|
||||||
require.ErrorIs(t, err, errClippedFileListing)
|
require.ErrorIs(t, err, errClippedFileListing)
|
||||||
@@ -418,3 +428,18 @@ func Test_decompressionStream_failsEarlyInsteadOfBuffering(t *testing.T) {
|
|||||||
assert.Less(t, bounded, uint64(8*intFile.MB),
|
assert.Less(t, bounded, uint64(8*intFile.MB),
|
||||||
"a bounded stream must stop at the cap, not buffer the whole member and check afterwards")
|
"a bounded stream must stop at the cap, not buffer the whole member and check afterwards")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessControlTar_DeclaredLicense(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tw := tar.NewWriter(&buf)
|
||||||
|
|
||||||
|
controlContent := "Package: test-package\nVersion: 1.0.0\nArchitecture: all\nLicense: MIT & (BSD-3-Clause | Apache-2.0) & ( GPL-2.0-or-later )\n"
|
||||||
|
writeTarEntry(t, tw, "control", controlContent)
|
||||||
|
|
||||||
|
require.NoError(t, tw.Close())
|
||||||
|
|
||||||
|
metadata, licenses, err := processControlTar(io.NopCloser(bytes.NewReader(buf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, metadata)
|
||||||
|
assert.Equal(t, []string{"MIT AND (BSD-3-Clause OR Apache-2.0) AND (GPL-2.0-or-later)"}, licenses)
|
||||||
|
}
|
||||||
|
|||||||
@@ -183,9 +183,11 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (*dpkgExtractedMetadata, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sourceName, sourceVersion := extractSourceVersion(raw.Source)
|
sourceName, sourceVersion := extractSourceVersion(raw.Source)
|
||||||
|
if sourceName != "" {
|
||||||
|
raw.Source = sourceName
|
||||||
|
}
|
||||||
if sourceVersion != "" {
|
if sourceVersion != "" {
|
||||||
raw.SourceVersion = sourceVersion
|
raw.SourceVersion = sourceVersion
|
||||||
raw.Source = sourceName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if raw.Package == "" {
|
if raw.Package == "" {
|
||||||
@@ -263,8 +265,14 @@ func extractAllFields(reader *bufio.Reader) (map[string]any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the source entry string is of the form "<name> (<version>)" then parse and return the components, if
|
// If the source entry string is of the form "<name> (<version>)" then parse and return the components, if
|
||||||
// of the "<name>" form, then return name and nil
|
// of the "<name>" form, then return name and nil. Yocto (opkg/ipk) instead writes the bitbake recipe filename
|
||||||
|
// ("<name>_<version>.bb"), which is split the same way.
|
||||||
func extractSourceVersion(source string) (string, string) {
|
func extractSourceVersion(source string) (string, string) {
|
||||||
|
if recipe, ok := strings.CutSuffix(source, ".bb"); ok && !strings.ContainsAny(recipe, " (") {
|
||||||
|
// bitbake reserves "_" as the recipe name / version separator
|
||||||
|
name, version, _ := strings.Cut(recipe, "_")
|
||||||
|
return name, version
|
||||||
|
}
|
||||||
// special handling for the Source field since it has formatted data
|
// special handling for the Source field since it has formatted data
|
||||||
match := internal.MatchNamedCaptureGroups(sourceRegexp, source)
|
match := internal.MatchNamedCaptureGroups(sourceRegexp, source)
|
||||||
return match["name"], match["version"]
|
return match["name"], match["version"]
|
||||||
|
|||||||
@@ -366,6 +366,16 @@ func TestSourceVersionExtract(t *testing.T) {
|
|||||||
input: "",
|
input: "",
|
||||||
expected: []string{"", ""},
|
expected: []string{"", ""},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bitbake recipe with version",
|
||||||
|
input: "zlib_1.3.2.bb",
|
||||||
|
expected: []string{"zlib", "1.3.2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bitbake recipe without version",
|
||||||
|
input: "packagegroup-core-boot.bb",
|
||||||
|
expected: []string{"packagegroup-core-boot", ""},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# assembles a Yocto-style .ipk the same way bitbake's package_ipk does (opkg-build: a GNU ar archive of
|
||||||
|
# debian-binary, control.tar.gz and data.tar.zst) without paying for a full Yocto build. control/ is copied
|
||||||
|
# verbatim from libz1_1.3.2-r0_x86-64-v3.ipk built by poky-tiny (wrynose); the library itself is a stub
|
||||||
|
# since only the archive shape and control metadata matter here.
|
||||||
|
FROM alpine:3.18.4@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86 AS build
|
||||||
|
|
||||||
|
RUN apk add --no-cache binutils zstd
|
||||||
|
|
||||||
|
COPY control /pkg/control
|
||||||
|
|
||||||
|
RUN mkdir -p /pkg/data/usr/lib \
|
||||||
|
&& echo stub > /pkg/data/usr/lib/libz.so.1.3.2 \
|
||||||
|
&& ln -s libz.so.1.3.2 /pkg/data/usr/lib/libz.so.1 \
|
||||||
|
&& cd /pkg \
|
||||||
|
&& echo 2.0 > debian-binary \
|
||||||
|
&& tar -C control -czf control.tar.gz . \
|
||||||
|
&& tar -C data -cf - . | zstd -q -o data.tar.zst \
|
||||||
|
&& ar rcD /libz1_1.3.2-r0_x86-64-v3.ipk debian-binary control.tar.gz data.tar.zst
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /libz1_1.3.2-r0_x86-64-v3.ipk /
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Package: libz1
|
||||||
|
Version: 1.3.2-r0
|
||||||
|
Description: Zlib Compression Library
|
||||||
|
Zlib is a general-purpose, patent-free, lossless data compression library
|
||||||
|
which is used by many different programs.
|
||||||
|
Section: libs
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Poky Maintainers <poky@lists.yoctoproject.org>
|
||||||
|
License: Zlib
|
||||||
|
Architecture: x86-64-v3
|
||||||
|
OE: zlib
|
||||||
|
Homepage: http://zlib.net/
|
||||||
|
Depends: musl (>= 1.2.6+git0+9fa28ece75)
|
||||||
|
Provides: zlib
|
||||||
|
Source: zlib_1.3.2.bb
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
if [ x"$D" = "x" ]; then
|
||||||
|
if [ -x /sbin/ldconfig ]; then /sbin/ldconfig ; fi
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user