github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/file/cataloger/secrets/cataloger.go (about) 1 package secrets 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "regexp" 8 "sort" 9 10 "github.com/nextlinux/gosbom/gosbom/event" 11 "github.com/nextlinux/gosbom/gosbom/file" 12 internal2 "github.com/nextlinux/gosbom/gosbom/file/cataloger/internal" 13 "github.com/nextlinux/gosbom/internal" 14 "github.com/nextlinux/gosbom/internal/bus" 15 "github.com/nextlinux/gosbom/internal/log" 16 "github.com/wagoodman/go-partybus" 17 "github.com/wagoodman/go-progress" 18 ) 19 20 var DefaultSecretsPatterns = map[string]string{ 21 "aws-access-key": `(?i)aws_access_key_id["'=:\s]*?(?P<value>(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})`, 22 "aws-secret-key": `(?i)aws_secret_access_key["'=:\s]*?(?P<value>[0-9a-zA-Z/+]{40})`, 23 "pem-private-key": `-----BEGIN (\S+ )?PRIVATE KEY(\sBLOCK)?-----((?P<value>(\n.*?)+)-----END (\S+ )?PRIVATE KEY(\sBLOCK)?-----)?`, 24 "docker-config-auth": `"auths"((.*\n)*.*?"auth"\s*:\s*"(?P<value>[^"]+)")?`, 25 "generic-api-key": `(?i)api(-|_)?key["'=:\s]*?(?P<value>[A-Z0-9]{20,60})["']?(\s|$)`, 26 } 27 28 // Deprecated: will be removed in gosbom v1.0.0 29 type Cataloger struct { 30 patterns map[string]*regexp.Regexp 31 revealValues bool 32 skipFilesAboveSize int64 33 } 34 35 // Deprecated: will be removed in gosbom v1.0.0 36 func NewCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*Cataloger, error) { 37 return &Cataloger{ 38 patterns: patterns, 39 revealValues: revealValues, 40 skipFilesAboveSize: maxFileSize, 41 }, nil 42 } 43 44 func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.SearchResult, error) { 45 results := make(map[file.Coordinates][]file.SearchResult) 46 locations := internal2.AllRegularFiles(resolver) 47 stage, prog, secretsDiscovered := secretsCatalogingProgress(int64(len(locations))) 48 for _, location := range locations { 49 stage.Current = location.RealPath 50 result, err := i.catalogLocation(resolver, location) 51 if internal.IsErrPathPermission(err) { 52 log.Debugf("secrets cataloger skipping - %+v", err) 53 continue 54 } 55 56 if err != nil { 57 return nil, err 58 } 59 if len(result) > 0 { 60 secretsDiscovered.Add(int64(len(result))) 61 results[location.Coordinates] = result 62 } 63 prog.Increment() 64 } 65 log.Debugf("secrets cataloger discovered %d secrets", secretsDiscovered.Current()) 66 prog.SetCompleted() 67 return results, nil 68 } 69 70 func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.SearchResult, error) { 71 metadata, err := resolver.FileMetadataByLocation(location) 72 if err != nil { 73 return nil, err 74 } 75 76 if metadata.Size() == 0 { 77 return nil, nil 78 } 79 80 if i.skipFilesAboveSize > 0 && metadata.Size() > i.skipFilesAboveSize { 81 return nil, nil 82 } 83 84 // TODO: in the future we can swap out search strategies here 85 secrets, err := catalogLocationByLine(resolver, location, i.patterns) 86 if err != nil { 87 return nil, internal.ErrPath{Context: "secrets-cataloger", Path: location.RealPath, Err: err} 88 } 89 90 if i.revealValues { 91 for idx, secret := range secrets { 92 value, err := extractValue(resolver, location, secret.SeekPosition, secret.Length) 93 if err != nil { 94 return nil, err 95 } 96 secrets[idx].Value = value 97 } 98 } 99 100 // sort by the start location of each secret as it appears in the location 101 sort.SliceStable(secrets, func(i, j int) bool { 102 return secrets[i].SeekPosition < secrets[j].SeekPosition 103 }) 104 105 return secrets, nil 106 } 107 108 func extractValue(resolver file.Resolver, location file.Location, start, length int64) (string, error) { 109 readCloser, err := resolver.FileContentsByLocation(location) 110 if err != nil { 111 return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err) 112 } 113 defer internal.CloseAndLogError(readCloser, location.VirtualPath) 114 115 n, err := io.CopyN(io.Discard, readCloser, start) 116 if err != nil { 117 return "", fmt.Errorf("unable to read contents for location=%q : %w", location, err) 118 } 119 if n != start { 120 return "", fmt.Errorf("unexpected seek location for location=%q : %d != %d", location, n, start) 121 } 122 123 var buf bytes.Buffer 124 n, err = io.CopyN(&buf, readCloser, length) 125 if err != nil { 126 return "", fmt.Errorf("unable to read secret value for location=%q : %w", location, err) 127 } 128 if n != length { 129 return "", fmt.Errorf("unexpected secret length for location=%q : %d != %d", location, n, length) 130 } 131 132 return buf.String(), nil 133 } 134 135 type Monitor struct { 136 progress.Stager 137 SecretsDiscovered progress.Monitorable 138 progress.Progressable 139 } 140 141 func secretsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual, *progress.Manual) { 142 stage := &progress.Stage{} 143 secretsDiscovered := &progress.Manual{} 144 prog := progress.NewManual(locations) 145 146 bus.Publish(partybus.Event{ 147 Type: event.SecretsCatalogerStarted, 148 Source: secretsDiscovered, 149 Value: Monitor{ 150 Stager: progress.Stager(stage), 151 SecretsDiscovered: secretsDiscovered, 152 Progressable: prog, 153 }, 154 }) 155 156 return stage, prog, secretsDiscovered 157 }