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  }