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 }