github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/secret/secret.go (about)

     1  package secret
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/samber/lo"
    13  	"golang.org/x/exp/slices"
    14  	"golang.org/x/xerrors"
    15  
    16  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    17  	"github.com/devseccon/trivy/pkg/fanal/secret"
    18  	"github.com/devseccon/trivy/pkg/fanal/types"
    19  	"github.com/devseccon/trivy/pkg/fanal/utils"
    20  )
    21  
    22  // To make sure SecretAnalyzer implements analyzer.Initializer
    23  var _ analyzer.Initializer = &SecretAnalyzer{}
    24  
    25  const version = 1
    26  
    27  var (
    28  	skipFiles = []string{
    29  		"go.mod",
    30  		"go.sum",
    31  		"package-lock.json",
    32  		"yarn.lock",
    33  		"pnpm-lock.yaml",
    34  		"Pipfile.lock",
    35  		"Gemfile.lock",
    36  	}
    37  	skipDirs = []string{".git", "node_modules"}
    38  	skipExts = []string{
    39  		".jpg", ".png", ".gif", ".doc", ".pdf", ".bin", ".svg", ".socket", ".deb", ".rpm",
    40  		".zip", ".gz", ".gzip", ".tar", ".pyc",
    41  	}
    42  )
    43  
    44  func init() {
    45  	// The scanner will be initialized later via InitScanner()
    46  	analyzer.RegisterAnalyzer(NewSecretAnalyzer(secret.Scanner{}, ""))
    47  }
    48  
    49  // SecretAnalyzer is an analyzer for secrets
    50  type SecretAnalyzer struct {
    51  	scanner    secret.Scanner
    52  	configPath string
    53  }
    54  
    55  func NewSecretAnalyzer(s secret.Scanner, configPath string) *SecretAnalyzer {
    56  	return &SecretAnalyzer{
    57  		scanner:    s,
    58  		configPath: configPath,
    59  	}
    60  }
    61  
    62  // Init initializes and sets a secret scanner
    63  func (a *SecretAnalyzer) Init(opt analyzer.AnalyzerOptions) error {
    64  	if opt.SecretScannerOption.ConfigPath == a.configPath && !lo.IsEmpty(a.scanner) {
    65  		// This check is for tools importing Trivy and customize analyzers
    66  		// Never reach here in Trivy OSS
    67  		return nil
    68  	}
    69  	configPath := opt.SecretScannerOption.ConfigPath
    70  	c, err := secret.ParseConfig(configPath)
    71  	if err != nil {
    72  		return xerrors.Errorf("secret config error: %w", err)
    73  	}
    74  	a.scanner = secret.NewScanner(c)
    75  	a.configPath = configPath
    76  	return nil
    77  }
    78  
    79  func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
    80  	// Do not scan binaries
    81  	binary, err := utils.IsBinary(input.Content, input.Info.Size())
    82  	if binary || err != nil {
    83  		return nil, nil
    84  	}
    85  
    86  	content, err := io.ReadAll(input.Content)
    87  	if err != nil {
    88  		return nil, xerrors.Errorf("read error %s: %w", input.FilePath, err)
    89  	}
    90  
    91  	content = bytes.ReplaceAll(content, []byte("\r"), []byte(""))
    92  
    93  	filePath := input.FilePath
    94  	// Files extracted from the image have an empty input.Dir.
    95  	// Also, paths to these files do not have "/" prefix.
    96  	// We need to add a "/" prefix to properly filter paths from the config file.
    97  	if input.Dir == "" { // add leading `/` for files extracted from image
    98  		filePath = fmt.Sprintf("/%s", filePath)
    99  	}
   100  
   101  	result := a.scanner.Scan(secret.ScanArgs{
   102  		FilePath: filePath,
   103  		Content:  content,
   104  	})
   105  
   106  	if len(result.Findings) == 0 {
   107  		return nil, nil
   108  	}
   109  
   110  	return &analyzer.AnalysisResult{
   111  		Secrets: []types.Secret{result},
   112  	}, nil
   113  }
   114  
   115  func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool {
   116  	// Skip small files
   117  	if fi.Size() < 10 {
   118  		return false
   119  	}
   120  
   121  	dir, fileName := filepath.Split(filePath)
   122  	dir = filepath.ToSlash(dir)
   123  	dirs := strings.Split(dir, "/")
   124  
   125  	// Check if the directory should be skipped
   126  	for _, skipDir := range skipDirs {
   127  		if slices.Contains(dirs, skipDir) {
   128  			return false
   129  		}
   130  	}
   131  
   132  	// Check if the file should be skipped
   133  	if slices.Contains(skipFiles, fileName) {
   134  		return false
   135  	}
   136  
   137  	// Skip the config file for secret scanning
   138  	if filepath.Base(a.configPath) == filePath {
   139  		return false
   140  	}
   141  
   142  	// Check if the file extension should be skipped
   143  	ext := filepath.Ext(fileName)
   144  	if slices.Contains(skipExts, ext) {
   145  		return false
   146  	}
   147  
   148  	if a.scanner.AllowPath(filePath) {
   149  		return false
   150  	}
   151  
   152  	return true
   153  }
   154  
   155  func (a *SecretAnalyzer) Type() analyzer.Type {
   156  	return analyzer.TypeSecret
   157  }
   158  
   159  func (a *SecretAnalyzer) Version() int {
   160  	return version
   161  }