Skip to content

Commit

Permalink
feat: work in progress restructure of pillager project
Browse files Browse the repository at this point in the history
  • Loading branch information
brittonhayes committed Nov 3, 2024
1 parent 6d6740d commit 9156c1e
Show file tree
Hide file tree
Showing 29 changed files with 615 additions and 897 deletions.
15 changes: 12 additions & 3 deletions cmd/pillager/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
// Package pillager is the entrypoint to the Pillager CLI
package main

import (
pillager "github.com/brittonhayes/pillager/internal/commands"
"os"

"github.com/brittonhayes/pillager/internal/commands"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)

func init() {
// Setup default logging
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}

func main() {
pillager.Execute()
commands.Execute()
}
85 changes: 51 additions & 34 deletions internal/commands/hunt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
package commands

import (
"fmt"
"os"
"runtime"

"github.com/brittonhayes/pillager/pkg/format"
"github.com/brittonhayes/pillager/pkg/hunter"
"github.com/brittonhayes/pillager/pkg/rules"
"github.com/brittonhayes/pillager/pkg/tui/model"
tea "github.com/charmbracelet/bubbletea"
"github.com/brittonhayes/pillager"
"github.com/brittonhayes/pillager/internal/scanner"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
)

Expand All @@ -24,6 +23,7 @@ var (
templ string
workers int
interactive bool
config string
)

// huntCmd represents the hunt command.
Expand Down Expand Up @@ -58,57 +58,74 @@ var huntCmd = &cobra.Command{
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// Read gitleaks config from file
// or fallback to default
gitleaksConfig := rules.NewLoader(
rules.WithFile(rulesConfig),
).Load()
if level != "" {
lvl, err := zerolog.ParseLevel(level)
if err != nil {
return fmt.Errorf("invalid log level: %w", err)
}
zerolog.SetGlobalLevel(lvl)
}

h, err := hunter.New(
hunter.WithGitleaksConfig(gitleaksConfig),
hunter.WithScanPath(args[0]),
hunter.WithWorkers(workers),
hunter.WithVerbose(verbose),
hunter.WithTemplate(templ),
hunter.WithRedact(redact),
hunter.WithFormat(format.StringToReporter(reporter)),
hunter.WithLogLevel(level),
)
configLoader := scanner.NewConfigLoader()
opts, err := configLoader.LoadConfig(config)
if err != nil {
return err
if config != "" {
return fmt.Errorf("failed to load config: %w", err)
}
opts = &pillager.Options{
ScanPath: args[0],
Workers: runtime.NumCPU(),
Verbose: false,
Template: "",
Redact: false,
Reporter: "json",
}
}

if interactive {
return runInteractive(h)
// Merge command line flags with config file
flagOpts := &pillager.Options{
ScanPath: args[0],
Redact: redact,
Verbose: verbose,
Workers: workers,
Reporter: reporter,
Template: templ,
}
configLoader.MergeWithFlags(opts, flagOpts)

results, err := h.Hunt()
s, err := scanner.NewGitleaksScanner(*opts)
if err != nil {
return err
return fmt.Errorf("failed to create scanner: %w", err)
}

if err = h.Report(os.Stdout, results); err != nil {
// if interactive {
// return runInteractive(scan)
// }

results, err := s.Scan(args[0])
if err != nil {
return err
}

return nil
return s.Reporter().Report(os.Stdout, results)
},
}

func runInteractive(h *hunter.Hunter) error {
m := model.NewModel(h)
p := tea.NewProgram(m, tea.WithAltScreen())
return p.Start()
}
// func runInteractive(h *scanner.Hunter) error {
// m := model.NewModel(h)
// p := tea.NewProgram(m, tea.WithAltScreen())
// return p.Start()
// }

func init() {
rootCmd.AddCommand(huntCmd)
huntCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "run in interactive mode")
huntCmd.Flags().IntVarP(&workers, "workers", "w", runtime.NumCPU(), "number of concurrent workers")
huntCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "enable scanner verbose output")
huntCmd.Flags().StringVarP(&level, "log-level", "l", "error", "set logging level")
huntCmd.Flags().StringVarP(&level, "log-level", "l", "info", "set logging level")
huntCmd.Flags().StringVarP(&config, "config", "c", "", "path to pillager config file")
huntCmd.Flags().StringVarP(&rulesConfig, "rules", "r", "", "path to gitleaks rules.toml config")
huntCmd.Flags().StringVarP(&reporter, "format", "f", "json", "set secret reporter (json, yaml)")
huntCmd.Flags().StringVarP(&reporter, "format", "f", "json", "set secret reporter format (json, yaml)")
huntCmd.Flags().BoolVar(&redact, "redact", false, "redact secret from results")
huntCmd.Flags().StringVarP(&templ, "template", "t", "", "set go text/template string for output format")
}
31 changes: 8 additions & 23 deletions pkg/rules/rules.go → internal/rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
package rules

import (
_ "embed"

"github.com/BurntSushi/toml"
"github.com/brittonhayes/pillager"
"github.com/brittonhayes/pillager/internal/validate"
"github.com/rs/zerolog/log"

Expand All @@ -16,16 +15,11 @@ import (
const ErrReadConfig = "Failed to read gitleaks config"

// These strings contain default configs. They are initialized at compile time via go:embed.
var (
RulesDefault = config.DefaultConfig

//go:embed rules_strict.toml
RulesStrict string
)
var RulesDefault = config.DefaultConfig

// Loader represents a gitleaks config loader.
type Loader struct {
loader config.ViperConfig
loader pillager.Options
}

// LoaderOption sets a parameter for the gitleaks config loader.
Expand All @@ -45,26 +39,17 @@ func NewLoader(opts ...LoaderOption) *Loader {
return &loader
}

// WithStrict enables more strict pillager scanning.
func (l *Loader) WithStrict() LoaderOption {
return func(l *Loader) {
if _, err := toml.Decode(RulesStrict, &l.loader); err != nil {
log.Fatal().Err(err).Msg(ErrReadConfig)
}
}
}

// Load parses the gitleaks configuration.
func (l *Loader) Load() config.Config {
config, err := l.loader.Translate()
if err != nil {
// Load parses the pillager configuration.
func (l *Loader) Load() *pillager.Options {
var config *pillager.Options
if _, err := toml.Decode(RulesDefault, &config); err != nil {
log.Fatal().Err(err).Msg(ErrReadConfig)
}

return config
}

// WithFile decodes a gitleaks config from a local file.
// WithFile decodes a pillager config from a local file.
func WithFile(file string) LoaderOption {
return func(l *Loader) {
if file == "" {
Expand Down
File renamed without changes.
File renamed without changes.
141 changes: 141 additions & 0 deletions internal/scanner/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package scanner

import (
"fmt"
"path/filepath"
"runtime"

"github.com/BurntSushi/toml"
"github.com/brittonhayes/pillager"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"github.com/zricethezav/gitleaks/v8/config"
)

// ConfigLoader handles loading configuration from files
type ConfigLoader struct {
v *viper.Viper
}

// NewConfigLoader creates a new configuration loader
func NewConfigLoader() *ConfigLoader {
v := viper.New()
v.SetDefault("verbose", false)
v.SetDefault("scan_path", ".")
v.SetDefault("template", "")
v.SetDefault("workers", runtime.NumCPU())
v.SetDefault("redact", false)
v.SetDefault("reporter", "json")

// Rules and allowlist defaults
v.SetDefault("rules", convertGitleaksRules())
v.SetDefault("allowlist.paths", []string{})
v.SetDefault("allowlist.regexes", []string{})
v.SetDefault("allowlist.commits", []string{})

return &ConfigLoader{v: v}
}

func convertGitleaksRules() []pillager.Rule {
var defaultConfig config.Config
if err := toml.Unmarshal([]byte(config.DefaultConfig), &defaultConfig); err != nil {
log.Fatal().Err(err).Msg("failed to parse default rules")
}

var rules []pillager.Rule
for _, rule := range defaultConfig.Rules {
rules = append(rules, pillager.Rule{
ID: rule.RuleID,
Description: rule.Description,
Regex: rule.Regex.String(),
Tags: rule.Tags,
})
}

return rules
}

// LoadConfig attempts to load configuration from a file
func (c *ConfigLoader) LoadConfig(configPath string) (*pillager.Options, error) {
if configPath != "" {
// Use specified config file
ext := filepath.Ext(configPath)
if ext == "" {
return nil, fmt.Errorf("config file must have an extension (.yaml, .toml, or .json)")
}
c.v.SetConfigType(ext[1:])
c.v.SetConfigFile(configPath)

if err := c.v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file %s: %w", configPath, err)
}
} else {
// Search for config in default locations
c.v.SetConfigName("pillager")
c.v.AddConfigPath(".")
c.v.AddConfigPath("$HOME/.pillager")
c.v.AddConfigPath("/etc/pillager/")

c.v.ReadInConfig()
}

// Create options from config
opts := &pillager.Options{
Workers: c.v.GetInt("workers"),
Verbose: c.v.GetBool("verbose"),
Template: c.v.GetString("template"),
Redact: c.v.GetBool("redact"),
Reporter: c.v.GetString("reporter"),
Rules: c.v.Get("rules").([]pillager.Rule),
Allowlist: pillager.Allowlist{
Paths: c.v.GetStringSlice("allowlist.paths"),
Regexes: c.v.GetStringSlice("allowlist.regexes"),
Commits: c.v.GetStringSlice("allowlist.commits"),
},
}

// Unmarshal rules if they exist
var rules []pillager.Rule
if err := c.v.UnmarshalKey("rules", &rules); err != nil {
return nil, fmt.Errorf("failed to parse rules configuration: %w", err)
}
opts.Rules = rules

return opts, nil
}

// MergeWithFlags merges configuration with command line flags
func (c *ConfigLoader) MergeWithFlags(opts *pillager.Options, flags *pillager.Options) {
if flags.Verbose {
opts.Verbose = flags.Verbose
}
if flags.Redact {
opts.Redact = flags.Redact
}
if flags.Workers > 0 {
opts.Workers = flags.Workers
}
if flags.Reporter != "" {
opts.Reporter = flags.Reporter
}
if flags.Template != "" {
opts.Template = flags.Template
}
if flags.ScanPath != "" {
opts.ScanPath = flags.ScanPath
}
// Merge rules if provided
if len(flags.Rules) > 0 {
opts.Rules = flags.Rules
}
// Merge allowlist if provided
if len(flags.Allowlist.Paths) > 0 {
opts.Allowlist.Paths = flags.Allowlist.Paths
}
if len(flags.Allowlist.Regexes) > 0 {
opts.Allowlist.Regexes = flags.Allowlist.Regexes
}
if len(flags.Allowlist.Commits) > 0 {
opts.Allowlist.Commits = flags.Allowlist.Commits
}
}
Loading

0 comments on commit 9156c1e

Please sign in to comment.