github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/examples/create_custom_sbom/alpine_configuration_cataloger.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"path"
     8  
     9  	"github.com/anchore/syft/syft/artifact"
    10  	"github.com/anchore/syft/syft/file"
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  /*
    15    This is a contrived cataloger that attempts to capture useful APK files from the image as if it were a package.
    16    This isn't a real cataloger, but it is a good example of how to use API elements to create a custom cataloger.
    17  */
    18  
    19  var _ pkg.Cataloger = (*alpineConfigurationCataloger)(nil)
    20  
    21  type alpineConfigurationCataloger struct {
    22  }
    23  
    24  func newAlpineConfigurationCataloger() pkg.Cataloger {
    25  	return alpineConfigurationCataloger{}
    26  }
    27  
    28  func (m alpineConfigurationCataloger) Name() string {
    29  	return "apk-configuration-cataloger"
    30  }
    31  
    32  func (m alpineConfigurationCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    33  	version, versionLocations, err := getVersion(resolver)
    34  	if err != nil {
    35  		return nil, nil, fmt.Errorf("unable to get alpine version: %w", err)
    36  	}
    37  	if len(versionLocations) == 0 {
    38  		// this doesn't mean we should stop cataloging, just that we don't have a version to use, thus no package to raise up
    39  		return nil, nil, nil
    40  	}
    41  
    42  	metadata, metadataLocations, err := newAlpineConfiguration(resolver)
    43  	if err != nil {
    44  		return nil, nil, err
    45  	}
    46  
    47  	var locations []file.Location
    48  	locations = append(locations, versionLocations...)
    49  	locations = append(locations, metadataLocations...)
    50  
    51  	p := newPackage(version, *metadata, locations...)
    52  
    53  	return []pkg.Package{p}, nil, nil
    54  }
    55  
    56  func newPackage(version string, metadata AlpineConfiguration, locations ...file.Location) pkg.Package {
    57  	return pkg.Package{
    58  		Name:      "alpine-configuration",
    59  		Version:   version,
    60  		Locations: file.NewLocationSet(locations...),
    61  		Type:      pkg.Type("system-configuration"), // you can make up your own package type here or use an existing one
    62  		Metadata:  metadata,
    63  	}
    64  }
    65  
    66  func newAlpineConfiguration(resolver file.Resolver) (*AlpineConfiguration, []file.Location, error) {
    67  	var locations []file.Location
    68  
    69  	keys, keyLocations, err := getAPKKeys(resolver)
    70  	if err != nil {
    71  		return nil, nil, err
    72  	}
    73  
    74  	locations = append(locations, keyLocations...)
    75  
    76  	return &AlpineConfiguration{
    77  		APKKeys: keys,
    78  	}, locations, nil
    79  
    80  }
    81  
    82  func getVersion(resolver file.Resolver) (string, []file.Location, error) {
    83  	locations, err := resolver.FilesByPath("/etc/alpine-release")
    84  	if err != nil {
    85  		return "", nil, fmt.Errorf("unable to get alpine version: %w", err)
    86  	}
    87  	if len(locations) == 0 {
    88  		return "", nil, nil
    89  	}
    90  
    91  	reader, err := resolver.FileContentsByLocation(locations[0])
    92  	if err != nil {
    93  		return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
    94  	}
    95  
    96  	version, err := io.ReadAll(reader)
    97  	if err != nil {
    98  		return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
    99  	}
   100  
   101  	return string(version), locations, nil
   102  }
   103  
   104  func getAPKKeys(resolver file.Resolver) (map[string]string, []file.Location, error) {
   105  	// name-to-content values
   106  	keyContent := make(map[string]string)
   107  
   108  	locations, err := resolver.FilesByGlob("/etc/apk/keys/*.rsa.pub")
   109  	if err != nil {
   110  		return nil, nil, fmt.Errorf("unable to get apk keys: %w", err)
   111  	}
   112  	for _, location := range locations {
   113  		basename := path.Base(location.RealPath)
   114  		reader, err := resolver.FileContentsByLocation(location)
   115  		content, err := io.ReadAll(reader)
   116  		if err != nil {
   117  			return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
   118  		}
   119  		keyContent[basename] = string(content)
   120  	}
   121  	return keyContent, locations, nil
   122  }
   123  
   124  type AlpineConfiguration struct {
   125  	APKKeys map[string]string `json:"apkKeys" yaml:"apkKeys"`
   126  	// Add more data you want to capture as part of the package metadata here...
   127  }