176 lines
4.0 KiB
Go
176 lines
4.0 KiB
Go
package buildpack
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/buildpacks/lifecycle/log"
|
|
)
|
|
|
|
const (
|
|
LayerTypeBuild LayerType = iota
|
|
LayerTypeCache
|
|
LayerTypeLaunch
|
|
)
|
|
|
|
const (
|
|
MediaTypeCycloneDX = "application/vnd.cyclonedx+json"
|
|
MediaTypeSPDX = "application/spdx+json"
|
|
MediaTypeSyft = "application/vnd.syft+json"
|
|
mediaTypeUnsupported = "unsupported"
|
|
|
|
ExtensionCycloneDX = "sbom.cdx.json"
|
|
ExtensionSPDX = "sbom.spdx.json"
|
|
ExtensionSyft = "sbom.syft.json"
|
|
)
|
|
|
|
type LayerType int
|
|
|
|
type BOMFile struct {
|
|
BuildpackID string
|
|
LayerName string
|
|
LayerType LayerType
|
|
Path string
|
|
}
|
|
|
|
// Name() returns the destination filename for a given BOM file
|
|
// cdx files should be renamed to "sbom.cdx.json"
|
|
// spdx files should be renamed to "sbom.spdx.json"
|
|
// syft files should be renamed to "sbom.syft.json"
|
|
// If the BOM is neither cdx, spdx, nor syft, the 2nd return argument
|
|
// will return an error to indicate an unsupported format
|
|
func (b *BOMFile) Name() (string, error) {
|
|
switch b.mediaType() {
|
|
case MediaTypeCycloneDX:
|
|
return ExtensionCycloneDX, nil
|
|
case MediaTypeSPDX:
|
|
return ExtensionSPDX, nil
|
|
case MediaTypeSyft:
|
|
return ExtensionSyft, nil
|
|
default:
|
|
return "", errors.Errorf("unsupported SBOM format: '%s'", b.Path)
|
|
}
|
|
}
|
|
|
|
func (b *BOMFile) mediaType() string {
|
|
name := filepath.Base(b.Path)
|
|
|
|
switch {
|
|
case strings.HasSuffix(name, "."+ExtensionCycloneDX):
|
|
return MediaTypeCycloneDX
|
|
case strings.HasSuffix(name, "."+ExtensionSPDX):
|
|
return MediaTypeSPDX
|
|
case strings.HasSuffix(name, "."+ExtensionSyft):
|
|
return MediaTypeSyft
|
|
default:
|
|
return mediaTypeUnsupported
|
|
}
|
|
}
|
|
|
|
func validateMediaTypes(bp GroupElement, bomfiles []BOMFile, declaredTypes []string) error {
|
|
ensureDeclared := func(declaredTypes []string, foundType string) error {
|
|
for _, declaredType := range declaredTypes {
|
|
dType, _, err := mime.ParseMediaType(declaredType)
|
|
if err != nil {
|
|
return errors.Wrap(err, "parsing declared media type")
|
|
}
|
|
if foundType == dType {
|
|
return nil
|
|
}
|
|
}
|
|
return errors.Errorf("undeclared SBOM media type: '%s'", foundType)
|
|
}
|
|
|
|
for _, bomFile := range bomfiles {
|
|
fileType := bomFile.mediaType()
|
|
switch fileType {
|
|
case mediaTypeUnsupported:
|
|
return errors.Errorf("unsupported SBOM file format: '%s'", bomFile.Path)
|
|
default:
|
|
if err := ensureDeclared(declaredTypes, fileType); err != nil {
|
|
return errors.Wrap(err, fmt.Sprintf("validating SBOM file '%s' for buildpack: '%s'", bomFile.Path, bp.String()))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func sbomGlob(layersDir string) (matches []string, err error) {
|
|
layerGlob := filepath.Join(layersDir, "*.sbom.*.json")
|
|
matches, err = filepath.Glob(layerGlob)
|
|
return
|
|
}
|
|
|
|
func (d *BpDescriptor) processSBOMFiles(layersDir string, bp GroupElement, bpLayers map[string]LayerMetadataFile, logger log.Logger) ([]BOMFile, error) {
|
|
var (
|
|
files []BOMFile
|
|
)
|
|
|
|
matches, err := sbomGlob(layersDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, m := range matches {
|
|
layerDir, file := filepath.Split(m)
|
|
layerName := strings.SplitN(file, ".", 2)[0]
|
|
|
|
if layerName == "launch" {
|
|
files = append(files, BOMFile{
|
|
BuildpackID: bp.ID,
|
|
LayerType: LayerTypeLaunch,
|
|
Path: m,
|
|
})
|
|
|
|
continue
|
|
}
|
|
|
|
if layerName == "build" {
|
|
files = append(files, BOMFile{
|
|
BuildpackID: bp.ID,
|
|
LayerType: LayerTypeBuild,
|
|
Path: m,
|
|
})
|
|
|
|
continue
|
|
}
|
|
|
|
meta, ok := bpLayers[filepath.Join(layerDir, layerName)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if meta.Launch {
|
|
files = append(files, BOMFile{
|
|
BuildpackID: bp.ID,
|
|
LayerName: layerName,
|
|
LayerType: LayerTypeLaunch,
|
|
Path: m,
|
|
})
|
|
} else {
|
|
files = append(files, BOMFile{
|
|
BuildpackID: bp.ID,
|
|
LayerName: layerName,
|
|
LayerType: LayerTypeBuild,
|
|
Path: m,
|
|
})
|
|
}
|
|
|
|
if meta.Cache {
|
|
files = append(files, BOMFile{
|
|
BuildpackID: bp.ID,
|
|
LayerName: layerName,
|
|
LayerType: LayerTypeCache,
|
|
Path: m,
|
|
})
|
|
}
|
|
}
|
|
|
|
return files, validateMediaTypes(bp, files, d.Buildpack.SBOM)
|
|
}
|