Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add validity check #206

Merged
merged 11 commits into from
Feb 19, 2024
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,28 @@ Flags:
--report-path strings path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif)
--rule strings select rules by name or tag to apply to this scan
--stdout-format string stdout output format, available formats are: json, yaml, sarif (default "yaml")
--validate trigger additional validation to check if discovered secrets are active or revoked
-v, --version version for 2ms

Use "2ms [command] --help" for more information about a command.
```

<!-- command-line:end -->

## Validity Check

From the help message: `--validate trigger additional validation to check if discovered secrets are active or revoked`.

The `--validate` flag will check the validity of the secrets found. For example, if it is a Github token, it will check if the token is valid by making a request to the Github API. We will use the less intrusive method to check the validity of the secret.

The result of the validation can be:

- `valid` - The secret is valid
- `revoked` - The secret is revoked
- `unknown` - We failed to check, or we are not checking the validity of the secret at all

If the `--validate` flag is not provided, the validation field will be omitted from the output, or its value will be an empty string.

## Special Rules

Special rules are rules that are not part of the default ruleset, usually because they are too noisy or too specific. You can use the `--add-special-rule` flag to add special rules by rule ID.
Expand Down
9 changes: 9 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
specialRulesFlagName = "add-special-rule"
ignoreOnExitFlagName = "ignore-on-exit"
maxTargetMegabytesFlagName = "max-target-megabytes"
validate = "validate"
)

var (
Expand All @@ -40,6 +41,7 @@ var (
ignoreVar []string
ignoreOnExitVar = ignoreOnExitNone
secretsConfigVar secrets.SecretsConfig
validateVar bool
)

var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -71,6 +73,7 @@ var channels = plugins.Channels{

var report = reporting.Init()
var secretsChan = make(chan *secrets.Secret)
var validationChan = make(chan *secrets.Secret)

func Execute() (int, error) {
vConfig.SetEnvPrefix(envPrefix)
Expand All @@ -89,6 +92,7 @@ func Execute() (int, error) {
rootCmd.PersistentFlags().StringSliceVar(&secretsConfigVar.SpecialList, specialRulesFlagName, []string{}, "special (non-default) rules to apply.\nThis list is not affected by the --rule and --ignore-rule flags.")
rootCmd.PersistentFlags().Var(&ignoreOnExitVar, ignoreOnExitFlagName, "defines which kind of non-zero exits code should be ignored\naccepts: all, results, errors, none\nexample: if 'results' is set, only engine errors will make 2ms exit code different from 0")
rootCmd.PersistentFlags().IntVar(&secretsConfigVar.MaxTargetMegabytes, maxTargetMegabytesFlagName, 0, "files larger than this will be skipped.\nOmit or set to 0 to disable this check.")
rootCmd.PersistentFlags().BoolVar(&validateVar, validate, false, "trigger additional validation to check if discovered secrets are active or revoked")

rootCmd.AddCommand(secrets.GetRulesCommand(&secretsConfigVar))

Expand Down Expand Up @@ -135,6 +139,11 @@ func preRun(cmd *cobra.Command, args []string) error {
channels.WaitGroup.Add(1)
go processSecrets()

if validateVar {
channels.WaitGroup.Add(1)
go processValidation()
}

return nil
}

Expand Down
15 changes: 15 additions & 0 deletions cmd/workers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ func processSecrets() {

for secret := range secretsChan {
report.TotalSecretsFound++
if validateVar {
validationChan <- secret
}
report.Results[secret.ID] = append(report.Results[secret.ID], secret)
}
close(validationChan)
}

func processValidation() {
defer channels.WaitGroup.Done()

wgValidation := &sync.WaitGroup{}
for secret := range validationChan {
wgValidation.Add(1)
go secret.Validate(wgValidation)
}
wgValidation.Wait()
}
72 changes: 64 additions & 8 deletions secrets/secret.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,68 @@
package secrets

import (
"fmt"
"net/http"
"sync"

"github.com/rs/zerolog/log"
)

type ValidationResult string

const (
Valid ValidationResult = "Valid"
Revoked ValidationResult = "Revoked"
Unknown ValidationResult = "Unknown"
)

type Secret struct {
ID string `json:"id"`
Source string `json:"source"`
RuleID string `json:"ruleId"`
StartLine int `json:"startLine"`
EndLine int `json:"endLine"`
StartColumn int `json:"startColumn"`
EndColumn int `json:"endColumn"`
Value string `json:"value"`
ID string `json:"id"`
Source string `json:"source"`
RuleID string `json:"ruleId"`
StartLine int `json:"startLine"`
EndLine int `json:"endLine"`
StartColumn int `json:"startColumn"`
EndColumn int `json:"endColumn"`
Value string `json:"value"`
ValidationStatus ValidationResult `json:"validationStatus,omitempty"`
}

type validationFunc = func(*Secret) ValidationResult

var ruleIDToFunction = map[string]validationFunc{
"github-fine-grained-pat": validateGithub,
"github-pat": validateGithub,
}

func (s *Secret) Validate(wg *sync.WaitGroup) {
defer wg.Done()
if f, ok := ruleIDToFunction[s.RuleID]; ok {
s.ValidationStatus = f(s)
} else {
s.ValidationStatus = Unknown
}
}

func validateGithub(s *Secret) ValidationResult {
const githubURL = "https://api.github.com/"

req, err := http.NewRequest("GET", githubURL, nil)
if err != nil {
log.Warn().Err(err).Msg("Failed to validate secret")
return Unknown
}
req.Header.Set("Authorization", fmt.Sprintf("token %s", s.Value))

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Warn().Err(err).Msg("Failed to validate secret")
return Unknown
}

if resp.StatusCode == http.StatusOK {
return Valid
}
return Revoked
}
Loading