mirror of
https://github.com/anchore/syft.git
synced 2026-10-14 07:07:18 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e72771b85 | ||
|
|
e72d68b0c6 | ||
|
|
f15d4a9984 | ||
|
|
b8d1a46e7e | ||
|
|
f35a252ecf | ||
|
|
05be9e25ce | ||
|
|
5e2ab0874d | ||
|
|
fb699496a8 | ||
|
|
0aea55f880 | ||
|
|
caff67289a | ||
|
|
82de24cf7c | ||
|
|
3db3efacdc | ||
|
|
6ccd460e59 | ||
|
|
7cb8e1fc14 | ||
|
|
c990f425a6 | ||
|
|
d41afe05eb | ||
|
|
0f5a9eed09 | ||
|
|
8420612724 | ||
|
|
03ee4fdf5e | ||
|
|
91e2fd8532 | ||
|
|
24f08e7738 | ||
|
|
a83506628c | ||
|
|
42f8601919 | ||
|
|
d2d532f4a8 | ||
|
|
1cea0ecd5c | ||
|
|
d2f053bc71 | ||
|
|
2fc344aba4 | ||
|
|
8b6c576d78 | ||
|
|
ab289933da |
@@ -21,3 +21,32 @@ ignore-packages:
|
||||
# All these files have been put into the public domain.
|
||||
# You can do whatever you want with these files.
|
||||
- github.com/xi2/xz
|
||||
|
||||
# from: https://gitlab.com/cznic/sqlite/-/blob/v1.15.4/LICENSE
|
||||
# This is a BSD-3-Clause license
|
||||
- modernc.org/libc
|
||||
- modernc.org/libc/errno
|
||||
- modernc.org/libc/fcntl
|
||||
- modernc.org/libc/fts
|
||||
- modernc.org/libc/grp
|
||||
- modernc.org/libc/langinfo
|
||||
- modernc.org/libc/limits
|
||||
- modernc.org/libc/netdb
|
||||
- modernc.org/libc/netinet/in
|
||||
- modernc.org/libc/poll
|
||||
- modernc.org/libc/pthread
|
||||
- modernc.org/libc/pwd
|
||||
- modernc.org/libc/signal
|
||||
- modernc.org/libc/stdio
|
||||
- modernc.org/libc/stdlib
|
||||
- modernc.org/libc/sys/socket
|
||||
- modernc.org/libc/sys/stat
|
||||
- modernc.org/libc/sys/types
|
||||
- modernc.org/libc/termios
|
||||
- modernc.org/libc/time
|
||||
- modernc.org/libc/unistd
|
||||
- modernc.org/libc/utime
|
||||
- modernc.org/libc/uuid/uuid
|
||||
- modernc.org/libc/wctype
|
||||
- modernc.org/mathutil
|
||||
- modernc.org/memory
|
||||
@@ -0,0 +1,17 @@
|
||||
# Uses https://github.com/actions/add-to-project example to add issues to Anchore OSS project
|
||||
name: Add issues to OSS project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
with:
|
||||
project-url: https://github.com/orgs/anchore/projects/22
|
||||
github-token: ${{ secrets.CI_WRITE_GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Uses https://github.com/actions/add-to-project example to add PR to Anchore OSS project
|
||||
name: Add pr to OSS project
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add pr to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
with:
|
||||
project-url: https://github.com/orgs/anchore/projects/22
|
||||
github-token: ${{ secrets.CI_WRITE_GITHUB_TOKEN }}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: true
|
||||
draft: false
|
||||
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
|
||||
A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner like [Grype](https://github.com/anchore/grype).
|
||||
|
||||
### Join our Virtual OSS Meetup!
|
||||
|
||||
You are invited to join us on June 15th, 11AM-Noon PT for our virtual open source meetup.
|
||||
|
||||
Hosts Amy Bass from Docker Desktop and Christopher Phillips from Anchore OSS will explore how Docker Extensions for Docker Desktop is supporting open source projects and we’ll have the latest update on Syft: in-toto attestations.
|
||||
|
||||
[Register here ->](https://get.anchore.com/anchore-oss-meetup-jun-15-2022/)
|
||||
|
||||
### Join our community meetings!
|
||||
|
||||
- Calendar: https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t
|
||||
@@ -25,12 +33,14 @@ A CLI tool and Go library for generating a Software Bill of Materials (SBOM) fro
|
||||
- Linux distribution identification
|
||||
- Works seamlessly with [Grype](https://github.com/anchore/grype) (a fast, modern vulnerability scanner)
|
||||
- Able to create signed SBOM attestations using the [in-toto specification](https://github.com/in-toto/attestation/blob/main/spec/README.md)
|
||||
- Convert between SBOM formats, such as CycloneDX, SPDX, and Syft's own format.
|
||||
|
||||
### Supported Ecosystems
|
||||
|
||||
- Alpine (apk)
|
||||
- Dart (pubs)
|
||||
- Debian (dpkg)
|
||||
- Dotnet (deps.json)
|
||||
- Go (go.mod, Go binaries)
|
||||
- Java (jar, ear, war, par, sar)
|
||||
- JavaScript (npm, yarn)
|
||||
@@ -56,6 +66,14 @@ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>
|
||||
```
|
||||
|
||||
### Chocolatey
|
||||
|
||||
The chocolatey distribution of syft is community maintained and not distributed by the anchore team
|
||||
|
||||
```powershell
|
||||
choco install syft -y
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
```bash
|
||||
brew tap anchore/syft
|
||||
@@ -92,9 +110,59 @@ The above output includes only software that is visible in the container (i.e.,
|
||||
syft <image> --scope all-layers
|
||||
```
|
||||
|
||||
#### Format conversion (experimental)
|
||||
|
||||
The ability to convert existing SBOMs means you can create SBOMs in different formats quickly, without the need to regenerate the SBOM from scratch, which may take significantly more time.
|
||||
|
||||
```
|
||||
syft convert <ORIGINAL-SBOM-FILE> -o <NEW-SBOM-FORMAT>[=<NEW-SBOM-FILE>]
|
||||
```
|
||||
|
||||
This feature is experimental and data might be lost when converting formats. Packages are the main SBOM component easily transferable across formats, whereas files and relationships, as well as other information Syft doesn't support, are more likely to be lost.
|
||||
|
||||
We support formats with wide community usage AND good encode/decode support by Syft. The supported formats are:
|
||||
- Syft JSON
|
||||
- SPDX 2.2 JSON
|
||||
- SPDX 2.2 tag-value
|
||||
- CycloneDX 1.4 JSON
|
||||
- CycloneDX 1.4 XML
|
||||
|
||||
Conversion example:
|
||||
```sh
|
||||
syft alpine:latest -o syft-json=sbom.syft.json # generate a syft SBOM
|
||||
syft convert sbom.syft.json -o cyclonedx-json=sbom.cdx.json # convert it to CycloneDX
|
||||
```
|
||||
|
||||
#### SBOM attestation
|
||||
|
||||
To generate an SBOM attestation for a container image:
|
||||
### Keyless support
|
||||
Syft supports generating attestations using cosign's [keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) signatures.
|
||||
|
||||
To use this feature with a format like CycloneDX json simply run:
|
||||
```
|
||||
syft attest --output cyclonedx-json <IMAGE WITH OCI WRITE ACCESS>
|
||||
```
|
||||
This command will open a web browser and allow the user to authenticate their OIDC identity as the root of trust for the attestation (Github, Google, Microsoft).
|
||||
|
||||
After authenticating, Syft will upload the attestation to the OCI registry specified by the image that the user has write access to.
|
||||
|
||||
You will need to make sure your credentials are configured for the OCI registry you are uploading to so that the attestation can write successfully.
|
||||
|
||||
Users can then verify the attestation(or any image with attestations) by running:
|
||||
```
|
||||
COSIGN_EXPERIMENTAL=1 cosign verify-attestation <IMAGE_WITH_ATTESTATIONS>
|
||||
```
|
||||
|
||||
Users should see that the uploaded attestation claims are validated, the claims exist within the transparency log, and certificates on the attestations were verified against [fulcio](https://github.com/SigStore/fulcio).
|
||||
There will also be a printout of the certificates subject `<user identity>` and the certificate issuer URL: `<provider of user identity (Github, Google, Microsoft)>`:
|
||||
```
|
||||
Certificate subject: test.email@testdomain.com
|
||||
Certificate issuer URL: https://accounts.google.com
|
||||
```
|
||||
|
||||
### Local private key support
|
||||
|
||||
To generate an SBOM attestation for a container image using a local private key:
|
||||
```
|
||||
syft attest --output [FORMAT] --key [KEY] [SOURCE] [flags]
|
||||
```
|
||||
@@ -160,6 +228,7 @@ Where the `formats` available are:
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
|
||||
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
|
||||
- `github`: A JSON report conforming to GitHub's dependency snapshot format.
|
||||
- `table`: A columnar summary (default).
|
||||
|
||||
#### Multiple outputs
|
||||
@@ -202,7 +271,7 @@ Here's a simple workflow to mount this config file as a secret into a container
|
||||
|
||||
```yaml
|
||||
# secret.yaml
|
||||
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
@@ -219,7 +288,7 @@ Here's a simple workflow to mount this config file as a secret into a container
|
||||
|
||||
```yaml
|
||||
# pod.yaml
|
||||
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@@ -466,7 +535,7 @@ anchore:
|
||||
SBOMs themselves can serve as input to different analysis tools. [Grype](https://github.com/anchore/grype), a vulnerability scanner CLI tool from Anchore, is one such tool. Publishers of container images can use attestations to enable their consumers to trust Syft-generated SBOM descriptions of those container images. To create and provide these attestations, image publishers can run `syft attest` in conjunction with the [cosign](https://github.com/sigstore/cosign) tool to attach SBOM attestations to their images.
|
||||
|
||||
#### Example attestation
|
||||
Note for the following example replace `docker.io/image:latest` with an image you own. You should also have push access to
|
||||
Note for the following example replace `docker.io/image:latest` with an image you own. You should also have push access to
|
||||
its remote reference. Replace `$MY_PRIVATE_KEY` with a private key you own or have generated with cosign.
|
||||
|
||||
```bash
|
||||
|
||||
+15
-1
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,7 +55,19 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *c
|
||||
checkForApplicationUpdate()
|
||||
}
|
||||
|
||||
return attest.Run(cmd.Context(), app, args)
|
||||
// build cosign key options for attestation
|
||||
ko := sigopts.KeyOpts{
|
||||
KeyRef: app.Attest.KeyRef,
|
||||
FulcioURL: app.Attest.FulcioURL,
|
||||
IDToken: app.Attest.FulcioIdentityToken,
|
||||
InsecureSkipFulcioVerify: app.Attest.InsecureSkipFulcioVerify,
|
||||
RekorURL: app.Attest.RekorURL,
|
||||
OIDCIssuer: app.Attest.OIDCIssuer,
|
||||
OIDCClientID: app.Attest.OIDCClientID,
|
||||
OIDCRedirectURL: app.Attest.OIDCRedirectURL,
|
||||
}
|
||||
|
||||
return attest.Run(cmd.Context(), app, ko, args)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+167
-24
@@ -6,7 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
@@ -18,15 +19,29 @@ import (
|
||||
"github.com/anchore/syft/internal/formats/cyclonedxjson"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json"
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/pkg/errors"
|
||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
||||
cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
|
||||
"github.com/sigstore/cosign/pkg/oci/mutate"
|
||||
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
|
||||
"github.com/sigstore/cosign/pkg/oci/static"
|
||||
sigs "github.com/sigstore/cosign/pkg/signature"
|
||||
"github.com/sigstore/cosign/pkg/types"
|
||||
"github.com/sigstore/rekor/pkg/generated/client"
|
||||
"github.com/sigstore/rekor/pkg/generated/models"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
@@ -43,7 +58,7 @@ var (
|
||||
intotoJSONDsseType = `application/vnd.in-toto+json`
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args []string) error {
|
||||
// We cannot generate an attestation for more than one output
|
||||
if len(app.Outputs) > 1 {
|
||||
return fmt.Errorf("unable to generate attestation for more than one output")
|
||||
@@ -59,17 +74,20 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
format := syft.FormatByName(app.Outputs[0])
|
||||
predicateType := formatPredicateType(format)
|
||||
if predicateType == "" {
|
||||
return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", options.FormatAliases(format.ID()), options.FormatAliases(allowedAttestFormats...))
|
||||
return fmt.Errorf(
|
||||
"could not produce attestation predicate for given format: %q. Available formats: %+v",
|
||||
options.FormatAliases(format.ID()),
|
||||
options.FormatAliases(allowedAttestFormats...),
|
||||
)
|
||||
}
|
||||
|
||||
passFunc, err := selectPassFunc(app.Attest.Key, app.Attest.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Attest.KeyRef != "" {
|
||||
passFunc, err := selectPassFunc(app.Attest.KeyRef, app.Attest.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ko := sign.KeyOpts{
|
||||
KeyRef: app.Attest.Key,
|
||||
PassFunc: passFunc,
|
||||
ko.PassFunc = passFunc
|
||||
}
|
||||
|
||||
sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)
|
||||
@@ -81,11 +99,12 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
eventBus := partybus.NewBus()
|
||||
stereoscope.SetBus(eventBus)
|
||||
syft.SetBus(eventBus)
|
||||
subscription := eventBus.Subscribe()
|
||||
|
||||
return eventloop.EventLoop(
|
||||
execWorker(app, *si, format, predicateType, sv),
|
||||
eventloop.SetupSignals(),
|
||||
eventBus.Subscribe(),
|
||||
subscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(options.IsVerbose(app), app.Quiet)...,
|
||||
)
|
||||
@@ -144,7 +163,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F
|
||||
return
|
||||
}
|
||||
|
||||
err = generateAttestation(sbomBytes, src, sv, predicateType)
|
||||
err = generateAttestation(app, sbomBytes, src, sv, predicateType)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
@@ -153,7 +172,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F
|
||||
return errs
|
||||
}
|
||||
|
||||
func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error {
|
||||
func generateAttestation(app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error {
|
||||
switch len(src.Image.Metadata.RepoDigests) {
|
||||
case 0:
|
||||
return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command")
|
||||
@@ -163,11 +182,22 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe
|
||||
}
|
||||
|
||||
wrapped := dsse.WrapSigner(sv, intotoJSONDsseType)
|
||||
ref, err := name.ParseReference(src.Metadata.ImageMetadata.UserInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest, err := ociremote.ResolveDigest(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, _ := v1.NewHash(digest.Identifier())
|
||||
|
||||
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
|
||||
Predicate: bytes.NewBuffer(predicate),
|
||||
Type: predicateType,
|
||||
Digest: findValidDigest(src.Image.Metadata.RepoDigests),
|
||||
Digest: h.Hex,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,24 +213,107 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe
|
||||
return errors.Wrap(err, "unable to sign SBOM")
|
||||
}
|
||||
|
||||
// We want to give the option to not upload the generated attestation
|
||||
// if passed or if the user is using local PKI
|
||||
if app.Attest.NoUpload || app.Attest.KeyRef != "" {
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.Exit,
|
||||
Value: func() error {
|
||||
_, err := os.Stdout.Write(signedPayload)
|
||||
return err
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return uploadAttestation(app, signedPayload, digest, sv)
|
||||
}
|
||||
|
||||
func trackUploadAttestation() (*progress.Stage, *progress.Manual) {
|
||||
stage := &progress.Stage{}
|
||||
prog := &progress.Manual{}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.UploadAttestation,
|
||||
Value: progress.StagedProgressable(&struct {
|
||||
progress.Stager
|
||||
progress.Progressable
|
||||
}{
|
||||
Stager: stage,
|
||||
Progressable: prog,
|
||||
}),
|
||||
})
|
||||
|
||||
return stage, prog
|
||||
}
|
||||
|
||||
// uploads signed SBOM payload to Rekor transparency log along with key information;
|
||||
// returns a bundle for attestation annotations
|
||||
// rekor bundle includes a signed payload and rekor timestamp;
|
||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
||||
// the user's image's OCI registry repository as *.att
|
||||
func uploadAttestation(app *config.Application, signedPayload []byte, digest name.Digest, sv *sign.SignerVerifier) error {
|
||||
// add application/vnd.dsse.envelope.v1+json as media type for other applications to decode attestation
|
||||
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
|
||||
if sv.Cert != nil {
|
||||
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
|
||||
}
|
||||
|
||||
stage, prog := trackUploadAttestation()
|
||||
defer prog.SetCompleted() // just in case we return early
|
||||
|
||||
prog.Total = 2
|
||||
stage.Current = "uploading signing information to transparency log"
|
||||
|
||||
// uploads payload to Rekor transparency log along with key information;
|
||||
// returns bundle for attesation annotations
|
||||
// rekor bundle includes a signed payload and rekor timestamp;
|
||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
||||
// the user's image's OCI registry repository as *.att
|
||||
bundle, err := uploadToTlog(context.TODO(), sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
|
||||
return cosign.TLogUploadInTotoAttestation(context.TODO(), r, signedPayload, b)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog.N = 1
|
||||
stage.Current = "uploading attestation to OCI registry"
|
||||
|
||||
// add bundle OCI attestation that is uploaded to
|
||||
opts = append(opts, static.WithBundle(bundle))
|
||||
sig, err := static.NewAttestation(signedPayload, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
se, err := ociremote.SignedEntity(digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newSE, err := mutate.AttachAttestationToEntity(se, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Publish the attestations associated with this entity
|
||||
err = ociremote.WriteAttestations(digest.Repository, newSE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog.SetCompleted()
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.Exit,
|
||||
Value: func() error {
|
||||
_, err := os.Stdout.Write(signedPayload)
|
||||
return err
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findValidDigest(digests []string) string {
|
||||
// since we are only using the OCI repo provider for this source we are safe that this is only 1 value
|
||||
// see https://github.com/anchore/stereoscope/blob/25ebd49a842b5ac0a20c2e2b4b81335b64ad248c/pkg/image/oci/registry_provider.go#L57-L63
|
||||
split := strings.Split(digests[0], "sha256:")
|
||||
return split[1]
|
||||
}
|
||||
|
||||
func formatPredicateType(format sbom.Format) string {
|
||||
switch format.ID() {
|
||||
case spdx22json.ID:
|
||||
@@ -214,3 +327,33 @@ func formatPredicateType(format sbom.Format) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)
|
||||
|
||||
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
|
||||
var rekorBytes []byte
|
||||
// Upload the cert or the public key, depending on what we have
|
||||
if sv.Cert != nil {
|
||||
rekorBytes = sv.Cert
|
||||
} else {
|
||||
pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rekorBytes = pemBytes
|
||||
}
|
||||
|
||||
rekorClient, err := rekor.NewClient(rekorURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry, err := upload(rekorClient, rekorBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entry.LogIndex != nil {
|
||||
log.Debugf("transparency log entry created with index: %v", *entry.LogIndex)
|
||||
}
|
||||
return cbundle.EntryToBundle(entry), nil
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ func New() (*cobra.Command, error) {
|
||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||
attestCmd := Attest(v, app, ro)
|
||||
poweruserCmd := PowerUser(v, app, ro)
|
||||
convertCmd := Convert(v, app, ro)
|
||||
|
||||
// rootCmd is currently an alias for the packages command
|
||||
rootCmd := &cobra.Command{
|
||||
@@ -84,6 +85,7 @@ func New() (*cobra.Command, error) {
|
||||
// Add sub-commands.
|
||||
rootCmd.AddCommand(packagesCmd)
|
||||
rootCmd.AddCommand(attestCmd)
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
rootCmd.AddCommand(poweruserCmd)
|
||||
rootCmd.AddCommand(Completion())
|
||||
rootCmd.AddCommand(Version(v, app))
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/cmd/syft/cli/convert"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout in table format, by default
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output goes to a file named img.cdx.json
|
||||
`
|
||||
)
|
||||
|
||||
func Convert(v *viper.Viper, app *config.Application, ro *options.RootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert [SOURCE-SBOM] -o [FORMAT]",
|
||||
Short: "Convert between SBOM formats",
|
||||
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
|
||||
Example: internal.Tprintf(convertExample, map[string]interface{}{
|
||||
"appName": internal.ApplicationName,
|
||||
"command": "convert",
|
||||
}),
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if err := app.LoadAllValues(v, ro.Config); err != nil {
|
||||
return fmt.Errorf("invalid application config: %w", err)
|
||||
}
|
||||
newLogWrapper(app)
|
||||
logApplicationConfig(app)
|
||||
return validateArgs(cmd, args)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if app.CheckForAppUpdate {
|
||||
checkForApplicationUpdate()
|
||||
}
|
||||
return convert.Run(cmd.Context(), app, args)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// this can only be a SBOM file
|
||||
userInput := args[0]
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sbom, _, err := syft.Decode(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode SBOM: %w", err)
|
||||
}
|
||||
|
||||
return writer.Write(*sbom)
|
||||
}
|
||||
@@ -6,17 +6,50 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultKeyFileName = "cosign.key"
|
||||
|
||||
type AttestOptions struct {
|
||||
Key string
|
||||
Key string
|
||||
Cert string
|
||||
CertChain string
|
||||
NoUpload bool
|
||||
Force bool
|
||||
Recursive bool
|
||||
|
||||
Rekor RekorOptions
|
||||
Fulcio FulcioOptions
|
||||
OIDC OIDCOptions
|
||||
}
|
||||
|
||||
var _ Interface = (*AttestOptions)(nil)
|
||||
|
||||
func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.PersistentFlags().StringVarP(&o.Key, "key", "", "cosign.key",
|
||||
if err := o.Rekor.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Fulcio.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.OIDC.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Key, "key", "", defaultKeyFileName,
|
||||
"path to the private key file to use for attestation")
|
||||
|
||||
return bindAttestationConfigOptions(cmd.PersistentFlags(), v)
|
||||
cmd.Flags().StringVarP(&o.Cert, "cert", "", "",
|
||||
"path to the x.509 certificate in PEM format to include in the OCI Signature")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.NoUpload, "no-upload", "", false,
|
||||
"do not upload the generated attestation")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.Force, "force", "", false,
|
||||
"skip warnings and confirmations")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.Recursive, "recursive", "", false,
|
||||
"if a multi-arch image is specified, additionally sign each discrete image")
|
||||
|
||||
return bindAttestationConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
@@ -24,5 +57,21 @@ func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.cert", flags.Lookup("cert")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.no-upload", flags.Lookup("no-upload")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.force", flags.Lookup("force")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.recursive", flags.Lookup("recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultFulcioURL = "https://fulcio.sigstore.dev"
|
||||
|
||||
// FulcioOptions is the wrapper for Fulcio related options.
|
||||
type FulcioOptions struct {
|
||||
URL string
|
||||
IdentityToken string
|
||||
InsecureSkipFulcioVerify bool
|
||||
}
|
||||
|
||||
var _ Interface = (*FulcioOptions)(nil)
|
||||
|
||||
// AddFlags implements Interface
|
||||
func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
// TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete.
|
||||
cmd.Flags().StringVar(&o.URL, "fulcio-url", defaultFulcioURL,
|
||||
"address of sigstore PKI server")
|
||||
|
||||
cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "",
|
||||
"identity token to use for certificate from fulcio")
|
||||
|
||||
cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false,
|
||||
"skip verifying fulcio certificat and the SCT (Signed Certificate Timestamp) (this should only be used for testing).")
|
||||
return bindFulcioConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.fulcio-url", flags.Lookup("fulcio-url")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.fulcio-identity-token", flags.Lookup("identity-token")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.insecure-skip-verify", flags.Lookup("insecure-skip-verify")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const DefaultOIDCIssuerURL = "https://oauth2.sigstore.dev/auth"
|
||||
|
||||
// OIDCOptions is the wrapper for OIDC related options.
|
||||
type OIDCOptions struct {
|
||||
Issuer string
|
||||
ClientID string
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
var _ Interface = (*OIDCOptions)(nil)
|
||||
|
||||
// AddFlags implements Interface
|
||||
func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVar(&o.Issuer, "oidc-issuer", DefaultOIDCIssuerURL,
|
||||
"OIDC provider to be used to issue ID token")
|
||||
|
||||
cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore",
|
||||
"OIDC client ID for application")
|
||||
|
||||
cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "",
|
||||
"OIDC redirect URL (Optional)")
|
||||
|
||||
return bindOIDCConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.oidc-client-id", flags.Lookup("oidc-client-id")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.oidc-redirect-url", flags.Lookup("oidc-redirect-url")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const DefaultRekorURL = "https://rekor.sigstore.dev"
|
||||
|
||||
// RekorOptions is the wrapper for Rekor related options.
|
||||
type RekorOptions struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
var _ Interface = (*RekorOptions)(nil)
|
||||
|
||||
// AddFlags implements Interface
|
||||
func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL,
|
||||
"address of rekor STL server")
|
||||
return bindRekorConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
// TODO: config re-design
|
||||
if err := v.BindPFlag("attest.rekor-url", flags.Lookup("rekor-url")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/table"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
// makeWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
|
||||
// or an error but neither both and if there is no error, sbom.Writer.Close() should be called
|
||||
func MakeWriter(outputs []string, defaultFile string) (sbom.Writer, error) {
|
||||
outputOptions, err := parseOutputs(outputs, defaultFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writer, err := sbom.NewWriter(outputOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// parseOptions utility to parse command-line option strings and retain the existing behavior of default format and file
|
||||
func parseOutputs(outputs []string, defaultFile string) (out []sbom.WriterOption, errs error) {
|
||||
// always should have one option -- we generally get the default of "table", but just make sure
|
||||
if len(outputs) == 0 {
|
||||
outputs = append(outputs, string(table.ID))
|
||||
}
|
||||
|
||||
for _, name := range outputs {
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
// split to at most two parts for <format>=<file>
|
||||
parts := strings.SplitN(name, "=", 2)
|
||||
|
||||
// the format name is the first part
|
||||
name = parts[0]
|
||||
|
||||
// default to the --file or empty string if not specified
|
||||
file := defaultFile
|
||||
|
||||
// If a file is specified as part of the output formatName, use that
|
||||
if len(parts) > 1 {
|
||||
file = parts[1]
|
||||
}
|
||||
|
||||
format := syft.FormatByName(name)
|
||||
if format == nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("bad output format: '%s'", name))
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, sbom.NewWriterOption(format, file))
|
||||
}
|
||||
return out, errs
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsSupportedFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
outputs []string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
outputs: []string{"json"},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
outputs: []string{"table", "json"},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
outputs: []string{"unknown"},
|
||||
wantErr: func(t assert.TestingT, err error, bla ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, "bad output format: 'unknown'")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := MakeWriter(tt.outputs, "")
|
||||
tt.wantErr(t, err)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/cmd/syft/cli/eventloop"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"github.com/anchore/syft/internal/anchore"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/formats/table"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
@@ -23,12 +21,11 @@ import (
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
writer, err := makeWriter(app.Outputs, app.File)
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,11 +46,12 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
eventBus := partybus.NewBus()
|
||||
stereoscope.SetBus(eventBus)
|
||||
syft.SetBus(eventBus)
|
||||
subscription := eventBus.Subscribe()
|
||||
|
||||
return eventloop.EventLoop(
|
||||
execWorker(app, *si, writer),
|
||||
eventloop.SetupSignals(),
|
||||
eventBus.Subscribe(),
|
||||
subscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(options.IsVerbose(app), app.Quiet)...,
|
||||
)
|
||||
@@ -187,54 +185,3 @@ func runPackageSbomUpload(src *source.Source, s sbom.SBOM, app *config.Applicati
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
|
||||
// or an error but neither both and if there is no error, sbom.Writer.Close() should be called
|
||||
func makeWriter(outputs []string, defaultFile string) (sbom.Writer, error) {
|
||||
outputOptions, err := parseOptions(outputs, defaultFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writer, err := sbom.NewWriter(outputOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// parseOptions utility to parse command-line option strings and retain the existing behavior of default format and file
|
||||
func parseOptions(outputs []string, defaultFile string) (out []sbom.WriterOption, errs error) {
|
||||
// always should have one option -- we generally get the default of "table", but just make sure
|
||||
if len(outputs) == 0 {
|
||||
outputs = append(outputs, string(table.ID))
|
||||
}
|
||||
|
||||
for _, name := range outputs {
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
// split to at most two parts for <format>=<file>
|
||||
parts := strings.SplitN(name, "=", 2)
|
||||
|
||||
// the format name is the first part
|
||||
name = parts[0]
|
||||
|
||||
// default to the --file or empty string if not specified
|
||||
file := defaultFile
|
||||
|
||||
// If a file is specified as part of the output formatName, use that
|
||||
if len(parts) > 1 {
|
||||
file = parts[1]
|
||||
}
|
||||
|
||||
format := syft.FormatByName(name)
|
||||
if format == nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("bad output format: '%s'", name))
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, sbom.NewWriterOption(format, file))
|
||||
}
|
||||
return out, errs
|
||||
}
|
||||
|
||||
@@ -53,11 +53,12 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
eventBus := partybus.NewBus()
|
||||
stereoscope.SetBus(eventBus)
|
||||
syft.SetBus(eventBus)
|
||||
subscription := eventBus.Subscribe()
|
||||
|
||||
return eventloop.EventLoop(
|
||||
execWorker(app, *si, writer),
|
||||
eventloop.SetupSignals(),
|
||||
eventBus.Subscribe(),
|
||||
subscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(options.IsVerbose(app), app.Quiet)...,
|
||||
)
|
||||
|
||||
@@ -10,17 +10,16 @@ require (
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
|
||||
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b
|
||||
github.com/anchore/stereoscope v0.0.0-20220406160859-c03a18a6b270
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220428202044-a072fa3cb6d7
|
||||
github.com/anchore/stereoscope v0.0.0-20220518185348-c97a3c6ffc67
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/facebookincubator/nvdtools v0.1.4
|
||||
github.com/go-test/deep v1.0.8
|
||||
github.com/google/go-cmp v0.5.7
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
@@ -29,18 +28,18 @@ require (
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/mitchellh/mapstructure v1.4.3
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pelletier/go-toml v1.9.4
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.2.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spdx/tools-golang v0.2.0
|
||||
github.com/spf13/afero v1.8.0
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/vifraa/gopom v0.1.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
|
||||
@@ -49,47 +48,45 @@ require (
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/docker/docker v20.10.12+incompatible
|
||||
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839
|
||||
github.com/in-toto/in-toto-golang v0.3.4-0.20211211042327-af1f9fb822bf
|
||||
github.com/sigstore/cosign v1.7.2
|
||||
github.com/sigstore/sigstore v1.2.1-0.20220401110139-0e610e39782f
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20220530130908-5101b033fa0d
|
||||
github.com/sigstore/cosign v1.9.0
|
||||
github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3
|
||||
github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5
|
||||
github.com/vbatts/go-mtree v0.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
bitbucket.org/creachadair/shell v0.0.6 // indirect
|
||||
cloud.google.com/go v0.100.2 // indirect
|
||||
cloud.google.com/go/compute v1.5.0 // indirect
|
||||
cloud.google.com/go/compute v1.6.1 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go/kms v1.4.0 // indirect
|
||||
cloud.google.com/go/storage v1.22.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v63.0.0+incompatible // indirect
|
||||
cloud.google.com/go/storage v1.22.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.25 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/PaesslerAG/gval v1.0.0 // indirect
|
||||
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
|
||||
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
||||
github.com/armon/go-metrics v0.3.10 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go v1.43.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.14.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.14.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.9.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.11.0 // indirect
|
||||
@@ -101,30 +98,39 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.10.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.15.0 // indirect
|
||||
github.com/aws/smithy-go v1.11.0 // indirect
|
||||
github.com/aws/smithy-go v1.11.2 // indirect
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 // indirect
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
|
||||
github.com/containerd/containerd v1.5.10 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
|
||||
github.com/docker/cli v20.10.12+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fullstorydev/grpcurl v1.8.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
@@ -133,7 +139,7 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/loads v0.21.1 // indirect
|
||||
github.com/go-openapi/runtime v0.23.3 // indirect
|
||||
github.com/go-openapi/runtime v0.24.1 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.2 // indirect
|
||||
github.com/go-openapi/swag v0.21.1 // indirect
|
||||
@@ -142,93 +148,106 @@ require (
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2 // indirect
|
||||
github.com/google/go-github/v42 v42.0.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/trillian v1.4.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.3 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.3 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/vault/api v1.5.0 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.4.1 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jedisct1/go-minisign v0.0.0-20210703085342-c1f07ee84431 // indirect
|
||||
github.com/jhump/protoreflect v1.9.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.14.2 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20220331220046-b23ab962616e // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 // indirect
|
||||
github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
|
||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
||||
github.com/theupdateframework/go-tuf v0.0.0-20220211205608-f0c3294f63b9 // indirect
|
||||
github.com/theupdateframework/go-tuf v0.3.0 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/transparency-dev/merkle v0.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/urfave/cli v1.22.5 // indirect
|
||||
github.com/xanzy/go-gitlab v0.62.0 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xanzy/go-gitlab v0.68.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zeebo/errs v1.2.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.1 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.305.1 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.305.4 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/etcdctl/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/etcdutl/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
|
||||
@@ -251,14 +270,24 @@ require (
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
google.golang.org/api v0.74.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
google.golang.org/api v0.81.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
|
||||
google.golang.org/grpc v1.46.2 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
k8s.io/api v0.23.5 // indirect
|
||||
k8s.io/apimachinery v0.23.5 // indirect
|
||||
k8s.io/client-go v0.23.5 // indirect
|
||||
@@ -266,6 +295,16 @@ require (
|
||||
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
knative.dev/pkg v0.0.0-20220325200448-1f7514acd0c2 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.22 // indirect
|
||||
modernc.org/ccgo/v3 v3.15.1 // indirect
|
||||
modernc.org/libc v1.14.1 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/sqlite v1.14.5 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/release-utils v0.6.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
@@ -273,61 +312,9 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.5.10 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.12+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.14.2 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
@@ -5,22 +5,35 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
||||
type attest struct {
|
||||
Key string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key
|
||||
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
||||
Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key
|
||||
KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key
|
||||
Cert string `yaml:"cert" json:"cert" mapstructure:"cert"`
|
||||
NoUpload bool `yaml:"no_upload" json:"noUpload" mapstructure:"no_upload"`
|
||||
Force bool `yaml:"force" json:"force" mapstructure:"force"`
|
||||
Recursive bool `yaml:"recursive" json:"recursive" mapstructure:"recursive"`
|
||||
Replace bool `yaml:"replace" json:"replace" mapstructure:"replace"`
|
||||
Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key
|
||||
FulcioURL string `yaml:"fulcio_url" json:"fulcioUrl" mapstructure:"fulcio_url"`
|
||||
FulcioIdentityToken string `yaml:"fulcio_identity_token" json:"fulcio_identity_token" mapstructure:"fulcio_identity_token"`
|
||||
InsecureSkipFulcioVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify" mapstructure:"insecure_skip_verify"`
|
||||
RekorURL string `yaml:"rekor_url" json:"rekorUrl" mapstructure:"rekor_url"`
|
||||
OIDCIssuer string `yaml:"oidc_issuer" json:"oidcIssuer" mapstructure:"oidc_issuer"`
|
||||
OIDCClientID string `yaml:"oidc_client_id" json:"oidcClientId" mapstructure:"oidc_client_id"`
|
||||
OIDCRedirectURL string `yaml:"oidc_redirect_url" json:"OIDCRedirectURL" mapstructure:"oidc_redirect_url"`
|
||||
}
|
||||
|
||||
func (cfg *attest) parseConfigValues() error {
|
||||
if cfg.Key != "" {
|
||||
expandedPath, err := homedir.Expand(cfg.Key)
|
||||
if cfg.KeyRef != "" {
|
||||
expandedPath, err := homedir.Expand(cfg.KeyRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to expand key path=%q: %w", cfg.Key, err)
|
||||
return fmt.Errorf("unable to expand key path=%q: %w", cfg.KeyRef, err)
|
||||
}
|
||||
cfg.Key = expandedPath
|
||||
cfg.KeyRef = expandedPath
|
||||
}
|
||||
|
||||
if cfg.Password == "" {
|
||||
@@ -34,5 +47,10 @@ func (cfg *attest) parseConfigValues() error {
|
||||
}
|
||||
|
||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("attest.key", "")
|
||||
v.SetDefault("attest.password", "")
|
||||
v.SetDefault("attest.fulcio_url", options.DefaultFulcioURL)
|
||||
v.SetDefault("attest.rekor_url", options.DefaultRekorURL)
|
||||
v.SetDefault("attest.oidc_issuer", options.DefaultOIDCIssuerURL)
|
||||
v.SetDefault("attest.oidc_client_id", "sigstore")
|
||||
}
|
||||
|
||||
@@ -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 = "3.2.2"
|
||||
JSONSchemaVersion = "3.3.0"
|
||||
)
|
||||
|
||||
@@ -3,5 +3,10 @@ set -eux
|
||||
|
||||
# $1 —— absolute path to destination file, should end with .zip, ideally
|
||||
# $2 —— absolute path to directory from which to add entries to the archive
|
||||
# $3 —— if files should be zip64 or not
|
||||
|
||||
pushd "$2" && find . -print | zip "$1" -@ && popd
|
||||
if [[$3]]; then
|
||||
pushd "$2" && find . -print | zip -fz "$1" -@ && popd
|
||||
else
|
||||
pushd "$2" && find . -print | zip "$1" -@ && popd
|
||||
fi
|
||||
|
||||
@@ -20,16 +20,20 @@ var expectedZipArchiveEntries = []string{
|
||||
}
|
||||
|
||||
// createZipArchive creates a new ZIP archive file at destinationArchivePath based on the directory found at
|
||||
// sourceDirPath.
|
||||
func createZipArchive(t testing.TB, sourceDirPath, destinationArchivePath string) {
|
||||
// sourceDirPath. It forces a zip64 archive if zip64 is "0".
|
||||
func createZipArchive(t testing.TB, sourceDirPath, destinationArchivePath string, zip64 bool) {
|
||||
t.Helper()
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get cwd: %+v", err)
|
||||
}
|
||||
zip64Arg := "0"
|
||||
if zip64 {
|
||||
zip64Arg = "1"
|
||||
}
|
||||
|
||||
cmd := exec.Command("./generate-zip-fixture-from-source-dir.sh", destinationArchivePath, path.Base(sourceDirPath))
|
||||
cmd := exec.Command("./generate-zip-fixture-from-source-dir.sh", destinationArchivePath, path.Base(sourceDirPath), zip64Arg)
|
||||
cmd.Dir = filepath.Join(cwd, "test-fixtures")
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
@@ -66,7 +70,7 @@ func assertNoError(t testing.TB, fn func() error) func() {
|
||||
// which should be called (typically deferred) by the caller, the path of the created zip archive, and an error,
|
||||
// which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil
|
||||
// (even if there's an error), and it should always be called.
|
||||
func setupZipFileTest(t testing.TB, sourceDirPath string) string {
|
||||
func setupZipFileTest(t testing.TB, sourceDirPath string, zip64 bool) string {
|
||||
t.Helper()
|
||||
|
||||
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
|
||||
@@ -84,7 +88,7 @@ func setupZipFileTest(t testing.TB, sourceDirPath string) string {
|
||||
|
||||
destinationArchiveFilePath := archivePrefix.Name() + ".zip"
|
||||
t.Logf("archive path: %s", destinationArchiveFilePath)
|
||||
createZipArchive(t, sourceDirPath, destinationArchiveFilePath)
|
||||
createZipArchive(t, sourceDirPath, destinationArchiveFilePath, zip64)
|
||||
|
||||
t.Cleanup(
|
||||
assertNoError(t,
|
||||
@@ -109,7 +113,7 @@ func ensureNestedZipExists(t *testing.T, sourceDirPath string) error {
|
||||
t.Helper()
|
||||
|
||||
nestedArchiveFilePath := path.Join(sourceDirPath, "nested.zip")
|
||||
createZipArchive(t, sourceDirPath, nestedArchiveFilePath)
|
||||
createZipArchive(t, sourceDirPath, nestedArchiveFilePath, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,42 @@ func TestNewZipFileManifest(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath)
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath, false)
|
||||
|
||||
actual, err := NewZipFileManifest(archiveFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to extract from unzip archive: %+v", err)
|
||||
}
|
||||
|
||||
if len(expectedZipArchiveEntries) != len(actual) {
|
||||
t.Fatalf("mismatched manifest: %d != %d", len(actual), len(expectedZipArchiveEntries))
|
||||
}
|
||||
|
||||
for _, e := range expectedZipArchiveEntries {
|
||||
_, ok := actual[e]
|
||||
if !ok {
|
||||
t.Errorf("missing path: %s", e)
|
||||
}
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
b, err := json.MarshalIndent(actual, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("can't show results: %+v", err)
|
||||
}
|
||||
|
||||
t.Errorf("full result: %s", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewZip64FileManifest(t *testing.T) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sourceDirPath := path.Join(cwd, "test-fixtures", "zip-source")
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath, true)
|
||||
|
||||
actual, err := NewZipFileManifest(archiveFilePath)
|
||||
if err != nil {
|
||||
@@ -62,7 +97,7 @@ func TestZipFileManifest_GlobMatch(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath)
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath, false)
|
||||
|
||||
z, err := NewZipFileManifest(archiveFilePath)
|
||||
if err != nil {
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestUnzipToDir(t *testing.T) {
|
||||
|
||||
goldenRootDir := filepath.Join(cwd, "test-fixtures")
|
||||
sourceDirPath := path.Join(goldenRootDir, "zip-source")
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath)
|
||||
archiveFilePath := setupZipFileTest(t, sourceDirPath, false)
|
||||
|
||||
unzipDestinationDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
|
||||
t.Cleanup(assertNoError(t, func() error {
|
||||
@@ -227,7 +227,7 @@ func prepZipSourceFixture(t testing.TB) string {
|
||||
|
||||
t.Logf("archive path: %s", archivePath)
|
||||
|
||||
createZipArchive(t, "zip-source", archivePrefix.Name())
|
||||
createZipArchive(t, "zip-source", archivePrefix.Name(), false)
|
||||
|
||||
return archivePath
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package file
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -13,7 +14,13 @@ import (
|
||||
// - https://github.com/golang/go/blob/go1.16.4/src/archive/zip/reader.go
|
||||
// findArchiveStartOffset is derived from the same stdlib utils, specifically the readDirectoryEnd function.
|
||||
|
||||
const directoryEndLen = 22
|
||||
const (
|
||||
directoryEndLen = 22
|
||||
directory64LocLen = 20
|
||||
directory64EndLen = 56
|
||||
directory64LocSignature = 0x07064b50
|
||||
directory64EndSignature = 0x06064b50
|
||||
)
|
||||
|
||||
// ZipReadCloser is a drop-in replacement for zip.ReadCloser (from zip.OpenReader) that additionally considers zips
|
||||
// that have bytes prefixed to the front of the archive (common with self-extracting jars).
|
||||
@@ -72,6 +79,12 @@ func (b *readBuf) uint32() uint32 {
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *readBuf) uint64() uint64 {
|
||||
v := binary.LittleEndian.Uint64(*b)
|
||||
*b = (*b)[8:]
|
||||
return v
|
||||
}
|
||||
|
||||
type directoryEnd struct {
|
||||
diskNbr uint32 // unused
|
||||
dirDiskNbr uint32 // unused
|
||||
@@ -82,6 +95,7 @@ type directoryEnd struct {
|
||||
}
|
||||
|
||||
// note: this is derived from readDirectoryEnd within the archive/zip package
|
||||
// nolint:gocognit
|
||||
func findArchiveStartOffset(r io.ReaderAt, size int64) (startOfArchive uint64, err error) {
|
||||
// look for directoryEndSignature in the last 1k, then in the last 65k
|
||||
var buf []byte
|
||||
@@ -120,13 +134,22 @@ func findArchiveStartOffset(r io.ReaderAt, size int64) (startOfArchive uint64, e
|
||||
directoryOffset: uint64(b.uint32()),
|
||||
}
|
||||
// Calculate where the zip data actually begins
|
||||
startOfArchive = uint64(directoryEndOffset) - d.directorySize - d.directoryOffset
|
||||
|
||||
// These values mean that the file can be a zip64 file
|
||||
if d.directoryRecords == 0xffff || d.directorySize == 0xffff || d.directoryOffset == 0xffffffff {
|
||||
p, err := findDirectory64End(r, directoryEndOffset)
|
||||
if err == nil && p >= 0 {
|
||||
directoryEndOffset = p
|
||||
err = readDirectory64End(r, p, d)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
startOfArchive = 0 // Prefixed data not supported
|
||||
}
|
||||
|
||||
startOfArchive = uint64(directoryEndOffset) - d.directorySize - d.directoryOffset
|
||||
|
||||
// Make sure directoryOffset points to somewhere in our file.
|
||||
if o := int64(d.directoryOffset); o < 0 || o >= size {
|
||||
return 0, zip.ErrFormat
|
||||
@@ -134,6 +157,56 @@ func findArchiveStartOffset(r io.ReaderAt, size int64) (startOfArchive uint64, e
|
||||
return startOfArchive, nil
|
||||
}
|
||||
|
||||
// findDirectory64End tries to read the zip64 locator just before the
|
||||
// directory end and returns the offset of the zip64 directory end if
|
||||
// found.
|
||||
func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error) {
|
||||
locOffset := directoryEndOffset - directory64LocLen
|
||||
if locOffset < 0 {
|
||||
return -1, nil // no need to look for a header outside the file
|
||||
}
|
||||
buf := make([]byte, directory64LocLen)
|
||||
if _, err := r.ReadAt(buf, locOffset); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
b := readBuf(buf)
|
||||
if sig := b.uint32(); sig != directory64LocSignature {
|
||||
return -1, nil
|
||||
}
|
||||
if b.uint32() != 0 { // number of the disk with the start of the zip64 end of central directory
|
||||
return -1, nil // the file is not a valid zip64-file
|
||||
}
|
||||
p := b.uint64() // relative offset of the zip64 end of central directory record
|
||||
if b.uint32() != 1 { // total number of disks
|
||||
return -1, nil // the file is not a valid zip64-file
|
||||
}
|
||||
return int64(p), nil
|
||||
}
|
||||
|
||||
// readDirectory64End reads the zip64 directory end and updates the
|
||||
// directory end with the zip64 directory end values.
|
||||
func readDirectory64End(r io.ReaderAt, offset int64, d *directoryEnd) (err error) {
|
||||
buf := make([]byte, directory64EndLen)
|
||||
if _, err := r.ReadAt(buf, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := readBuf(buf)
|
||||
if sig := b.uint32(); sig != directory64EndSignature {
|
||||
return errors.New("could not read directory64End")
|
||||
}
|
||||
|
||||
b = b[12:] // skip dir size, version and version needed (uint64 + 2x uint16)
|
||||
d.diskNbr = b.uint32() // number of this disk
|
||||
d.dirDiskNbr = b.uint32() // number of the disk with the start of the central directory
|
||||
d.dirRecordsThisDisk = b.uint64() // total number of entries in the central directory on this disk
|
||||
d.directoryRecords = b.uint64() // total number of entries in the central directory
|
||||
d.directorySize = b.uint64() // size of the central directory
|
||||
d.directoryOffset = b.uint64() // offset of start of central directory with respect to the starting disk number
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findSignatureInBlock(b []byte) int {
|
||||
for i := len(b) - directoryEndLen; i >= 0; i-- {
|
||||
// defined from directoryEndSignature
|
||||
|
||||
@@ -22,7 +22,7 @@ func GetValidator(format cyclonedx.BOMFileFormat) sbom.Validator {
|
||||
return err
|
||||
}
|
||||
// random JSON does not necessarily cause an error (e.g. SPDX)
|
||||
if (cyclonedx.BOM{} == *bom) {
|
||||
if (cyclonedx.BOM{} == *bom || bom.Components == nil) {
|
||||
return fmt.Errorf("not a valid CycloneDX document")
|
||||
}
|
||||
return nil
|
||||
@@ -31,7 +31,9 @@ func GetValidator(format cyclonedx.BOMFileFormat) sbom.Validator {
|
||||
|
||||
func GetDecoder(format cyclonedx.BOMFileFormat) sbom.Decoder {
|
||||
return func(reader io.Reader) (*sbom.SBOM, error) {
|
||||
bom := &cyclonedx.BOM{}
|
||||
bom := &cyclonedx.BOM{
|
||||
Components: &[]cyclonedx.Component{},
|
||||
}
|
||||
err := cyclonedx.NewBOMDecoder(reader, format).Decode(bom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -45,17 +47,17 @@ func GetDecoder(format cyclonedx.BOMFileFormat) sbom.Decoder {
|
||||
}
|
||||
|
||||
func toSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
|
||||
meta := source.Metadata{}
|
||||
if bom.Metadata != nil && bom.Metadata.Component != nil {
|
||||
meta = decodeMetadata(bom.Metadata.Component)
|
||||
if bom == nil {
|
||||
return nil, fmt.Errorf("no content defined in CycloneDX BOM")
|
||||
}
|
||||
|
||||
s := &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
LinuxDistribution: linuxReleaseFromComponents(*bom.Components),
|
||||
},
|
||||
Source: meta,
|
||||
//Descriptor: sbom.Descriptor{},
|
||||
Source: extractComponents(bom.Metadata),
|
||||
Descriptor: extractDescriptor(bom.Metadata),
|
||||
}
|
||||
|
||||
idMap := make(map[string]interface{})
|
||||
@@ -205,27 +207,45 @@ func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]int
|
||||
}
|
||||
}
|
||||
|
||||
func decodeMetadata(component *cyclonedx.Component) source.Metadata {
|
||||
switch component.Type {
|
||||
func extractComponents(meta *cyclonedx.Metadata) source.Metadata {
|
||||
if meta == nil || meta.Component == nil {
|
||||
return source.Metadata{}
|
||||
}
|
||||
c := meta.Component
|
||||
|
||||
image := source.ImageMetadata{
|
||||
UserInput: c.Name,
|
||||
ID: c.BOMRef,
|
||||
ManifestDigest: c.Version,
|
||||
}
|
||||
|
||||
switch c.Type {
|
||||
case cyclonedx.ComponentTypeContainer:
|
||||
return source.Metadata{
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: component.Name,
|
||||
ID: component.BOMRef,
|
||||
ManifestDigest: component.Version,
|
||||
},
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: image,
|
||||
}
|
||||
case cyclonedx.ComponentTypeFile:
|
||||
return source.Metadata{
|
||||
Scheme: source.FileScheme, // or source.DirectoryScheme
|
||||
Path: component.Name,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: component.Name,
|
||||
ID: component.BOMRef,
|
||||
ManifestDigest: component.Version,
|
||||
},
|
||||
Scheme: source.FileScheme, // or source.DirectoryScheme
|
||||
Path: c.Name,
|
||||
ImageMetadata: image,
|
||||
}
|
||||
}
|
||||
return source.Metadata{}
|
||||
}
|
||||
|
||||
// if there is more than one tool in meta.Tools' list the last item will be used
|
||||
// as descriptor. If there is a way to know which tool to use here please fix it.
|
||||
func extractDescriptor(meta *cyclonedx.Metadata) (desc sbom.Descriptor) {
|
||||
if meta == nil || meta.Tools == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range *meta.Tools {
|
||||
desc.Name = t.Name
|
||||
desc.Version = t.Version
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -283,3 +285,13 @@ func Test_missingDataDecode(t *testing.T) {
|
||||
|
||||
assert.Len(t, pkg.Licenses, 0)
|
||||
}
|
||||
|
||||
func Test_missingComponentsDecode(t *testing.T) {
|
||||
bom := &cyclonedx.BOM{}
|
||||
bomBytes, _ := json.Marshal(&bom)
|
||||
decode := GetDecoder(cyclonedx.BOMFileFormatJSON)
|
||||
|
||||
_, err := decode(bytes.NewReader(bomBytes))
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
URL: "",
|
||||
Type: cyclonedx.ERTypeBuildMeta,
|
||||
Hashes: &[]cyclonedx.Hash{{
|
||||
Algorithm: cyclonedx.HashAlgorithm(digest.Algorithm),
|
||||
Algorithm: toCycloneDXAlgorithm(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
}},
|
||||
})
|
||||
@@ -81,6 +81,21 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
return nil
|
||||
}
|
||||
|
||||
// supported algorithm in cycloneDX as of 1.4
|
||||
// "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512",
|
||||
// "SHA3-256", "SHA3-384", "SHA3-512", "BLAKE2b-256", "BLAKE2b-384", "BLAKE2b-512", "BLAKE3"
|
||||
// syft supported digests: cmd/syft/cli/eventloop/tasks.go
|
||||
// MD5, SHA1, SHA256
|
||||
func toCycloneDXAlgorithm(algorithm string) cyclonedx.HashAlgorithm {
|
||||
validMap := map[string]cyclonedx.HashAlgorithm{
|
||||
"sha1": cyclonedx.HashAlgorithm("SHA-1"),
|
||||
"md5": cyclonedx.HashAlgorithm("MD5"),
|
||||
"sha256": cyclonedx.HashAlgorithm("SHA-256"),
|
||||
}
|
||||
|
||||
return validMap[algorithm]
|
||||
}
|
||||
|
||||
func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||
if c.ExternalReferences == nil {
|
||||
return
|
||||
@@ -101,7 +116,7 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||
if ref.Hashes != nil {
|
||||
for _, hash := range *ref.Hashes {
|
||||
digests = append(digests, syftFile.Digest{
|
||||
Algorithm: string(hash.Algorithm),
|
||||
Algorithm: syftFile.CleanDigestAlgorithmName(string(hash.Algorithm)),
|
||||
Value: hash.Value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -17,13 +16,12 @@ import (
|
||||
|
||||
func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
cdxBOM := cyclonedx.NewBOM()
|
||||
versionInfo := version.FromBuild()
|
||||
|
||||
// NOTE(jonasagx): cycloneDX requires URN uuids (URN returns the RFC 2141 URN form of uuid):
|
||||
// https://github.com/CycloneDX/specification/blob/master/schema/bom-1.3-strict.schema.json#L36
|
||||
// "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
cdxBOM.SerialNumber = uuid.New().URN()
|
||||
cdxBOM.Metadata = toBomDescriptor(internal.ApplicationName, versionInfo.Version, s.Source)
|
||||
cdxBOM.Metadata = toBomDescriptor(internal.ApplicationName, s.Descriptor.Version, s.Source)
|
||||
|
||||
packages := s.Artifacts.PackageCatalog.Sorted()
|
||||
components := make([]cyclonedx.Component, len(packages))
|
||||
@@ -139,7 +137,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
||||
for _, r := range relationships {
|
||||
exists := isExpressiblePackageRelationship(r.Type)
|
||||
if !exists {
|
||||
log.Warnf("unable to convert relationship from CycloneDX 1.3 JSON, dropping: %+v", r)
|
||||
log.Debugf("unable to convert relationship from CycloneDX 1.4 JSON, dropping: %+v", r)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func DocumentName(srcMetadata source.Metadata) (string, error) {
|
||||
func DocumentName(srcMetadata source.Metadata) string {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
return cleanName(srcMetadata.ImageMetadata.UserInput), nil
|
||||
return cleanName(srcMetadata.ImageMetadata.UserInput)
|
||||
case source.DirectoryScheme, source.FileScheme:
|
||||
return cleanName(srcMetadata.Path), nil
|
||||
return cleanName(srcMetadata.Path)
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to determine document name from scheme=%q", srcMetadata.Scheme)
|
||||
}
|
||||
|
||||
func cleanName(name string) string {
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -59,8 +57,7 @@ func Test_DocumentName(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := DocumentName(test.srcMetadata)
|
||||
require.NoError(t, err)
|
||||
actual := DocumentName(test.srcMetadata)
|
||||
assert.True(t, strings.HasPrefix(actual, test.expected), fmt.Sprintf("actual name %q", actual))
|
||||
|
||||
// track each scheme tested (passed or not)
|
||||
|
||||
@@ -10,12 +10,9 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string, error) {
|
||||
name, err := DocumentName(srcMetadata)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return name, DocumentNamespace(name, srcMetadata), nil
|
||||
func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string) {
|
||||
name := DocumentName(srcMetadata)
|
||||
return name, DocumentNamespace(name, srcMetadata)
|
||||
}
|
||||
|
||||
func DocumentNamespace(name string, srcMetadata source.Metadata) string {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
func SourceInfo(p pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
case pkg.AlpmPkg:
|
||||
answer = "acquired package info from ALPM DB"
|
||||
case pkg.RpmPkg:
|
||||
answer = "acquired package info from RPM DB"
|
||||
case pkg.ApkPkg:
|
||||
@@ -17,6 +19,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from pubspec manifest"
|
||||
case pkg.DebPkg:
|
||||
answer = "acquired package info from DPKG DB"
|
||||
case pkg.DotnetPkg:
|
||||
answer = "acquired package info from dotnet project assets file"
|
||||
case pkg.NpmPkg:
|
||||
answer = "acquired package info from installed node module manifest file"
|
||||
case pkg.PythonPkg:
|
||||
|
||||
@@ -134,6 +134,22 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from pubspec manifest",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.DotnetPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from dotnet project assets file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.AlpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from ALPM DB",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -2,6 +2,7 @@ package spdxhelpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -24,7 +25,13 @@ func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
|
||||
|
||||
spdxIDMap := make(map[string]interface{})
|
||||
|
||||
src := source.Metadata{Scheme: source.UnknownScheme}
|
||||
if doc.CreationInfo != nil {
|
||||
src.Scheme = extractSchemeFromNamespace(doc.CreationInfo.DocumentNamespace)
|
||||
}
|
||||
|
||||
s := &sbom.SBOM{
|
||||
Source: src,
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{},
|
||||
@@ -42,6 +49,30 @@ func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NOTE(jonas): SPDX doesn't inform what an SBOM is about,
|
||||
// image, directory, for example. This is our best effort to determine
|
||||
// the scheme. Syft-generated SBOMs have in the namespace
|
||||
// field a type encoded, which we try to identify here.
|
||||
func extractSchemeFromNamespace(ns string) source.Scheme {
|
||||
u, err := url.Parse(ns)
|
||||
if err != nil {
|
||||
return source.UnknownScheme
|
||||
}
|
||||
|
||||
parts := strings.Split(u.Path, "/")
|
||||
for _, p := range parts {
|
||||
switch p {
|
||||
case "file":
|
||||
return source.FileScheme
|
||||
case "image":
|
||||
return source.ImageScheme
|
||||
case "dir":
|
||||
return source.DirectoryScheme
|
||||
}
|
||||
}
|
||||
return source.UnknownScheme
|
||||
}
|
||||
|
||||
func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release {
|
||||
for _, p := range doc.Packages {
|
||||
purlValue := findPURLValue(p)
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToSyftModel(t *testing.T) {
|
||||
@@ -195,3 +197,39 @@ func Test_extractMetadata(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSourceFromNamespaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
namespace string
|
||||
expected source.Scheme
|
||||
}{
|
||||
{
|
||||
namespace: "https://anchore.com/syft/file/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.FileScheme,
|
||||
},
|
||||
{
|
||||
namespace: "https://anchore.com/syft/image/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.ImageScheme,
|
||||
},
|
||||
{
|
||||
namespace: "https://anchore.com/syft/dir/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.DirectoryScheme,
|
||||
},
|
||||
{
|
||||
namespace: "https://another-host/blob/123",
|
||||
expected: source.UnknownScheme,
|
||||
},
|
||||
{
|
||||
namespace: "bla bla",
|
||||
expected: source.UnknownScheme,
|
||||
},
|
||||
{
|
||||
namespace: "",
|
||||
expected: source.UnknownScheme,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
require.Equal(t, tt.expected, extractSchemeFromNamespace(tt.namespace))
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:dec3f6b4-8458-48bb-b60d-dfd312f6ec4e",
|
||||
"serialNumber": "urn:uuid:3ea3363f-3945-4859-9ba1-9a395983d248",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-04-01T11:48:04-04:00",
|
||||
"timestamp": "2022-05-23T12:05:00-07:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
"version": "v0.42.0-bogus"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
|
||||
+6
-6
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:054d973e-fe99-4762-92e4-eaf01997ae41",
|
||||
"serialNumber": "urn:uuid:c825402b-bbfa-4ad5-81b1-6a8332a6a8b6",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-04-01T11:48:04-04:00",
|
||||
"timestamp": "2022-05-23T12:05:01-07:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
"version": "v0.42.0-bogus"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "e777314b02b362e4",
|
||||
"bom-ref": "e779c1ed804ba529",
|
||||
"type": "container",
|
||||
"name": "user-image-input",
|
||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
"value": "sha256:cd8f3884f1211d65c19ce5bbc5174bcd2ce8ba96b63e5b3693969a53279c4405"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
"value": "sha256:42d2ea51c688e6dc7be81a305acbe006d27a6ef0c26ae3888fd0d4ce44f69265"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
||||
BIN
Binary file not shown.
+3
-3
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:554fd820-210b-40c8-8c0b-75690274e21c" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:a259c072-aaaf-4a3f-a707-49f691b1e9d9" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-04-01T11:57:46-04:00</timestamp>
|
||||
<timestamp>2022-05-23T12:02:41-07:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
<name>syft</name>
|
||||
<version>[not provided]</version>
|
||||
<version>v0.42.0-bogus</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="163686ac6e30c752" type="file">
|
||||
|
||||
+6
-6
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:1535f940-172f-4d97-8280-d5a5764d1557" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:155802bd-09e5-4b95-9485-826b94447495" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-04-01T11:57:46-04:00</timestamp>
|
||||
<timestamp>2022-05-23T12:02:42-07:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
<name>syft</name>
|
||||
<version>[not provided]</version>
|
||||
<version>v0.42.0-bogus</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="e777314b02b362e4" type="container">
|
||||
<component bom-ref="e779c1ed804ba529" type="container">
|
||||
<name>user-image-input</name>
|
||||
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
||||
</component>
|
||||
@@ -30,7 +30,7 @@
|
||||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:layerID">sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59</property>
|
||||
<property name="syft:location:0:layerID">sha256:cd8f3884f1211d65c19ce5bbc5174bcd2ce8ba96b63e5b3693969a53279c4405</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
@@ -43,7 +43,7 @@
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:layerID">sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec</property>
|
||||
<property name="syft:location:0:layerID">sha256:42d2ea51c688e6dc7be81a305acbe006d27a6ef0c26ae3888fd0d4ce44f69265</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
|
||||
BIN
Binary file not shown.
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -19,8 +18,8 @@ import (
|
||||
// toGithubModel converts the provided SBOM to a GitHub dependency model
|
||||
func toGithubModel(s *sbom.SBOM) DependencySnapshot {
|
||||
scanTime := time.Now().Format(time.RFC3339) // TODO is there a record of this somewhere?
|
||||
v := version.FromBuild().Version
|
||||
if v == "[not provided]" {
|
||||
v := s.Descriptor.Version
|
||||
if v == "[not provided]" || v == "" {
|
||||
v = "0.0.0-dev"
|
||||
}
|
||||
return DependencySnapshot{
|
||||
|
||||
@@ -8,10 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
doc, err := toFormatModel(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
doc := toFormatModel(s)
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
|
||||
+4
-4
@@ -3,15 +3,15 @@
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-04-01T15:48:39.459232Z",
|
||||
"created": "2022-05-23T19:10:22.25645Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"licenseListVersion": "3.16"
|
||||
"licenseListVersion": "3.17"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-8d335d81-29c9-4236-84f1-2292ea92aaf5",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-81dbcbfa-251d-4ad5-9b01-be91afb16469",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-b85dbb4e6ece5082",
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-04-01T15:48:39.465643Z",
|
||||
"created": "2022-05-23T19:10:22.412847Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"licenseListVersion": "3.16"
|
||||
"licenseListVersion": "3.17"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-e64e0be8-5031-4eec-842d-e59fb6deb518",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-c9945597-78ce-4e9b-89d2-68b8e4e4ccb9",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-2a46171f91c8d4bc",
|
||||
|
||||
BIN
Binary file not shown.
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -20,11 +19,8 @@ import (
|
||||
)
|
||||
|
||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||
func toFormatModel(s sbom.SBOM) (*model.Document, error) {
|
||||
name, namespace, err := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func toFormatModel(s sbom.SBOM) *model.Document {
|
||||
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
||||
|
||||
return &model.Document{
|
||||
Element: model.Element{
|
||||
@@ -37,7 +33,7 @@ func toFormatModel(s sbom.SBOM) (*model.Document, error) {
|
||||
Creators: []string{
|
||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
||||
"Tool: " + internal.ApplicationName + "-" + s.Descriptor.Version,
|
||||
},
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
},
|
||||
@@ -46,7 +42,7 @@ func toFormatModel(s sbom.SBOM) (*model.Document, error) {
|
||||
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships),
|
||||
Files: toFiles(s),
|
||||
Relationships: toRelationships(s.Relationships),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []model.Package {
|
||||
|
||||
@@ -8,9 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
model, err := toFormatModel(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
model := toFormatModel(s)
|
||||
return tvsaver.Save2_2(model, output)
|
||||
}
|
||||
|
||||
@@ -53,9 +53,15 @@ func TestSPDXJSONSPDXIDs(t *testing.T) {
|
||||
Source: source.Metadata{
|
||||
Scheme: source.DirectoryScheme,
|
||||
},
|
||||
Descriptor: sbom.Descriptor{},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "v0.42.0-bogus",
|
||||
Configuration: map[string]string{
|
||||
"config-key": "config-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
*updateSpdxTagValue,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: .
|
||||
DocumentNamespace: https://anchore.com/syft/dir/644e30b7-5a31-4d0b-a903-b96c757921c2
|
||||
LicenseListVersion: 3.16
|
||||
DocumentNamespace: https://anchore.com/syft/dir/bdb67358-651c-4dd8-b5ee-5318936eb16a
|
||||
LicenseListVersion: 3.17
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-04-19T15:10:19Z
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-06-07T19:33:39Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
|
||||
+4
-4
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-d227b0f2-4ee8-4e10-ac43-019db86d16ff
|
||||
LicenseListVersion: 3.16
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-c6b20d03-1478-4513-9feb-1ec427d4b547
|
||||
LicenseListVersion: 3.17
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-04-01T15:48:44Z
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-05-24T22:51:02Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
||||
+4
-4
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-49f98c61-3418-4427-9e00-8b1c735e9799
|
||||
LicenseListVersion: 3.16
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-12a877bc-fe9b-40ef-aa9c-4d34f108d0d6
|
||||
LicenseListVersion: 3.17
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-04-01T15:48:44Z
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-05-24T22:51:02Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -9,18 +9,15 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
)
|
||||
|
||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||
// nolint:funlen
|
||||
func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
|
||||
name, namespace, err := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func toFormatModel(s sbom.SBOM) *spdx.Document2_2 {
|
||||
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
||||
|
||||
return &spdx.Document2_2{
|
||||
CreationInfo: &spdx.CreationInfo2_2{
|
||||
// 2.1: SPDX Version; should be in the format "SPDX-2.2"
|
||||
@@ -71,7 +68,7 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
|
||||
// Cardinality: mandatory, one or many
|
||||
CreatorPersons: nil,
|
||||
CreatorOrganizations: []string{"Anchore, Inc"},
|
||||
CreatorTools: []string{internal.ApplicationName + "-" + version.FromBuild().Version},
|
||||
CreatorTools: []string{internal.ApplicationName + "-" + s.Descriptor.Version},
|
||||
|
||||
// 2.9: Created: data format YYYY-MM-DDThh:mm:ssZ
|
||||
// Cardinality: mandatory, one
|
||||
@@ -86,7 +83,7 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
|
||||
DocumentComment: "",
|
||||
},
|
||||
Packages: toFormatPackages(s.Artifacts.PackageCatalog),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
|
||||
@@ -13,6 +13,10 @@ type LinuxRelease struct {
|
||||
IDLike IDLikes `json:"idLike,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
VersionID string `json:"versionID,omitempty"`
|
||||
VersionCodename string `json:"versionCodename,omitempty"`
|
||||
BuildID string `json:"buildID,omitempty"`
|
||||
ImageID string `json:"imageID,omitempty"`
|
||||
ImageVersion string `json:"imageVersion,omitempty"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
VariantID string `json:"variantID,omitempty"`
|
||||
HomeURL string `json:"homeURL,omitempty"`
|
||||
|
||||
@@ -47,7 +47,6 @@ func (p *packageMetadataUnpacker) String() string {
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
|
||||
// nolint:funlen
|
||||
func (p *Package) UnmarshalJSON(b []byte) error {
|
||||
var basic PackageBasicData
|
||||
if err := json.Unmarshal(b, &basic); err != nil {
|
||||
@@ -61,9 +60,19 @@ func (p *Package) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.MetadataType = unpacker.MetadataType
|
||||
return unpackMetadata(p, unpacker)
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
|
||||
p.MetadataType = unpacker.MetadataType
|
||||
switch p.MetadataType {
|
||||
case pkg.AlpmMetadataType:
|
||||
var payload pkg.AlpmMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.ApkMetadataType:
|
||||
var payload pkg.ApkMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
@@ -136,6 +145,12 @@ func (p *Package) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.DotnetDepsMetadataType:
|
||||
var payload pkg.DotnetDepsMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
default:
|
||||
log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.2.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.2.2.json"
|
||||
"version": "3.3.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.2.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.2.2.json"
|
||||
"version": "3.3.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
"layerID": "sha256:7ef28e9c2d56471ee090b578a678bdf28c3b5a311ca7b2e28c2a4185e5bb34c0"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -40,7 +40,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
"layerID": "sha256:86da8aee621161bea2efaf27a2709ddab5e7d44e30ecdfda728b02c03a28fd98"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
@@ -67,7 +67,7 @@
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
|
||||
"imageID": "sha256:5dd5f5f4247e4e946f555f0de7681a631a5240b614e52717d0aed04808e8c65f",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
@@ -77,17 +77,17 @@
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||
"digest": "sha256:7ef28e9c2d56471ee090b578a678bdf28c3b5a311ca7b2e28c2a4185e5bb34c0",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||
"digest": "sha256:86da8aee621161bea2efaf27a2709ddab5e7d44e30ecdfda728b02c03a28fd98",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1Njo1ZGQ1ZjVmNDI0N2U0ZTk0NmY1NTVmMGRlNzY4MWE2MzFhNTI0MGI2MTRlNTI3MTdkMGFlZDA0ODA4ZThjNjVmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo3ZWYyOGU5YzJkNTY0NzFlZTA5MGI1NzhhNjc4YmRmMjhjM2I1YTMxMWNhN2IyZTI4YzJhNDE4NWU1YmIzNGMwIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2Ojg2ZGE4YWVlNjIxMTYxYmVhMmVmYWYyN2EyNzA5ZGRhYjVlN2Q0NGUzMGVjZGZkYTcyOGIwMmMwM2EyOGZkOTgifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMDYtMDJUMTQ6MzQ6MzQuNzE5MTM1MTc0WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTA2LTAyVDE0OjM0OjM0LjY4NjkzMzI2M1oiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMDYtMDJUMTQ6MzQ6MzQuNzE5MTM1MTc0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6N2VmMjhlOWMyZDU2NDcxZWUwOTBiNTc4YTY3OGJkZjI4YzNiNWEzMTFjYTdiMmUyOGMyYTQxODVlNWJiMzRjMCIsInNoYTI1Njo4NmRhOGFlZTYyMTE2MWJlYTJlZmFmMjdhMjcwOWRkYWI1ZTdkNDRlMzBlY2RmZGE3MjhiMDJjMDNhMjhmZDk4Il19fQ==",
|
||||
"repoDigests": [],
|
||||
"architecture": "",
|
||||
"os": ""
|
||||
@@ -111,7 +111,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.2.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.2.2.json"
|
||||
"version": "3.3.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
@@ -55,6 +55,10 @@ func toLinuxReleaser(d *linux.Release) model.LinuxRelease {
|
||||
IDLike: d.IDLike,
|
||||
Version: d.Version,
|
||||
VersionID: d.VersionID,
|
||||
VersionCodename: d.VersionCodename,
|
||||
BuildID: d.BuildID,
|
||||
ImageID: d.ImageID,
|
||||
ImageVersion: d.ImageVersion,
|
||||
Variant: d.Variant,
|
||||
VariantID: d.VariantID,
|
||||
HomeURL: d.HomeURL,
|
||||
|
||||
@@ -38,6 +38,10 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||
IDLike: d.IDLike,
|
||||
Version: d.Version,
|
||||
VersionID: d.VersionID,
|
||||
VersionCodename: d.VersionCodename,
|
||||
BuildID: d.BuildID,
|
||||
ImageID: d.ImageID,
|
||||
ImageVersion: d.ImageVersion,
|
||||
Variant: d.Variant,
|
||||
VariantID: d.VariantID,
|
||||
HomeURL: d.HomeURL,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2022-02-07 09:34:56.622482 -0500 EST m=+0.351558414
|
||||
// This file was generated by robots at 2022-05-09 10:01:37.625063 -0400 EDT m=+0.423632834
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.16"
|
||||
const Version = "3.17"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -75,6 +75,9 @@ var licenseIDs = map[string]string{
|
||||
"apsl-2": "APSL-2.0",
|
||||
"apsl-2.0": "APSL-2.0",
|
||||
"apsl-2.0.0": "APSL-2.0",
|
||||
"arphic-1999": "Arphic-1999",
|
||||
"arphic-1999.0": "Arphic-1999",
|
||||
"arphic-1999.0.0": "Arphic-1999",
|
||||
"artistic-1": "Artistic-1.0",
|
||||
"artistic-1-cl8": "Artistic-1.0-cl8",
|
||||
"artistic-1-perl": "Artistic-1.0-Perl",
|
||||
@@ -87,9 +90,11 @@ var licenseIDs = map[string]string{
|
||||
"artistic-2": "Artistic-2.0",
|
||||
"artistic-2.0": "Artistic-2.0",
|
||||
"artistic-2.0.0": "Artistic-2.0",
|
||||
"baekmuk": "Baekmuk",
|
||||
"bahyph": "Bahyph",
|
||||
"barr": "Barr",
|
||||
"beerware": "Beerware",
|
||||
"bitstream-vera": "Bitstream-Vera",
|
||||
"bittorrent-1": "BitTorrent-1.0",
|
||||
"bittorrent-1.0": "BitTorrent-1.0",
|
||||
"bittorrent-1.0.0": "BitTorrent-1.0",
|
||||
@@ -609,6 +614,7 @@ var licenseIDs = map[string]string{
|
||||
"jasper-2.0.0": "JasPer-2.0",
|
||||
"jpnic": "JPNIC",
|
||||
"json": "JSON",
|
||||
"kicad-libraries-exception": "KiCad-libraries-exception",
|
||||
"lal-1": "LAL-1.2",
|
||||
"lal-1.2": "LAL-1.2",
|
||||
"lal-1.2.0": "LAL-1.2",
|
||||
@@ -710,6 +716,7 @@ var licenseIDs = map[string]string{
|
||||
"mpl-2.0-no-copyleft-exception": "MPL-2.0-no-copyleft-exception",
|
||||
"mpl-2.0.0": "MPL-2.0",
|
||||
"mpl-2.0.0-no-copyleft-exception": "MPL-2.0-no-copyleft-exception",
|
||||
"mplus": "mplus",
|
||||
"ms-pl": "MS-PL",
|
||||
"ms-rl": "MS-RL",
|
||||
"mtll": "MTLL",
|
||||
|
||||
@@ -28,6 +28,7 @@ can be extended to include specific package metadata struct shapes in the future
|
||||
// not matter as long as it is exported.
|
||||
type artifactMetadataContainer struct {
|
||||
Apk pkg.ApkMetadata
|
||||
Alpm pkg.AlpmMetadata
|
||||
Dpkg pkg.DpkgMetadata
|
||||
Gem pkg.GemMetadata
|
||||
Java pkg.JavaMetadata
|
||||
@@ -38,6 +39,7 @@ type artifactMetadataContainer struct {
|
||||
Go pkg.GolangBinMetadata
|
||||
Php pkg.PhpComposerJSONMetadata
|
||||
Dart pkg.DartPubMetadata
|
||||
Dotnet pkg.DotnetDepsMetadata
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,4 +31,7 @@ const (
|
||||
|
||||
// ImportStarted is a partybus event that occurs when an SBOM upload process has begun
|
||||
ImportStarted partybus.EventType = "syft-import-started-event"
|
||||
|
||||
// UploadAttestation is a partybus event that occurs when syft uploads an attestation to an OCI registry (+ any transparency log)
|
||||
UploadAttestation partybus.EventType = "syft-upload-attestation"
|
||||
)
|
||||
|
||||
@@ -151,3 +151,16 @@ func ParseImportStarted(e partybus.Event) (string, progress.StagedProgressable,
|
||||
|
||||
return host, prog, nil
|
||||
}
|
||||
|
||||
func ParseUploadAttestation(e partybus.Event) (progress.StagedProgressable, error) {
|
||||
if err := checkEventType(e.Type, event.UploadAttestation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prog, ok := e.Value.(progress.StagedProgressable)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ func FormatByName(name string) sbom.Format {
|
||||
return FormatByID(cyclonedxxml.ID)
|
||||
case "cyclonedxjson":
|
||||
return FormatByID(cyclonedxjson.ID)
|
||||
case "github":
|
||||
case "github", "githubjson":
|
||||
return FormatByID(github.ID)
|
||||
case "spdx", "spdxtv", "spdxtagvalue":
|
||||
return FormatByID(spdx22tagvalue.ID)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/formats/cyclonedxjson"
|
||||
"github.com/anchore/syft/internal/formats/cyclonedxxml"
|
||||
"github.com/anchore/syft/internal/formats/github"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json"
|
||||
"github.com/anchore/syft/internal/formats/spdx22tagvalue"
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
@@ -169,6 +170,17 @@ func TestFormatByName(t *testing.T) {
|
||||
name: "syft-json",
|
||||
want: syftjson.ID,
|
||||
},
|
||||
|
||||
// GitHub JSON
|
||||
{
|
||||
name: "github",
|
||||
want: github.ID,
|
||||
},
|
||||
|
||||
{
|
||||
name: "github-json",
|
||||
want: github.ID,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -111,6 +111,10 @@ func parseOsRelease(contents string) (*Release, error) {
|
||||
IDLike: idLike,
|
||||
Version: values["VERSION"],
|
||||
VersionID: values["VERSION_ID"],
|
||||
VersionCodename: values["VERSION_CODENAME"],
|
||||
BuildID: values["BUILD_ID"],
|
||||
ImageID: values["IMAGE_ID"],
|
||||
ImageVersion: values["IMAGE_VERSION"],
|
||||
Variant: values["VARIANT"],
|
||||
VariantID: values["VARIANT_ID"],
|
||||
HomeURL: values["HOME_URL"],
|
||||
|
||||
@@ -125,6 +125,7 @@ func TestIdentifyRelease(t *testing.T) {
|
||||
ID: "ubuntu",
|
||||
IDLike: []string{"debian"},
|
||||
Version: "20.04 LTS (Focal Fossa)",
|
||||
VersionCodename: "focal",
|
||||
VersionID: "20.04",
|
||||
HomeURL: "https://www.ubuntu.com/",
|
||||
SupportURL: "https://help.ubuntu.com/",
|
||||
@@ -217,6 +218,7 @@ func TestIdentifyRelease(t *testing.T) {
|
||||
Name: "Arch Linux",
|
||||
ID: "arch",
|
||||
IDLike: nil,
|
||||
BuildID: "rolling",
|
||||
HomeURL: "https://www.archlinux.org/",
|
||||
SupportURL: "https://bbs.archlinux.org/",
|
||||
BugReportURL: "https://bugs.archlinux.org/",
|
||||
@@ -349,6 +351,7 @@ func TestParseOsRelease(t *testing.T) {
|
||||
IDLike: []string{"debian"},
|
||||
Version: "20.04 LTS (Focal Fossa)",
|
||||
VersionID: "20.04",
|
||||
VersionCodename: "focal",
|
||||
HomeURL: "https://www.ubuntu.com/",
|
||||
SupportURL: "https://help.ubuntu.com/",
|
||||
BugReportURL: "https://bugs.launchpad.net/ubuntu/",
|
||||
|
||||
@@ -8,6 +8,10 @@ type Release struct {
|
||||
IDLike []string `cyclonedx:"idLike"` // list of operating system identifiers in the same syntax as the ID= setting. It should list identifiers of operating systems that are closely related to the local operating system in regards to packaging and programming interfaces.
|
||||
Version string // identifies the operating system version, excluding any OS name information, possibly including a release code name, and suitable for presentation to the user.
|
||||
VersionID string `cyclonedx:"versionID"` // identifies the operating system version, excluding any OS name information or release code name, and suitable for processing by scripts or usage in generated filenames.
|
||||
VersionCodename string `cyclonedx:"versionCodename"`
|
||||
BuildID string `cyclonedx:"buildID"` // A string uniquely identifying the system image originally used as the installation base.
|
||||
ImageID string `cyclonedx:"imageID"`
|
||||
ImageVersion string `cyclonedx:"imageVersion"`
|
||||
Variant string `cyclonedx:"variant"` // identifies a specific variant or edition of the operating system suitable for presentation to the user.
|
||||
VariantID string `cyclonedx:"variantID"` // identifies a specific variant or edition of the operating system. This may be interpreted by other packages in order to determine a divergent default configuration.
|
||||
HomeURL string
|
||||
@@ -30,6 +34,9 @@ func (r *Release) String() string {
|
||||
if r.Version != "" {
|
||||
return r.ID + " " + r.Version
|
||||
}
|
||||
if r.VersionID != "" {
|
||||
return r.ID + " " + r.VersionID
|
||||
}
|
||||
|
||||
return r.ID + " " + r.VersionID
|
||||
return r.ID + " " + r.BuildID
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
const AlpmDBGlob = "**/var/lib/pacman/local/**/desc"
|
||||
|
||||
type AlpmMetadata struct {
|
||||
BasePackage string `mapstructure:"base" json:"basepackage"`
|
||||
Package string `mapstructure:"name" json:"package"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Description string `mapstructure:"desc" json:"description"`
|
||||
Architecture string `mapstructure:"arch" json:"architecture"`
|
||||
Size int `mapstructure:"size" json:"size" cyclonedx:"size"`
|
||||
Packager string `mapstructure:"packager" json:"packager"`
|
||||
License string `mapstructure:"license" json:"license"`
|
||||
URL string `mapstructure:"url" json:"url"`
|
||||
Validation string `mapstructure:"validation" json:"validation"`
|
||||
Reason int `mapstructure:"reason" json:"reason"`
|
||||
Files []AlpmFileRecord `mapstructure:"files" json:"files"`
|
||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
|
||||
}
|
||||
|
||||
type AlpmFileRecord struct {
|
||||
Path string `mapstruture:"path" json:"path,omitempty"`
|
||||
Type string `mapstructure:"type" json:"type,omitempty"`
|
||||
UID string `mapstructure:"uid" json:"uid,omitempty"`
|
||||
GID string `mapstructure:"gid" json:"gid,omitempty"`
|
||||
Time time.Time `mapstructure:"time" json:"time,omitempty"`
|
||||
Size string `mapstructure:"size" json:"size,omitempty"`
|
||||
Link string `mapstructure:"link" json:"link,omitempty"`
|
||||
Digests []file.Digest `mapstructure:"digests" json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// PackageURL returns the PURL for the specific Arch Linux package (see https://github.com/package-url/purl-spec)
|
||||
func (m AlpmMetadata) PackageURL(distro *linux.Release) string {
|
||||
qualifiers := map[string]string{
|
||||
PURLQualifierArch: m.Architecture,
|
||||
}
|
||||
|
||||
if m.BasePackage != "" {
|
||||
qualifiers[PURLQualifierUpstream] = m.BasePackage
|
||||
}
|
||||
|
||||
distroID := ""
|
||||
if distro != nil {
|
||||
distroID = distro.ID
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
"alpm",
|
||||
distroID,
|
||||
m.Package,
|
||||
m.Version,
|
||||
purlQualifiers(
|
||||
qualifiers,
|
||||
distro,
|
||||
),
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func (m AlpmMetadata) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
if f.Path != "" {
|
||||
s.Add(f.Path)
|
||||
}
|
||||
}
|
||||
result = s.List()
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
func TestAlpmMetadata_pURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata AlpmMetadata
|
||||
distro linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
BuildID: "rolling",
|
||||
},
|
||||
expected: "pkg:alpm/arch/p@v?arch=a&distro=arch-rolling",
|
||||
},
|
||||
{
|
||||
name: "missing architecture",
|
||||
metadata: AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
},
|
||||
expected: "pkg:alpm/arch/p@v?distro=arch",
|
||||
},
|
||||
{
|
||||
metadata: AlpmMetadata{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
BuildID: "rolling",
|
||||
},
|
||||
expected: "pkg:alpm/arch/python@3.10.0?arch=any&distro=arch-rolling",
|
||||
},
|
||||
{
|
||||
metadata: AlpmMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
BuildID: "rolling",
|
||||
},
|
||||
expected: "pkg:alpm/arch/g%20plus%20plus@v84?arch=x86_64&distro=arch-rolling",
|
||||
},
|
||||
{
|
||||
name: "add source information as qualifier",
|
||||
metadata: AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
BasePackage: "origin",
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
BuildID: "rolling",
|
||||
},
|
||||
expected: "pkg:alpm/arch/p@v?arch=a&upstream=origin&distro=arch-rolling",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := test.metadata.PackageURL(&test.distro)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
// verify packageurl can parse
|
||||
purl, err := packageurl.FromString(actual)
|
||||
if err != nil {
|
||||
t.Errorf("cannot re-parse purl: %s", actual)
|
||||
}
|
||||
if purl.Name != test.metadata.Package {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.metadata.Package, purl.Name, true)
|
||||
t.Errorf("invalid purl name: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
if purl.Version != test.metadata.Version {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.metadata.Version, purl.Version, true)
|
||||
t.Errorf("invalid purl version: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
if purl.Qualifiers.Map()["arch"] != test.metadata.Architecture {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.metadata.Architecture, purl.Qualifiers.Map()["arch"], true)
|
||||
t.Errorf("invalid purl architecture: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package alpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const catalogerName = "alpmdb-cataloger"
|
||||
|
||||
type Cataloger struct{}
|
||||
|
||||
// NewAlpmdbCataloger returns a new ALPM DB cataloger object.
|
||||
func NewAlpmdbCataloger() *Cataloger {
|
||||
return &Cataloger{}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
func (c *Cataloger) Name() string {
|
||||
return catalogerName
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
fileMatches, err := resolver.FilesByGlob(pkg.AlpmDBGlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, location := range fileMatches {
|
||||
dbContentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
discoveredPkgs, err := parseAlpmDB(resolver, location.RealPath, dbContentReader)
|
||||
internal.CloseAndLogError(dbContentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to catalog package=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
pkgs = append(pkgs, discoveredPkgs...)
|
||||
}
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package alpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/vbatts/go-mtree"
|
||||
)
|
||||
|
||||
var (
|
||||
ignoredFiles = map[string]bool{
|
||||
"/set": true,
|
||||
".BUILDINFO": true,
|
||||
".PKGINFO": true,
|
||||
"": true,
|
||||
}
|
||||
)
|
||||
|
||||
func newAlpmDBPackage(d *pkg.AlpmMetadata) *pkg.Package {
|
||||
return &pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
FoundBy: catalogerName,
|
||||
Type: "alpm",
|
||||
Licenses: strings.Split(d.License, " "),
|
||||
MetadataType: pkg.AlpmMetadataType,
|
||||
Metadata: *d,
|
||||
}
|
||||
}
|
||||
|
||||
func newScanner(reader io.Reader) *bufio.Scanner {
|
||||
// This is taken from the apk parser
|
||||
// https://github.com/anchore/syft/blob/v0.47.0/syft/pkg/cataloger/apkdb/parse_apk_db.go#L37
|
||||
const maxScannerCapacity = 1024 * 1024
|
||||
bufScan := make([]byte, maxScannerCapacity)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Buffer(bufScan, maxScannerCapacity)
|
||||
onDoubleLF := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i > 0 && data[i-1] == '\n' && data[i] == '\n' {
|
||||
return i + 1, data[:i-1], nil
|
||||
}
|
||||
}
|
||||
if !atEOF {
|
||||
return 0, nil, nil
|
||||
}
|
||||
// deliver the last token (which could be an empty string)
|
||||
return 0, data, bufio.ErrFinalToken
|
||||
}
|
||||
|
||||
scanner.Split(onDoubleLF)
|
||||
return scanner
|
||||
}
|
||||
|
||||
func getFileReader(path string, resolver source.FileResolver) (io.Reader, error) {
|
||||
locs, err := resolver.FilesByPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Should we maybe check if we found the file
|
||||
dbContentReader, err := resolver.FileContentsByLocation(locs[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbContentReader, nil
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
|
||||
var entry pkg.AlpmMetadata
|
||||
var err error
|
||||
pkgFields := make(map[string]interface{})
|
||||
for b.Scan() {
|
||||
fields := strings.SplitN(b.Text(), "\n", 2)
|
||||
|
||||
// End of File
|
||||
if len(fields) == 1 {
|
||||
break
|
||||
}
|
||||
|
||||
// The alpm database surrounds the keys with %.
|
||||
key := strings.ReplaceAll(fields[0], "%", "")
|
||||
key = strings.ToLower(key)
|
||||
value := strings.TrimSpace(fields[1])
|
||||
|
||||
switch key {
|
||||
case "files":
|
||||
var files []map[string]string
|
||||
for _, f := range strings.Split(value, "\n") {
|
||||
path := fmt.Sprintf("/%s", f)
|
||||
if ok := ignoredFiles[path]; !ok {
|
||||
files = append(files, map[string]string{"path": path})
|
||||
}
|
||||
}
|
||||
pkgFields[key] = files
|
||||
case "backup":
|
||||
var backup []map[string]interface{}
|
||||
for _, f := range strings.Split(value, "\n") {
|
||||
fields := strings.SplitN(f, "\t", 2)
|
||||
path := fmt.Sprintf("/%s", fields[0])
|
||||
if ok := ignoredFiles[path]; !ok {
|
||||
backup = append(backup, map[string]interface{}{
|
||||
"path": path,
|
||||
"digests": []file.Digest{{
|
||||
Algorithm: "md5",
|
||||
Value: fields[1],
|
||||
}}})
|
||||
}
|
||||
}
|
||||
pkgFields[key] = backup
|
||||
case "reason":
|
||||
fallthrough
|
||||
case "size":
|
||||
pkgFields[key], err = strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s to integer", value)
|
||||
}
|
||||
default:
|
||||
pkgFields[key] = value
|
||||
}
|
||||
}
|
||||
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse ALPM metadata: %w", err)
|
||||
}
|
||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if entry.Backup == nil {
|
||||
entry.Backup = make([]pkg.AlpmFileRecord, 0)
|
||||
}
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
func parseMtree(r io.Reader) ([]pkg.AlpmFileRecord, error) {
|
||||
var err error
|
||||
var entries []pkg.AlpmFileRecord
|
||||
|
||||
r, err = gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
specDh, err := mtree.ParseSpec(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range specDh.Entries {
|
||||
var entry pkg.AlpmFileRecord
|
||||
entry.Digests = make([]file.Digest, 0)
|
||||
fileFields := make(map[string]interface{})
|
||||
if ok := ignoredFiles[f.Name]; ok {
|
||||
continue
|
||||
}
|
||||
path := fmt.Sprintf("/%s", f.Name)
|
||||
fileFields["path"] = path
|
||||
for _, kv := range f.Keywords {
|
||||
kw := string(kv.Keyword())
|
||||
switch kw {
|
||||
case "time":
|
||||
// All unix timestamps have a .0 suffixs.
|
||||
v := strings.Split(kv.Value(), ".")
|
||||
i, _ := strconv.ParseInt(v[0], 10, 64)
|
||||
tm := time.Unix(i, 0)
|
||||
fileFields[kw] = tm
|
||||
case "sha256digest":
|
||||
entry.Digests = append(entry.Digests, file.Digest{
|
||||
Algorithm: "sha256",
|
||||
Value: kv.Value(),
|
||||
})
|
||||
case "md5digest":
|
||||
entry.Digests = append(entry.Digests, file.Digest{
|
||||
Algorithm: "md5digest",
|
||||
Value: kv.Value(),
|
||||
})
|
||||
default:
|
||||
fileFields[kw] = kv.Value()
|
||||
}
|
||||
}
|
||||
if err := mapstructure.Decode(fileFields, &entry); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse ALPM mtree data: %w", err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func parseAlpmDBEntry(reader io.Reader) (*pkg.AlpmMetadata, error) {
|
||||
scanner := newScanner(reader)
|
||||
metadata, err := parseDatabase(scanner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if metadata == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func parseAlpmDB(resolver source.FileResolver, desc string, reader io.Reader) ([]pkg.Package, error) {
|
||||
metadata, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := filepath.Dir(desc)
|
||||
mtree := filepath.Join(base, "mtree")
|
||||
r, err := getFileReader(mtree, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgFiles, err := parseMtree(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The replace the files found the the pacman database with the files from the mtree These contain more metadata and
|
||||
// thus more useful.
|
||||
metadata.Files = pkgFiles
|
||||
|
||||
// We only really do this to get any backup database entries from the files database
|
||||
files := filepath.Join(base, "files")
|
||||
_, err = getFileReader(files, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filesMetadata, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if filesMetadata != nil {
|
||||
metadata.Backup = filesMetadata.Backup
|
||||
}
|
||||
|
||||
p := *newAlpmDBPackage(metadata)
|
||||
p.SetID()
|
||||
return []pkg.Package{p}, nil
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package alpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
func TestDatabaseParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected pkg.AlpmMetadata
|
||||
}{
|
||||
{
|
||||
name: "test alpm database parsing",
|
||||
expected: pkg.AlpmMetadata{
|
||||
Backup: []pkg.AlpmFileRecord{
|
||||
{
|
||||
Path: "/etc/pacman.conf",
|
||||
Digests: []file.Digest{{
|
||||
Algorithm: "md5",
|
||||
Value: "de541390e52468165b96511c4665bff4",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Path: "/etc/makepkg.conf",
|
||||
Digests: []file.Digest{{
|
||||
Algorithm: "md5",
|
||||
Value: "79fce043df7dfc676ae5ecb903762d8b",
|
||||
}},
|
||||
},
|
||||
},
|
||||
Files: []pkg.AlpmFileRecord{
|
||||
{
|
||||
Path: "/etc/",
|
||||
},
|
||||
{
|
||||
Path: "/etc/makepkg.conf",
|
||||
},
|
||||
{
|
||||
Path: "/etc/pacman.conf",
|
||||
},
|
||||
{
|
||||
Path: "/usr/",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/makepkg",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/makepkg-template",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/pacman",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/pacman-conf",
|
||||
},
|
||||
{
|
||||
Path: "/var/",
|
||||
},
|
||||
{
|
||||
Path: "/var/cache/",
|
||||
},
|
||||
{
|
||||
Path: "/var/cache/pacman/",
|
||||
},
|
||||
{
|
||||
Path: "/var/cache/pacman/pkg/",
|
||||
},
|
||||
{
|
||||
Path: "/var/lib/",
|
||||
},
|
||||
{
|
||||
Path: "/var/lib/pacman/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
file, err := os.Open("test-fixtures/files")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read test-fixtures/file: ", err)
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
t.Fatal("closing file failed:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
entry, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read file contents: ", err)
|
||||
}
|
||||
|
||||
if diff := deep.Equal(entry.Files, test.expected.Files); diff != nil {
|
||||
for _, d := range diff {
|
||||
t.Errorf("files diff: %+v", d)
|
||||
}
|
||||
}
|
||||
if diff := deep.Equal(entry.Backup, test.expected.Backup); diff != nil {
|
||||
for _, d := range diff {
|
||||
t.Errorf("backup diff: %+v", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parseTime(stime string) time.Time {
|
||||
t, _ := time.Parse(time.RFC3339, stime)
|
||||
return t
|
||||
}
|
||||
|
||||
func TestMtreeParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected []pkg.AlpmFileRecord
|
||||
}{
|
||||
{
|
||||
name: "test mtree parsing",
|
||||
expected: []pkg.AlpmFileRecord{
|
||||
{
|
||||
Path: "/etc",
|
||||
Type: "dir",
|
||||
Time: parseTime("2022-04-10T14:59:52+02:00"),
|
||||
Digests: make([]file.Digest, 0),
|
||||
},
|
||||
{
|
||||
Path: "/etc/pacman.d",
|
||||
Type: "dir",
|
||||
Time: parseTime("2022-04-10T14:59:52+02:00"),
|
||||
Digests: make([]file.Digest, 0),
|
||||
},
|
||||
{
|
||||
Path: "/etc/pacman.d/mirrorlist",
|
||||
Size: "44683",
|
||||
Time: parseTime("2022-04-10T14:59:52+02:00"),
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
Algorithm: "md5digest",
|
||||
Value: "81c39827e38c759d7e847f05db62c233",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "fc135ab26f2a227b9599b66a2f1ba325c445acb914d60e7ecf6e5997a87abe1e",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
file, err := os.Open("test-fixtures/mtree")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read test-fixtures/mtree: ", err)
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
t.Fatal("closing file failed:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
entry, err := parseMtree(reader)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read file contents: ", err)
|
||||
}
|
||||
|
||||
if diff := deep.Equal(entry, test.expected); diff != nil {
|
||||
for _, d := range diff {
|
||||
t.Errorf("files diff: %+v", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
%FILES%
|
||||
etc/
|
||||
etc/makepkg.conf
|
||||
etc/pacman.conf
|
||||
usr/
|
||||
usr/bin/
|
||||
usr/bin/makepkg
|
||||
usr/bin/makepkg-template
|
||||
usr/bin/pacman
|
||||
usr/bin/pacman-conf
|
||||
var/
|
||||
var/cache/
|
||||
var/cache/pacman/
|
||||
var/cache/pacman/pkg/
|
||||
var/lib/
|
||||
var/lib/pacman/
|
||||
|
||||
%BACKUP%
|
||||
etc/pacman.conf de541390e52468165b96511c4665bff4
|
||||
etc/makepkg.conf 79fce043df7dfc676ae5ecb903762d8b
|
||||
Binary file not shown.
@@ -8,9 +8,11 @@ package cataloger
|
||||
import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/alpm"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dart"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/deb"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
@@ -35,6 +37,7 @@ type Cataloger interface {
|
||||
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
||||
func ImageCatalogers(cfg Config) []Cataloger {
|
||||
return []Cataloger{
|
||||
alpm.NewAlpmdbCataloger(),
|
||||
ruby.NewGemSpecCataloger(),
|
||||
python.NewPythonPackageCataloger(),
|
||||
php.NewPHPComposerInstalledCataloger(),
|
||||
@@ -44,12 +47,14 @@ func ImageCatalogers(cfg Config) []Cataloger {
|
||||
java.NewJavaCataloger(cfg.Java()),
|
||||
apkdb.NewApkdbCataloger(),
|
||||
golang.NewGoModuleBinaryCataloger(),
|
||||
dotnet.NewDotnetDepsCataloger(),
|
||||
}
|
||||
}
|
||||
|
||||
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
|
||||
func DirectoryCatalogers(cfg Config) []Cataloger {
|
||||
return []Cataloger{
|
||||
alpm.NewAlpmdbCataloger(),
|
||||
ruby.NewGemFileLockCataloger(),
|
||||
python.NewPythonIndexCataloger(),
|
||||
python.NewPythonPackageCataloger(),
|
||||
@@ -63,12 +68,14 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
|
||||
golang.NewGoModFileCataloger(),
|
||||
rust.NewCargoLockCataloger(),
|
||||
dart.NewPubspecLockCataloger(),
|
||||
dotnet.NewDotnetDepsCataloger(),
|
||||
}
|
||||
}
|
||||
|
||||
// AllCatalogers returns all implemented catalogers
|
||||
func AllCatalogers(cfg Config) []Cataloger {
|
||||
return []Cataloger{
|
||||
alpm.NewAlpmdbCataloger(),
|
||||
ruby.NewGemFileLockCataloger(),
|
||||
ruby.NewGemSpecCataloger(),
|
||||
python.NewPythonIndexCataloger(),
|
||||
@@ -83,5 +90,6 @@ func AllCatalogers(cfg Config) []Cataloger {
|
||||
golang.NewGoModFileCataloger(),
|
||||
rust.NewCargoLockCataloger(),
|
||||
dart.NewPubspecLockCataloger(),
|
||||
dotnet.NewDotnetDepsCataloger(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +534,19 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"cpe:2.3:a:someone:something:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "go product with vendor candidates and an extra sub-item",
|
||||
p: pkg.Package{
|
||||
Name: "github.com/someone/something/more",
|
||||
Version: "3.2",
|
||||
FoundBy: "go-cataloger",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:someone:something\\/more:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate no CPEs for indeterminate golang package name",
|
||||
p: pkg.Package{
|
||||
|
||||
@@ -28,7 +28,9 @@ func candidateProductForGo(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
return pathElements[1]
|
||||
// returning the rest of the path here means longer CPEs, it helps avoiding false-positives
|
||||
// ref: https://github.com/anchore/grype/issues/676
|
||||
return strings.Join(pathElements[1:], "/")
|
||||
}
|
||||
|
||||
// candidateVendorForGo attempts to find a single vendor name in a best-effort attempt. This implementation prefers
|
||||
|
||||
@@ -41,7 +41,11 @@ func TestCandidateProductForGo(t *testing.T) {
|
||||
},
|
||||
{
|
||||
pkg: "github.com/someone/something/long/package/name",
|
||||
expected: "something",
|
||||
expected: "something/long/package/name",
|
||||
},
|
||||
{
|
||||
pkg: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -56,18 +56,46 @@ func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package,
|
||||
continue
|
||||
}
|
||||
|
||||
pkgsForRemoval := make(map[artifact.ID]struct{})
|
||||
var cleanedRelationships []artifact.Relationship
|
||||
for _, p := range discoveredPackages {
|
||||
p.FoundBy = c.upstreamCataloger
|
||||
p.Locations.Add(location)
|
||||
p.SetID()
|
||||
// doing it here so all packages have an ID,
|
||||
// IDs are later used to remove relationships
|
||||
if !pkg.IsValid(p) {
|
||||
pkgsForRemoval[p.ID()] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
packages = append(packages, *p)
|
||||
}
|
||||
|
||||
relationships = append(relationships, discoveredRelationships...)
|
||||
cleanedRelationships = removeRelationshipsWithArtifactIDs(pkgsForRemoval, discoveredRelationships)
|
||||
relationships = append(relationships, cleanedRelationships...)
|
||||
}
|
||||
return packages, relationships, nil
|
||||
}
|
||||
|
||||
func removeRelationshipsWithArtifactIDs(artifactsToExclude map[artifact.ID]struct{}, relationships []artifact.Relationship) []artifact.Relationship {
|
||||
if len(artifactsToExclude) == 0 || len(relationships) == 0 {
|
||||
// no removal to do
|
||||
return relationships
|
||||
}
|
||||
|
||||
var cleanedRelationships []artifact.Relationship
|
||||
for _, r := range relationships {
|
||||
_, removeTo := artifactsToExclude[r.To.ID()]
|
||||
_, removaFrom := artifactsToExclude[r.From.ID()]
|
||||
if !removeTo && !removaFrom {
|
||||
cleanedRelationships = append(cleanedRelationships, r)
|
||||
}
|
||||
}
|
||||
|
||||
return cleanedRelationships
|
||||
}
|
||||
|
||||
// SelectFiles takes a set of file trees and resolves and file references of interest for future cataloging
|
||||
func (c *GenericCataloger) selectFiles(resolver source.FilePathResolver) map[source.Location]ParserFn {
|
||||
var parserByLocation = make(map[source.Location]ParserFn)
|
||||
|
||||
@@ -7,28 +7,31 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func parser(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return []*pkg.Package{
|
||||
{
|
||||
Name: string(contents),
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func TestGenericCataloger(t *testing.T) {
|
||||
allParsedPathes := make(map[string]bool)
|
||||
parser := func(path string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
allParsedPathes[path] = true
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
p := &pkg.Package{Name: string(contents)}
|
||||
r := artifact.Relationship{From: p, To: p,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
return []*pkg.Package{p}, []artifact.Relationship{r}, nil
|
||||
}
|
||||
|
||||
globParsers := map[string]ParserFn{
|
||||
"**/a-path.txt": parser,
|
||||
"**/empty.txt": parser,
|
||||
}
|
||||
pathParsers := map[string]ParserFn{
|
||||
"test-fixtures/another-path.txt": parser,
|
||||
@@ -36,21 +39,27 @@ func TestGenericCataloger(t *testing.T) {
|
||||
}
|
||||
upstream := "some-other-cataloger"
|
||||
|
||||
expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}
|
||||
expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt", "test-fixtures/empty.txt"}
|
||||
resolver := source.NewMockResolverForPaths(expectedSelection...)
|
||||
cataloger := NewGenericCataloger(pathParsers, globParsers, upstream)
|
||||
|
||||
actualPkgs, relationships, err := cataloger.Catalog(resolver)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedPkgs := make(map[string]pkg.Package)
|
||||
for _, path := range expectedSelection {
|
||||
require.True(t, allParsedPathes[path])
|
||||
expectedPkgs[path] = pkg.Package{
|
||||
FoundBy: upstream,
|
||||
Name: fmt.Sprintf("%s file contents!", path),
|
||||
}
|
||||
}
|
||||
|
||||
actualPkgs, _, err := cataloger.Catalog(resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actualPkgs, len(expectedPkgs))
|
||||
assert.Len(t, allParsedPathes, len(expectedSelection))
|
||||
// empty.txt won't become a package
|
||||
assert.Len(t, actualPkgs, len(expectedPkgs)-1)
|
||||
// right now, a relationship is created for each package, but if the relationship includes an invalid package it should be dropped.
|
||||
assert.Len(t, relationships, len(actualPkgs))
|
||||
|
||||
for _, p := range actualPkgs {
|
||||
ref := p.Locations.ToSlice()[0]
|
||||
@@ -69,3 +78,99 @@ func TestGenericCataloger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_removeRelationshipsWithArtifactIDs(t *testing.T) {
|
||||
one := &pkg.Package{Name: "one", Version: "1.0"}
|
||||
two := &pkg.Package{Name: "two", Version: "1.0"}
|
||||
three := &pkg.Package{Name: "three", Version: "1.0"}
|
||||
four := &pkg.Package{Name: "four", Version: "bla"}
|
||||
five := &pkg.Package{Name: "five", Version: "1.0"}
|
||||
|
||||
pkgs := make([]artifact.Identifiable, 0)
|
||||
for _, p := range []*pkg.Package{one, two, three, four, five} {
|
||||
// IDs are necessary for comparison
|
||||
p.SetID()
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
remove map[artifact.ID]struct{}
|
||||
relationships []artifact.Relationship
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
name: "nothing-to-remove",
|
||||
args: args{
|
||||
relationships: []artifact.Relationship{
|
||||
{From: one, To: two},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{From: one, To: two},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove-all-relationships",
|
||||
args: args{
|
||||
remove: map[artifact.ID]struct{}{
|
||||
one.ID(): {},
|
||||
three.ID(): {},
|
||||
},
|
||||
relationships: []artifact.Relationship{
|
||||
{From: one, To: two},
|
||||
{From: two, To: three},
|
||||
{From: three, To: four},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship(nil),
|
||||
},
|
||||
{
|
||||
name: "remove-half-of-relationships",
|
||||
args: args{
|
||||
remove: map[artifact.ID]struct{}{
|
||||
one.ID(): {},
|
||||
},
|
||||
relationships: []artifact.Relationship{
|
||||
{From: one, To: two},
|
||||
{From: one, To: three},
|
||||
{From: two, To: three},
|
||||
{From: three, To: four},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{From: two, To: three},
|
||||
{From: three, To: four},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove-repeated-relationships",
|
||||
args: args{
|
||||
remove: map[artifact.ID]struct{}{
|
||||
one.ID(): {},
|
||||
two.ID(): {},
|
||||
},
|
||||
relationships: []artifact.Relationship{
|
||||
{From: one, To: two},
|
||||
{From: one, To: three},
|
||||
{From: two, To: three},
|
||||
{From: two, To: three},
|
||||
{From: three, To: four},
|
||||
{From: four, To: five},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{From: three, To: four},
|
||||
{From: four, To: five},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, removeRelationshipsWithArtifactIDs(tt.args.remove, tt.args.relationships), "removeRelationshipsWithArtifactIDs(%v, %v)", tt.args.remove, tt.args.relationships)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
Architecture: "all",
|
||||
Maintainer: "Steve Langasek <vorlon@debian.org>",
|
||||
InstalledSize: 1016,
|
||||
Description: `Runtime support for the PAM library
|
||||
Contains configuration files and directories required for
|
||||
authentication to work on Debian systems. This package is required
|
||||
on almost all installations.`,
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/pam.conf",
|
||||
|
||||
@@ -20,8 +20,8 @@ var (
|
||||
sourceRegexp = regexp.MustCompile(`(?P<name>\S+)( \((?P<version>.*)\))?`)
|
||||
)
|
||||
|
||||
func newDpkgPackage(d pkg.DpkgMetadata) pkg.Package {
|
||||
return pkg.Package{
|
||||
func newDpkgPackage(d pkg.DpkgMetadata) *pkg.Package {
|
||||
return &pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Type: pkg.DebPkg,
|
||||
@@ -46,8 +46,9 @@ func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Package != "" {
|
||||
packages = append(packages, newDpkgPackage(entry))
|
||||
p := newDpkgPackage(entry)
|
||||
if pkg.IsValid(p) {
|
||||
packages = append(packages, *p)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,20 @@ func TestSinglePackage(t *testing.T) {
|
||||
Architecture: "amd64",
|
||||
InstalledSize: 4064,
|
||||
Maintainer: "APT Development Team <deity@lists.debian.org>",
|
||||
Description: `commandline package manager
|
||||
This package provides commandline tools for searching and
|
||||
managing as well as querying information about packages
|
||||
as a low-level access to all features of the libapt-pkg library.
|
||||
.
|
||||
These include:
|
||||
* apt-get for retrieval of packages and information about them
|
||||
from authenticated sources and for installation, upgrade and
|
||||
removal of packages together with their dependencies
|
||||
* apt-cache for querying available information about installed
|
||||
as well as installable packages
|
||||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/apt/apt.conf.d/01autoremove",
|
||||
@@ -85,6 +99,20 @@ func TestSinglePackage(t *testing.T) {
|
||||
Architecture: "amd64",
|
||||
InstalledSize: 4000,
|
||||
Maintainer: "APT Development Team <deity@lists.debian.org>",
|
||||
Description: `commandline package manager
|
||||
This package provides commandline tools for searching and
|
||||
managing as well as querying information about packages
|
||||
as a low-level access to all features of the libapt-pkg library.
|
||||
.
|
||||
These include:
|
||||
* apt-get for retrieval of packages and information about them
|
||||
from authenticated sources and for installation, upgrade and
|
||||
removal of packages together with their dependencies
|
||||
* apt-cache for querying available information about installed
|
||||
as well as installable packages
|
||||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -121,6 +149,10 @@ func TestMultiplePackages(t *testing.T) {
|
||||
{
|
||||
name: "Test Multiple Package",
|
||||
expected: []pkg.DpkgMetadata{
|
||||
{
|
||||
Package: "no-version",
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
{
|
||||
Package: "tzdata",
|
||||
Version: "2020a-0+deb10u1",
|
||||
@@ -128,7 +160,13 @@ func TestMultiplePackages(t *testing.T) {
|
||||
Architecture: "all",
|
||||
InstalledSize: 3036,
|
||||
Maintainer: "GNU Libc Maintainers <debian-glibc@lists.debian.org>",
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
Description: `time zone and daylight-saving time data
|
||||
This package contains data required for the implementation of
|
||||
standard local time for many representative locations around the
|
||||
globe. It is updated periodically to reflect changes made by
|
||||
political bodies to time zone boundaries, UTC offsets, and
|
||||
daylight-saving rules.`,
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
{
|
||||
Package: "util-linux",
|
||||
@@ -136,6 +174,12 @@ func TestMultiplePackages(t *testing.T) {
|
||||
Architecture: "amd64",
|
||||
InstalledSize: 4327,
|
||||
Maintainer: "LaMont Jones <lamont@debian.org>",
|
||||
Description: `miscellaneous system utilities
|
||||
This package contains a number of important utilities, most of which
|
||||
are oriented towards maintenance of your system. Some of the more
|
||||
important utilities included in this package allow you to view kernel
|
||||
messages, create new filesystems, view block device information,
|
||||
interface with real time clock, etc.`,
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/default/hwclock",
|
||||
@@ -209,7 +253,7 @@ func TestMultiplePackages(t *testing.T) {
|
||||
t.Fatal("Unable to read file contents: ", err)
|
||||
}
|
||||
|
||||
if len(pkgs) != 2 {
|
||||
if len(pkgs) != 3 {
|
||||
t.Fatalf("unexpected number of entries: %d", len(pkgs))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
Package:
|
||||
|
||||
Package: libpam-runtime
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Package:
|
||||
@@ -1,3 +1,7 @@
|
||||
Package:
|
||||
|
||||
Package: no-version
|
||||
|
||||
Package: tzdata
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
|
||||
// NewDotnetDepsCataloger returns a new Dotnet cataloger object base on deps json files.
|
||||
func NewDotnetDepsCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/*.deps.json": parseDotnetDeps,
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "dotnet-deps-cataloger")
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseDotnetDeps
|
||||
|
||||
type dotnetDeps struct {
|
||||
Libraries map[string]dotnetDepsLibrary `json:"libraries"`
|
||||
}
|
||||
|
||||
type dotnetDepsLibrary struct {
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path"`
|
||||
Sha512 string `json:"sha512"`
|
||||
HashPath string `json:"hashPath"`
|
||||
}
|
||||
|
||||
func parseDotnetDeps(path string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []*pkg.Package
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var p dotnetDeps
|
||||
if err := dec.Decode(&p); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err)
|
||||
}
|
||||
|
||||
for nameVersion, lib := range p.Libraries {
|
||||
dotnetPkg := newDotnetDepsPackage(nameVersion, lib)
|
||||
|
||||
if dotnetPkg != nil {
|
||||
packages = append(packages, dotnetPkg)
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary) *pkg.Package {
|
||||
if lib.Type != "package" {
|
||||
return nil
|
||||
}
|
||||
|
||||
splitted := strings.Split(nameVersion, "/")
|
||||
name := splitted[0]
|
||||
version := splitted[1]
|
||||
|
||||
return &pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: &pkg.DotnetDepsMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Path: lib.Path,
|
||||
Sha512: lib.Sha512,
|
||||
HashPath: lib.HashPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user