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