Skip to content

Commit

Permalink
feat: add validity check (#206)
Browse files Browse the repository at this point in the history
This PR is the first implementation of validity check #191.

I added the flow of validation, controlled by the `--validate` flag, and
added validation for Github token.
  • Loading branch information
Baruch Odem (Rothkoff) committed Feb 19, 2024
1 parent 5a7ac9d commit 807e0b5
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 8 deletions.
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
}

0 comments on commit 807e0b5

Please sign in to comment.