Skip to content

Commit

Permalink
add syft 3.0.1; unit tests passing
Browse files Browse the repository at this point in the history
  • Loading branch information
Frankie Gallina-Jones authored and ryanmoran committed Apr 8, 2022
1 parent 23a81a1 commit 776ba7a
Show file tree
Hide file tree
Showing 23 changed files with 1,478 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
.idea
coverage.out
**/*.tar
9 changes: 9 additions & 0 deletions sbom/internal/formats/syft301/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Source
The contents of this directory is largely based on anchore/syft's internal
`syftjson` package. The version copied is from
[v0.41.1](https://github.com/anchore/syft/blob/07d3c9af52f241613971ccadd18c8f8ab67abc4e)
of Syft that supports Syft JSON Schema 3.0.1.

The implementations of `decoder` and `validator` have been omitted for
simplicity, since they are not required for buildpacks' SBOM generation.

19 changes: 19 additions & 0 deletions sbom/internal/formats/syft301/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package syft301

import (
"encoding/json"
"io"

"github.com/anchore/syft/syft/sbom"
)

func encoder(output io.Writer, s sbom.SBOM) error {
doc := ToFormatModel(s)

enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")

return enc.Encode(&doc)
}
206 changes: 206 additions & 0 deletions sbom/internal/formats/syft301/encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package syft301

import (
"flag"
"testing"

"github.com/anchore/syft/syft/file"

"github.com/anchore/syft/syft/artifact"

"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"

// "github.com/anchore/syft/internal/formats/common/testutils"
"github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/common/testutils"
)

var updateJson = flag.Bool("update-json", false, "update the *.golden files for json encoders")

func TestDirectoryEncoder(t *testing.T) {
testutils.AssertEncoderAgainstGoldenSnapshot(t,
Format(),
testutils.DirectoryInput(t),
*updateJson,
)
}

func TestImageEncoder(t *testing.T) {
testImage := "image-simple"
testutils.AssertEncoderAgainstGoldenImageSnapshot(t,
Format(),
testutils.ImageInput(t, testImage, testutils.FromSnapshot()),
testImage,
*updateJson,
)
}

func TestEncodeFullJSONDocument(t *testing.T) {
catalog := pkg.NewCatalog()

p1 := pkg.Package{
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/a/place/a",
},
},
},
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Files: []pkg.PythonFileRecord{},
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
}

p2 := pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/b/place/b",
},
},
},
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
Files: []pkg.DpkgFileRecord{},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
}

catalog.Add(p1)
catalog.Add(p2)

s := sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
FileMetadata: map[source.Coordinates]source.FileMetadata{
source.NewLocation("/a/place").Coordinates: {
Mode: 0775,
Type: "directory",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/a/place/a").Coordinates: {
Mode: 0775,
Type: "regularFile",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b").Coordinates: {
Mode: 0775,
Type: "symbolicLink",
LinkDestination: "/c",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b/place/b").Coordinates: {
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
FileDigests: map[source.Coordinates][]file.Digest{
source.NewLocation("/a/place/a").Coordinates: {
{
Algorithm: "sha256",
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
},
},
source.NewLocation("/b/place/b").Coordinates: {
{
Algorithm: "sha256",
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
},
},
},
FileContents: map[source.Coordinates]string{
source.NewLocation("/a/place/a").Coordinates: "the-contents",
},
LinuxDistribution: &linux.Release{
ID: "redhat",
Version: "7",
VersionID: "7",
IDLike: []string{
"rhel",
},
},
},
Relationships: []artifact.Relationship{
{
From: p1,
To: p2,
Type: artifact.OwnershipByFileOverlapRelationship,
Data: map[string]string{
"file": "path",
},
},
},
Source: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
UserInput: "user-image-input",
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
ManifestDigest: "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Tags: []string{
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b",
},
Size: 38,
Layers: []source.LayerMetadata{
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:3de16c5b8659a2e8d888b8ded8427be7a5686a3c8c4e4dd30de20f362827285b",
Size: 22,
},
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
Size: 16,
},
},
RawManifest: []byte("eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJh..."),
RawConfig: []byte("eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZp..."),
RepoDigests: []string{},
},
},
Descriptor: sbom.Descriptor{
Name: "syft",
Version: "v0.42.0-bogus",
// the application configuration should be persisted here, however, we do not want to import
// the application configuration in this package (it's reserved only for ingestion by the cmd package)
Configuration: map[string]string{
"config-key": "config-value",
},
},
}

testutils.AssertEncoderAgainstGoldenSnapshot(t,
Format(),
s,
*updateJson,
)
}
29 changes: 29 additions & 0 deletions sbom/internal/formats/syft301/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package syft301

import (
"io"

"github.com/anchore/syft/syft/sbom"
)

const ID sbom.FormatID = "syft-3.0.1-json"
const JSONSchemaVersion string = "3.0.1"

// Decoder not implemented because it's not needed for buildpacks' SBOM generation
var dummyDecoder func(io.Reader) (*sbom.SBOM, error) = func(input io.Reader) (*sbom.SBOM, error) {
return nil, nil
}

// Validator not implemented because it's not needed for buildpacks' SBOM generation
var dummyValidator func(io.Reader) error = func(input io.Reader) error {
return nil
}

func Format() sbom.Format {
return sbom.NewFormat(
ID,
encoder,
dummyDecoder,
dummyValidator,
)
}
25 changes: 25 additions & 0 deletions sbom/internal/formats/syft301/model/document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package model

// Document represents the syft cataloging findings as a JSON document
type Document struct {
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
ArtifactRelationships []Relationship `json:"artifactRelationships"`
Files []File `json:"files,omitempty"` // note: must have omitempty
Secrets []Secrets `json:"secrets,omitempty"` // note: must have omitempty
Source Source `json:"source"` // Source represents the original object that was cataloged
Distro LinuxRelease `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
}

// Descriptor describes what created the document as well as surrounding metadata
type Descriptor struct {
Name string `json:"name"`
Version string `json:"version"`
Configuration interface{} `json:"configuration,omitempty"`
}

type Schema struct {
Version string `json:"version"`
URL string `json:"url"`
}
25 changes: 25 additions & 0 deletions sbom/internal/formats/syft301/model/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package model

import (
"github.com/anchore/syft/syft/file"

"github.com/anchore/syft/syft/source"
)

type File struct {
ID string `json:"id"`
Location source.Coordinates `json:"location"`
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
Contents string `json:"contents,omitempty"`
Digests []file.Digest `json:"digests,omitempty"`
Classifications []file.Classification `json:"classifications,omitempty"`
}

type FileMetadataEntry struct {
Mode int `json:"mode"`
Type source.FileType `json:"type"`
LinkDestination string `json:"linkDestination,omitempty"`
UserID int `json:"userID"`
GroupID int `json:"groupID"`
MIMEType string `json:"mimeType"`
}
38 changes: 38 additions & 0 deletions sbom/internal/formats/syft301/model/linux_release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package model

import (
"encoding/json"
)

type IDLikes []string

type LinuxRelease struct {
PrettyName string `json:"prettyName,omitempty"`
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
IDLike IDLikes `json:"idLike,omitempty"`
Version string `json:"version,omitempty"`
VersionID string `json:"versionID,omitempty"`
Variant string `json:"variant,omitempty"`
VariantID string `json:"variantID,omitempty"`
HomeURL string `json:"homeURL,omitempty"`
SupportURL string `json:"supportURL,omitempty"`
BugReportURL string `json:"bugReportURL,omitempty"`
PrivacyPolicyURL string `json:"privacyPolicyURL,omitempty"`
CPEName string `json:"cpeName,omitempty"`
}

func (s *IDLikes) UnmarshalJSON(data []byte) error {
var str string
var strSlice []string

// we support unmarshalling from a single value to support syft json schema v2
if err := json.Unmarshal(data, &str); err == nil {
*s = []string{str}
} else if err := json.Unmarshal(data, &strSlice); err == nil {
*s = strSlice
} else {
return err
}
return nil
}
Loading

0 comments on commit 776ba7a

Please sign in to comment.