mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-15 19:33:32 +00:00
Compare commits
25 Commits
sem-ver-gh
...
add-gitlea
Author | SHA1 | Date | |
---|---|---|---|
353ff63298 | |||
8249043826 | |||
b3e68cf3fb | |||
781e0b24c8 | |||
28de8cddd7 | |||
ed3e53f9a3 | |||
9cb4d5abb7 | |||
efdd1e64c4 | |||
5b3be6063f | |||
12c399d4a9 | |||
ecd17e1d6d | |||
fb4c811414 | |||
3561c589b1 | |||
420d71d923 | |||
3db5c040c3 | |||
b4f336a5bb | |||
43e61c94f0 | |||
69fa4a80c5 | |||
cf9e8b8a6b | |||
c6d5498a42 | |||
ad7972e7e1 | |||
c6d8f24968 | |||
3c71bcaa8d | |||
8d09a45454 | |||
b17a40d83e |
@ -161,7 +161,7 @@ aurs:
|
||||
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
|
||||
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
|
||||
install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical"
|
||||
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/infisical"
|
||||
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/_infisical"
|
||||
install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish"
|
||||
# man pages
|
||||
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
||||
|
1782
backend/package-lock.json
generated
1782
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.309.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@aws-sdk/client-secrets-manager": "^3.312.0",
|
||||
"@godaddy/terminus": "^4.12.0",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.41.0",
|
||||
"@sentry/tracing": "^7.47.0",
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"argon2": "^0.30.3",
|
||||
"await-to-js": "^3.0.0",
|
||||
"aws-sdk": "^2.1338.0",
|
||||
"aws-sdk": "^2.1360.0",
|
||||
"axios": "^1.3.5",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
|
@ -86,4 +86,22 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
res.send()
|
||||
}
|
||||
|
||||
// TODO: validate workspace
|
||||
export const getFolderById = async (req: Request, res: Response) => {
|
||||
const { folderId } = req.params
|
||||
|
||||
const folder = await Folder.findById(folderId);
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" })
|
||||
}
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: folder.workspace as any,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
res.send({ folder })
|
||||
}
|
@ -6,7 +6,7 @@ import {
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { body, param } from 'express-validator';
|
||||
import { createFolder, deleteFolder } from '../../controllers/v1/secretsFolderController';
|
||||
import { createFolder, deleteFolder, getFolderById } from '../../controllers/v1/secretsFolderController';
|
||||
import { ADMIN, MEMBER } from '../../variables';
|
||||
|
||||
router.post(
|
||||
@ -36,5 +36,15 @@ router.delete(
|
||||
deleteFolder
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:folderId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
param('folderId').exists(),
|
||||
validateRequest,
|
||||
getFolderById
|
||||
);
|
||||
|
||||
|
||||
export default router;
|
3
cli/.infisicalignore
Normal file
3
cli/.infisicalignore
Normal file
@ -0,0 +1,3 @@
|
||||
bea0ff6e05a4de73a5db625d4ae181a015b50855:frontend/components/utilities/attemptLogin.js:stripe-access-token:147
|
||||
bea0ff6e05a4de73a5db625d4ae181a015b50855:backend/src/json/integrations.json:generic-api-key:5
|
||||
1961b92340e5d2613acae528b886c842427ce5d0:frontend/components/utilities/attemptLogin.js:stripe-access-token:148
|
85
cli/config/allowlist.go
Normal file
85
cli/config/allowlist.go
Normal file
@ -0,0 +1,85 @@
|
||||
// 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 (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// Regexes is slice of content regular expressions that are allowed to be ignored.
|
||||
Regexes []*regexp.Regexp
|
||||
|
||||
// RegexTarget
|
||||
RegexTarget string
|
||||
|
||||
// Paths is a slice of path regular expressions that are allowed to be ignored.
|
||||
Paths []*regexp.Regexp
|
||||
|
||||
// Commits is a slice of commit SHAs that are allowed to be ignored.
|
||||
Commits []string
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CommitAllowed returns true if the commit is allowed to be ignored.
|
||||
func (a *Allowlist) CommitAllowed(c string) bool {
|
||||
if c == "" {
|
||||
return false
|
||||
}
|
||||
for _, commit := range a.Commits {
|
||||
if commit == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PathAllowed returns true if the path is allowed to be ignored.
|
||||
func (a *Allowlist) PathAllowed(path string) bool {
|
||||
return anyRegexMatch(path, a.Paths)
|
||||
}
|
||||
|
||||
// RegexAllowed returns true if the regex is allowed to be ignored.
|
||||
func (a *Allowlist) RegexAllowed(s string) bool {
|
||||
return anyRegexMatch(s, a.Regexes)
|
||||
}
|
||||
|
||||
func (a *Allowlist) ContainsStopWord(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
for _, stopWord := range a.StopWords {
|
||||
if strings.Contains(s, strings.ToLower(stopWord)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
115
cli/config/allowlist_test.go
Normal file
115
cli/config/allowlist_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// 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 (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommitAllowed(t *testing.T) {
|
||||
tests := []struct {
|
||||
allowlist Allowlist
|
||||
commit string
|
||||
commitAllowed bool
|
||||
}{
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Commits: []string{"commitA"},
|
||||
},
|
||||
commit: "commitA",
|
||||
commitAllowed: true,
|
||||
},
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Commits: []string{"commitB"},
|
||||
},
|
||||
commit: "commitA",
|
||||
commitAllowed: false,
|
||||
},
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Commits: []string{"commitB"},
|
||||
},
|
||||
commit: "",
|
||||
commitAllowed: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.commitAllowed, tt.allowlist.CommitAllowed(tt.commit))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexAllowed(t *testing.T) {
|
||||
tests := []struct {
|
||||
allowlist Allowlist
|
||||
secret string
|
||||
regexAllowed bool
|
||||
}{
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Regexes: []*regexp.Regexp{regexp.MustCompile("matchthis")},
|
||||
},
|
||||
secret: "a secret: matchthis, done",
|
||||
regexAllowed: true,
|
||||
},
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Regexes: []*regexp.Regexp{regexp.MustCompile("matchthis")},
|
||||
},
|
||||
secret: "a secret",
|
||||
regexAllowed: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.regexAllowed, tt.allowlist.RegexAllowed(tt.secret))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathAllowed(t *testing.T) {
|
||||
tests := []struct {
|
||||
allowlist Allowlist
|
||||
path string
|
||||
pathAllowed bool
|
||||
}{
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Paths: []*regexp.Regexp{regexp.MustCompile("path")},
|
||||
},
|
||||
path: "a path",
|
||||
pathAllowed: true,
|
||||
},
|
||||
{
|
||||
allowlist: Allowlist{
|
||||
Paths: []*regexp.Regexp{regexp.MustCompile("path")},
|
||||
},
|
||||
path: "a ???",
|
||||
pathAllowed: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.pathAllowed, tt.allowlist.PathAllowed(tt.path))
|
||||
}
|
||||
}
|
279
cli/config/config.go
Normal file
279
cli/config/config.go
Normal file
@ -0,0 +1,279 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
//go:embed infisical-scan.toml
|
||||
var DefaultConfig string
|
||||
|
||||
// use to keep track of how many configs we can extend
|
||||
// yea I know, globals bad
|
||||
var extendDepth int
|
||||
|
||||
const maxExtendDepth = 2
|
||||
|
||||
const DefaultScanConfigFileName = ".infisical-scan.toml"
|
||||
const DefaultScanConfigEnvName = "INFISICAL_SCAN_CONFIG"
|
||||
const DefaultInfisicalIgnoreFineName = ".infisicalignore"
|
||||
|
||||
// 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 {
|
||||
Description string
|
||||
Extend Extend
|
||||
Rules []struct {
|
||||
ID string
|
||||
Description string
|
||||
Entropy float64
|
||||
SecretGroup int
|
||||
Regex string
|
||||
Keywords []string
|
||||
Path string
|
||||
Tags []string
|
||||
|
||||
Allowlist struct {
|
||||
RegexTarget string
|
||||
Regexes []string
|
||||
Paths []string
|
||||
Commits []string
|
||||
StopWords []string
|
||||
}
|
||||
}
|
||||
Allowlist struct {
|
||||
RegexTarget string
|
||||
Regexes []string
|
||||
Paths []string
|
||||
Commits []string
|
||||
StopWords []string
|
||||
}
|
||||
}
|
||||
|
||||
// Config is a configuration struct that contains rules and an allowlist if present.
|
||||
type Config struct {
|
||||
Extend Extend
|
||||
Path string
|
||||
Description string
|
||||
Rules map[string]Rule
|
||||
Allowlist Allowlist
|
||||
Keywords []string
|
||||
|
||||
// used to keep sarif results consistent
|
||||
orderedRules []string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (vc *ViperConfig) Translate() (Config, error) {
|
||||
var (
|
||||
keywords []string
|
||||
orderedRules []string
|
||||
)
|
||||
rulesMap := make(map[string]Rule)
|
||||
|
||||
for _, r := range vc.Rules {
|
||||
var allowlistRegexes []*regexp.Regexp
|
||||
for _, a := range r.Allowlist.Regexes {
|
||||
allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
|
||||
}
|
||||
var allowlistPaths []*regexp.Regexp
|
||||
for _, a := range r.Allowlist.Paths {
|
||||
allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
|
||||
}
|
||||
|
||||
if r.Keywords == nil {
|
||||
r.Keywords = []string{}
|
||||
} else {
|
||||
for _, k := range r.Keywords {
|
||||
keywords = append(keywords, strings.ToLower(k))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Tags == nil {
|
||||
r.Tags = []string{}
|
||||
}
|
||||
|
||||
var configRegex *regexp.Regexp
|
||||
var configPathRegex *regexp.Regexp
|
||||
if r.Regex == "" {
|
||||
configRegex = nil
|
||||
} else {
|
||||
configRegex = regexp.MustCompile(r.Regex)
|
||||
}
|
||||
if r.Path == "" {
|
||||
configPathRegex = nil
|
||||
} else {
|
||||
configPathRegex = regexp.MustCompile(r.Path)
|
||||
}
|
||||
r := Rule{
|
||||
Description: r.Description,
|
||||
RuleID: r.ID,
|
||||
Regex: configRegex,
|
||||
Path: configPathRegex,
|
||||
SecretGroup: r.SecretGroup,
|
||||
Entropy: r.Entropy,
|
||||
Tags: r.Tags,
|
||||
Keywords: r.Keywords,
|
||||
Allowlist: Allowlist{
|
||||
RegexTarget: r.Allowlist.RegexTarget,
|
||||
Regexes: allowlistRegexes,
|
||||
Paths: allowlistPaths,
|
||||
Commits: r.Allowlist.Commits,
|
||||
StopWords: r.Allowlist.StopWords,
|
||||
},
|
||||
}
|
||||
orderedRules = append(orderedRules, r.RuleID)
|
||||
|
||||
if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() {
|
||||
return Config{}, fmt.Errorf("%s invalid regex secret group %d, max regex secret group %d", r.Description, r.SecretGroup, r.Regex.NumSubexp())
|
||||
}
|
||||
rulesMap[r.RuleID] = r
|
||||
}
|
||||
var allowlistRegexes []*regexp.Regexp
|
||||
for _, a := range vc.Allowlist.Regexes {
|
||||
allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
|
||||
}
|
||||
var allowlistPaths []*regexp.Regexp
|
||||
for _, a := range vc.Allowlist.Paths {
|
||||
allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
|
||||
}
|
||||
c := Config{
|
||||
Description: vc.Description,
|
||||
Extend: vc.Extend,
|
||||
Rules: rulesMap,
|
||||
Allowlist: Allowlist{
|
||||
RegexTarget: vc.Allowlist.RegexTarget,
|
||||
Regexes: allowlistRegexes,
|
||||
Paths: allowlistPaths,
|
||||
Commits: vc.Allowlist.Commits,
|
||||
StopWords: vc.Allowlist.StopWords,
|
||||
},
|
||||
Keywords: keywords,
|
||||
orderedRules: orderedRules,
|
||||
}
|
||||
|
||||
if maxExtendDepth != extendDepth {
|
||||
// disallow both usedefault and path from being set
|
||||
if c.Extend.Path != "" && c.Extend.UseDefault {
|
||||
log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
|
||||
}
|
||||
if c.Extend.UseDefault {
|
||||
c.extendDefault()
|
||||
} else if c.Extend.Path != "" {
|
||||
c.extendPath()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Config) OrderedRules() []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() {
|
||||
extendDepth++
|
||||
viper.SetConfigType("toml")
|
||||
if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
defaultViperConfig := ViperConfig{}
|
||||
if err := viper.Unmarshal(&defaultViperConfig); err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
cfg, err := defaultViperConfig.Translate()
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
log.Debug().Msg("extending config with default config")
|
||||
c.extend(cfg)
|
||||
|
||||
}
|
||||
|
||||
func (c *Config) extendPath() {
|
||||
extendDepth++
|
||||
viper.SetConfigFile(c.Extend.Path)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
extensionViperConfig := ViperConfig{}
|
||||
if err := viper.Unmarshal(&extensionViperConfig); err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
cfg, err := extensionViperConfig.Translate()
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed to load extended config, err: %s", err)
|
||||
return
|
||||
}
|
||||
log.Debug().Msgf("extending config with %s", c.Extend.Path)
|
||||
c.extend(cfg)
|
||||
}
|
||||
|
||||
func (c *Config) extendURL() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (c *Config) extend(extensionConfig Config) {
|
||||
for ruleID, rule := range extensionConfig.Rules {
|
||||
if _, ok := c.Rules[ruleID]; !ok {
|
||||
log.Trace().Msgf("adding %s to base config", ruleID)
|
||||
c.Rules[ruleID] = rule
|
||||
c.Keywords = append(c.Keywords, rule.Keywords...)
|
||||
}
|
||||
}
|
||||
|
||||
// append allowlists, not attempting to merge
|
||||
c.Allowlist.Commits = append(c.Allowlist.Commits,
|
||||
extensionConfig.Allowlist.Commits...)
|
||||
c.Allowlist.Paths = append(c.Allowlist.Paths,
|
||||
extensionConfig.Allowlist.Paths...)
|
||||
c.Allowlist.Regexes = append(c.Allowlist.Regexes,
|
||||
extensionConfig.Allowlist.Regexes...)
|
||||
}
|
170
cli/config/config_test.go
Normal file
170
cli/config/config_test.go
Normal file
@ -0,0 +1,170 @@
|
||||
// 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"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const configPath = "../testdata/config/"
|
||||
|
||||
func TestTranslate(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
cfg Config
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
cfgName: "allow_aws_re",
|
||||
cfg: Config{
|
||||
Rules: map[string]Rule{"aws-access-key": {
|
||||
Description: "AWS Access Key",
|
||||
Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-access-key",
|
||||
Allowlist: Allowlist{
|
||||
Regexes: []*regexp.Regexp{
|
||||
regexp.MustCompile("AKIALALEMEL33243OLIA"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_commit",
|
||||
cfg: Config{
|
||||
Rules: map[string]Rule{"aws-access-key": {
|
||||
Description: "AWS Access Key",
|
||||
Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-access-key",
|
||||
Allowlist: Allowlist{
|
||||
Commits: []string{"allowthiscommit"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_path",
|
||||
cfg: Config{
|
||||
Rules: map[string]Rule{"aws-access-key": {
|
||||
Description: "AWS Access Key",
|
||||
Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-access-key",
|
||||
Allowlist: Allowlist{
|
||||
Paths: []*regexp.Regexp{
|
||||
regexp.MustCompile(".go"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "entropy_group",
|
||||
cfg: Config{
|
||||
Rules: map[string]Rule{"discord-api-key": {
|
||||
Description: "Discord API key",
|
||||
Regex: regexp.MustCompile(`(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]`),
|
||||
RuleID: "discord-api-key",
|
||||
Allowlist: Allowlist{},
|
||||
Entropy: 3.5,
|
||||
SecretGroup: 3,
|
||||
Tags: []string{},
|
||||
Keywords: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "bad_entropy_group",
|
||||
cfg: Config{},
|
||||
wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"),
|
||||
},
|
||||
{
|
||||
cfgName: "base",
|
||||
cfg: Config{
|
||||
Rules: map[string]Rule{
|
||||
"aws-access-key": {
|
||||
Description: "AWS Access Key",
|
||||
Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-access-key",
|
||||
},
|
||||
"aws-secret-key": {
|
||||
Description: "AWS Secret Key",
|
||||
Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-secret-key",
|
||||
},
|
||||
"aws-secret-key-again": {
|
||||
Description: "AWS Secret Key",
|
||||
Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
|
||||
Tags: []string{"key", "AWS"},
|
||||
Keywords: []string{},
|
||||
RuleID: "aws-secret-key-again",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
viper.Reset()
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName(tt.cfgName)
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if tt.wantError != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
assert.Equal(t, tt.wantError, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, cfg.Rules, tt.cfg.Rules)
|
||||
}
|
||||
}
|
2803
cli/config/infisical-scan.toml
Normal file
2803
cli/config/infisical-scan.toml
Normal file
File diff suppressed because it is too large
Load Diff
43
cli/config/rule.go
Normal file
43
cli/config/rule.go
Normal file
@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Rules contain information that define details on how to detect secrets
|
||||
type Rule struct {
|
||||
// Description is the description of the rule.
|
||||
Description string
|
||||
|
||||
// RuleID is a unique identifier for this rule
|
||||
RuleID 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
|
||||
|
||||
// Allowlist allows a rule to be ignored for specific
|
||||
// regexes, paths, and/or commits
|
||||
Allowlist Allowlist
|
||||
}
|
24
cli/config/utils.go
Normal file
24
cli/config/utils.go
Normal file
@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"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
|
||||
}
|
87
cli/detect/baseline.go
Normal file
87
cli/detect/baseline.go
Normal file
@ -0,0 +1,87 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
)
|
||||
|
||||
func IsNew(finding report.Finding, baseline []report.Finding) bool {
|
||||
// Explicitly testing each property as it gives significantly better performance in comparison to cmp.Equal(). Drawback is that
|
||||
// the code requires maintanance if/when the Finding struct changes
|
||||
for _, b := range baseline {
|
||||
|
||||
if finding.Author == b.Author &&
|
||||
finding.Commit == b.Commit &&
|
||||
finding.Date == b.Date &&
|
||||
finding.Description == b.Description &&
|
||||
finding.Email == b.Email &&
|
||||
finding.EndColumn == b.EndColumn &&
|
||||
finding.EndLine == b.EndLine &&
|
||||
finding.Entropy == b.Entropy &&
|
||||
finding.File == b.File &&
|
||||
// Omit checking finding.Fingerprint - if the format of the fingerprint changes, the users will see unexpected behaviour
|
||||
finding.Match == b.Match &&
|
||||
finding.Message == b.Message &&
|
||||
finding.RuleID == b.RuleID &&
|
||||
finding.Secret == b.Secret &&
|
||||
finding.StartColumn == b.StartColumn &&
|
||||
finding.StartLine == b.StartLine {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func LoadBaseline(baselinePath string) ([]report.Finding, error) {
|
||||
var previousFindings []report.Finding
|
||||
jsonFile, err := os.Open(baselinePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open %s", baselinePath)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if cerr := jsonFile.Close(); cerr != nil {
|
||||
log.Warn().Err(cerr).Msg("problem closing jsonFile handle")
|
||||
}
|
||||
}()
|
||||
|
||||
bytes, err := io.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read data from the file %s", baselinePath)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytes, &previousFindings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the format of the file %s is not supported", baselinePath)
|
||||
}
|
||||
|
||||
return previousFindings, nil
|
||||
}
|
160
cli/detect/baseline_test.go
Normal file
160
cli/detect/baseline_test.go
Normal file
@ -0,0 +1,160 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
)
|
||||
|
||||
func TestIsNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings report.Finding
|
||||
baseline []report.Finding
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
findings: report.Finding{
|
||||
Author: "a",
|
||||
Commit: "0000",
|
||||
},
|
||||
baseline: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "0000",
|
||||
},
|
||||
},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
findings: report.Finding{
|
||||
Author: "a",
|
||||
Commit: "0000",
|
||||
},
|
||||
baseline: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "0002",
|
||||
},
|
||||
},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
findings: report.Finding{
|
||||
Author: "a",
|
||||
Commit: "0000",
|
||||
Tags: []string{"a", "b"},
|
||||
},
|
||||
baseline: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "0000",
|
||||
Tags: []string{"a", "c"},
|
||||
},
|
||||
},
|
||||
expect: false, // Updated tags doesn't make it a new finding
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.expect, IsNew(test.findings, test.baseline))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileLoadBaseline(t *testing.T) {
|
||||
tests := []struct {
|
||||
Filename string
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Filename: "../testdata/baseline/baseline.csv",
|
||||
ExpectedError: errors.New("the format of the file ../testdata/baseline/baseline.csv is not supported"),
|
||||
},
|
||||
{
|
||||
Filename: "../testdata/baseline/baseline.sarif",
|
||||
ExpectedError: errors.New("the format of the file ../testdata/baseline/baseline.sarif is not supported"),
|
||||
},
|
||||
{
|
||||
Filename: "../testdata/baseline/notfound.json",
|
||||
ExpectedError: errors.New("could not open ../testdata/baseline/notfound.json"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := LoadBaseline(test.Filename)
|
||||
assert.Equal(t, test.ExpectedError.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreIssuesInBaseline(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings []report.Finding
|
||||
baseline []report.Finding
|
||||
expectCount int
|
||||
}{
|
||||
{
|
||||
findings: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "5",
|
||||
},
|
||||
},
|
||||
baseline: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "5",
|
||||
},
|
||||
},
|
||||
expectCount: 0,
|
||||
},
|
||||
{
|
||||
findings: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "5",
|
||||
Fingerprint: "a",
|
||||
},
|
||||
},
|
||||
baseline: []report.Finding{
|
||||
{
|
||||
Author: "a",
|
||||
Commit: "5",
|
||||
Fingerprint: "b",
|
||||
},
|
||||
},
|
||||
expectCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
d, _ := NewDetectorDefaultConfig()
|
||||
d.baseline = test.baseline
|
||||
for _, finding := range test.findings {
|
||||
d.addFinding(finding)
|
||||
}
|
||||
assert.Equal(t, test.expectCount, len(d.findings))
|
||||
}
|
||||
}
|
652
cli/detect/detect.go
Normal file
652
cli/detect/detect.go
Normal file
@ -0,0 +1,652 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/h2non/filetype"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
"github.com/Infisical/infisical-merge/detect/git"
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
|
||||
"github.com/fatih/semgroup"
|
||||
"github.com/gitleaks/go-gitdiff/gitdiff"
|
||||
ahocorasick "github.com/petar-dambovaliev/aho-corasick"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Type used to differentiate between git scan types:
|
||||
// $ gitleaks detect
|
||||
// $ gitleaks protect
|
||||
// $ gitleaks protect staged
|
||||
type GitScanType int
|
||||
|
||||
const (
|
||||
DetectType GitScanType = iota
|
||||
ProtectType
|
||||
ProtectStagedType
|
||||
|
||||
gitleaksAllowSignature = "infisical-scan:ignore"
|
||||
)
|
||||
|
||||
// Detector is the main detector struct
|
||||
type Detector struct {
|
||||
// Config is the configuration for the detector
|
||||
Config config.Config
|
||||
|
||||
// Redact is a flag to redact findings. This is exported
|
||||
// so users using gitleaks as a library can set this flag
|
||||
// without calling `detector.Start(cmd *cobra.Command)`
|
||||
Redact bool
|
||||
|
||||
// verbose is a flag to print findings
|
||||
Verbose bool
|
||||
|
||||
// files larger than this will be skipped
|
||||
MaxTargetMegaBytes int
|
||||
|
||||
// followSymlinks is a flag to enable scanning symlink files
|
||||
FollowSymlinks bool
|
||||
|
||||
// NoColor is a flag to disable color output
|
||||
NoColor bool
|
||||
|
||||
// commitMap is used to keep track of commits that have been scanned.
|
||||
// This is only used for logging purposes and git scans.
|
||||
commitMap map[string]bool
|
||||
|
||||
// findingMutex is to prevent concurrent access to the
|
||||
// findings slice when adding findings.
|
||||
findingMutex *sync.Mutex
|
||||
|
||||
// findings is a slice of report.Findings. This is the result
|
||||
// of the detector's scan which can then be used to generate a
|
||||
// report.
|
||||
findings []report.Finding
|
||||
|
||||
// prefilter is a ahocorasick struct used for doing efficient string
|
||||
// matching given a set of words (keywords from the rules in the config)
|
||||
prefilter ahocorasick.AhoCorasick
|
||||
|
||||
// a list of known findings that should be ignored
|
||||
baseline []report.Finding
|
||||
|
||||
// path to baseline
|
||||
baselinePath string
|
||||
|
||||
// gitleaksIgnore
|
||||
gitleaksIgnore map[string]bool
|
||||
}
|
||||
|
||||
// Fragment contains the data to be scanned
|
||||
type Fragment struct {
|
||||
// Raw is the raw content of the fragment
|
||||
Raw string
|
||||
|
||||
// FilePath is the path to the file if applicable
|
||||
FilePath string
|
||||
SymlinkFile string
|
||||
|
||||
// CommitSHA is the SHA of the commit if applicable
|
||||
CommitSHA string
|
||||
|
||||
// newlineIndices is a list of indices of newlines in the raw content.
|
||||
// This is used to calculate the line location of a finding
|
||||
newlineIndices [][]int
|
||||
|
||||
// keywords is a map of all the keywords contain within the contents
|
||||
// of this fragment
|
||||
keywords map[string]bool
|
||||
}
|
||||
|
||||
// NewDetector creates a new detector with the given config
|
||||
func NewDetector(cfg config.Config) *Detector {
|
||||
builder := ahocorasick.NewAhoCorasickBuilder(ahocorasick.Opts{
|
||||
AsciiCaseInsensitive: true,
|
||||
MatchOnlyWholeWords: false,
|
||||
MatchKind: ahocorasick.LeftMostLongestMatch,
|
||||
DFA: true,
|
||||
})
|
||||
|
||||
return &Detector{
|
||||
commitMap: make(map[string]bool),
|
||||
gitleaksIgnore: make(map[string]bool),
|
||||
findingMutex: &sync.Mutex{},
|
||||
findings: make([]report.Finding, 0),
|
||||
Config: cfg,
|
||||
prefilter: builder.Build(cfg.Keywords),
|
||||
}
|
||||
}
|
||||
|
||||
// NewDetectorDefaultConfig creates a new detector with the default config
|
||||
func NewDetectorDefaultConfig() (*Detector, error) {
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadConfig(strings.NewReader(config.DefaultConfig))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDetector(cfg), nil
|
||||
}
|
||||
|
||||
func (d *Detector) AddGitleaksIgnore(gitleaksIgnorePath string) error {
|
||||
log.Debug().Msg("found .gitleaksignore file")
|
||||
file, err := os.Open(gitleaksIgnorePath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// https://github.com/securego/gosec/issues/512
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Warn().Msgf("Error closing .gitleaksignore file: %s\n", err)
|
||||
}
|
||||
}()
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
d.gitleaksIgnore[scanner.Text()] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Detector) AddBaseline(baselinePath string, source string) error {
|
||||
if baselinePath != "" {
|
||||
absoluteSource, err := filepath.Abs(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
absoluteBaseline, err := filepath.Abs(baselinePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relativeBaseline, err := filepath.Rel(absoluteSource, absoluteBaseline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseline, err := LoadBaseline(baselinePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.baseline = baseline
|
||||
baselinePath = relativeBaseline
|
||||
|
||||
}
|
||||
|
||||
d.baselinePath = baselinePath
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectBytes scans the given bytes and returns a list of findings
|
||||
func (d *Detector) DetectBytes(content []byte) []report.Finding {
|
||||
return d.DetectString(string(content))
|
||||
}
|
||||
|
||||
// DetectString scans the given string and returns a list of findings
|
||||
func (d *Detector) DetectString(content string) []report.Finding {
|
||||
return d.Detect(Fragment{
|
||||
Raw: content,
|
||||
})
|
||||
}
|
||||
|
||||
// detectRule scans the given fragment for the given rule and returns a list of findings
|
||||
func (d *Detector) detectRule(fragment Fragment, rule config.Rule) []report.Finding {
|
||||
var findings []report.Finding
|
||||
|
||||
// check if filepath or commit is allowed for this rule
|
||||
if rule.Allowlist.CommitAllowed(fragment.CommitSHA) ||
|
||||
rule.Allowlist.PathAllowed(fragment.FilePath) {
|
||||
return findings
|
||||
}
|
||||
|
||||
if rule.Path != nil && rule.Regex == nil {
|
||||
// Path _only_ rule
|
||||
if rule.Path.Match([]byte(fragment.FilePath)) {
|
||||
finding := report.Finding{
|
||||
Description: rule.Description,
|
||||
File: fragment.FilePath,
|
||||
SymlinkFile: fragment.SymlinkFile,
|
||||
RuleID: rule.RuleID,
|
||||
Match: fmt.Sprintf("file detected: %s", fragment.FilePath),
|
||||
Tags: rule.Tags,
|
||||
}
|
||||
return append(findings, finding)
|
||||
}
|
||||
} else if rule.Path != nil {
|
||||
// if path is set _and_ a regex is set, then we need to check both
|
||||
// so if the path does not match, then we should return early and not
|
||||
// consider the regex
|
||||
if !rule.Path.Match([]byte(fragment.FilePath)) {
|
||||
return findings
|
||||
}
|
||||
}
|
||||
|
||||
// if path only rule, skip content checks
|
||||
if rule.Regex == nil {
|
||||
return findings
|
||||
}
|
||||
|
||||
// If flag configure and raw data size bigger then the flag
|
||||
if d.MaxTargetMegaBytes > 0 {
|
||||
rawLength := len(fragment.Raw) / 1000000
|
||||
if rawLength > d.MaxTargetMegaBytes {
|
||||
log.Debug().Msgf("skipping file: %s scan due to size: %d", fragment.FilePath, rawLength)
|
||||
return findings
|
||||
}
|
||||
}
|
||||
|
||||
matchIndices := rule.Regex.FindAllStringIndex(fragment.Raw, -1)
|
||||
for _, matchIndex := range matchIndices {
|
||||
// extract secret from match
|
||||
secret := strings.Trim(fragment.Raw[matchIndex[0]:matchIndex[1]], "\n")
|
||||
|
||||
// determine location of match. Note that the location
|
||||
// in the finding will be the line/column numbers of the _match_
|
||||
// not the _secret_, which will be different if the secretGroup
|
||||
// value is set for this rule
|
||||
loc := location(fragment, matchIndex)
|
||||
|
||||
if matchIndex[1] > loc.endLineIndex {
|
||||
loc.endLineIndex = matchIndex[1]
|
||||
}
|
||||
|
||||
finding := report.Finding{
|
||||
Description: rule.Description,
|
||||
File: fragment.FilePath,
|
||||
SymlinkFile: fragment.SymlinkFile,
|
||||
RuleID: rule.RuleID,
|
||||
StartLine: loc.startLine,
|
||||
EndLine: loc.endLine,
|
||||
StartColumn: loc.startColumn,
|
||||
EndColumn: loc.endColumn,
|
||||
Secret: secret,
|
||||
Match: secret,
|
||||
Tags: rule.Tags,
|
||||
Line: fragment.Raw[loc.startLineIndex:loc.endLineIndex],
|
||||
}
|
||||
|
||||
if strings.Contains(fragment.Raw[loc.startLineIndex:loc.endLineIndex],
|
||||
gitleaksAllowSignature) {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract secret from secret group if set
|
||||
if rule.SecretGroup != 0 {
|
||||
groups := rule.Regex.FindStringSubmatch(secret)
|
||||
if len(groups) <= rule.SecretGroup || len(groups) == 0 {
|
||||
// Config validation should prevent this
|
||||
continue
|
||||
}
|
||||
secret = groups[rule.SecretGroup]
|
||||
finding.Secret = secret
|
||||
}
|
||||
|
||||
// check if the regexTarget is defined in the allowlist "regexes" entry
|
||||
allowlistTarget := finding.Secret
|
||||
switch rule.Allowlist.RegexTarget {
|
||||
case "match":
|
||||
allowlistTarget = finding.Match
|
||||
case "line":
|
||||
allowlistTarget = finding.Line
|
||||
}
|
||||
|
||||
globalAllowlistTarget := finding.Secret
|
||||
switch d.Config.Allowlist.RegexTarget {
|
||||
case "match":
|
||||
globalAllowlistTarget = finding.Match
|
||||
case "line":
|
||||
globalAllowlistTarget = finding.Line
|
||||
}
|
||||
if rule.Allowlist.RegexAllowed(allowlistTarget) ||
|
||||
d.Config.Allowlist.RegexAllowed(globalAllowlistTarget) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the secret is in the list of stopwords
|
||||
if rule.Allowlist.ContainsStopWord(finding.Secret) ||
|
||||
d.Config.Allowlist.ContainsStopWord(finding.Secret) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check entropy
|
||||
entropy := shannonEntropy(finding.Secret)
|
||||
finding.Entropy = float32(entropy)
|
||||
if rule.Entropy != 0.0 {
|
||||
if entropy <= rule.Entropy {
|
||||
// entropy is too low, skip this finding
|
||||
continue
|
||||
}
|
||||
// NOTE: this is a goofy hack to get around the fact there golang's regex engine
|
||||
// does not support positive lookaheads. Ideally we would want to add a
|
||||
// restriction on generic rules regex that requires the secret match group
|
||||
// contains both numbers and alphabetical characters, not just alphabetical characters.
|
||||
// What this bit of code does is check if the ruleid is prepended with "generic" and enforces the
|
||||
// secret contains both digits and alphabetical characters.
|
||||
// TODO: this should be replaced with stop words
|
||||
if strings.HasPrefix(rule.RuleID, "generic") {
|
||||
if !containsDigit(secret) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findings = append(findings, finding)
|
||||
}
|
||||
return findings
|
||||
}
|
||||
|
||||
// GitScan accepts a *gitdiff.File channel which contents a git history generated from
|
||||
// the output of `git log -p ...`. startGitScan will look at each file (patch) in the history
|
||||
// and determine if the patch contains any findings.
|
||||
func (d *Detector) DetectGit(source string, logOpts string, gitScanType GitScanType) ([]report.Finding, error) {
|
||||
var (
|
||||
gitdiffFiles <-chan *gitdiff.File
|
||||
err error
|
||||
)
|
||||
switch gitScanType {
|
||||
case DetectType:
|
||||
gitdiffFiles, err = git.GitLog(source, logOpts)
|
||||
if err != nil {
|
||||
return d.findings, err
|
||||
}
|
||||
case ProtectType:
|
||||
gitdiffFiles, err = git.GitDiff(source, false)
|
||||
if err != nil {
|
||||
return d.findings, err
|
||||
}
|
||||
case ProtectStagedType:
|
||||
gitdiffFiles, err = git.GitDiff(source, true)
|
||||
if err != nil {
|
||||
return d.findings, err
|
||||
}
|
||||
}
|
||||
|
||||
s := semgroup.NewGroup(context.Background(), 4)
|
||||
|
||||
for gitdiffFile := range gitdiffFiles {
|
||||
gitdiffFile := gitdiffFile
|
||||
|
||||
// skip binary files
|
||||
if gitdiffFile.IsBinary || gitdiffFile.IsDelete {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if commit is allowed
|
||||
commitSHA := ""
|
||||
if gitdiffFile.PatchHeader != nil {
|
||||
commitSHA = gitdiffFile.PatchHeader.SHA
|
||||
if d.Config.Allowlist.CommitAllowed(gitdiffFile.PatchHeader.SHA) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
d.addCommit(commitSHA)
|
||||
|
||||
s.Go(func() error {
|
||||
for _, textFragment := range gitdiffFile.TextFragments {
|
||||
if textFragment == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fragment := Fragment{
|
||||
Raw: textFragment.Raw(gitdiff.OpAdd),
|
||||
CommitSHA: commitSHA,
|
||||
FilePath: gitdiffFile.NewName,
|
||||
}
|
||||
|
||||
for _, finding := range d.Detect(fragment) {
|
||||
d.addFinding(augmentGitFinding(finding, textFragment, gitdiffFile))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := s.Wait(); err != nil {
|
||||
return d.findings, err
|
||||
}
|
||||
log.Info().Msgf("%d commits scanned.", len(d.commitMap))
|
||||
log.Debug().Msg("Note: this number might be smaller than expected due to commits with no additions")
|
||||
if git.ErrEncountered {
|
||||
return d.findings, fmt.Errorf("%s", "git error encountered, see logs")
|
||||
}
|
||||
return d.findings, nil
|
||||
}
|
||||
|
||||
type scanTarget struct {
|
||||
Path string
|
||||
Symlink string
|
||||
}
|
||||
|
||||
// DetectFiles accepts a path to a source directory or file and begins a scan of the
|
||||
// file or directory.
|
||||
func (d *Detector) DetectFiles(source string) ([]report.Finding, error) {
|
||||
s := semgroup.NewGroup(context.Background(), 4)
|
||||
paths := make(chan scanTarget)
|
||||
s.Go(func() error {
|
||||
defer close(paths)
|
||||
return filepath.Walk(source,
|
||||
func(path string, fInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fInfo.Name() == ".git" && fInfo.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if fInfo.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
if fInfo.Mode().IsRegular() {
|
||||
paths <- scanTarget{
|
||||
Path: path,
|
||||
Symlink: "",
|
||||
}
|
||||
}
|
||||
if fInfo.Mode().Type() == fs.ModeSymlink && d.FollowSymlinks {
|
||||
realPath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
realPathFileInfo, _ := os.Stat(realPath)
|
||||
if realPathFileInfo.IsDir() {
|
||||
log.Debug().Msgf("found symlinked directory: %s -> %s [skipping]", path, realPath)
|
||||
return nil
|
||||
}
|
||||
paths <- scanTarget{
|
||||
Path: realPath,
|
||||
Symlink: path,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
for pa := range paths {
|
||||
p := pa
|
||||
s.Go(func() error {
|
||||
b, err := os.ReadFile(p.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mimetype, err := filetype.Match(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mimetype.MIME.Type == "application" {
|
||||
return nil // skip binary files
|
||||
}
|
||||
|
||||
fragment := Fragment{
|
||||
Raw: string(b),
|
||||
FilePath: p.Path,
|
||||
}
|
||||
if p.Symlink != "" {
|
||||
fragment.SymlinkFile = p.Symlink
|
||||
}
|
||||
for _, finding := range d.Detect(fragment) {
|
||||
// need to add 1 since line counting starts at 1
|
||||
finding.EndLine++
|
||||
finding.StartLine++
|
||||
d.addFinding(finding)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := s.Wait(); err != nil {
|
||||
return d.findings, err
|
||||
}
|
||||
|
||||
return d.findings, nil
|
||||
}
|
||||
|
||||
// DetectReader accepts an io.Reader and a buffer size for the reader in KB
|
||||
func (d *Detector) DetectReader(r io.Reader, bufSize int) ([]report.Finding, error) {
|
||||
reader := bufio.NewReader(r)
|
||||
buf := make([]byte, 0, 1000*bufSize)
|
||||
findings := []report.Finding{}
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf[:cap(buf)])
|
||||
buf = buf[:n]
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return findings, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
fragment := Fragment{
|
||||
Raw: string(buf),
|
||||
}
|
||||
for _, finding := range d.Detect(fragment) {
|
||||
findings = append(findings, finding)
|
||||
if d.Verbose {
|
||||
printFinding(finding, d.NoColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findings, nil
|
||||
}
|
||||
|
||||
// Detect scans the given fragment and returns a list of findings
|
||||
func (d *Detector) Detect(fragment Fragment) []report.Finding {
|
||||
var findings []report.Finding
|
||||
|
||||
// initiate fragment keywords
|
||||
fragment.keywords = make(map[string]bool)
|
||||
|
||||
// check if filepath is allowed
|
||||
if fragment.FilePath != "" && (d.Config.Allowlist.PathAllowed(fragment.FilePath) ||
|
||||
fragment.FilePath == d.Config.Path || (d.baselinePath != "" && fragment.FilePath == d.baselinePath)) {
|
||||
return findings
|
||||
}
|
||||
|
||||
// add newline indices for location calculation in detectRule
|
||||
fragment.newlineIndices = regexp.MustCompile("\n").FindAllStringIndex(fragment.Raw, -1)
|
||||
|
||||
// build keyword map for prefiltering rules
|
||||
normalizedRaw := strings.ToLower(fragment.Raw)
|
||||
matches := d.prefilter.FindAll(normalizedRaw)
|
||||
for _, m := range matches {
|
||||
fragment.keywords[normalizedRaw[m.Start():m.End()]] = true
|
||||
}
|
||||
|
||||
for _, rule := range d.Config.Rules {
|
||||
if len(rule.Keywords) == 0 {
|
||||
// if not keywords are associated with the rule always scan the
|
||||
// fragment using the rule
|
||||
findings = append(findings, d.detectRule(fragment, rule)...)
|
||||
continue
|
||||
}
|
||||
fragmentContainsKeyword := false
|
||||
// check if keywords are in the fragment
|
||||
for _, k := range rule.Keywords {
|
||||
if _, ok := fragment.keywords[strings.ToLower(k)]; ok {
|
||||
fragmentContainsKeyword = true
|
||||
}
|
||||
}
|
||||
if fragmentContainsKeyword {
|
||||
findings = append(findings, d.detectRule(fragment, rule)...)
|
||||
}
|
||||
}
|
||||
return filter(findings, d.Redact)
|
||||
}
|
||||
|
||||
// addFinding synchronously adds a finding to the findings slice
|
||||
func (d *Detector) addFinding(finding report.Finding) {
|
||||
if finding.Commit == "" {
|
||||
finding.Fingerprint = fmt.Sprintf("%s:%s:%d", finding.File, finding.RuleID, finding.StartLine)
|
||||
} else {
|
||||
finding.Fingerprint = fmt.Sprintf("%s:%s:%s:%d", finding.Commit, finding.File, finding.RuleID, finding.StartLine)
|
||||
}
|
||||
// check if we should ignore this finding
|
||||
if _, ok := d.gitleaksIgnore[finding.Fingerprint]; ok {
|
||||
log.Debug().Msgf("ignoring finding with Fingerprint %s",
|
||||
finding.Fingerprint)
|
||||
return
|
||||
}
|
||||
|
||||
if d.baseline != nil && !IsNew(finding, d.baseline) {
|
||||
log.Debug().Msgf("baseline duplicate -- ignoring finding with Fingerprint %s", finding.Fingerprint)
|
||||
return
|
||||
}
|
||||
|
||||
d.findingMutex.Lock()
|
||||
d.findings = append(d.findings, finding)
|
||||
if d.Verbose {
|
||||
printFinding(finding, d.NoColor)
|
||||
}
|
||||
d.findingMutex.Unlock()
|
||||
}
|
||||
|
||||
// addCommit synchronously adds a commit to the commit slice
|
||||
func (d *Detector) addCommit(commit string) {
|
||||
d.commitMap[commit] = true
|
||||
}
|
754
cli/detect/detect_test.go
Normal file
754
cli/detect/detect_test.go
Normal file
@ -0,0 +1,754 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
)
|
||||
|
||||
const configPath = "../testdata/config/"
|
||||
const repoBasePath = "../testdata/repos/"
|
||||
|
||||
func TestDetect(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
baselinePath string
|
||||
fragment Fragment
|
||||
// NOTE: for expected findings, all line numbers will be 0
|
||||
// because line deltas are added _after_ the finding is created.
|
||||
// I.e, if the finding is from a --no-git file, the line number will be
|
||||
// increase by 1 in DetectFromFiles(). If the finding is from git,
|
||||
// the line number will be increased by the patch delta.
|
||||
expectedFindings []report.Finding
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // infisical-scan:ignore"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \
|
||||
|
||||
\"AKIALALEMEL33243OKIA\ // infisical-scan:ignore"
|
||||
|
||||
`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OKIA\"
|
||||
|
||||
// infisical-scan:ignore"
|
||||
|
||||
`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
Secret: "AKIALALEMEL33243OKIA",
|
||||
Match: "AKIALALEMEL33243OKIA",
|
||||
File: "tmp.go",
|
||||
Line: `awsToken := \"AKIALALEMEL33243OKIA\"`,
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 15,
|
||||
EndColumn: 34,
|
||||
Entropy: 3.1464393,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "escaped_character_group",
|
||||
fragment: Fragment{
|
||||
Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "PyPI upload token",
|
||||
Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
|
||||
Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
|
||||
Line: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
|
||||
File: "tmp.go",
|
||||
RuleID: "pypi-upload-token",
|
||||
Tags: []string{"key", "pypi"},
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 1,
|
||||
EndColumn: 86,
|
||||
Entropy: 1.9606875,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Line: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
File: "tmp.go",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 15,
|
||||
EndColumn: 34,
|
||||
Entropy: 3.0841837,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
|
||||
FilePath: "tmp.sh",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Sidekiq Secret",
|
||||
Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;",
|
||||
Secret: "cafebabe:deadbeef",
|
||||
Line: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
|
||||
File: "tmp.sh",
|
||||
RuleID: "sidekiq-secret",
|
||||
Tags: []string{},
|
||||
Entropy: 2.6098502,
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 8,
|
||||
EndColumn: 60,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
|
||||
FilePath: "tmp.sh",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Sidekiq Secret",
|
||||
Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=\"cafebabe:deadbeef\"",
|
||||
Secret: "cafebabe:deadbeef",
|
||||
File: "tmp.sh",
|
||||
Line: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
|
||||
RuleID: "sidekiq-secret",
|
||||
Tags: []string{},
|
||||
Entropy: 2.6098502,
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 21,
|
||||
EndColumn: 74,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true¶m2=false#heading1"`,
|
||||
FilePath: "tmp.sh",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Sidekiq Sensitive URL",
|
||||
Match: "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:",
|
||||
Secret: "cafeb4b3:d3adb33f",
|
||||
File: "tmp.sh",
|
||||
Line: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true¶m2=false#heading1"`,
|
||||
RuleID: "sidekiq-sensitive-url",
|
||||
Tags: []string{},
|
||||
Entropy: 2.984234,
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 8,
|
||||
EndColumn: 58,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_aws_re",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_path",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_commit",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: "tmp.go",
|
||||
CommitSHA: "allowthiscommit",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "entropy_group",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Discord API key",
|
||||
Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
|
||||
Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
|
||||
Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
File: "tmp.go",
|
||||
RuleID: "discord-api-key",
|
||||
Tags: []string{},
|
||||
Entropy: 3.7906237,
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 7,
|
||||
EndColumn: 93,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "generic_with_py_path",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "generic_with_py_path",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.py",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Generic API Key",
|
||||
Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
|
||||
Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
|
||||
Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
File: "tmp.py",
|
||||
RuleID: "generic-api-key",
|
||||
Tags: []string{},
|
||||
Entropy: 3.7906237,
|
||||
StartLine: 0,
|
||||
EndLine: 0,
|
||||
StartColumn: 22,
|
||||
EndColumn: 93,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "path_only",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.py",
|
||||
},
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Python Files",
|
||||
Match: "file detected: tmp.py",
|
||||
File: "tmp.py",
|
||||
RuleID: "python-files-only",
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cfgName: "bad_entropy_group",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"),
|
||||
},
|
||||
{
|
||||
cfgName: "simple",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: filepath.Join(configPath, "simple.toml"),
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "allow_global_aws_re",
|
||||
fragment: Fragment{
|
||||
Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
|
||||
FilePath: "tmp.go",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "generic_with_py_path",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: "tmp.py",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
{
|
||||
cfgName: "path_only",
|
||||
baselinePath: ".baseline.json",
|
||||
fragment: Fragment{
|
||||
Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
|
||||
FilePath: ".baseline.json",
|
||||
},
|
||||
expectedFindings: []report.Finding{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
viper.Reset()
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName(tt.cfgName)
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
cfg.Path = filepath.Join(configPath, tt.cfgName+".toml")
|
||||
if tt.wantError != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
assert.Equal(t, tt.wantError, err)
|
||||
}
|
||||
d := NewDetector(cfg)
|
||||
d.baselinePath = tt.baselinePath
|
||||
|
||||
findings := d.Detect(tt.fragment)
|
||||
assert.ElementsMatch(t, tt.expectedFindings, findings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromGit tests the FromGit function
|
||||
func TestFromGit(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
source string
|
||||
logOpts string
|
||||
expectedFindings []report.Finding
|
||||
}{
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "small"),
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 20,
|
||||
EndLine: 20,
|
||||
StartColumn: 19,
|
||||
EndColumn: 38,
|
||||
Line: "\n awsToken := \"AKIALALEMEL33243OLIA\"",
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
File: "main.go",
|
||||
Date: "2021-11-02T23:37:53Z",
|
||||
Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587",
|
||||
Author: "Zachary Rice",
|
||||
Email: "zricer@protonmail.com",
|
||||
Message: "Accidentally add a secret",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
Entropy: 3.0841837,
|
||||
Fingerprint: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587:main.go:aws-access-key:20",
|
||||
},
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 9,
|
||||
EndLine: 9,
|
||||
StartColumn: 17,
|
||||
EndColumn: 36,
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
|
||||
File: "foo/foo.go",
|
||||
Date: "2021-11-02T23:48:06Z",
|
||||
Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
|
||||
Author: "Zach Rice",
|
||||
Email: "zricer@protonmail.com",
|
||||
Message: "adding foo package with secret",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
Entropy: 3.0841837,
|
||||
Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "small"),
|
||||
logOpts: "--all foo...",
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 9,
|
||||
EndLine: 9,
|
||||
StartColumn: 17,
|
||||
EndColumn: 36,
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Date: "2021-11-02T23:48:06Z",
|
||||
File: "foo/foo.go",
|
||||
Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
|
||||
Author: "Zach Rice",
|
||||
Email: "zricer@protonmail.com",
|
||||
Message: "adding foo package with secret",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
Entropy: 3.0841837,
|
||||
Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := moveDotGit("dotGit", ".git")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := moveDotGit(".git", "dotGit"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName("simple")
|
||||
viper.SetConfigType("toml")
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
detector := NewDetector(cfg)
|
||||
findings, err := detector.DetectGit(tt.source, tt.logOpts, DetectType)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, f := range findings {
|
||||
f.Match = "" // remove lines cause copying and pasting them has some wack formatting
|
||||
}
|
||||
assert.ElementsMatch(t, tt.expectedFindings, findings)
|
||||
}
|
||||
}
|
||||
func TestFromGitStaged(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
source string
|
||||
logOpts string
|
||||
expectedFindings []report.Finding
|
||||
}{
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "staged"),
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 7,
|
||||
EndLine: 7,
|
||||
StartColumn: 18,
|
||||
EndColumn: 37,
|
||||
Line: "\n\taws_token2 := \"AKIALALEMEL33243OLIA\" // this one is not",
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
File: "api/api.go",
|
||||
SymlinkFile: "",
|
||||
Commit: "",
|
||||
Entropy: 3.0841837,
|
||||
Author: "",
|
||||
Email: "",
|
||||
Date: "0001-01-01T00:00:00Z",
|
||||
Message: "",
|
||||
Tags: []string{
|
||||
"key",
|
||||
"AWS",
|
||||
},
|
||||
RuleID: "aws-access-key",
|
||||
Fingerprint: "api/api.go:aws-access-key:7",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := moveDotGit("dotGit", ".git")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := moveDotGit(".git", "dotGit"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName("simple")
|
||||
viper.SetConfigType("toml")
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
detector := NewDetector(cfg)
|
||||
detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore"))
|
||||
findings, err := detector.DetectGit(tt.source, tt.logOpts, ProtectStagedType)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, f := range findings {
|
||||
f.Match = "" // remove lines cause copying and pasting them has some wack formatting
|
||||
}
|
||||
assert.ElementsMatch(t, tt.expectedFindings, findings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromFiles tests the FromFiles function
|
||||
func TestFromFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
source string
|
||||
expectedFindings []report.Finding
|
||||
}{
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "nogit"),
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 20,
|
||||
EndLine: 20,
|
||||
StartColumn: 16,
|
||||
EndColumn: 35,
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
|
||||
File: "../testdata/repos/nogit/main.go",
|
||||
SymlinkFile: "",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
Entropy: 3.0841837,
|
||||
Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "nogit", "main.go"),
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "AWS Access Key",
|
||||
StartLine: 20,
|
||||
EndLine: 20,
|
||||
StartColumn: 16,
|
||||
EndColumn: 35,
|
||||
Match: "AKIALALEMEL33243OLIA",
|
||||
Secret: "AKIALALEMEL33243OLIA",
|
||||
Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
|
||||
File: "../testdata/repos/nogit/main.go",
|
||||
RuleID: "aws-access-key",
|
||||
Tags: []string{"key", "AWS"},
|
||||
Entropy: 3.0841837,
|
||||
Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName("simple")
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, _ := vc.Translate()
|
||||
detector := NewDetector(cfg)
|
||||
detector.FollowSymlinks = true
|
||||
findings, err := detector.DetectFiles(tt.source)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, tt.expectedFindings, findings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectWithSymlinks(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfgName string
|
||||
source string
|
||||
expectedFindings []report.Finding
|
||||
}{
|
||||
{
|
||||
source: filepath.Join(repoBasePath, "symlinks/file_symlink"),
|
||||
cfgName: "simple",
|
||||
expectedFindings: []report.Finding{
|
||||
{
|
||||
Description: "Asymmetric Private Key",
|
||||
StartLine: 1,
|
||||
EndLine: 1,
|
||||
StartColumn: 1,
|
||||
EndColumn: 35,
|
||||
Match: "-----BEGIN OPENSSH PRIVATE KEY-----",
|
||||
Secret: "-----BEGIN OPENSSH PRIVATE KEY-----",
|
||||
Line: "-----BEGIN OPENSSH PRIVATE KEY-----",
|
||||
File: "../testdata/repos/symlinks/source_file/id_ed25519",
|
||||
SymlinkFile: "../testdata/repos/symlinks/file_symlink/symlinked_id_ed25519",
|
||||
RuleID: "apkey",
|
||||
Tags: []string{"key", "AsymmetricPrivateKey"},
|
||||
Entropy: 3.587164,
|
||||
Fingerprint: "../testdata/repos/symlinks/source_file/id_ed25519:apkey:1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.SetConfigName("simple")
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var vc config.ViperConfig
|
||||
err = viper.Unmarshal(&vc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg, _ := vc.Translate()
|
||||
detector := NewDetector(cfg)
|
||||
detector.FollowSymlinks = true
|
||||
findings, err := detector.DetectFiles(tt.source)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.ElementsMatch(t, tt.expectedFindings, findings)
|
||||
}
|
||||
}
|
||||
|
||||
func moveDotGit(from, to string) error {
|
||||
repoDirs, err := os.ReadDir("../testdata/repos")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dir := range repoDirs {
|
||||
if to == ".git" {
|
||||
_, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
|
||||
if os.IsNotExist(err) {
|
||||
// dont want to delete the only copy of .git accidentally
|
||||
continue
|
||||
}
|
||||
os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
_, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
|
||||
fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
143
cli/detect/git/git.go
Normal file
143
cli/detect/git/git.go
Normal file
@ -0,0 +1,143 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gitleaks/go-gitdiff/gitdiff"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var ErrEncountered bool
|
||||
|
||||
// GitLog returns a channel of gitdiff.File objects from the
|
||||
// git log -p command for the given source.
|
||||
func GitLog(source string, logOpts string) (<-chan *gitdiff.File, error) {
|
||||
sourceClean := filepath.Clean(source)
|
||||
var cmd *exec.Cmd
|
||||
if logOpts != "" {
|
||||
args := []string{"-C", sourceClean, "log", "-p", "-U0"}
|
||||
args = append(args, strings.Split(logOpts, " ")...)
|
||||
cmd = exec.Command("git", args...)
|
||||
} else {
|
||||
cmd = exec.Command("git", "-C", sourceClean, "log", "-p", "-U0",
|
||||
"--full-history", "--all")
|
||||
}
|
||||
|
||||
log.Debug().Msgf("executing: %s", cmd.String())
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go listenForStdErr(stderr)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
return gitdiff.Parse(cmd, stdout)
|
||||
}
|
||||
|
||||
// GitDiff returns a channel of gitdiff.File objects from
|
||||
// the git diff command for the given source.
|
||||
func GitDiff(source string, staged bool) (<-chan *gitdiff.File, error) {
|
||||
sourceClean := filepath.Clean(source)
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0", ".")
|
||||
if staged {
|
||||
cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0",
|
||||
"--staged", ".")
|
||||
}
|
||||
log.Debug().Msgf("executing: %s", cmd.String())
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go listenForStdErr(stderr)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
return gitdiff.Parse(cmd, stdout)
|
||||
}
|
||||
|
||||
// listenForStdErr listens for stderr output from git and prints it to stdout
|
||||
// then exits with exit code 1
|
||||
func listenForStdErr(stderr io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
for scanner.Scan() {
|
||||
// if git throws one of the following errors:
|
||||
//
|
||||
// exhaustive rename detection was skipped due to too many files.
|
||||
// you may want to set your diff.renameLimit variable to at least
|
||||
// (some large number) and retry the command.
|
||||
//
|
||||
// inexact rename detection was skipped due to too many files.
|
||||
// you may want to set your diff.renameLimit variable to at least
|
||||
// (some large number) and retry the command.
|
||||
//
|
||||
// we skip exiting the program as git log -p/git diff will continue
|
||||
// to send data to stdout and finish executing. This next bit of
|
||||
// code prevents gitleaks from stopping mid scan if this error is
|
||||
// encountered
|
||||
if strings.Contains(scanner.Text(),
|
||||
"exhaustive rename detection was skipped") ||
|
||||
strings.Contains(scanner.Text(),
|
||||
"inexact rename detection was skipped") ||
|
||||
strings.Contains(scanner.Text(),
|
||||
"you may want to set your diff.renameLimit") {
|
||||
log.Warn().Msg(scanner.Text())
|
||||
} else {
|
||||
log.Error().Msgf("[git] %s", scanner.Text())
|
||||
|
||||
// asynchronously set this error flag to true so that we can
|
||||
// capture a log message and exit with a non-zero exit code
|
||||
// This value should get set before the `git` command exits so it's
|
||||
// safe-ish, although I know I know, bad practice.
|
||||
ErrEncountered = true
|
||||
}
|
||||
}
|
||||
}
|
158
cli/detect/git/git_test.go
Normal file
158
cli/detect/git/git_test.go
Normal file
@ -0,0 +1,158 @@
|
||||
package git_test
|
||||
|
||||
// TODO: commenting out this test for now because it's flaky. Alternatives to consider to get this working:
|
||||
// -- use `git stash` instead of `restore()`
|
||||
|
||||
// const repoBasePath = "../../testdata/repos/"
|
||||
|
||||
// const expectPath = "../../testdata/expected/"
|
||||
|
||||
// func TestGitLog(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// source string
|
||||
// logOpts string
|
||||
// expected string
|
||||
// }{
|
||||
// {
|
||||
// source: filepath.Join(repoBasePath, "small"),
|
||||
// expected: filepath.Join(expectPath, "git", "small.txt"),
|
||||
// },
|
||||
// {
|
||||
// source: filepath.Join(repoBasePath, "small"),
|
||||
// expected: filepath.Join(expectPath, "git", "small-branch-foo.txt"),
|
||||
// logOpts: "--all foo...",
|
||||
// },
|
||||
// }
|
||||
|
||||
// err := moveDotGit("dotGit", ".git")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer func() {
|
||||
// if err = moveDotGit(".git", "dotGit"); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// for _, tt := range tests {
|
||||
// files, err := git.GitLog(tt.source, tt.logOpts)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// var diffSb strings.Builder
|
||||
// for f := range files {
|
||||
// for _, tf := range f.TextFragments {
|
||||
// diffSb.WriteString(tf.Raw(gitdiff.OpAdd))
|
||||
// }
|
||||
// }
|
||||
|
||||
// expectedBytes, err := os.ReadFile(tt.expected)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// expected := string(expectedBytes)
|
||||
// if expected != diffSb.String() {
|
||||
// // write string builder to .got file using os.Create
|
||||
// err = os.WriteFile(strings.Replace(tt.expected, ".txt", ".got.txt", 1), []byte(diffSb.String()), 0644)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// t.Error("expected: ", expected, "got: ", diffSb.String())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestGitDiff(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// source string
|
||||
// expected string
|
||||
// additions string
|
||||
// target string
|
||||
// }{
|
||||
// {
|
||||
// source: filepath.Join(repoBasePath, "small"),
|
||||
// expected: "this line is added\nand another one",
|
||||
// additions: "this line is added\nand another one",
|
||||
// target: filepath.Join(repoBasePath, "small", "main.go"),
|
||||
// },
|
||||
// }
|
||||
|
||||
// err := moveDotGit("dotGit", ".git")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer func() {
|
||||
// if err = moveDotGit(".git", "dotGit"); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// for _, tt := range tests {
|
||||
// noChanges, err := os.ReadFile(tt.target)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// err = os.WriteFile(tt.target, []byte(tt.additions), 0644)
|
||||
// if err != nil {
|
||||
// restore(tt.target, noChanges, t)
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// files, err := git.GitDiff(tt.source, false)
|
||||
// if err != nil {
|
||||
// restore(tt.target, noChanges, t)
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// for f := range files {
|
||||
// sb := strings.Builder{}
|
||||
// for _, tf := range f.TextFragments {
|
||||
// sb.WriteString(tf.Raw(gitdiff.OpAdd))
|
||||
// }
|
||||
// if sb.String() != tt.expected {
|
||||
// restore(tt.target, noChanges, t)
|
||||
// t.Error("expected: ", tt.expected, "got: ", sb.String())
|
||||
// }
|
||||
// }
|
||||
// restore(tt.target, noChanges, t)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func restore(path string, data []byte, t *testing.T) {
|
||||
// err := os.WriteFile(path, data, 0644)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func moveDotGit(from, to string) error {
|
||||
// repoDirs, err := os.ReadDir("../../testdata/repos")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// for _, dir := range repoDirs {
|
||||
// if to == ".git" {
|
||||
// _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
|
||||
// if os.IsNotExist(err) {
|
||||
// // dont want to delete the only copy of .git accidentally
|
||||
// continue
|
||||
// }
|
||||
// os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
|
||||
// }
|
||||
// if !dir.IsDir() {
|
||||
// continue
|
||||
// }
|
||||
// _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
|
||||
// if os.IsNotExist(err) {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
|
||||
// fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
101
cli/detect/location.go
Normal file
101
cli/detect/location.go
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 detect
|
||||
|
||||
// Location represents a location in a file
|
||||
type Location struct {
|
||||
startLine int
|
||||
endLine int
|
||||
startColumn int
|
||||
endColumn int
|
||||
startLineIndex int
|
||||
endLineIndex int
|
||||
}
|
||||
|
||||
func location(fragment Fragment, matchIndex []int) Location {
|
||||
var (
|
||||
prevNewLine int
|
||||
location Location
|
||||
lineSet bool
|
||||
_lineNum int
|
||||
)
|
||||
|
||||
start := matchIndex[0]
|
||||
end := matchIndex[1]
|
||||
|
||||
// default startLineIndex to 0
|
||||
location.startLineIndex = 0
|
||||
|
||||
// Fixes: https://github.com/zricethezav/gitleaks/issues/1037
|
||||
// When a fragment does NOT have any newlines, a default "newline"
|
||||
// will be counted to make the subsequent location calculation logic work
|
||||
// for fragments will no newlines.
|
||||
if len(fragment.newlineIndices) == 0 {
|
||||
fragment.newlineIndices = [][]int{
|
||||
{len(fragment.Raw), len(fragment.Raw) + 1},
|
||||
}
|
||||
}
|
||||
|
||||
for lineNum, pair := range fragment.newlineIndices {
|
||||
_lineNum = lineNum
|
||||
newLineByteIndex := pair[0]
|
||||
if prevNewLine <= start && start < newLineByteIndex {
|
||||
lineSet = true
|
||||
location.startLine = lineNum
|
||||
location.endLine = lineNum
|
||||
location.startColumn = (start - prevNewLine) + 1 // +1 because counting starts at 1
|
||||
location.startLineIndex = prevNewLine
|
||||
location.endLineIndex = newLineByteIndex
|
||||
}
|
||||
if prevNewLine < end && end <= newLineByteIndex {
|
||||
location.endLine = lineNum
|
||||
location.endColumn = (end - prevNewLine)
|
||||
location.endLineIndex = newLineByteIndex
|
||||
}
|
||||
prevNewLine = pair[0]
|
||||
}
|
||||
|
||||
if !lineSet {
|
||||
// if lines never get set then that means the secret is most likely
|
||||
// on the last line of the diff output and the diff output does not have
|
||||
// a newline
|
||||
location.startColumn = (start - prevNewLine) + 1 // +1 because counting starts at 1
|
||||
location.endColumn = (end - prevNewLine)
|
||||
location.startLine = _lineNum + 1
|
||||
location.endLine = _lineNum + 1
|
||||
|
||||
// search for new line byte index
|
||||
i := 0
|
||||
for end+i < len(fragment.Raw) {
|
||||
if fragment.Raw[end+i] == '\n' {
|
||||
break
|
||||
}
|
||||
if fragment.Raw[end+i] == '\r' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
location.endLineIndex = end + i
|
||||
}
|
||||
return location
|
||||
}
|
82
cli/detect/location_test.go
Normal file
82
cli/detect/location_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetLocation tests the getLocation function.
|
||||
func TestGetLocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
linePairs [][]int
|
||||
start int
|
||||
end int
|
||||
wantLocation Location
|
||||
}{
|
||||
{
|
||||
linePairs: [][]int{
|
||||
{0, 39},
|
||||
{40, 55},
|
||||
{56, 57},
|
||||
},
|
||||
start: 35,
|
||||
end: 38,
|
||||
wantLocation: Location{
|
||||
startLine: 1,
|
||||
startColumn: 36,
|
||||
endLine: 1,
|
||||
endColumn: 38,
|
||||
startLineIndex: 0,
|
||||
endLineIndex: 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
linePairs: [][]int{
|
||||
{0, 39},
|
||||
{40, 55},
|
||||
{56, 57},
|
||||
},
|
||||
start: 40,
|
||||
end: 44,
|
||||
wantLocation: Location{
|
||||
startLine: 2,
|
||||
startColumn: 1,
|
||||
endLine: 2,
|
||||
endColumn: 4,
|
||||
startLineIndex: 40,
|
||||
endLineIndex: 56,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
loc := location(Fragment{newlineIndices: test.linePairs}, []int{test.start, test.end})
|
||||
if loc != test.wantLocation {
|
||||
t.Errorf("\nstartLine %d\nstartColumn: %d\nendLine: %d\nendColumn: %d\nstartLineIndex: %d\nendlineIndex %d",
|
||||
loc.startLine, loc.startColumn, loc.endLine, loc.endColumn, loc.startLineIndex, loc.endLineIndex)
|
||||
|
||||
t.Error("got", loc, "want", test.wantLocation)
|
||||
}
|
||||
}
|
||||
}
|
211
cli/detect/utils.go
Normal file
211
cli/detect/utils.go
Normal file
@ -0,0 +1,211 @@
|
||||
// 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 detect
|
||||
|
||||
import (
|
||||
// "encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
|
||||
"github.com/gitleaks/go-gitdiff/gitdiff"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// augmentGitFinding updates the start and end line numbers of a finding to include the
|
||||
// delta from the git diff
|
||||
func augmentGitFinding(finding report.Finding, textFragment *gitdiff.TextFragment, f *gitdiff.File) report.Finding {
|
||||
if !strings.HasPrefix(finding.Match, "file detected") {
|
||||
finding.StartLine += int(textFragment.NewPosition)
|
||||
finding.EndLine += int(textFragment.NewPosition)
|
||||
}
|
||||
|
||||
if f.PatchHeader != nil {
|
||||
finding.Commit = f.PatchHeader.SHA
|
||||
finding.Message = f.PatchHeader.Message()
|
||||
if f.PatchHeader.Author != nil {
|
||||
finding.Author = f.PatchHeader.Author.Name
|
||||
finding.Email = f.PatchHeader.Author.Email
|
||||
}
|
||||
finding.Date = f.PatchHeader.AuthorDate.UTC().Format(time.RFC3339)
|
||||
}
|
||||
return finding
|
||||
}
|
||||
|
||||
// shannonEntropy calculates the entropy of data using the formula defined here:
|
||||
// https://en.wiktionary.org/wiki/Shannon_entropy
|
||||
// Another way to think about what this is doing is calculating the number of bits
|
||||
// needed to on average encode the data. So, the higher the entropy, the more random the data, the
|
||||
// more bits needed to encode that data.
|
||||
func shannonEntropy(data string) (entropy float64) {
|
||||
if data == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
charCounts := make(map[rune]int)
|
||||
for _, char := range data {
|
||||
charCounts[char]++
|
||||
}
|
||||
|
||||
invLength := 1.0 / float64(len(data))
|
||||
for _, count := range charCounts {
|
||||
freq := float64(count) * invLength
|
||||
entropy -= freq * math.Log2(freq)
|
||||
}
|
||||
|
||||
return entropy
|
||||
}
|
||||
|
||||
// filter will dedupe and redact findings
|
||||
func filter(findings []report.Finding, redact bool) []report.Finding {
|
||||
var retFindings []report.Finding
|
||||
for _, f := range findings {
|
||||
include := true
|
||||
if strings.Contains(strings.ToLower(f.RuleID), "generic") {
|
||||
for _, fPrime := range findings {
|
||||
if f.StartLine == fPrime.StartLine &&
|
||||
f.Commit == fPrime.Commit &&
|
||||
f.RuleID != fPrime.RuleID &&
|
||||
strings.Contains(fPrime.Secret, f.Secret) &&
|
||||
!strings.Contains(strings.ToLower(fPrime.RuleID), "generic") {
|
||||
|
||||
genericMatch := strings.Replace(f.Match, f.Secret, "REDACTED", -1)
|
||||
betterMatch := strings.Replace(fPrime.Match, fPrime.Secret, "REDACTED", -1)
|
||||
log.Trace().Msgf("skipping %s finding (%s), %s rule takes precendence (%s)", f.RuleID, genericMatch, fPrime.RuleID, betterMatch)
|
||||
include = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if redact {
|
||||
f.Redact()
|
||||
}
|
||||
if include {
|
||||
retFindings = append(retFindings, f)
|
||||
}
|
||||
}
|
||||
return retFindings
|
||||
}
|
||||
|
||||
func printFinding(f report.Finding, noColor bool) {
|
||||
// trim all whitespace and tabs
|
||||
f.Line = strings.TrimSpace(f.Line)
|
||||
f.Secret = strings.TrimSpace(f.Secret)
|
||||
f.Match = strings.TrimSpace(f.Match)
|
||||
|
||||
isFileMatch := strings.HasPrefix(f.Match, "file detected:")
|
||||
skipColor := noColor
|
||||
finding := ""
|
||||
var secret lipgloss.Style
|
||||
|
||||
// Matches from filenames do not have a |line| or |secret|
|
||||
if !isFileMatch {
|
||||
matchInLineIDX := strings.Index(f.Line, f.Match)
|
||||
secretInMatchIdx := strings.Index(f.Match, f.Secret)
|
||||
|
||||
skipColor = false
|
||||
|
||||
if matchInLineIDX == -1 || noColor {
|
||||
skipColor = true
|
||||
matchInLineIDX = 0
|
||||
}
|
||||
|
||||
start := f.Line[0:matchInLineIDX]
|
||||
startMatchIdx := 0
|
||||
if matchInLineIDX > 20 {
|
||||
startMatchIdx = matchInLineIDX - 20
|
||||
start = "..." + f.Line[startMatchIdx:matchInLineIDX]
|
||||
}
|
||||
|
||||
matchBeginning := lipgloss.NewStyle().SetString(f.Match[0:secretInMatchIdx]).Foreground(lipgloss.Color("#f5d445"))
|
||||
secret = lipgloss.NewStyle().SetString(f.Secret).
|
||||
Bold(true).
|
||||
Italic(true).
|
||||
Foreground(lipgloss.Color("#f05c07"))
|
||||
matchEnd := lipgloss.NewStyle().SetString(f.Match[secretInMatchIdx+len(f.Secret):]).Foreground(lipgloss.Color("#f5d445"))
|
||||
|
||||
lineEndIdx := matchInLineIDX + len(f.Match)
|
||||
if len(f.Line)-1 <= lineEndIdx {
|
||||
lineEndIdx = len(f.Line) - 1
|
||||
}
|
||||
|
||||
lineEnd := f.Line[lineEndIdx:]
|
||||
|
||||
if len(f.Secret) > 100 {
|
||||
secret = lipgloss.NewStyle().SetString(f.Secret[0:100] + "...").
|
||||
Bold(true).
|
||||
Italic(true).
|
||||
Foreground(lipgloss.Color("#f05c07"))
|
||||
}
|
||||
if len(lineEnd) > 20 {
|
||||
lineEnd = lineEnd[0:20] + "..."
|
||||
}
|
||||
|
||||
finding = fmt.Sprintf("%s%s%s%s%s\n", strings.TrimPrefix(strings.TrimLeft(start, " "), "\n"), matchBeginning, secret, matchEnd, lineEnd)
|
||||
}
|
||||
|
||||
if skipColor || isFileMatch {
|
||||
fmt.Printf("%-12s %s\n", "Finding:", f.Match)
|
||||
fmt.Printf("%-12s %s\n", "Secret:", f.Secret)
|
||||
} else {
|
||||
fmt.Printf("%-12s %s", "Finding:", finding)
|
||||
fmt.Printf("%-12s %s\n", "Secret:", secret)
|
||||
}
|
||||
|
||||
fmt.Printf("%-12s %s\n", "RuleID:", f.RuleID)
|
||||
fmt.Printf("%-12s %f\n", "Entropy:", f.Entropy)
|
||||
if f.File == "" {
|
||||
fmt.Println("")
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-12s %s\n", "File:", f.File)
|
||||
fmt.Printf("%-12s %d\n", "Line:", f.StartLine)
|
||||
if f.Commit == "" {
|
||||
fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
|
||||
fmt.Println("")
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-12s %s\n", "Commit:", f.Commit)
|
||||
fmt.Printf("%-12s %s\n", "Author:", f.Author)
|
||||
fmt.Printf("%-12s %s\n", "Email:", f.Email)
|
||||
fmt.Printf("%-12s %s\n", "Date:", f.Date)
|
||||
fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func containsDigit(s string) bool {
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
28
cli/go.mod
28
cli/go.mod
@ -4,12 +4,20 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/99designs/keyring v1.2.2
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/fatih/semgroup v1.2.0
|
||||
github.com/gitleaks/go-gitdiff v0.8.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||
github.com/muesli/mango-cobra v1.2.0
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/roff v0.1.0
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/term v0.5.0
|
||||
)
|
||||
@ -19,22 +27,39 @@ require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-openapi/errors v0.20.2 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.3 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/muesli/mango v0.1.0 // indirect
|
||||
github.com/muesli/mango-pflag v0.1.0 // indirect
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@ -43,6 +68,5 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
||||
|
587
cli/go.sum
587
cli/go.sum
@ -1,9 +1,59 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
|
||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
@ -13,6 +63,12 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
@ -22,8 +78,26 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg=
|
||||
github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gitleaks/go-gitdiff v0.8.0 h1:7aExTZm+K/M/EQKOyYcub8rIAdWK6ONxPGuRzxmWW+0=
|
||||
github.com/gitleaks/go-gitdiff v0.8.0/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
|
||||
@ -32,35 +106,153 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||
@ -72,75 +264,458 @@ github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbY
|
||||
github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA=
|
||||
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
||||
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
||||
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
|
||||
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -3,8 +3,15 @@ Copyright (c) 2023 Infisical Inc.
|
||||
*/
|
||||
package main
|
||||
|
||||
import "github.com/Infisical/infisical-merge/packages/cmd"
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/cmd"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
cmd.Execute()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/config"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const USER_AGENT = "cli"
|
||||
@ -222,7 +222,7 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
log.Debugln(fmt.Errorf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response))
|
||||
log.Debug().Msgf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -30,11 +30,6 @@ var exportCmd = &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical export --env=prod --format=json > secrets.json",
|
||||
Args: cobra.NoArgs,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
toggleDebug(cmd, args)
|
||||
// util.RequireLogin()
|
||||
// util.RequireLocalWorkspaceFile()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
environmentName, _ := cmd.Flags().GetString("env")
|
||||
if !cmd.Flags().Changed("env") {
|
||||
@ -175,8 +170,7 @@ func formatAsJson(envs []models.SingleEnvironmentVariable) string {
|
||||
// Dump as a json array
|
||||
json, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to marshal environment variables to JSON")
|
||||
log.Debugln(err)
|
||||
log.Err(err).Msgf("Unable to marshal environment variables to JSON")
|
||||
return ""
|
||||
}
|
||||
return string(json)
|
||||
|
@ -12,14 +12,14 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/manifoldco/promptui"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// runCmd represents the run command
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Used to initialize your project with Infisical",
|
||||
Short: "Used to connect your local project with Infisical project",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical init",
|
||||
Args: cobra.ExactArgs(0),
|
||||
@ -30,8 +30,8 @@ var initCmd = &cobra.Command{
|
||||
if util.WorkspaceConfigFileExistsInCurrentPath() {
|
||||
shouldOverride, err := shouldOverrideWorkspacePrompt()
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse your answer")
|
||||
log.Debug(err)
|
||||
log.Error().Msg("Unable to parse your answer")
|
||||
log.Debug().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var debugLogging bool
|
||||
|
||||
type PlainFormatter struct {
|
||||
}
|
||||
|
||||
func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%s\n", entry.Message)), nil
|
||||
}
|
||||
func toggleDebug(cmd *cobra.Command, args []string) {
|
||||
if debugLogging {
|
||||
log.Info("Debug logs enabled")
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
} else {
|
||||
plainFormatter := new(PlainFormatter)
|
||||
log.SetFormatter(plainFormatter)
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/manifoldco/promptui"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
@ -44,12 +44,11 @@ var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login into your Infisical account",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
// if the key can't be found or there is an error getting current credentials from key ring, allow them to override
|
||||
if err != nil && (strings.Contains(err.Error(), "The specified item could not be found in the keyring") || strings.Contains(err.Error(), "unable to get key from Keyring") || strings.Contains(err.Error(), "GetUserCredsFromKeyRing")) {
|
||||
log.Debug(err)
|
||||
log.Debug().Err(err)
|
||||
} else if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
@ -67,7 +66,7 @@ var loginCmd = &cobra.Command{
|
||||
|
||||
//override domain
|
||||
domainQuery := true
|
||||
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
|
||||
if config.INFISICAL_URL_MANUAL_OVERRIDE != "" && config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
|
||||
overrideDomain, err := DomainOverridePrompt()
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
@ -97,8 +96,8 @@ var loginCmd = &cobra.Command{
|
||||
|
||||
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
|
||||
if err != nil {
|
||||
log.Infoln("Unable to authenticate with the provided credentials, please try again")
|
||||
log.Debugln(err)
|
||||
fmt.Println("Unable to authenticate with the provided credentials, please try again")
|
||||
log.Debug().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -152,7 +151,7 @@ var loginCmd = &cobra.Command{
|
||||
var decryptedPrivateKey []byte
|
||||
|
||||
if loginTwoResponse.EncryptionVersion == 1 {
|
||||
log.Debug("Login version 1")
|
||||
log.Debug().Msg("Login version 1")
|
||||
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
||||
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
||||
if err != nil {
|
||||
@ -175,7 +174,7 @@ var loginCmd = &cobra.Command{
|
||||
decryptedPrivateKey = computedDecryptedPrivateKey
|
||||
|
||||
} else if loginTwoResponse.EncryptionVersion == 2 {
|
||||
log.Debug("Login version 2")
|
||||
log.Debug().Msg("Login version 2")
|
||||
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
@ -239,7 +238,7 @@ var loginCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" {
|
||||
log.Debugf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
||||
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
||||
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
||||
}
|
||||
|
||||
@ -252,9 +251,9 @@ var loginCmd = &cobra.Command{
|
||||
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
|
||||
if err != nil {
|
||||
currentVault, _ := util.GetCurrentVaultBackend()
|
||||
log.Errorf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
||||
log.Errorln("To trouble shoot further, read https://infisical.com/docs/cli/faq")
|
||||
log.Debugln(err)
|
||||
log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
||||
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
||||
log.Debug().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -397,7 +396,7 @@ func askForLoginCredentials() (email string, password string, err error) {
|
||||
}
|
||||
|
||||
func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2Response, *api.GetLoginTwoV2Response, error) {
|
||||
log.Debugln("getFreshUserCredentials:", "email", email, "password", password)
|
||||
log.Debug().Msg(fmt.Sprint("getFreshUserCredentials: ", "email", email, "password: ", password))
|
||||
httpClient := resty.New()
|
||||
httpClient.SetRetryCount(5)
|
||||
|
||||
|
@ -12,13 +12,10 @@ import (
|
||||
|
||||
var resetCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "Used delete all Infisical related data on your machine",
|
||||
Short: "Used to delete all Infisical related data on your machine",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical reset",
|
||||
Args: cobra.NoArgs,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
toggleDebug(cmd, args)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// delete config
|
||||
_, pathToDir, err := util.GetFullConfigFilePath()
|
||||
|
@ -5,7 +5,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/config"
|
||||
@ -30,11 +33,13 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
|
||||
cobra.OnInitialize(initLog)
|
||||
rootCmd.PersistentFlags().StringP("log-level", "l", "info", "log level (trace, debug, info, warn, error, fatal)")
|
||||
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: INFISICAL_API_URL]")
|
||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
util.CheckForUpdate()
|
||||
if !util.IsRunningInDocker() {
|
||||
util.CheckForUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
|
||||
@ -44,5 +49,28 @@ func init() {
|
||||
config.INFISICAL_URL = envInfisicalBackendUrl
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func initLog() {
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
ll, err := rootCmd.Flags().GetString("log-level")
|
||||
if err != nil {
|
||||
log.Fatal().Msg(err.Error())
|
||||
}
|
||||
switch strings.ToLower(ll) {
|
||||
case "trace":
|
||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||
case "debug":
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
case "info":
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
case "warn":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "err", "error":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "fatal":
|
||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||
default:
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/fatih/color"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -28,7 +28,6 @@ var runCmd = &cobra.Command{
|
||||
Use: "run [any infisical run command flags] -- [your application start command]",
|
||||
Short: "Used to inject environments variables into your application process",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
// Check if the --command flag has been set
|
||||
commandFlagSet := cmd.Flags().Changed("command")
|
||||
@ -124,7 +123,7 @@ var runCmd = &cobra.Command{
|
||||
env = append(env, s)
|
||||
}
|
||||
|
||||
log.Debugf("injecting the following environment variables into shell: %v", env)
|
||||
log.Debug().Msgf("injecting the following environment variables into shell: %v", env)
|
||||
|
||||
if cmd.Flags().Changed("command") {
|
||||
command := cmd.Flag("command").Value.String()
|
||||
@ -217,7 +216,7 @@ func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env []
|
||||
cmd.Env = env
|
||||
|
||||
color.Green("Injecting %v Infisical secrets into your application process", secretsCount)
|
||||
log.Debugf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand)
|
||||
log.Debug().Msgf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand)
|
||||
|
||||
return execCmd(cmd)
|
||||
}
|
||||
|
413
cli/packages/cmd/scan.go
Normal file
413
cli/packages/cmd/scan.go
Normal file
@ -0,0 +1,413 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
"github.com/Infisical/infisical-merge/detect"
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const configDescription = `config file path
|
||||
order of precedence:
|
||||
1. --config flag
|
||||
2. env var INFISICAL_SCAN_CONFIG
|
||||
3. (--source/-s)/.infisical-scan.toml
|
||||
If none of the three options are used, then Infisical will use the default scan config`
|
||||
|
||||
func init() {
|
||||
// scan flag for only scan command
|
||||
scanCmd.Flags().String("log-opts", "", "git log options")
|
||||
scanCmd.Flags().Bool("no-git", false, "treat git repo as a regular directory and scan those files, --log-opts has no effect on the scan when --no-git is set")
|
||||
scanCmd.Flags().Bool("pipe", false, "scan input from stdin, ex: `cat some_file | infisical scan --pipe`")
|
||||
scanCmd.Flags().Bool("follow-symlinks", false, "scan files that are symlinks to other files")
|
||||
|
||||
// global scan flags
|
||||
scanCmd.PersistentFlags().StringP("config", "c", "", configDescription)
|
||||
scanCmd.PersistentFlags().Int("exit-code", 1, "exit code when leaks have been encountered")
|
||||
scanCmd.PersistentFlags().StringP("source", "s", ".", "path to source")
|
||||
scanCmd.PersistentFlags().StringP("report-path", "r", "", "report file")
|
||||
scanCmd.PersistentFlags().StringP("report-format", "f", "json", "output format (json, csv, sarif)")
|
||||
scanCmd.PersistentFlags().StringP("baseline-path", "b", "", "path to baseline with issues that can be ignored")
|
||||
scanCmd.PersistentFlags().BoolP("verbose", "v", false, "show verbose output from scan (which file, where in the file, what secret)")
|
||||
scanCmd.PersistentFlags().BoolP("no-color", "", false, "turn off color for verbose output")
|
||||
scanCmd.PersistentFlags().Int("max-target-megabytes", 0, "files larger than this will be skipped")
|
||||
scanCmd.PersistentFlags().Bool("redact", false, "redact secrets from logs and stdout")
|
||||
|
||||
// scan git changes command flags
|
||||
scanGitChangesCmd.Flags().Bool("staged", false, "detect secrets in a --staged state")
|
||||
scanGitChangesCmd.Flags().String("log-opts", "", "git log options")
|
||||
|
||||
// find config source
|
||||
err := viper.BindPFlag("config", scanCmd.PersistentFlags().Lookup("config"))
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("err binding config %s", err.Error())
|
||||
}
|
||||
|
||||
// add flags to main
|
||||
scanCmd.AddCommand(scanGitChangesCmd)
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
}
|
||||
|
||||
func initScanConfig(cmd *cobra.Command) {
|
||||
cfgPath, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
log.Fatal().Msg(err.Error())
|
||||
}
|
||||
|
||||
if cfgPath != "" {
|
||||
viper.SetConfigFile(cfgPath)
|
||||
log.Debug().Msgf("using scan config %s from `--config`", cfgPath)
|
||||
} else if os.Getenv(config.DefaultScanConfigEnvName) != "" {
|
||||
envPath := os.Getenv(config.DefaultScanConfigEnvName)
|
||||
viper.SetConfigFile(envPath)
|
||||
log.Debug().Msgf("using scan config from %s env var: %s", config.DefaultScanConfigEnvName, envPath)
|
||||
} else {
|
||||
source, err := cmd.Flags().GetString("source")
|
||||
if err != nil {
|
||||
log.Fatal().Msg(err.Error())
|
||||
}
|
||||
fileInfo, err := os.Stat(source)
|
||||
if err != nil {
|
||||
log.Fatal().Msg(err.Error())
|
||||
}
|
||||
|
||||
if !fileInfo.IsDir() {
|
||||
log.Debug().Msgf("unable to load scan config from %s since --source=%s is a file, using default config",
|
||||
filepath.Join(source, config.DefaultScanConfigFileName), source)
|
||||
viper.SetConfigType("toml")
|
||||
if err = viper.ReadConfig(strings.NewReader(config.DefaultConfig)); err != nil {
|
||||
log.Fatal().Msgf("err reading toml %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(source, config.DefaultScanConfigFileName)); os.IsNotExist(err) {
|
||||
log.Debug().Msgf("no scan config found in path %s, using default scan config", filepath.Join(source, config.DefaultScanConfigFileName))
|
||||
viper.SetConfigType("toml")
|
||||
if err = viper.ReadConfig(strings.NewReader(config.DefaultConfig)); err != nil {
|
||||
log.Fatal().Msgf("err reading default scan config toml %s", err.Error())
|
||||
}
|
||||
return
|
||||
} else {
|
||||
log.Debug().Msgf("using existing scan config %s from `(--source)/%s`", filepath.Join(source, config.DefaultScanConfigFileName), config.DefaultScanConfigFileName)
|
||||
}
|
||||
|
||||
viper.AddConfigPath(source)
|
||||
viper.SetConfigName(config.DefaultScanConfigFileName)
|
||||
viper.SetConfigType("toml")
|
||||
}
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Fatal().Msgf("unable to load scan config, err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan for leaked secrets in git history, directories, and files",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
initScanConfig(cmd)
|
||||
|
||||
var (
|
||||
vc config.ViperConfig
|
||||
findings []report.Finding
|
||||
err error
|
||||
)
|
||||
|
||||
// Load config
|
||||
if err = viper.Unmarshal(&vc); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to load config")
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to load config")
|
||||
}
|
||||
cfg.Path, _ = cmd.Flags().GetString("config")
|
||||
|
||||
// start timer
|
||||
start := time.Now()
|
||||
|
||||
// Setup detector
|
||||
detector := detect.NewDetector(cfg)
|
||||
detector.Config.Path, err = cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
source, err := cmd.Flags().GetString("source")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// if config path is not set, then use the {source}/.infisical-scan.toml path.
|
||||
// note that there may not be a `{source}/.infisical-scan.toml` file, this is ok.
|
||||
if detector.Config.Path == "" {
|
||||
detector.Config.Path = filepath.Join(source, config.DefaultScanConfigFileName)
|
||||
}
|
||||
// set verbose flag
|
||||
if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// set redact flag
|
||||
if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// set color flag
|
||||
if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
if fileExists(filepath.Join(source, config.DefaultInfisicalIgnoreFineName)) {
|
||||
if err = detector.AddGitleaksIgnore(filepath.Join(source, config.DefaultInfisicalIgnoreFineName)); err != nil {
|
||||
log.Fatal().Err(err).Msg("could not call AddInfisicalIgnore")
|
||||
}
|
||||
}
|
||||
|
||||
// ignore findings from the baseline (an existing report in json format generated earlier)
|
||||
baselinePath, _ := cmd.Flags().GetString("baseline-path")
|
||||
if baselinePath != "" {
|
||||
err = detector.AddBaseline(baselinePath, source)
|
||||
if err != nil {
|
||||
log.Error().Msgf("Could not load baseline. The path must point to report generated by `infisical scan` using the default format: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// set follow symlinks flag
|
||||
if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
// set exit code
|
||||
exitCode, err := cmd.Flags().GetInt("exit-code")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("could not get exit code")
|
||||
}
|
||||
|
||||
// determine what type of scan:
|
||||
// - git: scan the history of the repo
|
||||
// - no-git: scan files by treating the repo as a plain directory
|
||||
noGit, err := cmd.Flags().GetBool("no-git")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("could not call GetBool() for no-git")
|
||||
}
|
||||
fromPipe, err := cmd.Flags().GetBool("pipe")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
|
||||
log.Info().Msgf("scanning for exposed secrets...")
|
||||
|
||||
// start the detector scan
|
||||
if noGit {
|
||||
findings, err = detector.DetectFiles(source)
|
||||
if err != nil {
|
||||
// don't exit on error, just log it
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
} else if fromPipe {
|
||||
findings, err = detector.DetectReader(os.Stdin, 10)
|
||||
if err != nil {
|
||||
// log fatal to exit, no need to continue since a report
|
||||
// will not be generated when scanning from a pipe...for now
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
} else {
|
||||
var logOpts string
|
||||
logOpts, err = cmd.Flags().GetString("log-opts")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
findings, err = detector.DetectGit(source, logOpts, detect.DetectType)
|
||||
if err != nil {
|
||||
// don't exit on error, just log it
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
}
|
||||
|
||||
// log info about the scan
|
||||
if err == nil {
|
||||
log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start)))
|
||||
if len(findings) != 0 {
|
||||
log.Warn().Msgf("leaks found: %d", len(findings))
|
||||
} else {
|
||||
log.Info().Msg("no leaks found")
|
||||
}
|
||||
} else {
|
||||
log.Warn().Msgf("partial scan completed in %s", FormatDuration(time.Since(start)))
|
||||
if len(findings) != 0 {
|
||||
log.Warn().Msgf("%d leaks found in partial scan", len(findings))
|
||||
} else {
|
||||
log.Warn().Msg("no leaks found in partial scan")
|
||||
}
|
||||
}
|
||||
|
||||
// write report if desired
|
||||
reportPath, _ := cmd.Flags().GetString("report-path")
|
||||
ext, _ := cmd.Flags().GetString("report-format")
|
||||
if reportPath != "" {
|
||||
if err := report.Write(findings, cfg, ext, reportPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("could not write")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(findings) != 0 {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var scanGitChangesCmd = &cobra.Command{
|
||||
Use: "git-changes",
|
||||
Short: "Scan for secrets in uncommitted changes in a git repo",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
initScanConfig(cmd)
|
||||
|
||||
var vc config.ViperConfig
|
||||
|
||||
if err := viper.Unmarshal(&vc); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to load config")
|
||||
}
|
||||
cfg, err := vc.Translate()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to load config")
|
||||
}
|
||||
|
||||
cfg.Path, _ = cmd.Flags().GetString("config")
|
||||
exitCode, _ := cmd.Flags().GetInt("exit-code")
|
||||
staged, _ := cmd.Flags().GetBool("staged")
|
||||
start := time.Now()
|
||||
|
||||
// Setup detector
|
||||
detector := detect.NewDetector(cfg)
|
||||
detector.Config.Path, err = cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
source, err := cmd.Flags().GetString("source")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// if config path is not set, then use the {source}/.infisical-scan.toml path.
|
||||
// note that there may not be a `{source}/.infisical-scan.toml` file, this is ok.
|
||||
if detector.Config.Path == "" {
|
||||
detector.Config.Path = filepath.Join(source, config.DefaultScanConfigFileName)
|
||||
}
|
||||
// set verbose flag
|
||||
if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// set redact flag
|
||||
if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// set color flag
|
||||
if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
if fileExists(filepath.Join(source, config.DefaultInfisicalIgnoreFineName)) {
|
||||
if err = detector.AddGitleaksIgnore(filepath.Join(source, config.DefaultInfisicalIgnoreFineName)); err != nil {
|
||||
log.Fatal().Err(err).Msg("could not call AddInfisicalIgnore")
|
||||
}
|
||||
}
|
||||
|
||||
// get log options for git scan
|
||||
logOpts, err := cmd.Flags().GetString("log-opts")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
log.Info().Msgf("scanning for exposed secrets...")
|
||||
|
||||
// start git scan
|
||||
var findings []report.Finding
|
||||
if staged {
|
||||
findings, err = detector.DetectGit(source, logOpts, detect.ProtectStagedType)
|
||||
} else {
|
||||
findings, err = detector.DetectGit(source, logOpts, detect.ProtectType)
|
||||
}
|
||||
if err != nil {
|
||||
// don't exit on error, just log it
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
// log info about the scan
|
||||
log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start)))
|
||||
if len(findings) != 0 {
|
||||
log.Warn().Msgf("leaks found: %d", len(findings))
|
||||
} else {
|
||||
log.Info().Msg("no leaks found")
|
||||
}
|
||||
|
||||
reportPath, _ := cmd.Flags().GetString("report-path")
|
||||
ext, _ := cmd.Flags().GetString("report-format")
|
||||
if reportPath != "" {
|
||||
if err = report.Write(findings, cfg, ext, reportPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
}
|
||||
if len(findings) != 0 {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func fileExists(fileName string) bool {
|
||||
// check for a .infisicalignore file
|
||||
info, err := os.Stat(fileName)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
if info != nil && err == nil {
|
||||
if !info.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FormatDuration(d time.Duration) string {
|
||||
scale := 100 * time.Second
|
||||
// look for the max scale that is smaller than d
|
||||
for scale > d {
|
||||
scale = scale / 10
|
||||
}
|
||||
return d.Round(scale / 100).String()
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/Infisical/infisical-merge/packages/visualize"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -28,7 +28,6 @@ var secretsCmd = &cobra.Command{
|
||||
Short: "Used to create, read update and delete secrets",
|
||||
Use: "secrets",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
environmentName, _ := cmd.Flags().GetString("env")
|
||||
@ -73,7 +72,6 @@ var secretsGetCmd = &cobra.Command{
|
||||
Use: "get [secrets]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRun: toggleDebug,
|
||||
Run: getSecretsByNames,
|
||||
}
|
||||
|
||||
@ -83,7 +81,6 @@ var secretsGenerateExampleEnvCmd = &cobra.Command{
|
||||
Use: "generate-example-env",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.NoArgs,
|
||||
PreRun: toggleDebug,
|
||||
Run: generateExampleEnv,
|
||||
}
|
||||
|
||||
@ -92,7 +89,6 @@ var secretsSetCmd = &cobra.Command{
|
||||
Short: "Used set secrets",
|
||||
Use: "set [secrets]",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLocalWorkspaceFile()
|
||||
@ -134,7 +130,7 @@ var secretsSetCmd = &cobra.Command{
|
||||
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
||||
|
||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||
log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
util.PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||
}
|
||||
|
||||
@ -278,7 +274,6 @@ var secretsDeleteCmd = &cobra.Command{
|
||||
Short: "Used to delete secrets by name",
|
||||
Use: "delete [secrets]",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
environmentName, _ := cmd.Flags().GetString("env")
|
||||
|
@ -4,9 +4,11 @@ Copyright (c) 2023 Infisical Inc.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -15,25 +17,24 @@ var vaultSetCmd = &cobra.Command{
|
||||
Use: "set [vault-name]",
|
||||
Short: "Used to set the vault backend to store your login details securely at rest",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
wantedVaultTypeName := args[0]
|
||||
currentVaultBackend, err := util.GetCurrentVaultBackend()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||
log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if wantedVaultTypeName == string(currentVaultBackend) {
|
||||
log.Errorf("You are already on vault backend [%s]", currentVaultBackend)
|
||||
log.Error().Msgf("You are already on vault backend [%s]", currentVaultBackend)
|
||||
return
|
||||
}
|
||||
|
||||
if isVaultToSwitchToValid(wantedVaultTypeName) {
|
||||
configFile, err := util.GetConfigFile()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||
log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -42,13 +43,13 @@ var vaultSetCmd = &cobra.Command{
|
||||
|
||||
err = util.WriteConfigFile(&configFile)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]", wantedVaultTypeName, err)
|
||||
log.Error().Msgf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]", wantedVaultTypeName, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Successfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName)
|
||||
fmt.Printf("\nSuccessfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName)
|
||||
} else {
|
||||
log.Errorf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends())
|
||||
log.Error().Msgf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends())
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -58,7 +59,6 @@ var vaultCmd = &cobra.Command{
|
||||
Use: "vault",
|
||||
Short: "Used to manage where your Infisical login token is saved on your machine",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
printAvailableVaultBackends()
|
||||
@ -66,17 +66,17 @@ var vaultCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func printAvailableVaultBackends() {
|
||||
log.Infof("The following vaults are available on your system:")
|
||||
fmt.Printf("The following vaults are available on your system:")
|
||||
for _, backend := range keyring.AvailableBackends() {
|
||||
log.Infof("- %s", backend)
|
||||
fmt.Printf("\n- %s", backend)
|
||||
}
|
||||
|
||||
currentVaultBackend, err := util.GetCurrentVaultBackend()
|
||||
if err != nil {
|
||||
log.Errorf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err)
|
||||
log.Error().Msgf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err)
|
||||
}
|
||||
|
||||
log.Infof("\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend))
|
||||
fmt.Printf("\n\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend))
|
||||
}
|
||||
|
||||
// Checks if the vault that the user wants to switch to is a valid available vault
|
||||
|
@ -1,17 +0,0 @@
|
||||
package models
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
// Custom error type so that we can give helpful messages in CLI
|
||||
type Error struct {
|
||||
Err error
|
||||
FriendlyMessage string
|
||||
}
|
||||
|
||||
func (e *Error) printFriendlyMessage() {
|
||||
log.Infoln(e.FriendlyMessage)
|
||||
}
|
||||
|
||||
func (e *Error) printDebuError() {
|
||||
log.Debugln(e.Err)
|
||||
}
|
@ -5,15 +5,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func CheckForUpdate() {
|
||||
@ -22,7 +22,7 @@ func CheckForUpdate() {
|
||||
}
|
||||
latestVersion, err := getLatestTag("Infisical", "infisical")
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
log.Debug().Err(err)
|
||||
// do nothing and continue
|
||||
return
|
||||
}
|
||||
@ -51,7 +51,7 @@ func CheckForUpdate() {
|
||||
}
|
||||
|
||||
func getLatestTag(repoOwner string, repoName string) (string, error) {
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", repoOwner, repoName)
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -67,29 +67,18 @@ func getLatestTag(repoOwner string, repoName string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var tags []struct {
|
||||
Name string `json:"name"`
|
||||
var releaseTag struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &tags); err != nil {
|
||||
if err := json.Unmarshal(body, &releaseTag); err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal github response: %w", err)
|
||||
}
|
||||
|
||||
// Filter tags with prefix "infisical-cli/"
|
||||
prefix := "infisical-cli/v"
|
||||
var validTags []string
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(tag.Name, prefix) {
|
||||
validTags = append(validTags, tag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validTags) == 0 {
|
||||
return "", errors.New("no tags for the CLI is found")
|
||||
}
|
||||
tag_prefix := "infisical-cli/v"
|
||||
|
||||
// Extract the version from the first valid tag
|
||||
version := strings.TrimPrefix(validTags[0], prefix)
|
||||
version := strings.TrimPrefix(releaseTag.TagName, tag_prefix)
|
||||
|
||||
return version, nil
|
||||
}
|
||||
@ -143,3 +132,16 @@ func getLinuxPackageManager() string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsRunningInDocker() bool {
|
||||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
cgroup, err := ioutil.ReadFile("/proc/self/cgroup")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(string(cgroup), "docker")
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/config"
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func WriteInitalConfig(userCredentials *models.UserCredentials) error {
|
||||
@ -73,7 +73,7 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
|
||||
func ConfigFileExists() bool {
|
||||
fullConfigFileURI, _, err := GetFullConfigFilePath()
|
||||
if err != nil {
|
||||
log.Debugln("There was an error when creating the full path to config file", err)
|
||||
log.Debug().Err(err).Msgf("There was an error when creating the full path to config file")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ func WorkspaceConfigFileExistsInCurrentPath() bool {
|
||||
if _, err := os.Stat(INFISICAL_WORKSPACE_CONFIG_FILE_NAME); err == nil {
|
||||
return true
|
||||
} else {
|
||||
log.Debugln(err)
|
||||
log.Debug().Err(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@ func FindWorkspaceConfigFile() (string, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
// file found
|
||||
log.Debugf("FindWorkspaceConfigFile: workspace file found at [path=%s]", path)
|
||||
log.Debug().Msgf("FindWorkspaceConfigFile: workspace file found at [path=%s]", path)
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
@ -12,9 +12,8 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/api"
|
||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) {
|
||||
@ -97,7 +96,7 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work
|
||||
}
|
||||
|
||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||
log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||
}
|
||||
|
||||
@ -136,12 +135,12 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
||||
|
||||
if infisicalToken == "" {
|
||||
if isConnected {
|
||||
log.Debug("GetAllEnvironmentVariables: Connected to internet, checking logged in creds")
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds")
|
||||
RequireLocalWorkspaceFile()
|
||||
RequireLogin()
|
||||
}
|
||||
|
||||
log.Debug("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details")
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details")
|
||||
|
||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
@ -164,7 +163,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
||||
}
|
||||
|
||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs)
|
||||
log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
||||
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
||||
|
||||
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
|
||||
if errorToReturn == nil {
|
||||
@ -182,7 +181,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Debug("Trying to fetch secrets using service token")
|
||||
log.Debug().Msg("Trying to fetch secrets using service token")
|
||||
secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
|
||||
|
||||
// if serviceTokenDetails.Environment != params.Environment {
|
||||
@ -515,7 +514,7 @@ func DeleteBackupSecrets() error {
|
||||
func GetEnvFromWorkspaceFile() string {
|
||||
workspaceFile, err := GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
log.Debugf("getEnvFromWorkspaceFile: [err=%s]", err)
|
||||
log.Debug().Msgf("getEnvFromWorkspaceFile: [err=%s]", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -529,17 +528,17 @@ func GetEnvFromWorkspaceFile() string {
|
||||
func GetEnvelopmentBasedOnGitBranch(workspaceFile models.WorkspaceConfigFile) string {
|
||||
branch, err := getCurrentBranch()
|
||||
if err != nil {
|
||||
log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
|
||||
log.Debug().Msgf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
|
||||
}
|
||||
|
||||
envBasedOnGitBranch, ok := workspaceFile.GitBranchToEnvironmentMapping[branch]
|
||||
|
||||
log.Debugf("GetEnvelopmentBasedOnGitBranch: [envBasedOnGitBranch=%s] [ok=%t]", envBasedOnGitBranch, ok)
|
||||
log.Debug().Msgf("GetEnvelopmentBasedOnGitBranch: [envBasedOnGitBranch=%s] [ok=%t]", envBasedOnGitBranch, ok)
|
||||
|
||||
if err == nil && ok {
|
||||
return envBasedOnGitBranch
|
||||
} else {
|
||||
log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
|
||||
log.Debug().Msgf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/muesli/ansi"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
@ -39,9 +39,9 @@ func Table(headers [3]string, rows [][3]string) {
|
||||
width, _, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil {
|
||||
if shouldTruncate {
|
||||
log.Errorf("error getting terminal size: %s", err)
|
||||
log.Error().Msgf("error getting terminal size: %s", err)
|
||||
} else {
|
||||
log.Debug(err)
|
||||
log.Debug().Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
25
cli/report/constants.go
Normal file
25
cli/report/constants.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 report
|
||||
|
||||
const version = "v8.0.0"
|
||||
const driver = "gitleaks"
|
81
cli/report/csv.go
Normal file
81
cli/report/csv.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// writeCsv writes the list of findings to a writeCloser.
|
||||
func writeCsv(f []Finding, w io.WriteCloser) error {
|
||||
if len(f) == 0 {
|
||||
return nil
|
||||
}
|
||||
defer w.Close()
|
||||
cw := csv.NewWriter(w)
|
||||
err := cw.Write([]string{"RuleID",
|
||||
"Commit",
|
||||
"File",
|
||||
"SymlinkFile",
|
||||
"Secret",
|
||||
"Match",
|
||||
"StartLine",
|
||||
"EndLine",
|
||||
"StartColumn",
|
||||
"EndColumn",
|
||||
"Author",
|
||||
"Message",
|
||||
"Date",
|
||||
"Email",
|
||||
"Fingerprint",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range f {
|
||||
err = cw.Write([]string{f.RuleID,
|
||||
f.Commit,
|
||||
f.File,
|
||||
f.SymlinkFile,
|
||||
f.Secret,
|
||||
f.Match,
|
||||
strconv.Itoa(f.StartLine),
|
||||
strconv.Itoa(f.EndLine),
|
||||
strconv.Itoa(f.StartColumn),
|
||||
strconv.Itoa(f.EndColumn),
|
||||
f.Author,
|
||||
f.Message,
|
||||
f.Date,
|
||||
f.Email,
|
||||
f.Fingerprint,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cw.Flush()
|
||||
return cw.Error()
|
||||
}
|
108
cli/report/csv_test.go
Normal file
108
cli/report/csv_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteCSV(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings []Finding
|
||||
testReportName string
|
||||
expected string
|
||||
wantEmpty bool
|
||||
}{
|
||||
{
|
||||
testReportName: "simple",
|
||||
expected: filepath.Join(expectPath, "report", "csv_simple.csv"),
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
Match: "line containing secret",
|
||||
Secret: "a secret",
|
||||
StartLine: 1,
|
||||
EndLine: 2,
|
||||
StartColumn: 1,
|
||||
EndColumn: 2,
|
||||
Message: "opps",
|
||||
File: "auth.py",
|
||||
SymlinkFile: "",
|
||||
Commit: "0000000000000000",
|
||||
Author: "John Doe",
|
||||
Email: "johndoe@gmail.com",
|
||||
Date: "10-19-2003",
|
||||
Fingerprint: "fingerprint",
|
||||
},
|
||||
}},
|
||||
{
|
||||
|
||||
wantEmpty: true,
|
||||
testReportName: "empty",
|
||||
expected: filepath.Join(expectPath, "report", "this_should_not_exist.csv"),
|
||||
findings: []Finding{}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".csv"))
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
err = writeCsv(test.findings, tmpfile)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
got, err := os.ReadFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
if test.wantEmpty {
|
||||
if len(got) > 0 {
|
||||
t.Errorf("Expected empty file, got %s", got)
|
||||
}
|
||||
os.Remove(tmpfile.Name())
|
||||
continue
|
||||
}
|
||||
want, err := os.ReadFile(test.expected)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if string(got) != string(want) {
|
||||
err = os.WriteFile(strings.Replace(test.expected, ".csv", ".got.csv", 1), got, 0644)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Errorf("got %s, want %s", string(got), string(want))
|
||||
}
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
}
|
||||
}
|
72
cli/report/finding.go
Normal file
72
cli/report/finding.go
Normal file
@ -0,0 +1,72 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Finding contains information about strings that
|
||||
// have been captured by a tree-sitter query.
|
||||
type Finding struct {
|
||||
Description string
|
||||
StartLine int
|
||||
EndLine int
|
||||
StartColumn int
|
||||
EndColumn int
|
||||
|
||||
Line string `json:"-"`
|
||||
|
||||
Match string
|
||||
|
||||
// Secret contains the full content of what is matched in
|
||||
// the tree-sitter query.
|
||||
Secret string
|
||||
|
||||
// File is the name of the file containing the finding
|
||||
File string
|
||||
SymlinkFile string
|
||||
Commit string
|
||||
|
||||
// Entropy is the shannon entropy of Value
|
||||
Entropy float32
|
||||
|
||||
Author string
|
||||
Email string
|
||||
Date string
|
||||
Message string
|
||||
Tags []string
|
||||
|
||||
// Rule is the name of the rule that was matched
|
||||
RuleID string
|
||||
|
||||
// unique identifer
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
// Redact removes sensitive information from a finding.
|
||||
func (f *Finding) Redact() {
|
||||
f.Line = strings.Replace(f.Line, f.Secret, "REDACTED", -1)
|
||||
f.Match = strings.Replace(f.Match, f.Secret, "REDACTED", -1)
|
||||
f.Secret = "REDACTED"
|
||||
}
|
48
cli/report/finding_test.go
Normal file
48
cli/report/finding_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 report
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRedact(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings []Finding
|
||||
redact bool
|
||||
}{
|
||||
{
|
||||
redact: true,
|
||||
findings: []Finding{
|
||||
{
|
||||
Secret: "line containing secret",
|
||||
Match: "secret",
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
for _, f := range test.findings {
|
||||
f.Redact()
|
||||
if f.Secret != "REDACTED" {
|
||||
t.Error("redact not redacting: ", f.Secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
cli/report/json.go
Normal file
37
cli/report/json.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
func writeJson(findings []Finding, w io.WriteCloser) error {
|
||||
if len(findings) == 0 {
|
||||
findings = []Finding{}
|
||||
}
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(findings)
|
||||
}
|
111
cli/report/json_test.go
Normal file
111
cli/report/json_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings []Finding
|
||||
testReportName string
|
||||
expected string
|
||||
wantEmpty bool
|
||||
}{
|
||||
{
|
||||
testReportName: "simple",
|
||||
expected: filepath.Join(expectPath, "report", "json_simple.json"),
|
||||
findings: []Finding{
|
||||
{
|
||||
|
||||
Description: "",
|
||||
RuleID: "test-rule",
|
||||
Match: "line containing secret",
|
||||
Secret: "a secret",
|
||||
StartLine: 1,
|
||||
EndLine: 2,
|
||||
StartColumn: 1,
|
||||
EndColumn: 2,
|
||||
Message: "opps",
|
||||
File: "auth.py",
|
||||
SymlinkFile: "",
|
||||
Commit: "0000000000000000",
|
||||
Author: "John Doe",
|
||||
Email: "johndoe@gmail.com",
|
||||
Date: "10-19-2003",
|
||||
Tags: []string{},
|
||||
},
|
||||
}},
|
||||
{
|
||||
|
||||
testReportName: "empty",
|
||||
expected: filepath.Join(expectPath, "report", "empty.json"),
|
||||
findings: []Finding{}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// create tmp file using os.TempDir()
|
||||
tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".json"))
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
err = writeJson(test.findings, tmpfile)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
got, err := os.ReadFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
if test.wantEmpty {
|
||||
if len(got) > 0 {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Errorf("Expected empty file, got %s", got)
|
||||
}
|
||||
os.Remove(tmpfile.Name())
|
||||
continue
|
||||
}
|
||||
want, err := os.ReadFile(test.expected)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if string(got) != string(want) {
|
||||
err = os.WriteFile(strings.Replace(test.expected, ".json", ".got.json", 1), got, 0644)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Errorf("got %s, want %s", string(got), string(want))
|
||||
}
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
}
|
||||
}
|
54
cli/report/report.go
Normal file
54
cli/report/report.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://cwe.mitre.org/data/definitions/798.html
|
||||
CWE = "CWE-798"
|
||||
CWE_DESCRIPTION = "Use of Hard-coded Credentials"
|
||||
)
|
||||
|
||||
func Write(findings []Finding, cfg config.Config, ext string, reportPath string) error {
|
||||
file, err := os.Create(reportPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ext = strings.ToLower(ext)
|
||||
switch ext {
|
||||
case ".json", "json":
|
||||
err = writeJson(findings, file)
|
||||
case ".csv", "csv":
|
||||
err = writeCsv(findings, file)
|
||||
case ".sarif", "sarif":
|
||||
err = writeSarif(cfg, findings, file)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
133
cli/report/report_test.go
Normal file
133
cli/report/report_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
)
|
||||
|
||||
const (
|
||||
expectPath = "../testdata/expected/"
|
||||
tmpPath = "../testdata/tmp"
|
||||
)
|
||||
|
||||
func TestReport(t *testing.T) {
|
||||
tests := []struct {
|
||||
findings []Finding
|
||||
ext string
|
||||
wantEmpty bool
|
||||
}{
|
||||
{
|
||||
ext: "json",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ext: ".json",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ext: ".jsonj",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
wantEmpty: true,
|
||||
},
|
||||
{
|
||||
ext: ".csv",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ext: "csv",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ext: "CSV",
|
||||
findings: []Finding{
|
||||
{
|
||||
RuleID: "test-rule",
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// ext: "SARIF",
|
||||
// findings: []Finding{
|
||||
// {
|
||||
// RuleID: "test-rule",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
tmpfile, err := os.Create(filepath.Join(tmpPath, strconv.Itoa(i)+test.ext))
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
err = Write(test.findings, config.Config{}, test.ext, tmpfile.Name())
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
got, err := os.ReadFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
t.Error(err)
|
||||
}
|
||||
os.Remove(tmpfile.Name())
|
||||
|
||||
if len(got) == 0 && !test.wantEmpty {
|
||||
t.Errorf("got empty file with extension " + test.ext)
|
||||
}
|
||||
|
||||
if test.wantEmpty {
|
||||
if len(got) > 0 {
|
||||
t.Errorf("Expected empty file, got %s", got)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
237
cli/report/sarif.go
Normal file
237
cli/report/sarif.go
Normal file
@ -0,0 +1,237 @@
|
||||
// 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 report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Infisical/infisical-merge/config"
|
||||
)
|
||||
|
||||
func writeSarif(cfg config.Config, findings []Finding, w io.WriteCloser) error {
|
||||
sarif := Sarif{
|
||||
Schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
Version: "2.1.0",
|
||||
Runs: getRuns(cfg, findings),
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(sarif)
|
||||
}
|
||||
|
||||
func getRuns(cfg config.Config, findings []Finding) []Runs {
|
||||
return []Runs{
|
||||
{
|
||||
Tool: getTool(cfg),
|
||||
Results: getResults(findings),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTool(cfg config.Config) Tool {
|
||||
tool := Tool{
|
||||
Driver: Driver{
|
||||
Name: driver,
|
||||
SemanticVersion: version,
|
||||
InformationUri: "https://github.com/Infisical/infisical",
|
||||
Rules: getRules(cfg),
|
||||
},
|
||||
}
|
||||
|
||||
// if this tool has no rules, ensure that it is represented as [] instead of null/nil
|
||||
if hasEmptyRules(tool) {
|
||||
tool.Driver.Rules = make([]Rules, 0)
|
||||
}
|
||||
|
||||
return tool
|
||||
}
|
||||
|
||||
func hasEmptyRules(tool Tool) bool {
|
||||
return len(tool.Driver.Rules) == 0
|
||||
}
|
||||
|
||||
func getRules(cfg config.Config) []Rules {
|
||||
// TODO for _, rule := range cfg.Rules {
|
||||
var rules []Rules
|
||||
for _, rule := range cfg.OrderedRules() {
|
||||
shortDescription := ShortDescription{
|
||||
Text: rule.Description,
|
||||
}
|
||||
if rule.Regex != nil {
|
||||
shortDescription = ShortDescription{
|
||||
Text: rule.Regex.String(),
|
||||
}
|
||||
} else if rule.Path != nil {
|
||||
shortDescription = ShortDescription{
|
||||
Text: rule.Path.String(),
|
||||
}
|
||||
}
|
||||
rules = append(rules, Rules{
|
||||
ID: rule.RuleID,
|
||||
Name: rule.Description,
|
||||
Description: shortDescription,
|
||||
})
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func messageText(f Finding) string {
|
||||
if f.Commit == "" {
|
||||
return fmt.Sprintf("%s has detected secret for file %s.", f.RuleID, f.File)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s has detected secret for file %s at commit %s.", f.RuleID, f.File, f.Commit)
|
||||
|
||||
}
|
||||
|
||||
func getResults(findings []Finding) []Results {
|
||||
results := []Results{}
|
||||
for _, f := range findings {
|
||||
r := Results{
|
||||
Message: Message{
|
||||
Text: messageText(f),
|
||||
},
|
||||
RuleId: f.RuleID,
|
||||
Locations: getLocation(f),
|
||||
// This information goes in partial fingerprings until revision
|
||||
// data can be added somewhere else
|
||||
PartialFingerPrints: PartialFingerPrints{
|
||||
CommitSha: f.Commit,
|
||||
Email: f.Email,
|
||||
CommitMessage: f.Message,
|
||||
Date: f.Date,
|
||||
Author: f.Author,
|
||||
},
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func getLocation(f Finding) []Locations {
|
||||
uri := f.File
|
||||
if f.SymlinkFile != "" {
|
||||
uri = f.SymlinkFile
|
||||
}
|
||||
return []Locations{
|
||||
{
|
||||
PhysicalLocation: PhysicalLocation{
|
||||
ArtifactLocation: ArtifactLocation{
|
||||
URI: uri,
|
||||
},
|
||||
Region: Region{
|
||||
StartLine: f.StartLine,
|
||||
EndLine: f.EndLine,
|
||||
StartColumn: f.StartColumn,
|
||||
EndColumn: f.EndColumn,
|
||||
Snippet: Snippet{
|
||||
Text: f.Secret,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PartialFingerPrints struct {
|
||||
CommitSha string `json:"commitSha"`
|
||||
Email string `json:"email"`
|
||||
Author string `json:"author"`
|
||||
Date string `json:"date"`
|
||||
CommitMessage string `json:"commitMessage"`
|
||||
}
|
||||
|
||||
type Sarif struct {
|
||||
Schema string `json:"$schema"`
|
||||
Version string `json:"version"`
|
||||
Runs []Runs `json:"runs"`
|
||||
}
|
||||
|
||||
type ShortDescription struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type FullDescription struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type Rules struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description ShortDescription `json:"shortDescription"`
|
||||
}
|
||||
|
||||
type Driver struct {
|
||||
Name string `json:"name"`
|
||||
SemanticVersion string `json:"semanticVersion"`
|
||||
InformationUri string `json:"informationUri"`
|
||||
Rules []Rules `json:"rules"`
|
||||
}
|
||||
|
||||
type Tool struct {
|
||||
Driver Driver `json:"driver"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type ArtifactLocation struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
type Region struct {
|
||||
StartLine int `json:"startLine"`
|
||||
StartColumn int `json:"startColumn"`
|
||||
EndLine int `json:"endLine"`
|
||||
EndColumn int `json:"endColumn"`
|
||||
Snippet Snippet `json:"snippet"`
|
||||
}
|
||||
|
||||
type Snippet struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type PhysicalLocation struct {
|
||||
ArtifactLocation ArtifactLocation `json:"artifactLocation"`
|
||||
Region Region `json:"region"`
|
||||
}
|
||||
|
||||
type Locations struct {
|
||||
PhysicalLocation PhysicalLocation `json:"physicalLocation"`
|
||||
}
|
||||
|
||||
type Results struct {
|
||||
Message Message `json:"message"`
|
||||
RuleId string `json:"ruleId"`
|
||||
Locations []Locations `json:"locations"`
|
||||
PartialFingerPrints `json:"partialFingerprints"`
|
||||
}
|
||||
|
||||
type Runs struct {
|
||||
Tool Tool `json:"tool"`
|
||||
Results []Results `json:"results"`
|
||||
}
|
122
cli/report/sarif_test.go
Normal file
122
cli/report/sarif_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
// 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 report
|
||||
|
||||
const configPath = "../testdata/config/"
|
||||
|
||||
// func TestWriteSarif(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// findings []Finding
|
||||
// testReportName string
|
||||
// expected string
|
||||
// wantEmpty bool
|
||||
// cfgName string
|
||||
// }{
|
||||
// {
|
||||
// cfgName: "simple",
|
||||
// testReportName: "simple",
|
||||
// expected: filepath.Join(expectPath, "report", "sarif_simple.sarif"),
|
||||
// findings: []Finding{
|
||||
// {
|
||||
|
||||
// Description: "A test rule",
|
||||
// RuleID: "test-rule",
|
||||
// Match: "line containing secret",
|
||||
// Secret: "a secret",
|
||||
// StartLine: 1,
|
||||
// EndLine: 2,
|
||||
// StartColumn: 1,
|
||||
// EndColumn: 2,
|
||||
// Message: "opps",
|
||||
// File: "auth.py",
|
||||
// Commit: "0000000000000000",
|
||||
// Author: "John Doe",
|
||||
// Email: "johndoe@gmail.com",
|
||||
// Date: "10-19-2003",
|
||||
// Tags: []string{},
|
||||
// },
|
||||
// }},
|
||||
// }
|
||||
|
||||
// for _, test := range tests {
|
||||
// // create tmp file using os.TempDir()
|
||||
// tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".json"))
|
||||
// if err != nil {
|
||||
// os.Remove(tmpfile.Name())
|
||||
// t.Error(err)
|
||||
// }
|
||||
// viper.Reset()
|
||||
// viper.AddConfigPath(configPath)
|
||||
// viper.SetConfigName(test.cfgName)
|
||||
// viper.SetConfigType("toml")
|
||||
// err = viper.ReadInConfig()
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// var vc config.ViperConfig
|
||||
// err = viper.Unmarshal(&vc)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// cfg, err := vc.Translate()
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// err = writeSarif(cfg, test.findings, tmpfile)
|
||||
// fmt.Println(cfg)
|
||||
// if err != nil {
|
||||
// os.Remove(tmpfile.Name())
|
||||
// t.Error(err)
|
||||
// }
|
||||
// got, err := os.ReadFile(tmpfile.Name())
|
||||
// if err != nil {
|
||||
// os.Remove(tmpfile.Name())
|
||||
// t.Error(err)
|
||||
// }
|
||||
// if test.wantEmpty {
|
||||
// if len(got) > 0 {
|
||||
// os.Remove(tmpfile.Name())
|
||||
// t.Errorf("Expected empty file, got %s", got)
|
||||
// }
|
||||
// os.Remove(tmpfile.Name())
|
||||
// continue
|
||||
// }
|
||||
// want, err := os.ReadFile(test.expected)
|
||||
// if err != nil {
|
||||
// os.Remove(tmpfile.Name())
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// if string(got) != string(want) {
|
||||
// err = os.WriteFile(strings.Replace(test.expected, ".sarif", ".got.sarif", 1), got, 0644)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// t.Errorf("got %s, want %s", string(got), string(want))
|
||||
// }
|
||||
|
||||
// os.Remove(tmpfile.Name())
|
||||
// }
|
||||
// }
|
2
cli/testdata/baseline/baseline.csv
vendored
Normal file
2
cli/testdata/baseline/baseline.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
RuleID,Commit,File,Secret,Match,StartLine,EndLine,StartColumn,EndColumn,Author,Message,Date,Email,Fingerprint
|
||||
1,b,c,f,s,m,s,e,s,e,a,m,f,r,f
|
|
40
cli/testdata/baseline/baseline.json
vendored
Normal file
40
cli/testdata/baseline/baseline.json
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
[
|
||||
{
|
||||
"Description": "PyPI upload token",
|
||||
"StartLine": 32,
|
||||
"EndLine": 32,
|
||||
"StartColumn": 21,
|
||||
"EndColumn": 106,
|
||||
"Match": "************************",
|
||||
"Secret": "************************",
|
||||
"File": "detect/detect_test.go",
|
||||
"Commit": "9326f35380636bcbe61e94b0584d1618c4b5c2c2",
|
||||
"Entropy": 1.9606875,
|
||||
"Author": "****",
|
||||
"Email": "****",
|
||||
"Date": "2022-03-07T14:33:06Z",
|
||||
"Message": "Escape - character in regex character groups (#802)\n\n* fix char escape\n\n* add test\n\n* fix verbosity in make test",
|
||||
"Tags": [],
|
||||
"RuleID": "pypi-upload-token",
|
||||
"Fingerprint": "9326f35380636bcbe61e94b0584d1618c4b5c2c2:detect/detect_test.go:pypi-upload-token:32"
|
||||
},
|
||||
{
|
||||
"Description": "PyPI upload token",
|
||||
"StartLine": 33,
|
||||
"EndLine": 33,
|
||||
"StartColumn": 21,
|
||||
"EndColumn": 106,
|
||||
"Match": "************************",
|
||||
"Secret": "************************",
|
||||
"File": "detect/detect_test.go",
|
||||
"Commit": "9326f35380636bcbe61e94b0584d1618c4b5c2c2",
|
||||
"Entropy": 1.9606875,
|
||||
"Author": "****",
|
||||
"Email": "****",
|
||||
"Date": "2022-03-07T14:33:06Z",
|
||||
"Message": "Escape - character in regex character groups (#802)\n\n* fix char escape\n\n* add test\n\n* fix verbosity in make test",
|
||||
"Tags": [],
|
||||
"RuleID": "pypi-upload-token",
|
||||
"Fingerprint": "9326f35380636bcbe61e94b0584d1618c4b5c2c2:detect/detect_test.go:pypi-upload-token:33"
|
||||
}
|
||||
]
|
6
cli/testdata/baseline/baseline.sarif
vendored
Normal file
6
cli/testdata/baseline/baseline.sarif
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
]
|
||||
}
|
9
cli/testdata/config/allow_aws_re.toml
vendored
Normal file
9
cli/testdata/config/allow_aws_re.toml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
title = "simple config with allowlist for aws"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
||||
[rules.allowlist]
|
||||
regexes = ['''AKIALALEMEL33243OLIA''']
|
9
cli/testdata/config/allow_commit.toml
vendored
Normal file
9
cli/testdata/config/allow_commit.toml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
title = "simple config with allowlist for a specific commit"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
||||
[rules.allowlist]
|
||||
commits = ['''allowthiscommit''']
|
8
cli/testdata/config/allow_global_aws_re.toml
vendored
Normal file
8
cli/testdata/config/allow_global_aws_re.toml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
||||
|
||||
[allowlist]
|
||||
regexes = ['''AKIALALEMEL33243OLIA''']
|
9
cli/testdata/config/allow_path.toml
vendored
Normal file
9
cli/testdata/config/allow_path.toml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
title = "simple config with allowlist for .go files"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
||||
[rules.allowlist]
|
||||
paths = ['''.go''']
|
8
cli/testdata/config/bad_entropy_group.toml
vendored
Executable file
8
cli/testdata/config/bad_entropy_group.toml
vendored
Executable file
@ -0,0 +1,8 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[[rules]]
|
||||
id = "discord-api-key"
|
||||
description = "Discord API key"
|
||||
regex = '''(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]'''
|
||||
secretGroup = 5
|
||||
entropy = 3.5
|
10
cli/testdata/config/base.toml
vendored
Normal file
10
cli/testdata/config/base.toml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[extend]
|
||||
path="../testdata/config/extend_1.toml"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Secret Key"
|
||||
id = "aws-secret-key"
|
||||
regex = '''(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}'''
|
||||
tags = ["key", "AWS"]
|
8
cli/testdata/config/entropy_group.toml
vendored
Executable file
8
cli/testdata/config/entropy_group.toml
vendored
Executable file
@ -0,0 +1,8 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[[rules]]
|
||||
id = "discord-api-key"
|
||||
description = "Discord API key"
|
||||
regex = '''(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]'''
|
||||
secretGroup = 3
|
||||
entropy = 3.5
|
8
cli/testdata/config/escaped_character_group.toml
vendored
Normal file
8
cli/testdata/config/escaped_character_group.toml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
title = "gitleaks config"
|
||||
# https://learnxinyminutes.com/docs/toml/ for toml reference
|
||||
|
||||
[[rules]]
|
||||
id = "pypi-upload-token"
|
||||
description = "PyPI upload token"
|
||||
regex = '''pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}'''
|
||||
tags = ["key", "pypi"]
|
10
cli/testdata/config/extend_1.toml
vendored
Normal file
10
cli/testdata/config/extend_1.toml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
title = "gitleaks extended 1"
|
||||
|
||||
[extend]
|
||||
path="../testdata/config/extend_2.toml"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
10
cli/testdata/config/extend_2.toml
vendored
Normal file
10
cli/testdata/config/extend_2.toml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
title = "gitleaks extended 2"
|
||||
|
||||
[extend]
|
||||
path="../testdata/config/extend_3.toml"
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Secret Key"
|
||||
id = "aws-secret-key-again"
|
||||
regex = '''(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}'''
|
||||
tags = ["key", "AWS"]
|
9
cli/testdata/config/extend_3.toml
vendored
Normal file
9
cli/testdata/config/extend_3.toml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
title = "gitleaks extended 3"
|
||||
|
||||
## This should not be loaded since we can only extend configs to a depth of 3
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Secret Key"
|
||||
id = "aws-secret-key-again-again"
|
||||
regex = '''(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}'''
|
||||
tags = ["key", "AWS"]
|
8
cli/testdata/config/generic.toml
vendored
Normal file
8
cli/testdata/config/generic.toml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[[rules]]
|
||||
description = "Generic API Key"
|
||||
id = "generic-api-key"
|
||||
regex = '''(?i)((key|api|token|secret|password)[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9a-zA-Z\-_=]{8,64})['\"]'''
|
||||
entropy = 3.7
|
||||
secretGroup = 4
|
36
cli/testdata/config/generic_with_py_path.toml
vendored
Normal file
36
cli/testdata/config/generic_with_py_path.toml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[[rules]]
|
||||
description = "Generic API Key"
|
||||
id = "generic-api-key"
|
||||
regex = '''(?i)((key|api|token|secret|password)[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9a-zA-Z\-_=]{8,64})['\"]'''
|
||||
path = '''.py'''
|
||||
entropy = 3.7
|
||||
secretGroup = 4
|
||||
|
||||
[allowlist]
|
||||
description = "global allow lists"
|
||||
regexes = [
|
||||
'''219-09-9999''',
|
||||
'''078-05-1120''',
|
||||
'''(9[0-9]{2}|666)-\d{2}-\d{4}''',
|
||||
'''process''',
|
||||
'''getenv''',
|
||||
'''\.env''',
|
||||
'''env\(''',
|
||||
'''env\.''',
|
||||
'''setting''',
|
||||
'''load''',
|
||||
'''token''',
|
||||
'''password''',
|
||||
'''secret''',
|
||||
'''api\_key''',
|
||||
'''apikey''',
|
||||
'''api\-key''',
|
||||
]
|
||||
paths = [
|
||||
'''gitleaks.toml''',
|
||||
'''(.*?)(jpg|gif|doc|pdf|bin|svg|socket)$''',
|
||||
'''(go.mod|go.sum)$'''
|
||||
]
|
||||
|
6
cli/testdata/config/path_only.toml
vendored
Normal file
6
cli/testdata/config/path_only.toml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[[rules]]
|
||||
description = "Python Files"
|
||||
id = "python-files-only"
|
||||
path = '''.py'''
|
222
cli/testdata/config/simple.toml
vendored
Normal file
222
cli/testdata/config/simple.toml
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
title = "gitleaks config"
|
||||
# https://learnxinyminutes.com/docs/toml/ for toml reference
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Access Key"
|
||||
id = "aws-access-key"
|
||||
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
|
||||
tags = ["key", "AWS"]
|
||||
|
||||
[[rules]]
|
||||
description = "AWS Secret Key"
|
||||
id = "aws-secret-key"
|
||||
regex = '''(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}'''
|
||||
tags = ["key", "AWS"]
|
||||
|
||||
[[rules]]
|
||||
description = "AWS MWS key"
|
||||
id = "aws-mws-key"
|
||||
regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'''
|
||||
tags = ["key", "AWS", "MWS"]
|
||||
|
||||
[[rules]]
|
||||
description = "Facebook Secret Key"
|
||||
id = "facebook-secret-key"
|
||||
regex = '''(?i)(facebook|fb)(.{0,20})?(?-i)['\"][0-9a-f]{32}['\"]'''
|
||||
tags = ["key", "Facebook"]
|
||||
|
||||
[[rules]]
|
||||
description = "Facebook Client ID"
|
||||
id = "facebook-client-id"
|
||||
regex = '''(?i)(facebook|fb)(.{0,20})?['\"][0-9]{13,17}['\"]'''
|
||||
tags = ["key", "Facebook"]
|
||||
|
||||
[[rules]]
|
||||
description = "Twitter Secret Key"
|
||||
id = "twitter-secret-key"
|
||||
regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{35,44}['\"]'''
|
||||
tags = ["key", "Twitter"]
|
||||
|
||||
[[rules]]
|
||||
description = "Twitter Client ID"
|
||||
id = "twitter-client-id"
|
||||
regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{18,25}['\"]'''
|
||||
tags = ["client", "Twitter"]
|
||||
|
||||
[[rules]]
|
||||
description = "Github Personal Access Token"
|
||||
id = "github-pat"
|
||||
regex = '''ghp_[0-9a-zA-Z]{36}'''
|
||||
tags = ["key", "Github"]
|
||||
[[rules]]
|
||||
description = "Github OAuth Access Token"
|
||||
id = "github-oauth"
|
||||
regex = '''gho_[0-9a-zA-Z]{36}'''
|
||||
tags = ["key", "Github"]
|
||||
[[rules]]
|
||||
id = "github-app"
|
||||
description = "Github App Token"
|
||||
regex = '''(ghu|ghs)_[0-9a-zA-Z]{36}'''
|
||||
tags = ["key", "Github"]
|
||||
[[rules]]
|
||||
id = "github-refresh"
|
||||
description = "Github Refresh Token"
|
||||
regex = '''ghr_[0-9a-zA-Z]{76}'''
|
||||
tags = ["key", "Github"]
|
||||
|
||||
[[rules]]
|
||||
id = "linkedin-client"
|
||||
description = "LinkedIn Client ID"
|
||||
regex = '''(?i)linkedin(.{0,20})?(?-i)[0-9a-z]{12}'''
|
||||
tags = ["client", "LinkedIn"]
|
||||
|
||||
[[rules]]
|
||||
id = "linkedin-secret"
|
||||
description = "LinkedIn Secret Key"
|
||||
regex = '''(?i)linkedin(.{0,20})?[0-9a-z]{16}'''
|
||||
tags = ["secret", "LinkedIn"]
|
||||
|
||||
[[rules]]
|
||||
id = "slack"
|
||||
description = "Slack"
|
||||
regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?'''
|
||||
tags = ["key", "Slack"]
|
||||
|
||||
[[rules]]
|
||||
id = "apkey"
|
||||
description = "Asymmetric Private Key"
|
||||
regex = '''-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----'''
|
||||
tags = ["key", "AsymmetricPrivateKey"]
|
||||
|
||||
[[rules]]
|
||||
id = "google"
|
||||
description = "Google API key"
|
||||
regex = '''AIza[0-9A-Za-z\-_]{35}'''
|
||||
tags = ["key", "Google"]
|
||||
|
||||
[[rules]]
|
||||
id = "google"
|
||||
description = "Google (GCP) Service Account"
|
||||
regex = '''"type": "service_account"'''
|
||||
tags = ["key", "Google"]
|
||||
|
||||
[[rules]]
|
||||
id = "heroku"
|
||||
description = "Heroku API key"
|
||||
regex = '''(?i)heroku(.{0,20})?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'''
|
||||
tags = ["key", "Heroku"]
|
||||
|
||||
[[rules]]
|
||||
id = "mailchimp"
|
||||
description = "MailChimp API key"
|
||||
regex = '''(?i)(mailchimp|mc)(.{0,20})?[0-9a-f]{32}-us[0-9]{1,2}'''
|
||||
tags = ["key", "Mailchimp"]
|
||||
|
||||
[[rules]]
|
||||
id = "mailgun"
|
||||
description = "Mailgun API key"
|
||||
regex = '''((?i)(mailgun|mg)(.{0,20})?)?key-[0-9a-z]{32}'''
|
||||
tags = ["key", "Mailgun"]
|
||||
|
||||
[[rules]]
|
||||
id = "paypal"
|
||||
description = "PayPal Braintree access token"
|
||||
regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}'''
|
||||
tags = ["key", "Paypal"]
|
||||
|
||||
[[rules]]
|
||||
id = "piacatic"
|
||||
description = "Picatic API key"
|
||||
regex = '''sk_live_[0-9a-z]{32}'''
|
||||
tags = ["key", "Picatic"]
|
||||
|
||||
[[rules]]
|
||||
id = "sendgrid"
|
||||
description = "SendGrid API Key"
|
||||
regex = '''SG\.[\w_]{16,32}\.[\w_]{16,64}'''
|
||||
tags = ["key", "SendGrid"]
|
||||
|
||||
[[rules]]
|
||||
description = "Sidekiq Secret"
|
||||
id = "sidekiq-secret"
|
||||
regex = '''(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)'''
|
||||
secretGroup = 1
|
||||
keywords = [
|
||||
"bundle_enterprise__contribsys__com","bundle_gems__contribsys__com",
|
||||
]
|
||||
|
||||
[[rules]]
|
||||
description = "Sidekiq Sensitive URL"
|
||||
id = "sidekiq-sensitive-url"
|
||||
regex = '''(?i)\b(http(?:s??):\/\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\/|\#|\?|:]|$)'''
|
||||
secretGroup = 2
|
||||
keywords = [
|
||||
"gems.contribsys.com","enterprise.contribsys.com",
|
||||
]
|
||||
|
||||
[[rules]]
|
||||
id = "slack-webhook"
|
||||
description = "Slack Webhook"
|
||||
regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{24}'''
|
||||
tags = ["key", "slack"]
|
||||
|
||||
[[rules]]
|
||||
id = "stripe"
|
||||
description = "Stripe API key"
|
||||
regex = '''(?i)stripe(.{0,20})?[sr]k_live_[0-9a-zA-Z]{24}'''
|
||||
tags = ["key", "Stripe"]
|
||||
|
||||
[[rules]]
|
||||
id = "square"
|
||||
description = "Square access token"
|
||||
regex = '''sq0atp-[0-9A-Za-z\-_]{22}'''
|
||||
tags = ["key", "square"]
|
||||
|
||||
[[rules]]
|
||||
id = "square-oauth"
|
||||
description = "Square OAuth secret"
|
||||
regex = '''sq0csp-[0-9A-Za-z\-_]{43}'''
|
||||
tags = ["key", "square"]
|
||||
|
||||
[[rules]]
|
||||
id = "twilio"
|
||||
description = "Twilio API key"
|
||||
regex = '''(?i)twilio(.{0,20})?SK[0-9a-f]{32}'''
|
||||
tags = ["key", "twilio"]
|
||||
|
||||
[[rules]]
|
||||
id = "dynatrace"
|
||||
description = "Dynatrace ttoken"
|
||||
regex = '''dt0[a-zA-Z]{1}[0-9]{2}\.[A-Z0-9]{24}\.[A-Z0-9]{64}'''
|
||||
tags = ["key", "Dynatrace"]
|
||||
|
||||
[[rules]]
|
||||
id = "shopify"
|
||||
description = "Shopify shared secret"
|
||||
regex = '''shpss_[a-fA-F0-9]{32}'''
|
||||
tags = ["key", "Shopify"]
|
||||
|
||||
[[rules]]
|
||||
id = "shopify-access"
|
||||
description = "Shopify access token"
|
||||
regex = '''shpat_[a-fA-F0-9]{32}'''
|
||||
tags = ["key", "Shopify"]
|
||||
|
||||
[[rules]]
|
||||
id = "shopify-custom"
|
||||
description = "Shopify custom app access token"
|
||||
regex = '''shpca_[a-fA-F0-9]{32}'''
|
||||
tags = ["key", "Shopify"]
|
||||
|
||||
[[rules]]
|
||||
id = "shopify-private"
|
||||
description = "Shopify private app access token"
|
||||
regex = '''shppa_[a-fA-F0-9]{32}'''
|
||||
tags = ["key", "Shopify"]
|
||||
|
||||
[[rules]]
|
||||
id = "pypi"
|
||||
description = "PyPI upload token"
|
||||
regex = '''pypi-AgEIcHlwaS5vcmc[A-Za-z0-9-_]{50,1000}'''
|
||||
tags = ["key", "pypi"]
|
||||
|
17
cli/testdata/expected/git/small-branch-foo.txt
vendored
Normal file
17
cli/testdata/expected/git/small-branch-foo.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
// seems safer
|
||||
aws_token := os.Getenv("AWS_TOKEN")
|
||||
package foo
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Foo() {
|
||||
fmt.Println("foo")
|
||||
|
||||
// seems safe
|
||||
aws_token := "AKIALALEMEL33243OLIA"
|
||||
fmt.Println(aws_token)
|
||||
}
|
67
cli/testdata/expected/git/small.txt
vendored
Normal file
67
cli/testdata/expected/git/small.txt
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
// seems safer
|
||||
aws_token := os.Getenv("AWS_TOKEN")
|
||||
package foo
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Foo() {
|
||||
fmt.Println("foo")
|
||||
|
||||
// seems safe
|
||||
aws_token := "AKIALALEMEL33243OLIA"
|
||||
fmt.Println(aws_token)
|
||||
}
|
||||
package api
|
||||
|
||||
import "fmt"
|
||||
|
||||
func PrintHello() {
|
||||
fmt.Println("hello")
|
||||
}
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
var a = "initial"
|
||||
fmt.Println(a)
|
||||
var b, c int = 1, 2
|
||||
fmt.Println(b, c)
|
||||
var d = true
|
||||
fmt.Println(d)
|
||||
var e int
|
||||
fmt.Println(e)
|
||||
// load secret via env
|
||||
awsToken := os.Getenv("AWS_TOKEN")
|
||||
|
||||
f := "apple"
|
||||
fmt.Println(f)
|
||||
|
||||
// opps I added a secret at line 20
|
||||
awsToken := "AKIALALEMEL33243OLIA"
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
|
||||
var a = "initial"
|
||||
fmt.Println(a)
|
||||
|
||||
var b, c int = 1, 2
|
||||
fmt.Println(b, c)
|
||||
|
||||
var d = true
|
||||
fmt.Println(d)
|
||||
|
||||
var e int
|
||||
fmt.Println(e)
|
||||
|
||||
f := "apple"
|
||||
fmt.Println(f)
|
||||
}
|
||||
# test
|
||||
This is a repo used for testing gitleaks
|
2
cli/testdata/expected/report/csv_simple.csv
vendored
Normal file
2
cli/testdata/expected/report/csv_simple.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
RuleID,Commit,File,SymlinkFile,Secret,Match,StartLine,EndLine,StartColumn,EndColumn,Author,Message,Date,Email,Fingerprint
|
||||
test-rule,0000000000000000,auth.py,,a secret,line containing secret,1,2,1,2,John Doe,opps,10-19-2003,johndoe@gmail.com,fingerprint
|
|
1
cli/testdata/expected/report/empty.json
vendored
Normal file
1
cli/testdata/expected/report/empty.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
[]
|
22
cli/testdata/expected/report/json_simple.json
vendored
Normal file
22
cli/testdata/expected/report/json_simple.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"Description": "",
|
||||
"StartLine": 1,
|
||||
"EndLine": 2,
|
||||
"StartColumn": 1,
|
||||
"EndColumn": 2,
|
||||
"Match": "line containing secret",
|
||||
"Secret": "a secret",
|
||||
"File": "auth.py",
|
||||
"SymlinkFile": "",
|
||||
"Commit": "0000000000000000",
|
||||
"Entropy": 0,
|
||||
"Author": "John Doe",
|
||||
"Email": "johndoe@gmail.com",
|
||||
"Date": "10-19-2003",
|
||||
"Message": "opps",
|
||||
"Tags": [],
|
||||
"RuleID": "test-rule",
|
||||
"Fingerprint": ""
|
||||
}
|
||||
]
|
302
cli/testdata/expected/report/sarif_simple.got.sarif
vendored
Normal file
302
cli/testdata/expected/report/sarif_simple.got.sarif
vendored
Normal file
@ -0,0 +1,302 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "gitleaks",
|
||||
"semanticVersion": "v8.0.0",
|
||||
"informationUri": "https://github.com/Infisical/infisical",
|
||||
"rules": [
|
||||
{
|
||||
"id": "aws-access-key",
|
||||
"name": "AWS Access Key",
|
||||
"shortDescription": {
|
||||
"text": "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aws-secret-key",
|
||||
"name": "AWS Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)aws_(.{0,20})?=?.[\\'\\\"0-9a-zA-Z\\/+]{40}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aws-mws-key",
|
||||
"name": "AWS MWS key",
|
||||
"shortDescription": {
|
||||
"text": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facebook-secret-key",
|
||||
"name": "Facebook Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(facebook|fb)(.{0,20})?(?-i)['\\\"][0-9a-f]{32}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facebook-client-id",
|
||||
"name": "Facebook Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(facebook|fb)(.{0,20})?['\\\"][0-9]{13,17}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twitter-secret-key",
|
||||
"name": "Twitter Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twitter(.{0,20})?['\\\"][0-9a-z]{35,44}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twitter-client-id",
|
||||
"name": "Twitter Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twitter(.{0,20})?['\\\"][0-9a-z]{18,25}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-pat",
|
||||
"name": "Github Personal Access Token",
|
||||
"shortDescription": {
|
||||
"text": "ghp_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-oauth",
|
||||
"name": "Github OAuth Access Token",
|
||||
"shortDescription": {
|
||||
"text": "gho_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-app",
|
||||
"name": "Github App Token",
|
||||
"shortDescription": {
|
||||
"text": "(ghu|ghs)_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-refresh",
|
||||
"name": "Github Refresh Token",
|
||||
"shortDescription": {
|
||||
"text": "ghr_[0-9a-zA-Z]{76}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "linkedin-client",
|
||||
"name": "LinkedIn Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)linkedin(.{0,20})?(?-i)[0-9a-z]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "linkedin-secret",
|
||||
"name": "LinkedIn Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)linkedin(.{0,20})?[0-9a-z]{16}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "slack",
|
||||
"name": "Slack",
|
||||
"shortDescription": {
|
||||
"text": "xox[baprs]-([0-9a-zA-Z]{10,48})?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "apkey",
|
||||
"name": "Asymmetric Private Key",
|
||||
"shortDescription": {
|
||||
"text": "-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google (GCP) Service Account",
|
||||
"shortDescription": {
|
||||
"text": "\"type\": \"service_account\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google (GCP) Service Account",
|
||||
"shortDescription": {
|
||||
"text": "\"type\": \"service_account\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heroku",
|
||||
"name": "Heroku API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)heroku(.{0,20})?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mailchimp",
|
||||
"name": "MailChimp API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(mailchimp|mc)(.{0,20})?[0-9a-f]{32}-us[0-9]{1,2}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mailgun",
|
||||
"name": "Mailgun API key",
|
||||
"shortDescription": {
|
||||
"text": "((?i)(mailgun|mg)(.{0,20})?)?key-[0-9a-z]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "paypal",
|
||||
"name": "PayPal Braintree access token",
|
||||
"shortDescription": {
|
||||
"text": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "piacatic",
|
||||
"name": "Picatic API key",
|
||||
"shortDescription": {
|
||||
"text": "sk_live_[0-9a-z]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sendgrid",
|
||||
"name": "SendGrid API Key",
|
||||
"shortDescription": {
|
||||
"text": "SG\\.[\\w_]{16,32}\\.[\\w_]{16,64}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sidekiq-secret",
|
||||
"name": "Sidekiq Secret",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|\u003e|:=|\\|\\|:|\u003c=|=\u003e|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\\\"|\\n|\\r|\\s|\\x60|;]|$)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sidekiq-sensitive-url",
|
||||
"name": "Sidekiq Sensitive URL",
|
||||
"shortDescription": {
|
||||
"text": "(?i)\\b(http(?:s??):\\/\\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\\/|\\#|\\?|:]|$)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "slack-webhook",
|
||||
"name": "Slack Webhook",
|
||||
"shortDescription": {
|
||||
"text": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{24}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stripe",
|
||||
"name": "Stripe API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)stripe(.{0,20})?[sr]k_live_[0-9a-zA-Z]{24}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "square",
|
||||
"name": "Square access token",
|
||||
"shortDescription": {
|
||||
"text": "sq0atp-[0-9A-Za-z\\-_]{22}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "square-oauth",
|
||||
"name": "Square OAuth secret",
|
||||
"shortDescription": {
|
||||
"text": "sq0csp-[0-9A-Za-z\\-_]{43}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twilio",
|
||||
"name": "Twilio API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twilio(.{0,20})?SK[0-9a-f]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dynatrace",
|
||||
"name": "Dynatrace ttoken",
|
||||
"shortDescription": {
|
||||
"text": "dt0[a-zA-Z]{1}[0-9]{2}\\.[A-Z0-9]{24}\\.[A-Z0-9]{64}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify",
|
||||
"name": "Shopify shared secret",
|
||||
"shortDescription": {
|
||||
"text": "shpss_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-access",
|
||||
"name": "Shopify access token",
|
||||
"shortDescription": {
|
||||
"text": "shpat_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-custom",
|
||||
"name": "Shopify custom app access token",
|
||||
"shortDescription": {
|
||||
"text": "shpca_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-private",
|
||||
"name": "Shopify private app access token",
|
||||
"shortDescription": {
|
||||
"text": "shppa_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pypi",
|
||||
"name": "PyPI upload token",
|
||||
"shortDescription": {
|
||||
"text": "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9-_]{50,1000}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"message": {
|
||||
"text": "test-rule has detected secret for file auth.py at commit 0000000000000000."
|
||||
},
|
||||
"ruleId": "test-rule",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "auth.py"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 1,
|
||||
"startColumn": 1,
|
||||
"endLine": 2,
|
||||
"endColumn": 2,
|
||||
"snippet": {
|
||||
"text": "a secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"partialFingerprints": {
|
||||
"commitSha": "0000000000000000",
|
||||
"email": "johndoe@gmail.com",
|
||||
"author": "John Doe",
|
||||
"date": "10-19-2003",
|
||||
"commitMessage": "opps"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
302
cli/testdata/expected/report/sarif_simple.sarif
vendored
Normal file
302
cli/testdata/expected/report/sarif_simple.sarif
vendored
Normal file
@ -0,0 +1,302 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "gitleaks",
|
||||
"semanticVersion": "v8.0.0",
|
||||
"informationUri": "https://github.com/gitleaks/gitleaks",
|
||||
"rules": [
|
||||
{
|
||||
"id": "aws-access-key",
|
||||
"name": "AWS Access Key",
|
||||
"shortDescription": {
|
||||
"text": "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aws-secret-key",
|
||||
"name": "AWS Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)aws_(.{0,20})?=?.[\\'\\\"0-9a-zA-Z\\/+]{40}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aws-mws-key",
|
||||
"name": "AWS MWS key",
|
||||
"shortDescription": {
|
||||
"text": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facebook-secret-key",
|
||||
"name": "Facebook Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(facebook|fb)(.{0,20})?(?-i)['\\\"][0-9a-f]{32}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facebook-client-id",
|
||||
"name": "Facebook Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(facebook|fb)(.{0,20})?['\\\"][0-9]{13,17}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twitter-secret-key",
|
||||
"name": "Twitter Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twitter(.{0,20})?['\\\"][0-9a-z]{35,44}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twitter-client-id",
|
||||
"name": "Twitter Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twitter(.{0,20})?['\\\"][0-9a-z]{18,25}['\\\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-pat",
|
||||
"name": "Github Personal Access Token",
|
||||
"shortDescription": {
|
||||
"text": "ghp_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-oauth",
|
||||
"name": "Github OAuth Access Token",
|
||||
"shortDescription": {
|
||||
"text": "gho_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-app",
|
||||
"name": "Github App Token",
|
||||
"shortDescription": {
|
||||
"text": "(ghu|ghs)_[0-9a-zA-Z]{36}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "github-refresh",
|
||||
"name": "Github Refresh Token",
|
||||
"shortDescription": {
|
||||
"text": "ghr_[0-9a-zA-Z]{76}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "linkedin-client",
|
||||
"name": "LinkedIn Client ID",
|
||||
"shortDescription": {
|
||||
"text": "(?i)linkedin(.{0,20})?(?-i)[0-9a-z]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "linkedin-secret",
|
||||
"name": "LinkedIn Secret Key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)linkedin(.{0,20})?[0-9a-z]{16}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "slack",
|
||||
"name": "Slack",
|
||||
"shortDescription": {
|
||||
"text": "xox[baprs]-([0-9a-zA-Z]{10,48})?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "apkey",
|
||||
"name": "Asymmetric Private Key",
|
||||
"shortDescription": {
|
||||
"text": "-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google (GCP) Service Account",
|
||||
"shortDescription": {
|
||||
"text": "\"type\": \"service_account\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google (GCP) Service Account",
|
||||
"shortDescription": {
|
||||
"text": "\"type\": \"service_account\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heroku",
|
||||
"name": "Heroku API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)heroku(.{0,20})?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mailchimp",
|
||||
"name": "MailChimp API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(mailchimp|mc)(.{0,20})?[0-9a-f]{32}-us[0-9]{1,2}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mailgun",
|
||||
"name": "Mailgun API key",
|
||||
"shortDescription": {
|
||||
"text": "((?i)(mailgun|mg)(.{0,20})?)?key-[0-9a-z]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "paypal",
|
||||
"name": "PayPal Braintree access token",
|
||||
"shortDescription": {
|
||||
"text": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "piacatic",
|
||||
"name": "Picatic API key",
|
||||
"shortDescription": {
|
||||
"text": "sk_live_[0-9a-z]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sendgrid",
|
||||
"name": "SendGrid API Key",
|
||||
"shortDescription": {
|
||||
"text": "SG\\.[\\w_]{16,32}\\.[\\w_]{16,64}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sidekiq-secret",
|
||||
"name": "Sidekiq Secret",
|
||||
"shortDescription": {
|
||||
"text": "(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|\u003e|:=|\\|\\|:|\u003c=|=\u003e|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\\\"|\\n|\\r|\\s|\\x60|;]|$)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sidekiq-sensitive-url",
|
||||
"name": "Sidekiq Sensitive URL",
|
||||
"shortDescription": {
|
||||
"text": "(?i)\\b(http(?:s??):\\/\\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\\/|\\#|\\?|:]|$)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "slack-webhook",
|
||||
"name": "Slack Webhook",
|
||||
"shortDescription": {
|
||||
"text": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{24}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stripe",
|
||||
"name": "Stripe API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)stripe(.{0,20})?[sr]k_live_[0-9a-zA-Z]{24}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "square",
|
||||
"name": "Square access token",
|
||||
"shortDescription": {
|
||||
"text": "sq0atp-[0-9A-Za-z\\-_]{22}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "square-oauth",
|
||||
"name": "Square OAuth secret",
|
||||
"shortDescription": {
|
||||
"text": "sq0csp-[0-9A-Za-z\\-_]{43}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "twilio",
|
||||
"name": "Twilio API key",
|
||||
"shortDescription": {
|
||||
"text": "(?i)twilio(.{0,20})?SK[0-9a-f]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dynatrace",
|
||||
"name": "Dynatrace ttoken",
|
||||
"shortDescription": {
|
||||
"text": "dt0[a-zA-Z]{1}[0-9]{2}\\.[A-Z0-9]{24}\\.[A-Z0-9]{64}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify",
|
||||
"name": "Shopify shared secret",
|
||||
"shortDescription": {
|
||||
"text": "shpss_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-access",
|
||||
"name": "Shopify access token",
|
||||
"shortDescription": {
|
||||
"text": "shpat_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-custom",
|
||||
"name": "Shopify custom app access token",
|
||||
"shortDescription": {
|
||||
"text": "shpca_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shopify-private",
|
||||
"name": "Shopify private app access token",
|
||||
"shortDescription": {
|
||||
"text": "shppa_[a-fA-F0-9]{32}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pypi",
|
||||
"name": "PyPI upload token",
|
||||
"shortDescription": {
|
||||
"text": "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9-_]{50,1000}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"message": {
|
||||
"text": "test-rule has detected secret for file auth.py at commit 0000000000000000."
|
||||
},
|
||||
"ruleId": "test-rule",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "auth.py"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 1,
|
||||
"startColumn": 1,
|
||||
"endLine": 2,
|
||||
"endColumn": 2,
|
||||
"snippet": {
|
||||
"text": "a secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"partialFingerprints": {
|
||||
"commitSha": "0000000000000000",
|
||||
"email": "johndoe@gmail.com",
|
||||
"author": "John Doe",
|
||||
"date": "10-19-2003",
|
||||
"commitMessage": "opps"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
24
cli/testdata/repos/nogit/main.go
vendored
Normal file
24
cli/testdata/repos/nogit/main.go
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
|
||||
var a = "initial"
|
||||
fmt.Println(a)
|
||||
|
||||
var b, c int = 1, 2
|
||||
fmt.Println(b, c)
|
||||
|
||||
var d = true
|
||||
fmt.Println(d)
|
||||
|
||||
var e int
|
||||
fmt.Println(e)
|
||||
|
||||
// opps I added a secret at line 20
|
||||
awsToken := "AKIALALEMEL33243OLIA"
|
||||
|
||||
f := "apple"
|
||||
fmt.Println(f)
|
||||
}
|
2
cli/testdata/repos/small/README.md
vendored
Normal file
2
cli/testdata/repos/small/README.md
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# test
|
||||
This is a repo used for testing gitleaks
|
7
cli/testdata/repos/small/api/api.go
vendored
Normal file
7
cli/testdata/repos/small/api/api.go
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package api
|
||||
|
||||
import "fmt"
|
||||
|
||||
func PrintHello() {
|
||||
fmt.Println("hello")
|
||||
}
|
1
cli/testdata/repos/small/dotGit/COMMIT_EDITMSG
vendored
Normal file
1
cli/testdata/repos/small/dotGit/COMMIT_EDITMSG
vendored
Normal file
@ -0,0 +1 @@
|
||||
removing secret from foo package
|
1
cli/testdata/repos/small/dotGit/FETCH_HEAD
vendored
Normal file
1
cli/testdata/repos/small/dotGit/FETCH_HEAD
vendored
Normal file
@ -0,0 +1 @@
|
||||
2e1db472eeba53f06c4026ae4566ea022e36598e branch 'main' of github.com:gitleaks/test
|
1
cli/testdata/repos/small/dotGit/HEAD
vendored
Normal file
1
cli/testdata/repos/small/dotGit/HEAD
vendored
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/main
|
1
cli/testdata/repos/small/dotGit/ORIG_HEAD
vendored
Normal file
1
cli/testdata/repos/small/dotGit/ORIG_HEAD
vendored
Normal file
@ -0,0 +1 @@
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587
|
13
cli/testdata/repos/small/dotGit/config
vendored
Normal file
13
cli/testdata/repos/small/dotGit/config
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = git@github.com:gitleaks/test.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[branch "main"]
|
||||
remote = origin
|
||||
merge = refs/heads/main
|
1
cli/testdata/repos/small/dotGit/description
vendored
Normal file
1
cli/testdata/repos/small/dotGit/description
vendored
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
BIN
cli/testdata/repos/small/dotGit/index
vendored
Normal file
BIN
cli/testdata/repos/small/dotGit/index
vendored
Normal file
Binary file not shown.
6
cli/testdata/repos/small/dotGit/info/exclude
vendored
Normal file
6
cli/testdata/repos/small/dotGit/info/exclude
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
13
cli/testdata/repos/small/dotGit/logs/HEAD
vendored
Normal file
13
cli/testdata/repos/small/dotGit/logs/HEAD
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
0000000000000000000000000000000000000000 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896329 -0500 clone: from github.com:gitleaks/test.git
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896362 -0500 checkout: moving from main to remove-secrets
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 906335481df9a4b48906c90318b4fac76b67fe73 Zach Rice <zricer@protonmail.com> 1635896426 -0500 commit: load token via env var
|
||||
906335481df9a4b48906c90318b4fac76b67fe73 a122b33c6bad3ee54724f52f2caad385ab1982ab Zach Rice <zricer@protonmail.com> 1635896518 -0500 commit: add api package
|
||||
a122b33c6bad3ee54724f52f2caad385ab1982ab a122b33c6bad3ee54724f52f2caad385ab1982ab Zach Rice <zricer@protonmail.com> 1635896543 -0500 checkout: moving from remove-secrets to api-pkg
|
||||
a122b33c6bad3ee54724f52f2caad385ab1982ab 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896644 -0500 checkout: moving from api-pkg to main
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635896648 -0500 pull origin main: Fast-forward
|
||||
2e1db472eeba53f06c4026ae4566ea022e36598e 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635896716 -0500 checkout: moving from main to foo
|
||||
2e1db472eeba53f06c4026ae4566ea022e36598e 491504d5a31946ce75e22554cc34203d8e5ff3ca Zach Rice <zricer@protonmail.com> 1635896886 -0500 commit: adding foo package with secret
|
||||
491504d5a31946ce75e22554cc34203d8e5ff3ca f1b58b97808f8e744f6a23c693859df5b5968901 Zach Rice <zricer@protonmail.com> 1635896931 -0500 commit: removing secret from foo package
|
||||
f1b58b97808f8e744f6a23c693859df5b5968901 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635897009 -0500 checkout: moving from foo to main
|
||||
2e1db472eeba53f06c4026ae4566ea022e36598e f1b58b97808f8e744f6a23c693859df5b5968901 Zach Rice <zricer@protonmail.com> 1635897062 -0500 checkout: moving from main to foo
|
||||
f1b58b97808f8e744f6a23c693859df5b5968901 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635897508 -0500 checkout: moving from foo to main
|
1
cli/testdata/repos/small/dotGit/logs/refs/heads/api-pkg
vendored
Normal file
1
cli/testdata/repos/small/dotGit/logs/refs/heads/api-pkg
vendored
Normal file
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 a122b33c6bad3ee54724f52f2caad385ab1982ab Zach Rice <zricer@protonmail.com> 1635896543 -0500 branch: Created from HEAD
|
3
cli/testdata/repos/small/dotGit/logs/refs/heads/foo
vendored
Normal file
3
cli/testdata/repos/small/dotGit/logs/refs/heads/foo
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
0000000000000000000000000000000000000000 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635896716 -0500 branch: Created from HEAD
|
||||
2e1db472eeba53f06c4026ae4566ea022e36598e 491504d5a31946ce75e22554cc34203d8e5ff3ca Zach Rice <zricer@protonmail.com> 1635896886 -0500 commit: adding foo package with secret
|
||||
491504d5a31946ce75e22554cc34203d8e5ff3ca f1b58b97808f8e744f6a23c693859df5b5968901 Zach Rice <zricer@protonmail.com> 1635896931 -0500 commit: removing secret from foo package
|
2
cli/testdata/repos/small/dotGit/logs/refs/heads/main
vendored
Normal file
2
cli/testdata/repos/small/dotGit/logs/refs/heads/main
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
0000000000000000000000000000000000000000 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896329 -0500 clone: from github.com:gitleaks/test.git
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635896648 -0500 pull origin main: Fast-forward
|
3
cli/testdata/repos/small/dotGit/logs/refs/heads/remove-secrets
vendored
Normal file
3
cli/testdata/repos/small/dotGit/logs/refs/heads/remove-secrets
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
0000000000000000000000000000000000000000 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896362 -0500 branch: Created from HEAD
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 906335481df9a4b48906c90318b4fac76b67fe73 Zach Rice <zricer@protonmail.com> 1635896426 -0500 commit: load token via env var
|
||||
906335481df9a4b48906c90318b4fac76b67fe73 a122b33c6bad3ee54724f52f2caad385ab1982ab Zach Rice <zricer@protonmail.com> 1635896518 -0500 commit: add api package
|
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/HEAD
vendored
Normal file
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/HEAD
vendored
Normal file
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 Zach Rice <zricer@protonmail.com> 1635896329 -0500 clone: from github.com:gitleaks/test.git
|
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/api-pkg
vendored
Normal file
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/api-pkg
vendored
Normal file
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 a122b33c6bad3ee54724f52f2caad385ab1982ab Zach Rice <zricer@protonmail.com> 1635896552 -0500 update by push
|
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/foo
vendored
Normal file
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/foo
vendored
Normal file
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 f1b58b97808f8e744f6a23c693859df5b5968901 Zach Rice <zricer@protonmail.com> 1635896935 -0500 update by push
|
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/main
vendored
Normal file
1
cli/testdata/repos/small/dotGit/logs/refs/remotes/origin/main
vendored
Normal file
@ -0,0 +1 @@
|
||||
1b6da43b82b22e4eaa10bcf8ee591e91abbfc587 2e1db472eeba53f06c4026ae4566ea022e36598e Zach Rice <zricer@protonmail.com> 1635896648 -0500 pull origin main: fast-forward
|
1
cli/testdata/repos/small/dotGit/objects/02/d85657604c34e7b7fbb324a0c6c8b13c2c3760
vendored
Normal file
1
cli/testdata/repos/small/dotGit/objects/02/d85657604c34e7b7fbb324a0c6c8b13c2c3760
vendored
Normal file
@ -0,0 +1 @@
|
||||
xU<>1<0B>0`<60>ܯ8nJ<6E><4A>.<2E><><EFBFBD>:*(8<><38>\<5C><>ɕ$<24> <20>w3 Nox<6F>{<7B>$6<>f1<66>~<7E>wF'0<>YbF
|
BIN
cli/testdata/repos/small/dotGit/objects/15/2888a42422b2ff5868b8d003d626120a9cb738
vendored
Normal file
BIN
cli/testdata/repos/small/dotGit/objects/15/2888a42422b2ff5868b8d003d626120a9cb738
vendored
Normal file
Binary file not shown.
BIN
cli/testdata/repos/small/dotGit/objects/2e/1db472eeba53f06c4026ae4566ea022e36598e
vendored
Normal file
BIN
cli/testdata/repos/small/dotGit/objects/2e/1db472eeba53f06c4026ae4566ea022e36598e
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user