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