github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/sbom/cataloger.go (about)

     1  /*
     2  Package sbom provides a concrete Cataloger implementation for capturing packages embedded within SBOM files.
     3  */
     4  package sbom
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  
    12  	"github.com/anchore/syft/internal/log"
    13  	"github.com/anchore/syft/syft/artifact"
    14  	"github.com/anchore/syft/syft/file"
    15  	"github.com/anchore/syft/syft/format"
    16  	"github.com/anchore/syft/syft/pkg"
    17  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    18  )
    19  
    20  const catalogerName = "sbom-cataloger"
    21  
    22  // NewCataloger returns a new SBOM cataloger object loaded from saved SBOM JSON.
    23  func NewCataloger() pkg.Cataloger {
    24  	return generic.NewCataloger(catalogerName).
    25  		WithParserByGlobs(parseSBOM,
    26  			"**/*.syft.json",
    27  			"**/*.bom.*",
    28  			"**/*.bom",
    29  			"**/bom",
    30  			"**/*.sbom.*",
    31  			"**/*.sbom",
    32  			"**/sbom",
    33  			"**/*.cdx.*",
    34  			"**/*.cdx",
    35  			"**/*.spdx.*",
    36  			"**/*.spdx",
    37  		)
    38  }
    39  
    40  func parseSBOM(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    41  	readSeeker, err := adaptToReadSeeker(reader)
    42  	if err != nil {
    43  		return nil, nil, fmt.Errorf("unable to read SBOM file %q: %w", reader.Location.RealPath, err)
    44  	}
    45  	s, _, _, err := format.Decode(readSeeker)
    46  	if err != nil {
    47  		return nil, nil, err
    48  	}
    49  
    50  	if s == nil {
    51  		log.WithFields("path", reader.Location.RealPath).Trace("file is not an SBOM")
    52  		return nil, nil, nil
    53  	}
    54  
    55  	var pkgs []pkg.Package
    56  	relationships := s.Relationships
    57  	for _, p := range s.Artifacts.Packages.Sorted() {
    58  		// replace all locations on the package with the location of the SBOM file.
    59  		// Why not keep the original list of locations? Since the "locations" field is meant to capture
    60  		// where there is evidence of this file, and the catalogers have not run against any file other than,
    61  		// the SBOM, this is the only location that is relevant for this cataloger.
    62  		p.Locations = file.NewLocationSet(
    63  			reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    64  		)
    65  		p.FoundBy = catalogerName
    66  
    67  		pkgs = append(pkgs, p)
    68  		relationships = append(relationships, artifact.Relationship{
    69  			From: p,
    70  			To:   reader.Location.Coordinates,
    71  			Type: artifact.DescribedByRelationship,
    72  		})
    73  	}
    74  
    75  	return pkgs, relationships, nil
    76  }
    77  
    78  func adaptToReadSeeker(reader io.Reader) (io.ReadSeeker, error) {
    79  	// with the stereoscope API and default file.Resolver implementation here in syft, odds are very high that
    80  	// the underlying reader is already a ReadSeeker, so we can just return it as-is. We still want to
    81  	if rs, ok := reader.(io.ReadSeeker); ok {
    82  		return rs, nil
    83  	}
    84  
    85  	log.Debug("SBOM cataloger reader is not a ReadSeeker, reading entire SBOM into memory")
    86  
    87  	var buff bytes.Buffer
    88  	_, err := io.Copy(&buff, reader)
    89  	return bytes.NewReader(buff.Bytes()), err
    90  }