mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-20 20:04:24 +00:00
Compare commits
70 Commits
infisical-
...
infisical-
Author | SHA1 | Date | |
---|---|---|---|
6aab90590f | |||
f7466d4855 | |||
ea2565ed35 | |||
4586656b85 | |||
e4953398df | |||
7722231656 | |||
845a476974 | |||
fc19a17f4b | |||
0890b1912f | |||
82ecc2d7dc | |||
460bdbb91c | |||
446a63a917 | |||
d67cb7b507 | |||
353ff63298 | |||
9f40266f5c | |||
8af8a1d3d5 | |||
631423fbc8 | |||
4383779377 | |||
8249043826 | |||
20294ee233 | |||
c5a924e935 | |||
429bfd27b2 | |||
c99c873d78 | |||
092a6911ce | |||
a9b642e618 | |||
919ddf5de2 | |||
89a89af4e6 | |||
b3e68cf3fb | |||
960063e61a | |||
abf4eaf6db | |||
739f97f5c9 | |||
faed5c1821 | |||
c95598aaa6 | |||
e791684f4d | |||
d32c5fb869 | |||
abbf1918dc | |||
876d0119d3 | |||
6d70dc437e | |||
174e22a2bc | |||
f4815641d8 | |||
5b95c255ec | |||
3123f6fc1f | |||
a913cd97a4 | |||
781e0b24c8 | |||
28de8cddd7 | |||
ed3e53f9a3 | |||
9cb4d5abb7 | |||
efdd1e64c4 | |||
5b3be6063f | |||
12c399d4a9 | |||
ecd17e1d6d | |||
fb4c811414 | |||
3561c589b1 | |||
420d71d923 | |||
3db5c040c3 | |||
b4f336a5bb | |||
43e61c94f0 | |||
69fa4a80c5 | |||
cf9e8b8a6b | |||
c6d5498a42 | |||
7aa5ef844c | |||
ad7972e7e1 | |||
c6d8f24968 | |||
d8ff0bef0d | |||
29b96246b9 | |||
8503c9355b | |||
3c71bcaa8d | |||
8d09a45454 | |||
fd9387a25e | |||
b17a40d83e |
.github/workflows
.goreleaser.yamlREADME.mdbackend
cli
.infisicalignore
docker-compose.dev.ymlconfig
detect
go.modgo.summain.gopackages
api
cmd
models
telemetry
util
visualize
report
constants.gocsv.gocsv_test.gofinding.gofinding_test.gojson.gojson_test.goreport.goreport_test.gosarif.gosarif_test.go
testdata
baseline
config
allow_aws_re.tomlallow_commit.tomlallow_global_aws_re.tomlallow_path.tomlbad_entropy_group.tomlbase.tomlentropy_group.tomlescaped_character_group.tomlextend_1.tomlextend_2.tomlextend_3.tomlgeneric.tomlgeneric_with_py_path.tomlpath_only.tomlsimple.toml
expected
git
report
repos
nogit
small
README.md
api
dotGit
COMMIT_EDITMSGFETCH_HEADHEADORIG_HEADconfigdescriptionindexpacked-refs
main.goinfo
logs
objects
02
15
2e
49
5c
78
90
9a
a1
a5
a9
bc
d8
da
e5
f1
pack
refs
staged
.gitleaksignoreREADME.md
api
dotGit
COMMIT_EDITMSGFETCH_HEADHEADORIG_HEADconfigdescriptionindexpacked-refs
main.goinfo
logs
objects
02
15
2e
46
49
5c
65
66
78
90
9a
a1
a5
a9
b1
bc
bf
d8
da
e5
f1
pack
refs
symlinks
tmp
docs
cli
documentation/getting-started
images
spring-maven-debug-1.pngspring-maven-debug-2.pngspring-maven-debug-3.pngspring-maven-debug-4.pngspring-maven-debug-5.png
integrations/frameworks
mint.jsonself-hosting/deployment-options
frontend
next-i18next.config.jsnext.config.jspackage-lock.jsonpackage.jsonconst.ts
public/locales
en
activity.jsonbilling.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-api-key.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
es
activity.jsonbilling.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-api-key.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
fr
activity.jsonbilling.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
ko
billing.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
pt-BR
activity.jsonbilling.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
tr
activity.jsonbilling.jsoncommon.jsondashboard.jsonintegrations.jsonlogin.jsonmfa.jsonnav.jsonsection-api-key.jsonsection-incident.jsonsection-members.jsonsection-password.jsonsection-token.jsonsettings-members.jsonsettings-org.jsonsettings-personal.jsonsettings-project.jsonsignup.jsontranslations.json
src
components
basic
EventFilter.tsxLayout.tsx
dialog
ActivateBotDialog.tsxAddApiKeyDialog.tsxAddIncidentContactDialog.tsxAddProjectMemberDialog.tsxAddServiceTokenDialog.tsx
table
billing
dashboard
integrations
login
navigation
signup
utilities
v2
ee/components
hooks/api/keys
i18n.tslayouts/AppLayout
pages
_app.tsxsignup.tsx
activity
dashboard
home
integrations
[id].tsx
login.tsxnoprojects.tsxpassword-reset.tsxaws-parameter-store
aws-secret-manager
azure-key-vault
circleci
flyio
github
gitlab
heroku
netlify
railway
render
supabase
travisci
vercel
settings
billing
org/[id]
personal
project
users
verify-email.tsxviews
DashboardPage
Settings
OrgSettingsPage
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
CopyProjectIDSection
ProjectIndexSecretsSection
ProjectNameChangeSection
ServiceTokenSection
@ -8,12 +8,40 @@ jobs:
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
- name: 🧪 Run tests
|
||||
run: npm run test:ci
|
||||
working-directory: backend
|
||||
- uses: paulhatch/semantic-version@v5.0.2
|
||||
id: version
|
||||
with:
|
||||
# The prefix to use to identify tags
|
||||
tag_prefix: "infisical-standalone/v"
|
||||
# A string which, if present in a git commit, indicates that a change represents a
|
||||
# major (breaking) change, supports regular expressions wrapped with '/'
|
||||
major_pattern: "(MAJOR)"
|
||||
# Same as above except indicating a minor change, supports regular expressions wrapped with '/'
|
||||
minor_pattern: "(MINOR)"
|
||||
# A string to determine the format of the version output
|
||||
version_format: "${major}.${minor}.${patch}-prerelease${increment}"
|
||||
# Optional path to check for changes. If any changes are detected in the path the
|
||||
# 'changed' output will true. Enter multiple paths separated by spaces.
|
||||
change_path: "backend,frontend"
|
||||
# Prevents pre-v1.0.0 version from automatically incrementing the major version.
|
||||
# If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged.
|
||||
enable_prerelease_mode: true
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
- name: version output
|
||||
run: |
|
||||
echo "Output Value: ${{ steps.version.outputs.major }}"
|
||||
echo "Output Value: ${{ steps.version.outputs.minor }}"
|
||||
echo "Output Value: ${{ steps.version.outputs.patch }}"
|
||||
echo "Output Value: ${{ steps.version.outputs.version }}"
|
||||
echo "Output Value: ${{ steps.version.outputs.version_type }}"
|
||||
echo "Output Value: ${{ steps.version.outputs.increment }}"
|
||||
- name: Save commit hashes for tag
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
1
.github/workflows/release_build.yml
vendored
1
.github/workflows/release_build.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
|
||||
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
|
@ -18,7 +18,9 @@ monorepo:
|
||||
builds:
|
||||
- id: darwin-build
|
||||
binary: infisical
|
||||
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
ldflags:
|
||||
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||
flags:
|
||||
- -trimpath
|
||||
env:
|
||||
@ -36,7 +38,9 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
binary: infisical
|
||||
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
ldflags:
|
||||
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
@ -156,6 +160,15 @@ aurs:
|
||||
install -Dm755 "./infisical" "${pkgdir}/usr/bin/infisical"
|
||||
# license
|
||||
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/infisical/LICENSE"
|
||||
# completions
|
||||
mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
|
||||
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.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"
|
||||
|
||||
# dockers:
|
||||
# - dockerfile: cli/docker/Dockerfile
|
||||
|
24
README.md
24
README.md
@ -89,6 +89,30 @@ git clone https://github.com/Infisical/infisical && cd infisical && copy .env.ex
|
||||
|
||||
Create an account at `http://localhost:80`
|
||||
|
||||
### Scan and prevent secret leaks
|
||||
On top managing secrets with Infisical, you can also scan for over 140+ secret types in your files, directories and git repositories.
|
||||
|
||||
To scan your full git history, run:
|
||||
|
||||
```
|
||||
infisical scan --verbose
|
||||
```
|
||||
|
||||
To scan your uncommitted git changes, run:
|
||||
|
||||
```
|
||||
infisical scan git-changes --verbose
|
||||
```
|
||||
|
||||
You can also scan your uncommited but staged changes by running the command below. This command can also be used as a pre-commit hook to prevent secret leak.
|
||||
|
||||
```
|
||||
infisical scan git-changes --staged --verbose
|
||||
```
|
||||
|
||||
Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
||||
|
||||
|
||||
## Open-source vs. paid
|
||||
|
||||
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license in the future.
|
||||
|
1936
backend/package-lock.json
generated
1936
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.319.0",
|
||||
"@godaddy/terminus": "^4.12.0",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.41.0",
|
||||
"@sentry/tracing": "^7.47.0",
|
||||
"@sentry/node": "^7.49.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.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
|
@ -19,7 +19,8 @@ export const getOrganizations = async (req: Request, res: Response) => {
|
||||
try {
|
||||
organizations = (
|
||||
await MembershipOrg.find({
|
||||
user: req.user._id
|
||||
user: req.user._id,
|
||||
status: ACCEPTED
|
||||
}).populate('organization')
|
||||
).map((m) => m.organization);
|
||||
} catch (err) {
|
||||
|
@ -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 })
|
||||
}
|
@ -1554,8 +1554,14 @@ const syncSecretsGitLab = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
interface GitLabSecret {
|
||||
key: string;
|
||||
value: string;
|
||||
environment_scope: string;
|
||||
}
|
||||
|
||||
// get secrets from gitlab
|
||||
const getSecretsRes = (
|
||||
const getSecretsRes: GitLabSecret[] = (
|
||||
await request.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
|
||||
{
|
||||
@ -1565,7 +1571,11 @@ const syncSecretsGitLab = async ({
|
||||
},
|
||||
}
|
||||
)
|
||||
).data;
|
||||
)
|
||||
.data
|
||||
.filter((secret: GitLabSecret) =>
|
||||
secret.environment_scope === integration.targetEnvironment
|
||||
);
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||
@ -1578,7 +1588,7 @@ const syncSecretsGitLab = async ({
|
||||
protected: false,
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope:'*'
|
||||
environment_scope: integration.targetEnvironment
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
@ -1589,21 +1599,23 @@ const syncSecretsGitLab = async ({
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// udpate secret
|
||||
await request.put(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key]
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "application/json",
|
||||
// update secret
|
||||
if (secrets[key] !== existingSecret.value) {
|
||||
await request.put(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key]
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1611,7 +1623,7 @@ const syncSecretsGitLab = async ({
|
||||
for await (const sec of getSecretsRes) {
|
||||
if (!(sec.key in secrets)) {
|
||||
await request.delete(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
@ -1620,7 +1632,7 @@ const syncSecretsGitLab = async ({
|
||||
);
|
||||
}
|
||||
}
|
||||
}catch (err) {
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error("Failed to sync secrets to GitLab");
|
||||
|
@ -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
|
||||
}
|
31
cli/go.mod
31
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,42 @@ 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/denisbrodbeck/machineid v1.0.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/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a // 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
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // 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 +71,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
|
||||
)
|
||||
|
597
cli/go.sum
597
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,13 @@ 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.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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=
|
||||
@ -20,10 +77,30 @@ github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnG
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
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 +109,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 +267,465 @@ 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/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a h1:Ey0XWvrg6u6hyIn1Kd/jCCmL+bMv9El81tvuGBbxZGg=
|
||||
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
|
||||
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.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
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/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
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/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
|
||||
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,8 @@ import (
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -30,11 +31,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") {
|
||||
@ -100,6 +96,8 @@ var exportCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
fmt.Print(output)
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:export", posthog.NewProperties().Set("secretsCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -175,8 +173,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,15 @@ 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/posthog/posthog-go"
|
||||
"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 +31,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
|
||||
}
|
||||
|
||||
@ -78,6 +79,9 @@ var initCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:init", posthog.NewProperties().Set("version", util.CLI_VERSION))
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,8 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/manifoldco/promptui"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
@ -44,12 +45,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 +67,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 +97,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 +152,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 +175,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 +239,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 +252,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
|
||||
}
|
||||
|
||||
@ -276,6 +276,7 @@ var loginCmd = &cobra.Command{
|
||||
plainBold.Println("\nQuick links")
|
||||
fmt.Println("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage")
|
||||
fmt.Println("- Stuck? Join our slack for quick support https://infisical.com/slack")
|
||||
Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -397,7 +398,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)
|
||||
|
||||
|
@ -7,18 +7,16 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
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()
|
||||
@ -40,6 +38,7 @@ var resetCmd = &cobra.Command{
|
||||
util.DeleteBackupSecrets()
|
||||
|
||||
util.PrintSuccessMessage("Reset successful")
|
||||
Telemetry.CaptureEvent("cli-command:reset", posthog.NewProperties().Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,19 @@ 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"
|
||||
"github.com/Infisical/infisical-merge/packages/telemetry"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
)
|
||||
|
||||
var Telemetry *telemetry.Telemetry
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "infisical",
|
||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||
@ -30,11 +36,14 @@ 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().Bool("telemetry", true, "Infisical collects non-sensitive telemetry data to enhance features and improve user experience. Participation is voluntary")
|
||||
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
|
||||
@ -45,4 +54,30 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
isTelemetryOn, _ := rootCmd.PersistentFlags().GetBool("telemetry")
|
||||
Telemetry = telemetry.NewTelemetry(isTelemetryOn)
|
||||
}
|
||||
|
||||
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,8 @@ 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/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -28,7 +29,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 +124,9 @@ 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)
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:run", posthog.NewProperties().Set("secretsCount", len(secrets)).Set("environment", environmentName).Set("isUsingServiceToken", infisicalToken != "").Set("single-command", strings.Join(args, " ")).Set("multi-command", cmd.Flag("command").Value.String()).Set("version", util.CLI_VERSION))
|
||||
|
||||
if cmd.Flags().Changed("command") {
|
||||
command := cmd.Flag("command").Value.String()
|
||||
@ -217,7 +219,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)
|
||||
}
|
||||
|
419
cli/packages/cmd/scan.go
Normal file
419
cli/packages/cmd/scan.go
Normal file
@ -0,0 +1,419 @@
|
||||
// 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/packages/util"
|
||||
"github.com/Infisical/infisical-merge/report"
|
||||
"github.com/posthog/posthog-go"
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:scan", posthog.NewProperties().Set("risks", len(findings)).Set("version", util.CLI_VERSION))
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:scan git-changes", posthog.NewProperties().Set("risks", len(findings)).Set("version", util.CLI_VERSION))
|
||||
|
||||
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,8 @@ 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/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -28,7 +29,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")
|
||||
@ -64,6 +64,7 @@ var secretsCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
visualize.PrintAllSecretDetails(secrets)
|
||||
Telemetry.CaptureEvent("cli-command:secrets", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -73,7 +74,6 @@ var secretsGetCmd = &cobra.Command{
|
||||
Use: "get [secrets]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRun: toggleDebug,
|
||||
Run: getSecretsByNames,
|
||||
}
|
||||
|
||||
@ -83,7 +83,6 @@ var secretsGenerateExampleEnvCmd = &cobra.Command{
|
||||
Use: "generate-example-env",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.NoArgs,
|
||||
PreRun: toggleDebug,
|
||||
Run: generateExampleEnv,
|
||||
}
|
||||
|
||||
@ -92,7 +91,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 +132,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")
|
||||
}
|
||||
|
||||
@ -270,6 +268,8 @@ var secretsSetCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
visualize.Table(headers, rows)
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:secrets set", posthog.NewProperties().Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -278,7 +278,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")
|
||||
@ -338,6 +337,7 @@ var secretsDeleteCmd = &cobra.Command{
|
||||
|
||||
fmt.Printf("secret name(s) [%v] have been deleted from your project \n", strings.Join(args, ", "))
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:secrets delete", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -382,6 +382,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
visualize.PrintAllSecretDetails(requestedSecrets)
|
||||
Telemetry.CaptureEvent("cli-command:secrets get", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
}
|
||||
|
||||
func generateExampleEnv(cmd *cobra.Command, args []string) {
|
||||
@ -570,6 +571,8 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(strings.Join(dashedList, ""))
|
||||
}
|
||||
fmt.Println(strings.Join(fullyGeneratedDocuments, ""))
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:generate-example-env", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
}
|
||||
|
||||
func CenterString(s string, numStars int) string {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -78,6 +79,8 @@ var switchCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
util.HandleError(err, "")
|
||||
}
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:user switch", posthog.NewProperties().Set("numberOfLoggedInProfiles", len(loggedInProfiles)).Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
@ -174,7 +177,7 @@ var domainCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
util.HandleError(err, "")
|
||||
}
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:user domain", posthog.NewProperties().Set("version", util.CLI_VERSION))
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,12 @@ 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/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -15,25 +18,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 +44,15 @@ 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)
|
||||
|
||||
Telemetry.CaptureEvent("cli-command:vault set", posthog.NewProperties().Set("currentVault", currentVaultBackend).Set("wantedVault", wantedVaultTypeName).Set("version", util.CLI_VERSION))
|
||||
} 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 +62,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 +69,19 @@ 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))
|
||||
Telemetry.CaptureEvent("cli-command:vault", posthog.NewProperties().Set("currentVault", currentVaultBackend).Set("version", util.CLI_VERSION))
|
||||
|
||||
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)
|
||||
}
|
69
cli/packages/telemetry/telemetry.go
Normal file
69
cli/packages/telemetry/telemetry.go
Normal file
@ -0,0 +1,69 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/posthog/posthog-go"
|
||||
)
|
||||
|
||||
var POSTHOG_API_KEY_FOR_CLI string
|
||||
|
||||
type Telemetry struct {
|
||||
isEnabled bool
|
||||
posthogClient posthog.Client
|
||||
}
|
||||
|
||||
func NewTelemetry(telemetryIsEnabled bool) *Telemetry {
|
||||
if POSTHOG_API_KEY_FOR_CLI != "" {
|
||||
client, _ := posthog.NewWithConfig(
|
||||
POSTHOG_API_KEY_FOR_CLI,
|
||||
posthog.Config{},
|
||||
)
|
||||
|
||||
return &Telemetry{isEnabled: telemetryIsEnabled, posthogClient: client}
|
||||
} else {
|
||||
return &Telemetry{isEnabled: false}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Telemetry) CaptureEvent(eventName string, properties posthog.Properties) {
|
||||
userIdentity, err := t.GetDistinctId()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.isEnabled {
|
||||
t.posthogClient.Enqueue(posthog.Capture{
|
||||
DistinctId: userIdentity,
|
||||
Event: eventName,
|
||||
Properties: properties,
|
||||
})
|
||||
|
||||
defer t.posthogClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Telemetry) GetDistinctId() (string, error) {
|
||||
var distinctId string
|
||||
var outputErr error
|
||||
|
||||
machineId, err := machineid.ID()
|
||||
if err != nil {
|
||||
outputErr = err
|
||||
}
|
||||
|
||||
userDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
outputErr = err
|
||||
}
|
||||
|
||||
if userDetails.IsUserLoggedIn && userDetails.UserCredentials.Email != "" {
|
||||
distinctId = userDetails.UserCredentials.Email
|
||||
} else if machineId != "" {
|
||||
distinctId = "anonymous_cli_" + machineId
|
||||
} else {
|
||||
distinctId = ""
|
||||
}
|
||||
|
||||
return distinctId, outputErr
|
||||
}
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user