Skip to content

Commit

Permalink
extract stats and webserver; reorganise session
Browse files Browse the repository at this point in the history
  • Loading branch information
rumenvasilev committed Sep 7, 2023
1 parent 65f673f commit b1d241c
Show file tree
Hide file tree
Showing 25 changed files with 648 additions and 655 deletions.
23 changes: 0 additions & 23 deletions .editorconfig

This file was deleted.

2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
- Implement proper server wait, not select{}
- Store md5 in session, avoiding duplicate calc
- New Makefile
- Overhaul updateRules.go
- Start webserver everywhere

## Features
- Add exit codes, so CI could detect if scan failed
Expand Down
7 changes: 3 additions & 4 deletions cmd/scan/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package scan

import (
"github.com/rumenvasilev/rvsecret/internal/core"
"github.com/rumenvasilev/rvsecret/internal/config"
"github.com/rumenvasilev/rvsecret/version"

"github.com/spf13/cobra"
Expand All @@ -19,9 +19,8 @@ var (
)

func init() {
// TODO-RV: Move this to each sub-command
cobra.OnInitialize(core.SetConfig)

cobra.OnInitialize(config.SetConfig)
// Global flags under `scan` command
ScanCmd.PersistentFlags().String("bind-address", "127.0.0.1", "The IP address for the webserver")
ScanCmd.PersistentFlags().Int("bind-port", 9393, "The port for the webserver")
ScanCmd.PersistentFlags().Int("confidence-level", 3, "The confidence level of the expressions used to find matches")
Expand Down
198 changes: 198 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package config

import (
"errors"
"fmt"
"os"

"github.com/mitchellh/go-homedir"
"github.com/rumenvasilev/rvsecret/internal/pkg/api"
"github.com/rumenvasilev/rvsecret/internal/util"
"github.com/rumenvasilev/rvsecret/version"
"github.com/spf13/viper"
)

// cfg holds the configuration data the commands
var cfg *viper.Viper

// defaultIgnoreExtensions is an array of extensions that if they match a file that file will be excluded
var defaultIgnoreExtensions = []string{"jpg", "jpeg", "png", "gif", "bmp", "tiff",
"tif", "psd", "xcf"}

// defaultIgnorePaths is an array of directories that will be excluded from all types of scans.
var defaultIgnorePaths = []string{"node_modules/", "vendor/bundle", "vendor/cache", "/proc/"}

// DefaultValues is a map of all flag default values and other mutable variables
var defaultValues = map[string]interface{}{
"bind-address": "127.0.0.1",
"bind-port": 9393,
"commit-depth": -1,
"config-file": "$HOME/.rvsecret/config.yaml",
"csv": false,
"debug": false,
"ignore-extension": nil,
"ignore-path": nil,
"in-mem-clone": false,
"json": false,
"max-file-size": 10,
"num-threads": -1,
"local-paths": nil,
"scan-forks": false,
"scan-tests": false,
"scan-type": "",
"silent": false,
"confidence-level": 3,
"signature-file": "$HOME/.rvsecret/signatures/default.yaml",
"signature-path": "$HOME/.rvsecret/signatures/",
"signatures-path": "$HOME/.rvsecret/signatures/",
"signatures-url": "https://github.com/rumenvasilev/rvsecret-signatures",
"signatures-version": "",
"scan-dir": nil,
"scan-file": nil,
"hide-secrets": false,
"rules-url": "",
"test-signatures": false,
"web-server": false,
// Github
"add-org-members": false,
"github-enterprise-url": "",
"github-url": "https://api.github.com",
"github-api-token": "",
"github-orgs": nil,
"github-repos": nil,
"github-users": nil,
// Gitlab
"gitlab-targets": nil,
//"gitlab-url": "", // TODO set the default
"gitlab-api-token": "",
}

// SetConfig will set the defaults, and load a config file and environment variables if they are present
func SetConfig() {
for key, value := range defaultValues {
viper.SetDefault(key, value)
}

configFile := viper.GetString("config-file")

if configFile != defaultValues["config-file"] {
viper.SetConfigFile(configFile)
} else {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

viper.AddConfigPath(home + "/.rvsecret/")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
}

if err := viper.ReadInConfig(); err != nil {
fmt.Println("Couldn't load Viper config.", err)
// os.Exit(1)
}

viper.AutomaticEnv()

cfg = viper.GetViper()
}

type Config struct {
AppVersion string
BindAddress string
BindPort int
CommitDepth int
ConfidenceLevel int
CSVOutput bool
Debug bool
ExpandOrgs bool
GithubAccessToken string
GithubEnterpriseURL string
GitlabAccessToken string
GitlabTargets []string
GitlabURL string
HideSecrets bool
InMemClone bool
JSONOutput bool
LocalPaths []string
MaxFileSize int64
ScanFork bool
ScanTests bool
ScanType api.ScanType
Silent bool
SkippableExt []string
SkippablePath []string
SignatureFiles []string
Threads int
UserDirtyNames []string
UserDirtyOrgs []string
UserDirtyRepos []string
WebServer bool
}

// TODO detect scanType automatically
func Load(scanType api.ScanType) (*Config, error) {
c := Config{
BindAddress: cfg.GetString("bind-address"),
BindPort: cfg.GetInt("bind-port"),
CommitDepth: setCommitDepth(cfg.GetFloat64("commit-depth")),
CSVOutput: cfg.GetBool("csv"),
Debug: cfg.GetBool("debug"),
ExpandOrgs: cfg.GetBool("expand-orgs"),
GithubEnterpriseURL: cfg.GetString("github-enterprise-url"),
GithubAccessToken: cfg.GetString("github-api-token"),
UserDirtyRepos: cfg.GetStringSlice("github-repos"),
UserDirtyOrgs: cfg.GetStringSlice("github-orgs"),
UserDirtyNames: cfg.GetStringSlice("github-users"),
GitlabAccessToken: cfg.GetString("gitlab-api-token"),
GitlabTargets: cfg.GetStringSlice("gitlab-targets"),
HideSecrets: cfg.GetBool("hide-secrets"),
InMemClone: cfg.GetBool("in-mem-clone"),
JSONOutput: cfg.GetBool("json"),
MaxFileSize: cfg.GetInt64("max-file-size"),
ConfidenceLevel: cfg.GetInt("confidence-level"),
ScanFork: cfg.GetBool("scan-forks"),
ScanTests: cfg.GetBool("scan-tests"),
ScanType: scanType,
Silent: cfg.GetBool("silent"),
SignatureFiles: cfg.GetStringSlice("signature-file"),
Threads: cfg.GetInt("num-threads"),
AppVersion: version.AppVersion(),
WebServer: cfg.GetBool("web-server"),
}

switch scanType {
case api.LocalGit:
c.LocalPaths = cfg.GetStringSlice("local-repos")
case api.LocalPath:
c.LocalPaths = cfg.GetStringSlice("local-paths")
case api.GithubEnterprise:
if c.GithubEnterpriseURL == "" {
return nil, errors.New("Github enterprise URL is not set.")
}
}

// Add the default directories to the sess if they don't already exist
c.SkippablePath = util.AppendToSlice(true, defaultIgnorePaths, c.SkippablePath)
// add any additional paths the user requested to exclude to the pre-defined slice
c.SkippablePath = util.AppendToSlice(true, cfg.GetStringSlice("ignore-path"), c.SkippablePath)

// the default ignorable extensions
c.SkippableExt = util.AppendToSlice(false, defaultIgnoreExtensions, c.SkippableExt)
// add any additional extensions the user requested to ignore
c.SkippableExt = util.AppendToSlice(true, cfg.GetStringSlice("ignore-extension"), c.SkippableExt)

return &c, nil
}

// setCommitDepth will set the commit depth for the current session. This is an ugly way of doing it
// but for the moment it works fine.
// TODO dynamically acquire the commit depth of a given repo
func setCommitDepth(c float64) int {
if c == -1 {
return 9999999999
}
return int(c)
}
27 changes: 14 additions & 13 deletions internal/core/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
_coreapi "github.com/rumenvasilev/rvsecret/internal/core/api"
"github.com/rumenvasilev/rvsecret/internal/log"
"github.com/rumenvasilev/rvsecret/internal/matchfile"
"github.com/rumenvasilev/rvsecret/internal/stats"
"github.com/rumenvasilev/rvsecret/internal/util"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
Expand All @@ -21,9 +22,9 @@ import (
// are controlled by flags. If a directory, file, or the content pass through all of the filters then
// it is scanned once per each signature which may lead to a specific secret matching multiple rules
// and then generating multiple findings.
func AnalyzeRepositories(sess *Session, stats *Stats, log *log.Logger) {
stats.UpdateStatus(StatusAnalyzing)
repoCnt := len(sess.Repositories)
func AnalyzeRepositories(sess *Session, stats *stats.Stats, log *log.Logger) {
stats.UpdateStatus(_coreapi.StatusAnalyzing)
repoCnt := len(sess.State.Repositories)
if repoCnt == 0 {
log.Error("No repositories have been gathered.")
}
Expand All @@ -34,11 +35,11 @@ func AnalyzeRepositories(sess *Session, stats *Stats, log *log.Logger) {
// Calculate the number of threads based on the flag and the number of repos. If the number of repos
// being scanned is less than the number of threads the user requested, then the thread count is the
// number of repos.
threadNum := sess.Threads
log.Debug("Defaulting threadNum to %d", sess.Threads)
threadNum := sess.Config.Threads
log.Debug("Defaulting threadNum to %d", sess.Config.Threads)
if repoCnt <= 1 {
threadNum = 1
} else if repoCnt <= sess.Threads {
} else if repoCnt <= sess.Config.Threads {
log.Debug("Setting threadNum to %d", repoCnt)
threadNum = repoCnt
}
Expand All @@ -52,7 +53,7 @@ func AnalyzeRepositories(sess *Session, stats *Stats, log *log.Logger) {
}

// Feed repos to the analyzer workers
for _, repo := range sess.Repositories {
for _, repo := range sess.State.Repositories {
ch <- *repo
}

Expand All @@ -62,7 +63,7 @@ func AnalyzeRepositories(sess *Session, stats *Stats, log *log.Logger) {
wg.Wait()
}

func analyzeWorker(tid int, ch chan _coreapi.Repository, wg *sync.WaitGroup, sess *Session, stats *Stats, log *log.Logger) {
func analyzeWorker(tid int, ch chan _coreapi.Repository, wg *sync.WaitGroup, sess *Session, stats *stats.Stats, log *log.Logger) {
for {
log.Debug("[THREAD #%d] Requesting new repository to analyze...", tid)
repo, ok := <-ch
Expand Down Expand Up @@ -102,7 +103,7 @@ func cleanUpPath(path string, log *log.Logger) {
}

func (sess *Session) analyzeHistory(clone *git.Repository, tid int, path string, repo _coreapi.Repository) {
stats := sess.Stats
stats := sess.State.Stats
log := sess.Out

// Get the full commit history for the repo
Expand Down Expand Up @@ -142,7 +143,7 @@ func (sess *Session) analyzeHistory(clone *git.Repository, tid int, path string,

// isDirtyCommit will analyze all the changes and return bool if there's a dirty commit
func (sess *Session) isDirtyCommit(commit *object.Commit, repo _coreapi.Repository, clone *git.Repository, path string, tid int) bool {
stats := sess.Stats
stats := sess.State.Stats
log := sess.Out

// This will be used to increment the dirty commit stat if any matches are found. A dirty commit
Expand Down Expand Up @@ -171,7 +172,7 @@ func (sess *Session) isDirtyCommit(commit *object.Commit, repo _coreapi.Reposito
mf := matchfile.New(fullFilePath)

// Check if file has to be ignored
if ok, msg := ignoredFile(sess.ScanTests, sess.MaxFileSize, fullFilePath, mf, sess.SkippableExt, sess.SkippablePath); ok {
if ok, msg := ignoredFile(sess.Config.ScanTests, sess.Config.MaxFileSize, fullFilePath, mf, sess.Config.SkippableExt, sess.Config.SkippablePath); ok {
log.Debug("[THREAD #%d][%s] %s %s", tid, repo.CloneURL, fPath, msg)
stats.IncrementFilesIgnored()
continue
Expand Down Expand Up @@ -203,7 +204,7 @@ func (sess *Session) isDirtyCommit(commit *object.Commit, repo _coreapi.Reposito
for k, v := range matchMap {
// Default to no content, only publish information if explicitly allowed to
content = ""
if matchMap != nil && !sess.HideSecrets {
if matchMap != nil && !sess.Config.HideSecrets {
// This sets the content for the finding, in this case the actual secret
// is the content. This can be removed and hidden via a commandline flag.
cleanK := strings.SplitAfterN(k, "_", 2)
Expand Down Expand Up @@ -274,7 +275,7 @@ func createFinding(changeAction, content string, commit *object.Commit, sig Sign
CommitMessage: strings.TrimSpace(commit.Message),
Description: sig.Description(),
FilePath: fPath,
AppVersion: sess.AppVersion,
AppVersion: sess.Config.AppVersion,
LineNumber: strconv.Itoa(lineNum),
RepositoryName: repo.Name,
RepositoryOwner: repo.Owner,
Expand Down
10 changes: 10 additions & 0 deletions internal/core/api/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ type Repository struct {
Description string
Homepage string
}

type Status string

// These are various environment variables and tool statuses used in auth and displaying messages
const (
StatusInitializing Status = "initializing"
StatusGathering Status = "gathering"
StatusAnalyzing Status = "analyzing"
StatusFinished Status = "finished"
)
17 changes: 17 additions & 0 deletions internal/core/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,24 @@ package core

import (
_ "embed"
"time"

"github.com/rumenvasilev/rvsecret/internal/config"
"github.com/rumenvasilev/rvsecret/internal/log"
"github.com/rumenvasilev/rvsecret/internal/stats"
"github.com/rumenvasilev/rvsecret/version"
)

//go:embed resources/banner.txt
var ASCIIBanner string

func HeaderInfo(cfg config.Config, stats *stats.Stats, log *log.Logger) {
if !cfg.JSONOutput && !cfg.CSVOutput {
log.Warn("%s", ASCIIBanner)
log.Important("%s v%s started at %s", version.Name, cfg.AppVersion, stats.StartedAt.Format(time.RFC3339))
log.Important("Loaded %d signatures.", len(Signatures))
if cfg.WebServer {
log.Important("Web interface available at http://%s:%d/public", cfg.BindAddress, cfg.BindPort)
}
}
}
Loading

0 comments on commit b1d241c

Please sign in to comment.