mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(cli): upgrade secret scanner
This commit is contained in:
159
cli/detect/config/allowlist.go
Normal file
159
cli/detect/config/allowlist.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Zachary Rice
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/Infisical/infisical-merge/detect/regexp"
|
||||
)
|
||||
|
||||
type AllowlistMatchCondition int
|
||||
|
||||
const (
|
||||
AllowlistMatchOr AllowlistMatchCondition = iota
|
||||
AllowlistMatchAnd
|
||||
)
|
||||
|
||||
func (a AllowlistMatchCondition) String() string {
|
||||
return [...]string{
|
||||
"OR",
|
||||
"AND",
|
||||
}[a]
|
||||
}
|
||||
|
||||
// Allowlist allows a rule to be ignored for specific
|
||||
// regexes, paths, and/or commits
|
||||
type Allowlist struct {
|
||||
// Short human readable description of the allowlist.
|
||||
Description string
|
||||
|
||||
// MatchCondition determines whether all criteria must match.
|
||||
MatchCondition AllowlistMatchCondition
|
||||
|
||||
// Commits is a slice of commit SHAs that are allowed to be ignored. Defaults to "OR".
|
||||
Commits []string
|
||||
|
||||
// Paths is a slice of path regular expressions that are allowed to be ignored.
|
||||
Paths []*regexp.Regexp
|
||||
|
||||
// Can be `match` or `line`.
|
||||
//
|
||||
// If `match` the _Regexes_ will be tested against the match of the _Rule.Regex_.
|
||||
//
|
||||
// If `line` the _Regexes_ will be tested against the entire line.
|
||||
//
|
||||
// If RegexTarget is empty, it will be tested against the found secret.
|
||||
RegexTarget string
|
||||
|
||||
// Regexes is slice of content regular expressions that are allowed to be ignored.
|
||||
Regexes []*regexp.Regexp
|
||||
|
||||
// StopWords is a slice of stop words that are allowed to be ignored.
|
||||
// This targets the _secret_, not the content of the regex match like the
|
||||
// Regexes slice.
|
||||
StopWords []string
|
||||
|
||||
// validated is an internal flag to track whether `Validate()` has been called.
|
||||
validated bool
|
||||
}
|
||||
|
||||
func (a *Allowlist) Validate() error {
|
||||
if a.validated {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disallow empty allowlists.
|
||||
if len(a.Commits) == 0 &&
|
||||
len(a.Paths) == 0 &&
|
||||
len(a.Regexes) == 0 &&
|
||||
len(a.StopWords) == 0 {
|
||||
return fmt.Errorf("must contain at least one check for: commits, paths, regexes, or stopwords")
|
||||
}
|
||||
|
||||
// Deduplicate commits and stopwords.
|
||||
if len(a.Commits) > 0 {
|
||||
uniqueCommits := make(map[string]struct{})
|
||||
for _, commit := range a.Commits {
|
||||
uniqueCommits[commit] = struct{}{}
|
||||
}
|
||||
a.Commits = maps.Keys(uniqueCommits)
|
||||
}
|
||||
if len(a.StopWords) > 0 {
|
||||
uniqueStopwords := make(map[string]struct{})
|
||||
for _, stopWord := range a.StopWords {
|
||||
uniqueStopwords[stopWord] = struct{}{}
|
||||
}
|
||||
a.StopWords = maps.Keys(uniqueStopwords)
|
||||
}
|
||||
|
||||
a.validated = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitAllowed returns true if the commit is allowed to be ignored.
|
||||
func (a *Allowlist) CommitAllowed(c string) (bool, string) {
|
||||
if a == nil || c == "" {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
for _, commit := range a.Commits {
|
||||
if commit == c {
|
||||
return true, c
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// PathAllowed returns true if the path is allowed to be ignored.
|
||||
func (a *Allowlist) PathAllowed(path string) bool {
|
||||
if a == nil || path == "" {
|
||||
return false
|
||||
}
|
||||
return anyRegexMatch(path, a.Paths)
|
||||
}
|
||||
|
||||
// RegexAllowed returns true if the regex is allowed to be ignored.
|
||||
func (a *Allowlist) RegexAllowed(secret string) bool {
|
||||
if a == nil || secret == "" {
|
||||
return false
|
||||
}
|
||||
return anyRegexMatch(secret, a.Regexes)
|
||||
}
|
||||
|
||||
func (a *Allowlist) ContainsStopWord(s string) (bool, string) {
|
||||
if a == nil || s == "" {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
s = strings.ToLower(s)
|
||||
for _, stopWord := range a.StopWords {
|
||||
if strings.Contains(s, strings.ToLower(stopWord)) {
|
||||
return true, stopWord
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
426
cli/detect/config/config.go
Normal file
426
cli/detect/config/config.go
Normal file
@@ -0,0 +1,426 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Zachary Rice
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/Infisical/infisical-merge/detect/logging"
|
||||
"github.com/Infisical/infisical-merge/detect/regexp"
|
||||
)
|
||||
|
||||
const DefaultScanConfigFileName = ".infisical-scan.toml"
|
||||
const DefaultScanConfigEnvName = "INFISICAL_SCAN_CONFIG"
|
||||
const DefaultInfisicalIgnoreFineName = ".infisicalignore"
|
||||
|
||||
var (
|
||||
//go:embed gitleaks.toml
|
||||
DefaultConfig string
|
||||
|
||||
// use to keep track of how many configs we can extend
|
||||
// yea I know, globals bad
|
||||
extendDepth int
|
||||
)
|
||||
|
||||
const maxExtendDepth = 2
|
||||
|
||||
// ViperConfig is the config struct used by the Viper config package
|
||||
// to parse the config file. This struct does not include regular expressions.
|
||||
// It is used as an intermediary to convert the Viper config to the Config struct.
|
||||
type ViperConfig struct {
|
||||
Title string
|
||||
Description string
|
||||
Extend Extend
|
||||
Rules []struct {
|
||||
ID string
|
||||
Description string
|
||||
Path string
|
||||
Regex string
|
||||
SecretGroup int
|
||||
Entropy float64
|
||||
Keywords []string
|
||||
Tags []string
|
||||
|
||||
// Deprecated: this is a shim for backwards-compatibility.
|
||||
// TODO: Remove this in 9.x.
|
||||
AllowList *viperRuleAllowlist
|
||||
Allowlists []*viperRuleAllowlist
|
||||
}
|
||||
// Deprecated: this is a shim for backwards-compatibility.
|
||||
// TODO: Remove this in 9.x.
|
||||
AllowList *viperGlobalAllowlist
|
||||
Allowlists []*viperGlobalAllowlist
|
||||
}
|
||||
|
||||
type viperRuleAllowlist struct {
|
||||
Description string
|
||||
Condition string
|
||||
Commits []string
|
||||
Paths []string
|
||||
RegexTarget string
|
||||
Regexes []string
|
||||
StopWords []string
|
||||
}
|
||||
|
||||
type viperGlobalAllowlist struct {
|
||||
TargetRules []string
|
||||
viperRuleAllowlist `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Config is a configuration struct that contains rules and an allowlist if present.
|
||||
type Config struct {
|
||||
Title string
|
||||
Extend Extend
|
||||
Path string
|
||||
Description string
|
||||
Rules map[string]Rule
|
||||
Keywords map[string]struct{}
|
||||
// used to keep sarif results consistent
|
||||
OrderedRules []string
|
||||
Allowlists []*Allowlist
|
||||
}
|
||||
|
||||
// Extend is a struct that allows users to define how they want their
|
||||
// configuration extended by other configuration files.
|
||||
type Extend struct {
|
||||
Path string
|
||||
URL string
|
||||
UseDefault bool
|
||||
DisabledRules []string
|
||||
}
|
||||
|
||||
func (vc *ViperConfig) Translate() (Config, error) {
|
||||
var (
|
||||
keywords = make(map[string]struct{})
|
||||
orderedRules []string
|
||||
rulesMap = make(map[string]Rule)
|
||||
ruleAllowlists = make(map[string][]*Allowlist)
|
||||
)
|
||||
|
||||
// Validate individual rules.
|
||||
for _, vr := range vc.Rules {
|
||||
var (
|
||||
pathPat *regexp.Regexp
|
||||
regexPat *regexp.Regexp
|
||||
)
|
||||
if vr.Path != "" {
|
||||
pathPat = regexp.MustCompile(vr.Path)
|
||||
}
|
||||
if vr.Regex != "" {
|
||||
regexPat = regexp.MustCompile(vr.Regex)
|
||||
}
|
||||
if vr.Keywords == nil {
|
||||
vr.Keywords = []string{}
|
||||
} else {
|
||||
for i, k := range vr.Keywords {
|
||||
keyword := strings.ToLower(k)
|
||||
keywords[keyword] = struct{}{}
|
||||
vr.Keywords[i] = keyword
|
||||
}
|
||||
}
|
||||
if vr.Tags == nil {
|
||||
vr.Tags = []string{}
|
||||
}
|
||||
cr := Rule{
|
||||
RuleID: vr.ID,
|
||||
Description: vr.Description,
|
||||
Regex: regexPat,
|
||||
SecretGroup: vr.SecretGroup,
|
||||
Entropy: vr.Entropy,
|
||||
Path: pathPat,
|
||||
Keywords: vr.Keywords,
|
||||
Tags: vr.Tags,
|
||||
}
|
||||
|
||||
// Parse the rule allowlists, including the older format for backwards compatibility.
|
||||
if vr.AllowList != nil {
|
||||
// TODO: Remove this in v9.
|
||||
if len(vr.Allowlists) > 0 {
|
||||
return Config{}, fmt.Errorf("%s: [rules.allowlist] is deprecated, it cannot be used alongside [[rules.allowlist]]", cr.RuleID)
|
||||
}
|
||||
vr.Allowlists = append(vr.Allowlists, vr.AllowList)
|
||||
}
|
||||
for _, a := range vr.Allowlists {
|
||||
allowlist, err := parseAllowlist(a)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("%s: [[rules.allowlists]] %w", cr.RuleID, err)
|
||||
}
|
||||
cr.Allowlists = append(cr.Allowlists, allowlist)
|
||||
}
|
||||
orderedRules = append(orderedRules, cr.RuleID)
|
||||
rulesMap[cr.RuleID] = cr
|
||||
}
|
||||
|
||||
// Assemble the config.
|
||||
c := Config{
|
||||
Title: vc.Title,
|
||||
Description: vc.Description,
|
||||
Extend: vc.Extend,
|
||||
Rules: rulesMap,
|
||||
Keywords: keywords,
|
||||
OrderedRules: orderedRules,
|
||||
}
|
||||
// Parse the config allowlists, including the older format for backwards compatibility.
|
||||
if vc.AllowList != nil {
|
||||
// TODO: Remove this in v9.
|
||||
if len(vc.Allowlists) > 0 {
|
||||
return Config{}, errors.New("[allowlist] is deprecated, it cannot be used alongside [[allowlists]]")
|
||||
}
|
||||
vc.Allowlists = append(vc.Allowlists, vc.AllowList)
|
||||
}
|
||||
for _, a := range vc.Allowlists {
|
||||
allowlist, err := parseAllowlist(&a.viperRuleAllowlist)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("[[allowlists]] %w", err)
|
||||
}
|
||||
// Allowlists with |targetRules| aren't added to the global list.
|
||||
if len(a.TargetRules) > 0 {
|
||||
for _, ruleID := range a.TargetRules {
|
||||
// It's not possible to validate |ruleID| until after extend.
|
||||
ruleAllowlists[ruleID] = append(ruleAllowlists[ruleID], allowlist)
|
||||
}
|
||||
} else {
|
||||
c.Allowlists = append(c.Allowlists, allowlist)
|
||||
}
|
||||
}
|
||||
|
||||
if maxExtendDepth != extendDepth {
|
||||
// disallow both usedefault and path from being set
|
||||
if c.Extend.Path != "" && c.Extend.UseDefault {
|
||||
return Config{}, errors.New("unable to load config due to extend.path and extend.useDefault being set")
|
||||
}
|
||||
if c.Extend.UseDefault {
|
||||
if err := c.extendDefault(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
} else if c.Extend.Path != "" {
|
||||
if err := c.extendPath(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the rules after everything has been assembled (including extended configs).
|
||||
if extendDepth == 0 {
|
||||
for _, rule := range c.Rules {
|
||||
if err := rule.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Populate targeted configs.
|
||||
for ruleID, allowlists := range ruleAllowlists {
|
||||
rule, ok := c.Rules[ruleID]
|
||||
if !ok {
|
||||
return Config{}, fmt.Errorf("[[allowlists]] target rule ID '%s' does not exist", ruleID)
|
||||
}
|
||||
rule.Allowlists = append(rule.Allowlists, allowlists...)
|
||||
c.Rules[ruleID] = rule
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func parseAllowlist(a *viperRuleAllowlist) (*Allowlist, error) {
|
||||
var matchCondition AllowlistMatchCondition
|
||||
switch strings.ToUpper(a.Condition) {
|
||||
case "AND", "&&":
|
||||
matchCondition = AllowlistMatchAnd
|
||||
case "", "OR", "||":
|
||||
matchCondition = AllowlistMatchOr
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown allowlist |condition| '%s' (expected 'and', 'or')", a.Condition)
|
||||
}
|
||||
|
||||
// Validate the target.
|
||||
regexTarget := a.RegexTarget
|
||||
if regexTarget != "" {
|
||||
switch regexTarget {
|
||||
case "secret":
|
||||
regexTarget = ""
|
||||
case "match", "line":
|
||||
// do nothing
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown allowlist |regexTarget| '%s' (expected 'match', 'line')", regexTarget)
|
||||
}
|
||||
}
|
||||
var allowlistRegexes []*regexp.Regexp
|
||||
for _, a := range a.Regexes {
|
||||
allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
|
||||
}
|
||||
var allowlistPaths []*regexp.Regexp
|
||||
for _, a := range a.Paths {
|
||||
allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
|
||||
}
|
||||
|
||||
allowlist := &Allowlist{
|
||||
Description: a.Description,
|
||||
MatchCondition: matchCondition,
|
||||
Commits: a.Commits,
|
||||
Paths: allowlistPaths,
|
||||
RegexTarget: regexTarget,
|
||||
Regexes: allowlistRegexes,
|
||||
StopWords: a.StopWords,
|
||||
}
|
||||
if err := allowlist.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allowlist, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetOrderedRules() []Rule {
|
||||
var orderedRules []Rule
|
||||
for _, id := range c.OrderedRules {
|
||||
if _, ok := c.Rules[id]; ok {
|
||||
orderedRules = append(orderedRules, c.Rules[id])
|
||||
}
|
||||
}
|
||||
return orderedRules
|
||||
}
|
||||
|
||||
func (c *Config) extendDefault() error {
|
||||
extendDepth++
|
||||
viper.SetConfigType("toml")
|
||||
if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
|
||||
return fmt.Errorf("failed to load extended default config, err: %w", err)
|
||||
}
|
||||
defaultViperConfig := ViperConfig{}
|
||||
if err := viper.Unmarshal(&defaultViperConfig); err != nil {
|
||||
return fmt.Errorf("failed to load extended default config, err: %w", err)
|
||||
}
|
||||
cfg, err := defaultViperConfig.Translate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load extended default config, err: %w", err)
|
||||
|
||||
}
|
||||
logging.Debug().Msg("extending config with default config")
|
||||
c.extend(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) extendPath() error {
|
||||
extendDepth++
|
||||
viper.SetConfigFile(c.Extend.Path)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("failed to load extended config, err: %w", err)
|
||||
}
|
||||
extensionViperConfig := ViperConfig{}
|
||||
if err := viper.Unmarshal(&extensionViperConfig); err != nil {
|
||||
return fmt.Errorf("failed to load extended config, err: %w", err)
|
||||
}
|
||||
cfg, err := extensionViperConfig.Translate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load extended config, err: %w", err)
|
||||
}
|
||||
logging.Debug().Msgf("extending config with %s", c.Extend.Path)
|
||||
c.extend(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) extendURL() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (c *Config) extend(extensionConfig Config) {
|
||||
// Get config name for helpful log messages.
|
||||
var configName string
|
||||
if c.Extend.Path != "" {
|
||||
configName = c.Extend.Path
|
||||
} else {
|
||||
configName = "default"
|
||||
}
|
||||
// Convert |Config.DisabledRules| into a map for ease of access.
|
||||
disabledRuleIDs := map[string]struct{}{}
|
||||
for _, id := range c.Extend.DisabledRules {
|
||||
if _, ok := extensionConfig.Rules[id]; !ok {
|
||||
logging.Warn().
|
||||
Str("rule-id", id).
|
||||
Str("config", configName).
|
||||
Msg("Disabled rule doesn't exist in extended config.")
|
||||
}
|
||||
disabledRuleIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
for ruleID, baseRule := range extensionConfig.Rules {
|
||||
// Skip the rule.
|
||||
if _, ok := disabledRuleIDs[ruleID]; ok {
|
||||
logging.Debug().
|
||||
Str("rule-id", ruleID).
|
||||
Str("config", configName).
|
||||
Msg("Ignoring rule from extended config.")
|
||||
continue
|
||||
}
|
||||
|
||||
currentRule, ok := c.Rules[ruleID]
|
||||
if !ok {
|
||||
// Rule doesn't exist, add it to the config.
|
||||
c.Rules[ruleID] = baseRule
|
||||
for _, k := range baseRule.Keywords {
|
||||
c.Keywords[k] = struct{}{}
|
||||
}
|
||||
c.OrderedRules = append(c.OrderedRules, ruleID)
|
||||
} else {
|
||||
// Rule exists, merge our changes into the base.
|
||||
if currentRule.Description != "" {
|
||||
baseRule.Description = currentRule.Description
|
||||
}
|
||||
if currentRule.Entropy != 0 {
|
||||
baseRule.Entropy = currentRule.Entropy
|
||||
}
|
||||
if currentRule.SecretGroup != 0 {
|
||||
baseRule.SecretGroup = currentRule.SecretGroup
|
||||
}
|
||||
if currentRule.Regex != nil {
|
||||
baseRule.Regex = currentRule.Regex
|
||||
}
|
||||
if currentRule.Path != nil {
|
||||
baseRule.Path = currentRule.Path
|
||||
}
|
||||
baseRule.Tags = append(baseRule.Tags, currentRule.Tags...)
|
||||
baseRule.Keywords = append(baseRule.Keywords, currentRule.Keywords...)
|
||||
for _, a := range currentRule.Allowlists {
|
||||
baseRule.Allowlists = append(baseRule.Allowlists, a)
|
||||
}
|
||||
// The keywords from the base rule and the extended rule must be merged into the global keywords list
|
||||
for _, k := range baseRule.Keywords {
|
||||
c.Keywords[k] = struct{}{}
|
||||
}
|
||||
c.Rules[ruleID] = baseRule
|
||||
}
|
||||
}
|
||||
|
||||
// append allowlists, not attempting to merge
|
||||
for _, a := range extensionConfig.Allowlists {
|
||||
c.Allowlists = append(c.Allowlists, a)
|
||||
}
|
||||
|
||||
// sort to keep extended rules in order
|
||||
sort.Strings(c.OrderedRules)
|
||||
}
|
||||
3130
cli/detect/config/gitleaks.toml
Normal file
3130
cli/detect/config/gitleaks.toml
Normal file
File diff suppressed because it is too large
Load Diff
114
cli/detect/config/rule.go
Normal file
114
cli/detect/config/rule.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Zachary Rice
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical-merge/detect/regexp"
|
||||
)
|
||||
|
||||
// Rules contain information that define details on how to detect secrets
|
||||
type Rule struct {
|
||||
// RuleID is a unique identifier for this rule
|
||||
RuleID string
|
||||
|
||||
// Description is the description of the rule.
|
||||
Description string
|
||||
|
||||
// Entropy is a float representing the minimum shannon
|
||||
// entropy a regex group must have to be considered a secret.
|
||||
Entropy float64
|
||||
|
||||
// SecretGroup is an int used to extract secret from regex
|
||||
// match and used as the group that will have its entropy
|
||||
// checked if `entropy` is set.
|
||||
SecretGroup int
|
||||
|
||||
// Regex is a golang regular expression used to detect secrets.
|
||||
Regex *regexp.Regexp
|
||||
|
||||
// Path is a golang regular expression used to
|
||||
// filter secrets by path
|
||||
Path *regexp.Regexp
|
||||
|
||||
// Tags is an array of strings used for metadata
|
||||
// and reporting purposes.
|
||||
Tags []string
|
||||
|
||||
// Keywords are used for pre-regex check filtering. Rules that contain
|
||||
// keywords will perform a quick string compare check to make sure the
|
||||
// keyword(s) are in the content being scanned.
|
||||
Keywords []string
|
||||
|
||||
// Allowlists allows a rule to be ignored for specific commits, paths, regexes, and/or stopwords.
|
||||
Allowlists []*Allowlist
|
||||
|
||||
// validated is an internal flag to track whether `Validate()` has been called.
|
||||
validated bool
|
||||
}
|
||||
|
||||
// Validate guards against common misconfigurations.
|
||||
func (r *Rule) Validate() error {
|
||||
if r.validated {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure |id| is present.
|
||||
if strings.TrimSpace(r.RuleID) == "" {
|
||||
// Try to provide helpful context, since |id| is empty.
|
||||
var context string
|
||||
if r.Regex != nil {
|
||||
context = ", regex: " + r.Regex.String()
|
||||
} else if r.Path != nil {
|
||||
context = ", path: " + r.Path.String()
|
||||
} else if r.Description != "" {
|
||||
context = ", description: " + r.Description
|
||||
}
|
||||
return fmt.Errorf("rule |id| is missing or empty" + context)
|
||||
}
|
||||
|
||||
// Ensure the rule actually matches something.
|
||||
if r.Regex == nil && r.Path == nil {
|
||||
return fmt.Errorf("%s: both |regex| and |path| are empty, this rule will have no effect", r.RuleID)
|
||||
}
|
||||
|
||||
// Ensure |secretGroup| works.
|
||||
if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() {
|
||||
return fmt.Errorf("%s: invalid regex secret group %d, max regex secret group %d", r.RuleID, r.SecretGroup, r.Regex.NumSubexp())
|
||||
}
|
||||
|
||||
for _, allowlist := range r.Allowlists {
|
||||
// This will probably never happen.
|
||||
if allowlist == nil {
|
||||
continue
|
||||
}
|
||||
if err := allowlist.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", r.RuleID, err)
|
||||
}
|
||||
}
|
||||
|
||||
r.validated = true
|
||||
return nil
|
||||
}
|
||||
46
cli/detect/config/utils.go
Normal file
46
cli/detect/config/utils.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Zachary Rice
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/Infisical/infisical-merge/detect/regexp"
|
||||
)
|
||||
|
||||
func anyRegexMatch(f string, res []*regexp.Regexp) bool {
|
||||
for _, re := range res {
|
||||
if regexMatched(f, re) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func regexMatched(f string, re *regexp.Regexp) bool {
|
||||
if re == nil {
|
||||
return false
|
||||
}
|
||||
if re.FindString(f) != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user