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 }