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