Initial CLI w/ partially implemented Git source and demo detector (#1)

This commit is contained in:
Dustin Decker 2022-01-13 12:02:24 -08:00 committed by GitHub
parent 9edeb164f4
commit 4218c39d99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 22428 additions and 0 deletions

0
Dockerfile Normal file
View file

32
Makefile Normal file
View file

@ -0,0 +1,32 @@
PROTOS_IMAGE=us-docker.pkg.dev/thog-artifacts/public/go-ci-1.17-1
.PHONY: check
.PHONY: test
.PHONY: test-race
.PHONY: run
.PHONY: install
.PHONY: protos
.PHONY: protos-windows
.PHONY: vendor
install:
CGO_ENABLED=0 go install .
check:
go fmt $(shell go list ./... | grep -v /vendor/)
go vet $(shell go list ./... | grep -v /vendor/)
test:
CGO_ENABLED=0 go test $(shell go list ./... | grep -v /vendor/)
test-race:
CGO_ENABLED=1 go test -race $(shell go list ./... | grep -v /vendor/)
bench:
CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
run:
CGO_ENABLED=0 go run . git file://.
protos:
docker run -u "$(shell id -u)" -v "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; /pwd/scripts/gen_proto.sh"

0
README.md Normal file
View file

67
go.mod Normal file
View file

@ -0,0 +1,67 @@
module github.com/trufflesecurity/trufflehog
go 1.17
require (
cloud.google.com/go/secretmanager v1.0.0
github.com/bradleyfalzon/ghinstallation v1.1.1
github.com/envoyproxy/protoc-gen-validate v0.6.2
github.com/go-errors/errors v1.4.1
github.com/go-git/go-git/v5 v5.4.2
github.com/google/go-github/v41 v41.0.0
github.com/h2non/filetype v1.1.3
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/joho/godotenv v1.4.0
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-colorable v0.1.12
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
github.com/xanzy/go-gitlab v0.54.3
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368
google.golang.org/protobuf v1.27.1
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
require (
cloud.google.com/go v0.94.1 // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-github/v29 v29.0.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/mod v0.5.0 // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.57.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/grpc v1.40.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

697
go.sum Normal file
View file

@ -0,0 +1,697 @@
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 v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1 h1:DwuSvDZ1pTYGbXo8yOJevCTr3BoBlE+OVkHAKiYQUXc=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
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/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/secretmanager v1.0.0 h1:Wbw6lsRrpatsE8GVpuwYqImn+sY5DmRjaEImYPwcSMY=
cloud.google.com/go/secretmanager v1.0.0/go.mod h1:+Qkm5qxIJ5mk74xxIXA+87fseaY1JLYBcFPQoc/GQxg=
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/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/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I=
github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
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/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
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/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.4.1 h1:IvVlgbzSsaUNudsw5dcXSzF3EWyXTi5XrAdngnuhRyg=
github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
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/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 h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
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/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
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/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
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/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
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/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
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/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/xanzy/go-gitlab v0.54.3 h1:fPfZ3Jcu5dPc3xyIYtAALZsEgoyKNFNuULD+TdJ7Zvk=
github.com/xanzy/go-gitlab v0.54.3/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
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=
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 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
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-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-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-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-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
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-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-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-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-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
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-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
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.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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-20190902080502-41f04d3bba15/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=

101
main.go Normal file
View file

@ -0,0 +1,101 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"runtime"
"strconv"
"github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/decoders"
"github.com/trufflesecurity/trufflehog/pkg/engine"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
func main() {
cli := kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
debug := cli.Flag("debug", "Run in debug mode").Bool()
jsonOut := cli.Flag("json", "Output in JSON format.").Bool()
concurrency := cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
verification := cli.Flag("verification", "Verify the results.").Bool()
// rules := cli.Flag("rules", "Path to file with custom rules.").String()
gitScan := cli.Command("git", "Find credentials in git repositories.")
gitScanURI := gitScan.Arg("uri", "Git repository URL. https:// or file:// schema expected.").Required().String()
// gitScanIncludePaths := gitScan.Flag("include_paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
// gitScanExcludePaths := gitScan.Flag("exclude_paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
// gitScanSinceCommit := gitScan.Flag("since_commit", "Commit to start scan from.").String()
gitScanBranch := gitScan.Flag("branch", "Branch to scan.").String()
// gitScanMaxDepth := gitScan.Flag("max_depth", "Maximum depth of commits to scan.").Int()
gitScan.Flag("allow", "No-op flag for backwards compat.").Bool()
gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool()
gitScan.Flag("regex", "No-op flag for backwards compat.").Bool()
githubScan := cli.Command("github", "Find credentials in GitHub repositories.")
// githubScanTarget := githubScan.Arg("target", "GitHub target. Can be a repository, user or organization.").Required().String()
// githubScanToken := githubScan.Flag("token", "GitHub token.").String()
gitlabScan := cli.Command("gitlab", "Find credentials in GitLab repositories.")
// gitlabScanTarget := gitlabScan.Arg("target", "GitLab target. Can be a repository, user or organization.").Required().String()
// gitlabScanToken := gitlabScan.Flag("token", "GitLab token.").String()
// bitbucketScan := cli.Command("bitbucket", "Find credentials in Bitbucket repositories.")
// bitbucketScanTarget := bitbucketScan.Arg("target", "Bitbucket target. Can be a repository, user or organization.").Required().String()
// bitbucketScanToken := bitbucketScan.Flag("token", "Bitbucket token.").String()
// filesystemScan := cli.Command("filesystem", "Find credentials in filesystem.")
// filesystemScanPath := filesystemScan.Arg("path", "Path to scan.").Required().String()
// filesystemScanRecursive := filesystemScan.Flag("recursive", "Scan recursively.").Short('r').Bool()
// filesystemScanIncludePaths := filesystemScan.Flag("include_paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
// filesystemScanExcludePaths := filesystemScan.Flag("exclude_paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
cmd := kingpin.MustParse(cli.Parse(os.Args[1:]))
if *jsonOut {
logrus.SetFormatter(&logrus.JSONFormatter{})
}
if *debug {
logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.InfoLevel)
}
ctx := context.TODO()
e := engine.Start(ctx,
engine.WithConcurrency(*concurrency),
engine.WithDecoders(decoders.DefaultDecoders()...),
engine.WithDetectors(*verification, engine.DefaultDetectors()...),
)
switch cmd {
case gitScan.FullCommand():
err := e.ScanGit(ctx, *gitScanURI, *gitScanBranch, "HEAD")
if err != nil {
logrus.WithError(err).Fatal("Failed to scan git.")
}
case githubScan.FullCommand():
log.Fatal("github not implemented")
case gitlabScan.FullCommand():
log.Fatal("gitlab not implemented")
}
// deal with the results from e.ResultsChan()
for r := range e.ResultsChan() {
if *jsonOut {
// todo - add parity to trufflehog's existing output for git
// source
out, err := json.Marshal(r)
if err != nil {
logrus.WithError(err).Fatal("could not marshal result")
}
fmt.Println(string(out))
} else {
fmt.Printf("%+v\n", r)
}
}
}

5
pkg/common/depaware.go Normal file
View file

@ -0,0 +1,5 @@
package common
import (
_ "github.com/tailscale/depaware/depaware"
)

130
pkg/common/http.go Normal file
View file

@ -0,0 +1,130 @@
package common
import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"strings"
"time"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
var caCerts = []string{
// CN = ISRG Root X1
// TODO: Expires Monday, June 4, 2035 at 4:04:38 AM Pacific
`
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
`,
// CN = ISRG Root X2
// TODO: Expires September 17, 2040 at 9:00:00 AM Pacific Daylight Time
`
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----
`,
}
func PinnedCertPool() *x509.CertPool {
trustedCerts := x509.NewCertPool()
for _, cert := range caCerts {
trustedCerts.AppendCertsFromPEM([]byte(strings.TrimSpace(cert)))
}
return trustedCerts
}
type CustomTransport struct {
T http.RoundTripper
}
func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("User-Agent", "TruffleHog")
return t.T.RoundTrip(req)
}
func NewCustomTransport(T http.RoundTripper) *CustomTransport {
if T == nil {
T = http.DefaultTransport
}
return &CustomTransport{T}
}
func PinnedRetryableHttpClient() *http.Client {
httpClient := retryablehttp.NewClient()
httpClient.Logger = nil
httpClient.HTTPClient.Transport = NewCustomTransport(&http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: PinnedCertPool(),
},
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
})
return httpClient.StandardClient()
}
func RetryableHttpClient() *http.Client {
httpClient := retryablehttp.NewClient()
httpClient.RetryMax = 3
httpClient.Logger = nil
httpClient.HTTPClient.Timeout = 3 * time.Second
httpClient.HTTPClient.Transport = NewCustomTransport(nil)
return httpClient.StandardClient()
}
func SaneHttpClient() *http.Client {
httpClient := &http.Client{}
httpClient.Timeout = time.Second * 2
httpClient.Transport = NewCustomTransport(nil)
return httpClient
}

55
pkg/common/secrets.go Normal file
View file

@ -0,0 +1,55 @@
package common
import (
"context"
"fmt"
"time"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"github.com/joho/godotenv"
"github.com/pkg/errors"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)
type Secret struct{ kv map[string]string }
func (s *Secret) MustGetField(name string) string {
val, ok := s.kv[name]
if !ok {
panic(errors.Errorf("field %s not found", name))
}
return val
}
func GetTestSecret(ctx context.Context) (secret *Secret, err error) {
return GetSecret(ctx, "trufflehog-testing", "test")
}
func GetSecret(ctx context.Context, gcpProject, name string) (secret *Secret, err error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
parent := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", gcpProject, name)
client, err := secretmanager.NewClient(ctx)
if err != nil {
return nil, errors.Errorf("failed to create secretmanager client: %v", err)
}
defer client.Close()
req := &secretmanagerpb.AccessSecretVersionRequest{
Name: parent,
}
result, err := client.AccessSecretVersion(ctx, req)
if err != nil {
return nil, errors.Errorf("failed to access secret version: %v", err)
}
data, err := godotenv.Unmarshal(string(result.Payload.Data))
if err != nil {
return nil, err
}
return &Secret{kv: data}, nil
}

27
pkg/common/vars.go Normal file
View file

@ -0,0 +1,27 @@
package common
import (
"path/filepath"
"github.com/h2non/filetype"
)
var (
KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
IGNORED_EXTENSIONS = []string{"pdf", "mp4", "avi", "mpeg", "mpg", "mov", "wmv", "m4p", "swf", "mp2", "flv", "vob", "webm", "hdv", "3gp", "ogg", "mp3", "wav", "flac", "tif", "tiff", "jpg", "jpeg", "png", "gif", "zip", "webp"}
)
func SkipFile(filename string, data []byte) bool {
if filepath.Ext(filename) == "" {
//no sepcified extension, check mimetype
if filetype.IsArchive(data[:256]) {
return true
}
}
for _, ext := range IGNORED_EXTENSIONS {
if filepath.Ext(filename) == ext {
return true
}
}
return false
}

36
pkg/decoders/decoders.go Normal file
View file

@ -0,0 +1,36 @@
package decoders
import (
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
func DefaultDecoders() []Decoder {
return []Decoder{
&Plain{},
}
}
type Decoder interface {
FromChunk(chunk *sources.Chunk) *sources.Chunk
}
// Fuzz is an entrypoint for go-fuzz, which is an AFL-style fuzzing tool.
// This one attempts to uncover any panics during decoding.
func Fuzz(data []byte) int {
decoded := false
for i, decoder := range DefaultDecoders() {
// Skip the first decoder (plain), because it will always decode and give
// priority to the input (return 1)
if i == 0 {
continue
}
chunk := decoder.FromChunk(&sources.Chunk{Data: data})
if chunk != nil {
decoded = true
}
}
if decoded {
return 1 // prioritize the input
}
return -1 // don't add input to the corpus
}

14
pkg/decoders/plain.go Normal file
View file

@ -0,0 +1,14 @@
package decoders
import (
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
// Ensure the Decoder satisfies the interface at compile time
var _ Decoder = (*Plain)(nil)
type Plain struct{}
func (d *Plain) FromChunk(chunk *sources.Chunk) *sources.Chunk {
return chunk
}

View file

@ -0,0 +1,77 @@
package detectors
import (
"context"
"github.com/trufflesecurity/trufflehog/pkg/pb/detectorspb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
// Detector defines and interface for scanning for and verifying secrets.
type Detector interface {
// FromData will scan bytes for results, and optionally verify them.
FromData(ctx context.Context, verify bool, data []byte) ([]Result, error)
// Keywords are used for efficiently pre-filtering chunks using substring operations.
// Use unique identifiers that are part of the secret if you can, or the provider name.
Keywords() []string
}
type Result struct {
// DetectorType is the type of Detector.
DetectorType detectorspb.DetectorType
Verified bool
// Raw contains the raw secret identifier data. Prefer IDs over secrets since it is used for deduping after hashing.
Raw []byte
// Redacted contains the redacted version of the raw secret identification data for display purposes.
// A secret ID should be used if available.
Redacted string
ExtraData map[string]string
StructuredData *detectorspb.StructuredData
}
type ResultWithMetadata struct {
// SourceMetadata contains source-specific contextual information
SourceMetadata *source_metadatapb.MetaData
// SourceID is the ID of the source that the API uses to map secrets to specific sources.
SourceID int64
// SourceType is the type of Source.
SourceType sourcespb.SourceType
// SourceName is the name of the Source.
SourceName string
Result
}
func CopyMetadata(chunk *sources.Chunk, result Result) ResultWithMetadata {
return ResultWithMetadata{
SourceMetadata: chunk.SourceMetadata,
SourceID: chunk.SourceID,
SourceType: chunk.SourceType,
SourceName: chunk.SourceName,
Result: result,
}
}
// CleanResults returns all verified secrets, and if there are no verified secrets,
// just one unverified secret if there are any.
func CleanResults(results []Result) []Result {
if len(results) == 0 {
return results
}
var cleaned = make([]Result, 0)
for _, s := range results {
if s.Verified {
cleaned = append(cleaned, s)
}
}
if len(cleaned) == 0 {
return results[:1]
}
return cleaned
}

View file

@ -0,0 +1,57 @@
package testdetector
import (
"context"
"regexp"
"strings"
"github.com/trufflesecurity/trufflehog/pkg/common"
"github.com/trufflesecurity/trufflehog/pkg/detectors"
"github.com/trufflesecurity/trufflehog/pkg/pb/detectorspb"
)
type Detector struct{}
// Ensure the Detector satisfies the interface at compile time
var _ detectors.Detector = (*Detector)(nil)
var (
client = common.SaneHttpClient()
// Make sure that your group is surrounded in boundry characters such as below to reduce false positives
keyPat = regexp.MustCompile(`\b(test)\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (d Detector) Keywords() []string {
return []string{"test"}
}
// FromData will find and optionally verify testdetector secrets in a given set of bytes.
func (d Detector) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Raw: []byte(resMatch),
}
if verify {
s1.Verified = true
}
results = append(results, s1)
}
return detectors.CleanResults(results), nil
}

12
pkg/engine/defaults.go Normal file
View file

@ -0,0 +1,12 @@
package engine
import (
"github.com/trufflesecurity/trufflehog/pkg/detectors"
"github.com/trufflesecurity/trufflehog/pkg/detectors/testdetector"
)
func DefaultDetectors() []detectors.Detector {
return []detectors.Detector{
&testdetector.Detector{},
}
}

140
pkg/engine/engine.go Normal file
View file

@ -0,0 +1,140 @@
package engine
import (
"context"
"runtime"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/decoders"
"github.com/trufflesecurity/trufflehog/pkg/detectors"
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
type Engine struct {
concurrency int
chunks chan *sources.Chunk
results chan detectors.ResultWithMetadata
decoders []decoders.Decoder
detectors map[bool][]detectors.Detector
}
type EngineOption func(*Engine)
func WithConcurrency(concurrency int) EngineOption {
return func(e *Engine) {
e.concurrency = concurrency
}
}
func WithDetectors(verify bool, d ...detectors.Detector) EngineOption {
return func(e *Engine) {
if e.detectors == nil {
e.detectors = make(map[bool][]detectors.Detector)
}
if e.detectors[verify] == nil {
e.detectors[verify] = []detectors.Detector{}
}
e.detectors[verify] = append(e.detectors[verify], d...)
}
}
func WithDecoders(decoders ...decoders.Decoder) EngineOption {
return func(e *Engine) {
e.decoders = decoders
}
}
func Start(ctx context.Context, options ...EngineOption) *Engine {
e := &Engine{
chunks: make(chan *sources.Chunk),
results: make(chan detectors.ResultWithMetadata),
}
for _, option := range options {
option(e)
}
// set defaults
if e.concurrency == 0 {
numCPU := runtime.NumCPU()
logrus.Warn("No concurrency specified, defaulting to ", numCPU)
e.concurrency = numCPU
}
var workerWg sync.WaitGroup
for i := 0; i < e.concurrency; i++ {
workerWg.Add(1)
go func() {
e.detectorWorker(ctx)
workerWg.Done()
}()
}
go func() {
// close results chan when all workers are done
workerWg.Wait()
// not entirely sure why results don't get processed without this pause
// since we've put all results on the channel at this point.
time.Sleep(time.Second)
close(e.ResultsChan())
}()
if len(e.decoders) == 0 {
e.decoders = decoders.DefaultDecoders()
}
if len(e.detectors) == 0 {
e.detectors = map[bool][]detectors.Detector{}
e.detectors[true] = DefaultDetectors()
}
return e
}
func (e *Engine) ChunksChan() chan *sources.Chunk {
return e.chunks
}
func (e *Engine) ResultsChan() chan detectors.ResultWithMetadata {
return e.results
}
func (e *Engine) detectorWorker(ctx context.Context) {
for chunk := range e.chunks {
for _, decoder := range e.decoders {
decoded := decoder.FromChunk(chunk)
if decoded == nil {
continue
}
dataLower := strings.ToLower(string(decoded.Data))
for verify, detectorsSet := range e.detectors {
for _, detector := range detectorsSet {
foundKeyword := false
for _, kw := range detector.Keywords() {
if strings.Contains(dataLower, strings.ToLower(kw)) {
foundKeyword = true
break
}
}
if !foundKeyword {
continue
}
results, err := detector.FromData(ctx, verify, decoded.Data)
if err != nil {
logrus.WithError(err).Error("could not scan chunk")
continue
}
for _, result := range results {
e.results <- detectors.CopyMetadata(chunk, result)
}
}
}
}
}
}

111
pkg/engine/git.go Normal file
View file

@ -0,0 +1,111 @@
package engine
import (
"context"
"errors"
"fmt"
"runtime"
"strings"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sources/git"
)
func (e *Engine) ScanGit(ctx context.Context, gitScanURI, gitScanBranch, headRef string) error {
var path string
switch {
case strings.HasPrefix(gitScanURI, "file://"):
path = strings.TrimPrefix(gitScanURI, "file://")
case strings.HasPrefix(gitScanURI, "https://"):
// TODO: clone repo and get path
return errors.New("TODO: clone repo and get path")
default:
logrus.Fatalf("Unsupported Git URI: %s", gitScanURI)
}
repo, err := gogit.PlainOpenWithOptions(path, &gogit.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return fmt.Errorf("could open repo: %s: %w", path, err)
}
scanOptions := &gogit.LogOptions{
All: true,
Order: gogit.LogOrderCommitterTime,
}
if gitScanBranch != "" {
baseHash, err := git.TryAdditionalBaseRefs(repo, gitScanBranch)
if err != nil {
return fmt.Errorf("could not parse base revision: %q: %w", gitScanBranch, err)
}
headHash, err := repo.ResolveRevision(plumbing.Revision(headRef))
if err != nil {
return fmt.Errorf("could not parse revision: %q: %w", headRef, err)
}
baseCommit, err := repo.CommitObject(*baseHash)
if err != nil {
return fmt.Errorf("could not find commit: %q: %w", headRef, err)
}
logrus.WithFields(logrus.Fields{
"commit": baseCommit.Hash.String(),
}).Debug("resolved base reference")
headCommit, err := repo.CommitObject(*headHash)
if err != nil {
return fmt.Errorf("could not find commit: %q: %w", headRef, err)
}
logrus.WithFields(logrus.Fields{
"commit": headCommit.Hash.String(),
}).Debug("resolved head reference")
mergeBase, err := baseCommit.MergeBase(headCommit)
if err != nil {
return fmt.Errorf("could not find common base between the given references: %q: %w", headRef, err)
}
if len(mergeBase) == 0 {
return fmt.Errorf("no common mergeable base between the given references: %q: %w", headRef, err)
}
logrus.WithFields(logrus.Fields{
"commit": mergeBase[0].Hash.String(),
}).Debug("resolved common merge base between references")
scanOptions = &gogit.LogOptions{
From: *headHash,
Order: gogit.LogOrderCommitterTime,
}
}
gitSource := git.NewGit(sourcespb.SourceType_SOURCE_TYPE_GIT, 0, 0, "local", true, runtime.NumCPU(),
func(file, email, commit, repository string) *source_metadatapb.MetaData {
return &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Git{
Git: &source_metadatapb.Git{
Commit: commit,
File: file,
Email: email,
Repository: repository,
},
},
}
})
go func() {
err := gitSource.ScanRepo(ctx, repo, scanOptions, &object.Commit{}, e.ChunksChan())
if err != nil {
logrus.WithError(err).Fatal("could not scan repo")
}
close(e.ChunksChan())
}()
return nil
}

82
pkg/giturl/giturl.go Normal file
View file

@ -0,0 +1,82 @@
package giturl
import (
"net/url"
"strings"
"github.com/pkg/errors"
)
func NormalizeBitbucketRepo(repoURL string) (string, error) {
if !strings.HasPrefix(repoURL, "https") {
return "", errors.New("Bitbucket requires https repo urls: e.g. https://bitbucket.org/org/repo.git")
}
return NormalizeOrgRepoURL("Gitlab", repoURL)
}
func NormalizeGerritProject(project string) (string, error) {
return "", errors.Errorf("Not yet implemented")
}
func NormalizeGithubRepo(repoURL string) (string, error) {
return NormalizeOrgRepoURL("Github", repoURL)
}
func NormalizeGitlabRepo(repoURL string) (string, error) {
if !strings.HasPrefix(repoURL, "https") {
return "", errors.New("Gitlab requires https repo urls: e.g. https://gitlab.com/org/repo.git")
}
return NormalizeOrgRepoURL("Gitlab", repoURL)
}
// NormalizeOrgRepoURL attempts to normalize repos for any provider using the example.com/org/repo style.
// e.g. %s, Gitlab and Bitbucket
func NormalizeOrgRepoURL(provider, repoURL string) (string, error) {
if strings.HasSuffix(repoURL, ".git") {
return repoURL, nil
}
parsed, err := url.Parse(repoURL)
if err != nil {
return "", errors.Wrapf(err, "Unable to parse %s Repo URL", provider)
}
// The provider repo url should have a path length of 3
// 0 / 1 / 2 (3 total)
// e.g. example.com/org/repo
// If it is 1, or the 2nd section is empty, it's likely just the org.
// If it is 3, there's something at the end that shouldn't be there.
// Let the user know in any case.
switch parts := strings.Split(parsed.Path, "/"); {
case len(parts) <= 1:
return "", errors.Errorf("%s repo appears to be missing the path. Repo url: %q", provider, repoURL)
case len(parts) == 2:
org := parts[1]
if len(org) == 0 {
return "", errors.Errorf("%s repo appears to be missing the org name. Repo url: %q", provider, repoURL)
} else {
return "", errors.Errorf("%s repo appears to be missing the repo name. Org: %q Repo url: %q", provider, org, repoURL)
}
case len(parts) == 3:
org, repo := parts[1], parts[2]
if len(org) == 0 {
return "", errors.Errorf("%s repo appears to be missing the org name. Repo url: %q", provider, repoURL)
}
if len(repo) == 0 {
return "", errors.Errorf("%s repo appears to be missing the repo name. Org: %q Repo url: %q", provider, org, repoURL)
}
case len(parts) > 3:
return "", errors.Errorf("%s repo appears to be too long or contains a trailing slash. Repo url: %q", provider, repoURL)
}
// If we're here it's probably a provider repo without ".git" at the end, so add it and return
parsed.Path += ".git"
return parsed.String(), nil
}

95
pkg/giturl/giturl_test.go Normal file
View file

@ -0,0 +1,95 @@
package giturl
import (
"testing"
"github.com/pkg/errors"
)
func Test_NormalizeOrgRepoURL(t *testing.T) {
tests := map[string]struct {
Provider string
Repo string
Out string
Err error
}{
"github is good": {Provider: "Github", Repo: "https://github.com/org/repo", Out: "https://github.com/org/repo.git", Err: nil},
"gitlab is good": {Provider: "Gitlab", Repo: "https://gitlab.com/org/repo", Out: "https://gitlab.com/org/repo.git", Err: nil},
"bitbucket is good": {Provider: "Bitbucket", Repo: "https://bitbucket.com/org/repo", Out: "https://bitbucket.com/org/repo.git", Err: nil},
"example provider is good": {Provider: "example", Repo: "https://example.com/org/repo", Out: "https://example.com/org/repo.git", Err: nil},
"example provider problem": {Provider: "example", Repo: "https://example.com/org", Out: "", Err: errors.Errorf("example repo appears to be missing the repo name. Org: %q Repo url: %q", "org", "https://example.com/org")},
"no path": {Provider: "Github", Repo: "https://github.com", Out: "", Err: errors.Errorf("Github repo appears to be missing the path. Repo url: %q", "https://github.com")},
"org but no repo": {Provider: "Github", Repo: "https://github.com/org", Out: "", Err: errors.Errorf("Github repo appears to be missing the repo name. Org: %q Repo url: %q", "org", "https://github.com/org")},
"org but no repo with slash": {Provider: "Github", Repo: "https://github.com/org/", Out: "", Err: errors.Errorf("Github repo appears to be missing the repo name. Org: %q Repo url: %q", "org", "https://github.com/org/")},
"two slashes": {Provider: "Github", Repo: "https://github.com//", Out: "", Err: errors.Errorf("Github repo appears to be missing the org name. Repo url: %q", "https://github.com//")},
"repo with trailing slash": {Provider: "Github", Repo: "https://github.com/org/repo/", Out: "", Err: errors.Errorf("Github repo appears to be too long or contains a trailing slash. Repo url: %q", "https://github.com/org/repo/")},
"too many url path parts": {Provider: "Github", Repo: "https://github.com/org/repo/unknown/", Out: "", Err: errors.Errorf("Github repo appears to be too long or contains a trailing slash. Repo url: %q", "https://github.com/org/repo/unknown/")},
}
for name, tt := range tests {
out, err := NormalizeOrgRepoURL(tt.Provider, tt.Repo)
switch {
case err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err.Error(), tt.Err.Error())
case (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err, tt.Err)
}
if out != tt.Out {
t.Errorf("Test %q, output does not match expected out, got: %q want: %q", name, out, tt.Out)
}
}
}
func Test_NormalizeBitbucketRepo(t *testing.T) {
tests := map[string]struct {
Repo string
Out string
Err error
}{
"good": {Repo: "https://bitbucket.org/org/repo", Out: "https://bitbucket.org/org/repo.git", Err: nil},
"bitbucket needs https": {Repo: "git@bitbucket.org:org/repo.git", Out: "", Err: errors.New("Bitbucket requires https repo urls: e.g. https://bitbucket.org/org/repo.git")},
}
for name, tt := range tests {
out, err := NormalizeBitbucketRepo(tt.Repo)
switch {
case err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err.Error(), tt.Err.Error())
case (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err, tt.Err)
}
if out != tt.Out {
t.Errorf("Test %q, output does not match expected out, got: %q want: %q", name, out, tt.Out)
}
}
}
func Test_NormalizeGitlabRepo(t *testing.T) {
tests := map[string]struct {
Repo string
Out string
Err error
}{
"good": {Repo: "https://gitlab.com/org/repo", Out: "https://gitlab.com/org/repo.git", Err: nil},
"gitlab needs https": {Repo: "git@gitlab.com:org/repo.git:", Out: "", Err: errors.New("Gitlab requires https repo urls: e.g. https://gitlab.com/org/repo.git")},
}
for name, tt := range tests {
out, err := NormalizeGitlabRepo(tt.Repo)
switch {
case err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err.Error(), tt.Err.Error())
case (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):
t.Errorf("Test %q, error does not match expected error, \n got: %v \nwant: %v", name, err, tt.Err)
}
if out != tt.Out {
t.Errorf("Test %q, output does not match expected out, got: %q want: %q", name, out, tt.Out)
}
}
}

View file

@ -0,0 +1,839 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.1
// source: credentials.proto
package credentialspb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Unauthenticated struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Unauthenticated) Reset() {
*x = Unauthenticated{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Unauthenticated) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Unauthenticated) ProtoMessage() {}
func (x *Unauthenticated) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Unauthenticated.ProtoReflect.Descriptor instead.
func (*Unauthenticated) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{0}
}
type CloudEnvironment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *CloudEnvironment) Reset() {
*x = CloudEnvironment{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloudEnvironment) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloudEnvironment) ProtoMessage() {}
func (x *CloudEnvironment) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloudEnvironment.ProtoReflect.Descriptor instead.
func (*CloudEnvironment) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{1}
}
type BasicAuth struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
}
func (x *BasicAuth) Reset() {
*x = BasicAuth{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BasicAuth) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BasicAuth) ProtoMessage() {}
func (x *BasicAuth) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BasicAuth.ProtoReflect.Descriptor instead.
func (*BasicAuth) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{2}
}
func (x *BasicAuth) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *BasicAuth) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
type ClientCrednetials struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
ClientSecret string `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"`
}
func (x *ClientCrednetials) Reset() {
*x = ClientCrednetials{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClientCrednetials) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientCrednetials) ProtoMessage() {}
func (x *ClientCrednetials) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClientCrednetials.ProtoReflect.Descriptor instead.
func (*ClientCrednetials) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{3}
}
func (x *ClientCrednetials) GetTenantId() string {
if x != nil {
return x.TenantId
}
return ""
}
func (x *ClientCrednetials) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *ClientCrednetials) GetClientSecret() string {
if x != nil {
return x.ClientSecret
}
return ""
}
type ClientCertificate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
CertificatePath string `protobuf:"bytes,3,opt,name=certificate_path,json=certificatePath,proto3" json:"certificate_path,omitempty"`
CertificatePassword string `protobuf:"bytes,4,opt,name=certificate_password,json=certificatePassword,proto3" json:"certificate_password,omitempty"`
}
func (x *ClientCertificate) Reset() {
*x = ClientCertificate{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClientCertificate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientCertificate) ProtoMessage() {}
func (x *ClientCertificate) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClientCertificate.ProtoReflect.Descriptor instead.
func (*ClientCertificate) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{4}
}
func (x *ClientCertificate) GetTenantId() string {
if x != nil {
return x.TenantId
}
return ""
}
func (x *ClientCertificate) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *ClientCertificate) GetCertificatePath() string {
if x != nil {
return x.CertificatePath
}
return ""
}
func (x *ClientCertificate) GetCertificatePassword() string {
if x != nil {
return x.CertificatePassword
}
return ""
}
type Oauth2 struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
ClientSecret string `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"`
}
func (x *Oauth2) Reset() {
*x = Oauth2{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Oauth2) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Oauth2) ProtoMessage() {}
func (x *Oauth2) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Oauth2.ProtoReflect.Descriptor instead.
func (*Oauth2) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{5}
}
func (x *Oauth2) GetRefreshToken() string {
if x != nil {
return x.RefreshToken
}
return ""
}
func (x *Oauth2) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *Oauth2) GetClientSecret() string {
if x != nil {
return x.ClientSecret
}
return ""
}
type KeySecret struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
}
func (x *KeySecret) Reset() {
*x = KeySecret{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeySecret) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeySecret) ProtoMessage() {}
func (x *KeySecret) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeySecret.ProtoReflect.Descriptor instead.
func (*KeySecret) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{6}
}
func (x *KeySecret) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *KeySecret) GetSecret() string {
if x != nil {
return x.Secret
}
return ""
}
type AWS struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
Region string `protobuf:"bytes,3,opt,name=region,proto3" json:"region,omitempty"`
}
func (x *AWS) Reset() {
*x = AWS{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AWS) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AWS) ProtoMessage() {}
func (x *AWS) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AWS.ProtoReflect.Descriptor instead.
func (*AWS) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{7}
}
func (x *AWS) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *AWS) GetSecret() string {
if x != nil {
return x.Secret
}
return ""
}
func (x *AWS) GetRegion() string {
if x != nil {
return x.Region
}
return ""
}
type SES struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Creds *AWS `protobuf:"bytes,1,opt,name=creds,proto3" json:"creds,omitempty"`
Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
Recipients []string `protobuf:"bytes,3,rep,name=recipients,proto3" json:"recipients,omitempty"`
}
func (x *SES) Reset() {
*x = SES{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SES) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SES) ProtoMessage() {}
func (x *SES) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SES.ProtoReflect.Descriptor instead.
func (*SES) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{8}
}
func (x *SES) GetCreds() *AWS {
if x != nil {
return x.Creds
}
return nil
}
func (x *SES) GetSender() string {
if x != nil {
return x.Sender
}
return ""
}
func (x *SES) GetRecipients() []string {
if x != nil {
return x.Recipients
}
return nil
}
type GitHubApp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PrivateKey string `protobuf:"bytes,1,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"`
InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"`
}
func (x *GitHubApp) Reset() {
*x = GitHubApp{}
if protoimpl.UnsafeEnabled {
mi := &file_credentials_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GitHubApp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GitHubApp) ProtoMessage() {}
func (x *GitHubApp) ProtoReflect() protoreflect.Message {
mi := &file_credentials_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GitHubApp.ProtoReflect.Descriptor instead.
func (*GitHubApp) Descriptor() ([]byte, []int) {
return file_credentials_proto_rawDescGZIP(), []int{9}
}
func (x *GitHubApp) GetPrivateKey() string {
if x != nil {
return x.PrivateKey
}
return ""
}
func (x *GitHubApp) GetInstallationId() string {
if x != nil {
return x.InstallationId
}
return ""
}
func (x *GitHubApp) GetAppId() string {
if x != nil {
return x.AppId
}
return ""
}
var File_credentials_proto protoreflect.FileDescriptor
var file_credentials_proto_rawDesc = []byte{
0x0a, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,
0x22, 0x11, 0x0a, 0x0f, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,
0x74, 0x65, 0x64, 0x22, 0x12, 0x0a, 0x10, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x45, 0x6e, 0x76, 0x69,
0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x09, 0x42, 0x61, 0x73, 0x69, 0x63,
0x41, 0x75, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x72, 0x0a, 0x11,
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x64, 0x6e, 0x65, 0x74, 0x69, 0x61, 0x6c,
0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b,
0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
0x22, 0xab, 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e,
0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64,
0x12, 0x29, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f,
0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x65, 0x72, 0x74,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x14, 0x63,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x65, 0x72, 0x74, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x6f,
0x0a, 0x06, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72,
0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a,
0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22,
0x35, 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16,
0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x47, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f,
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x22,
0x65, 0x0a, 0x03, 0x53, 0x45, 0x53, 0x12, 0x26, 0x0a, 0x05, 0x63, 0x72, 0x65, 0x64, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x05, 0x63, 0x72, 0x65, 0x64, 0x73, 0x12, 0x16,
0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69,
0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x69,
0x70, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x6c, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62,
0x41, 0x70, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69,
0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a,
0x06, 0x61, 0x70, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61,
0x70, 0x70, 0x49, 0x64, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,
0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x70, 0x6b,
0x67, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_credentials_proto_rawDescOnce sync.Once
file_credentials_proto_rawDescData = file_credentials_proto_rawDesc
)
func file_credentials_proto_rawDescGZIP() []byte {
file_credentials_proto_rawDescOnce.Do(func() {
file_credentials_proto_rawDescData = protoimpl.X.CompressGZIP(file_credentials_proto_rawDescData)
})
return file_credentials_proto_rawDescData
}
var file_credentials_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_credentials_proto_goTypes = []interface{}{
(*Unauthenticated)(nil), // 0: credentials.Unauthenticated
(*CloudEnvironment)(nil), // 1: credentials.CloudEnvironment
(*BasicAuth)(nil), // 2: credentials.BasicAuth
(*ClientCrednetials)(nil), // 3: credentials.ClientCrednetials
(*ClientCertificate)(nil), // 4: credentials.ClientCertificate
(*Oauth2)(nil), // 5: credentials.Oauth2
(*KeySecret)(nil), // 6: credentials.KeySecret
(*AWS)(nil), // 7: credentials.AWS
(*SES)(nil), // 8: credentials.SES
(*GitHubApp)(nil), // 9: credentials.GitHubApp
}
var file_credentials_proto_depIdxs = []int32{
7, // 0: credentials.SES.creds:type_name -> credentials.AWS
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_credentials_proto_init() }
func file_credentials_proto_init() {
if File_credentials_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_credentials_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Unauthenticated); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CloudEnvironment); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BasicAuth); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClientCrednetials); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClientCertificate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Oauth2); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeySecret); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AWS); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SES); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_credentials_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GitHubApp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_credentials_proto_rawDesc,
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_credentials_proto_goTypes,
DependencyIndexes: file_credentials_proto_depIdxs,
MessageInfos: file_credentials_proto_msgTypes,
}.Build()
File_credentials_proto = out.File
file_credentials_proto_rawDesc = nil
file_credentials_proto_goTypes = nil
file_credentials_proto_depIdxs = nil
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,546 @@
// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: detectors.proto
package detectorspb
import (
"bytes"
"errors"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
"google.golang.org/protobuf/types/known/anypb"
)
// ensure the imports are used
var (
_ = bytes.MinRead
_ = errors.New("")
_ = fmt.Print
_ = utf8.UTFMax
_ = (*regexp.Regexp)(nil)
_ = (*strings.Reader)(nil)
_ = net.IPv4len
_ = time.Duration(0)
_ = (*url.URL)(nil)
_ = (*mail.Address)(nil)
_ = anypb.Any{}
_ = sort.Sort
)
// Validate checks the field values on Result with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *Result) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on Result with the rules defined in the
// proto definition for this message. If any rules are violated, the result is
// a list of violation errors wrapped in ResultMultiError, or nil if none found.
func (m *Result) ValidateAll() error {
return m.validate(true)
}
func (m *Result) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for SourceId
// no validation rules for Redacted
// no validation rules for Verified
// no validation rules for Hash
// no validation rules for ExtraData
if all {
switch v := interface{}(m.GetStructuredData()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, ResultValidationError{
field: "StructuredData",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, ResultValidationError{
field: "StructuredData",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetStructuredData()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return ResultValidationError{
field: "StructuredData",
reason: "embedded message failed validation",
cause: err,
}
}
}
if len(errors) > 0 {
return ResultMultiError(errors)
}
return nil
}
// ResultMultiError is an error wrapping multiple validation errors returned by
// Result.ValidateAll() if the designated constraints aren't met.
type ResultMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ResultMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m ResultMultiError) AllErrors() []error { return m }
// ResultValidationError is the validation error returned by Result.Validate if
// the designated constraints aren't met.
type ResultValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ResultValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ResultValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ResultValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ResultValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ResultValidationError) ErrorName() string { return "ResultValidationError" }
// Error satisfies the builtin error interface
func (e ResultValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sResult.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ResultValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ResultValidationError{}
// Validate checks the field values on StructuredData with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *StructuredData) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on StructuredData with the rules defined
// in the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in StructuredDataMultiError,
// or nil if none found.
func (m *StructuredData) ValidateAll() error {
return m.validate(true)
}
func (m *StructuredData) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
for idx, item := range m.GetTlsPrivateKey() {
_, _ = idx, item
if all {
switch v := interface{}(item).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, StructuredDataValidationError{
field: fmt.Sprintf("TlsPrivateKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, StructuredDataValidationError{
field: fmt.Sprintf("TlsPrivateKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return StructuredDataValidationError{
field: fmt.Sprintf("TlsPrivateKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
}
}
}
}
for idx, item := range m.GetGithubSshKey() {
_, _ = idx, item
if all {
switch v := interface{}(item).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, StructuredDataValidationError{
field: fmt.Sprintf("GithubSshKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, StructuredDataValidationError{
field: fmt.Sprintf("GithubSshKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return StructuredDataValidationError{
field: fmt.Sprintf("GithubSshKey[%v]", idx),
reason: "embedded message failed validation",
cause: err,
}
}
}
}
if len(errors) > 0 {
return StructuredDataMultiError(errors)
}
return nil
}
// StructuredDataMultiError is an error wrapping multiple validation errors
// returned by StructuredData.ValidateAll() if the designated constraints
// aren't met.
type StructuredDataMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m StructuredDataMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m StructuredDataMultiError) AllErrors() []error { return m }
// StructuredDataValidationError is the validation error returned by
// StructuredData.Validate if the designated constraints aren't met.
type StructuredDataValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e StructuredDataValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e StructuredDataValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e StructuredDataValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e StructuredDataValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e StructuredDataValidationError) ErrorName() string { return "StructuredDataValidationError" }
// Error satisfies the builtin error interface
func (e StructuredDataValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sStructuredData.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = StructuredDataValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = StructuredDataValidationError{}
// Validate checks the field values on TlsPrivateKey with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *TlsPrivateKey) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on TlsPrivateKey with the rules defined
// in the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in TlsPrivateKeyMultiError, or
// nil if none found.
func (m *TlsPrivateKey) ValidateAll() error {
return m.validate(true)
}
func (m *TlsPrivateKey) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for CertificateFingerprint
// no validation rules for VerificationUrl
// no validation rules for ExpirationTimestamp
if len(errors) > 0 {
return TlsPrivateKeyMultiError(errors)
}
return nil
}
// TlsPrivateKeyMultiError is an error wrapping multiple validation errors
// returned by TlsPrivateKey.ValidateAll() if the designated constraints
// aren't met.
type TlsPrivateKeyMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m TlsPrivateKeyMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m TlsPrivateKeyMultiError) AllErrors() []error { return m }
// TlsPrivateKeyValidationError is the validation error returned by
// TlsPrivateKey.Validate if the designated constraints aren't met.
type TlsPrivateKeyValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e TlsPrivateKeyValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e TlsPrivateKeyValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e TlsPrivateKeyValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e TlsPrivateKeyValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e TlsPrivateKeyValidationError) ErrorName() string { return "TlsPrivateKeyValidationError" }
// Error satisfies the builtin error interface
func (e TlsPrivateKeyValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sTlsPrivateKey.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = TlsPrivateKeyValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = TlsPrivateKeyValidationError{}
// Validate checks the field values on GitHubSSHKey with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *GitHubSSHKey) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on GitHubSSHKey with the rules defined
// in the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in GitHubSSHKeyMultiError, or
// nil if none found.
func (m *GitHubSSHKey) ValidateAll() error {
return m.validate(true)
}
func (m *GitHubSSHKey) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for User
// no validation rules for PublicKeyFingerprint
if len(errors) > 0 {
return GitHubSSHKeyMultiError(errors)
}
return nil
}
// GitHubSSHKeyMultiError is an error wrapping multiple validation errors
// returned by GitHubSSHKey.ValidateAll() if the designated constraints aren't met.
type GitHubSSHKeyMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m GitHubSSHKeyMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m GitHubSSHKeyMultiError) AllErrors() []error { return m }
// GitHubSSHKeyValidationError is the validation error returned by
// GitHubSSHKey.Validate if the designated constraints aren't met.
type GitHubSSHKeyValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e GitHubSSHKeyValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e GitHubSSHKeyValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e GitHubSSHKeyValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e GitHubSSHKeyValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e GitHubSSHKeyValidationError) ErrorName() string { return "GitHubSSHKeyValidationError" }
// Error satisfies the builtin error interface
func (e GitHubSSHKeyValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sGitHubSSHKey.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = GitHubSSHKeyValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = GitHubSSHKeyValidationError{}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

9
pkg/sanitizer/utf8.go Normal file
View file

@ -0,0 +1,9 @@
package sanitizer
import (
"strings"
)
func UTF8(in string) string {
return strings.Replace(strings.ToValidUTF8(in, "❗"), "\x00", "", -1)
}

View file

@ -0,0 +1,43 @@
package sanitizer
import "testing"
func TestUTF8(t *testing.T) {
type args struct {
in string
}
tests := []struct {
name string
args args
want string
}{
{
name: "valid",
args: args{
in: "hello123",
},
want: "hello123",
},
{
name: "santized",
args: args{
in: "Gr\351gory Smith",
},
want: "Gr❗gory Smith",
},
{
name: "santized",
args: args{
in: "no \x00 nulls because postgres does not support it in text fields",
},
want: "no nulls because postgres does not support it in text fields",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := UTF8(tt.args.in); got != tt.want {
t.Errorf("UTF8() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,185 @@
package filesystem
import (
"bufio"
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"github.com/go-errors/errors"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/pkg/common"
"github.com/trufflesecurity/trufflehog/pkg/sanitizer"
"github.com/trufflesecurity/trufflehog/pkg/sources"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
)
const (
// These buffer sizes are mainly driven by our largest credential size, which is GCP @ ~2.25KB.
// Having a peek size larger than that ensures that we have complete credential coverage in our chunks.
BufferSize = 10 * 1024 // 10KB
PeekSize = 3 * 1024 // 3KB
)
type Source struct {
name string
sourceId int64
jobId int64
verify bool
paths []string
aCtx context.Context
log *log.Entry
sources.Progress
}
// Ensure the Source satisfies the interface at compile time
var _ sources.Source = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
func (s *Source) Type() sourcespb.SourceType {
return sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM
}
func (s *Source) SourceID() int64 {
return s.sourceId
}
func (s *Source) JobID() int64 {
return s.jobId
}
// Init returns an initialized Filesystem source.
func (s *Source) Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error {
s.log = log.WithField("source", s.Type()).WithField("name", name)
s.aCtx = aCtx
s.name = name
s.sourceId = sourceId
s.jobId = jobId
s.verify = verify
var conn sourcespb.Filesystem
err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})
if err != nil {
errors.WrapPrefix(err, "error unmarshalling connection", 0)
}
s.paths = conn.Directories
return nil
}
func isDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), err
}
// Chunks emits chunks of bytes over a channel.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error {
for i, path := range s.paths {
s.SetProgressComplete(i, len(s.paths), fmt.Sprintf("Path: %s", path))
cleanPath := filepath.Clean(path)
done := false
go func() {
<-ctx.Done()
done = true
}()
err := fs.WalkDir(os.DirFS(cleanPath), ".", func(relativePath string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
path := filepath.Join(cleanPath, relativePath)
if ok, _ := isDirectory(path); ok {
return nil
}
inputFile, err := os.Open(path)
if err != nil {
log.Warn(err)
return nil
}
defer inputFile.Close()
reader := bufio.NewReaderSize(bufio.NewReader(inputFile), BufferSize)
firstChunk := true
for {
if done {
return nil
}
end := BufferSize
buf := make([]byte, BufferSize)
n, err := reader.Read(buf)
if n < BufferSize {
end = n
}
if end > 0 {
data := buf[0:end]
if firstChunk {
firstChunk = false
if common.SkipFile(path, data) {
return nil
}
}
// We are peeking in case a secret exists in our chunk boundaries,
// but we never care if we've run into a peek error.
peekData, _ := reader.Peek(PeekSize)
chunksChan <- &sources.Chunk{
SourceType: s.Type(),
SourceName: s.name,
SourceID: s.SourceID(),
Data: append(data, peekData...),
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Filesystem{
Filesystem: &source_metadatapb.Filesystem{
File: sanitizer.UTF8(path),
},
},
},
Verify: s.verify,
}
}
// io.EOF can be emmitted when 0<n<buffer size
if err != nil {
if errors.Is(err, io.EOF) {
return nil
} else {
return err
}
}
}
})
if err != nil && err != io.EOF {
return errors.New(err)
}
if done {
return nil
}
}
return nil
}

View file

@ -0,0 +1,84 @@
package filesystem
import (
"context"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"google.golang.org/protobuf/types/known/anypb"
log "github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
func TestSource_Scan(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
type init struct {
name string
verify bool
connection *sourcespb.Filesystem
}
tests := []struct {
name string
init init
wantSourceMetadata *source_metadatapb.MetaData
wantErr bool
}{
{
name: "get a chunk",
init: init{
name: "this repo",
connection: &sourcespb.Filesystem{
Directories: []string{"."},
},
verify: true,
},
wantSourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Filesystem{
Filesystem: &source_metadatapb.Filesystem{
File: "filesystem.go",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{ForceColors: true})
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
return
}
chunksCh := make(chan *sources.Chunk, 1)
// TODO: this is kind of bad, if it errors right away we don't see it as a test failure.
// Debugging this usually requires setting a breakpoint on L78 and running test w/ debug.
go func() {
err = s.Chunks(ctx, chunksCh)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Chunks() error = %v, wantErr %v", err, tt.wantErr)
return
}
}()
gotChunk := <-chunksCh
if diff := pretty.Compare(gotChunk.SourceMetadata, tt.wantSourceMetadata); diff != "" {
t.Errorf("Source.Chunks() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}

541
pkg/sources/git/git.go Normal file
View file

@ -0,0 +1,541 @@
package git
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/go-errors/errors"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/diff"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/plumbing/transport/http"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sanitizer"
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
type Source struct {
name string
sourceId int64
jobId int64
verify bool
git *Git
aCtx context.Context
sources.Progress
conn *sourcespb.Git
}
type Git struct {
sourceType sourcespb.SourceType
sourceName string
sourceID int64
jobID int64
sourceMetadataFunc func(file, email, commit, repository string) *source_metadatapb.MetaData
verify bool
// sem is used to limit concurrency
sem *semaphore.Weighted
}
func NewGit(sourceType sourcespb.SourceType, jobID, sourceID int64, sourceName string, verify bool, concurrency int,
sourceMetadataFunc func(file, email, commit, repository string) *source_metadatapb.MetaData,
) *Git {
return &Git{
sourceType: sourceType,
sourceName: sourceName,
sourceID: sourceID,
jobID: jobID,
sourceMetadataFunc: sourceMetadataFunc,
verify: verify,
sem: semaphore.NewWeighted(int64(concurrency)),
}
}
// Ensure the Source satisfies the interface at compile time
var _ sources.Source = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
func (s *Source) Type() sourcespb.SourceType {
return sourcespb.SourceType_SOURCE_TYPE_GIT
}
func (s *Source) SourceID() int64 {
return s.sourceId
}
func (s *Source) JobID() int64 {
return s.jobId
}
// Init returns an initialized GitHub source.
func (s *Source) Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error {
s.aCtx = aCtx
s.name = name
s.sourceId = sourceId
s.jobId = jobId
s.verify = verify
var conn sourcespb.Git
err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})
if err != nil {
errors.WrapPrefix(err, "error unmarshalling connection", 0)
}
s.conn = &conn
s.git = NewGit(s.Type(), s.jobId, s.sourceId, s.name, s.verify, runtime.NumCPU(),
func(file, email, commit, repository string) *source_metadatapb.MetaData {
return &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Git{
Git: &source_metadatapb.Git{
Commit: sanitizer.UTF8(commit),
File: sanitizer.UTF8(file),
Email: sanitizer.UTF8(email),
Repository: sanitizer.UTF8(repository),
},
},
}
})
return nil
}
// Chunks emits chunks of bytes over a channel.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error {
switch cred := s.conn.GetCredential().(type) {
case *sourcespb.Git_BasicAuth:
user := cred.BasicAuth.Username
token := cred.BasicAuth.Password
for i, repoURI := range s.conn.Repositories {
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", repoURI))
if len(repoURI) == 0 {
continue
}
path, repo, err := CloneRepoUsingToken(token, repoURI, user)
defer os.RemoveAll(path)
if err != nil {
return err
}
err = s.git.ScanRepo(ctx, repo, &git.LogOptions{All: true}, nil, chunksChan)
if err != nil {
return err
}
}
case *sourcespb.Git_Unauthenticated:
for i, repoURI := range s.conn.Repositories {
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", repoURI))
if len(repoURI) == 0 {
continue
}
path, repo, err := CloneRepoUsingUnauthenticated(repoURI)
defer os.RemoveAll(path)
if err != nil {
return err
}
err = s.git.ScanRepo(ctx, repo, &git.LogOptions{All: true}, nil, chunksChan)
if err != nil {
return err
}
}
default:
return errors.New("invalid connection type for git source")
}
for i, u := range s.conn.Directories {
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", u))
if len(u) == 0 {
continue
}
if !strings.HasSuffix(u, "git") {
//try paths instead of url
repo, err := RepoFromPath(u)
if err != nil {
return err
}
if strings.HasPrefix(u, filepath.Join(os.TempDir(), "trufflehog")) {
defer os.RemoveAll(u)
}
err = s.git.ScanRepo(ctx, repo, &git.LogOptions{All: true}, nil, chunksChan)
if err != nil {
return err
}
}
}
return nil
}
func RepoFromPath(path string) (*git.Repository, error) {
return git.PlainOpen(path)
}
func CloneRepoUsingToken(token, url, user string) (clonePath string, repo *git.Repository, err error) {
log.Debugf("Scanning Repo: %s", url)
if user == "" {
user = "cloner"
}
//some git clones require username not just token
cloneOptions := &git.CloneOptions{
URL: url,
Auth: &http.BasicAuth{
Username: user,
Password: token,
},
}
clonePath, err = ioutil.TempDir(os.TempDir(), "trufflehog")
if err != nil {
return
}
repo, err = git.PlainClone(clonePath, false, cloneOptions)
safeRepo, err := stripPassword(url)
if err != nil {
err = errors.New(err)
return
}
if _, ok := err.(*os.PathError); ok {
log.WithField("repo", safeRepo).WithError(err).Error("error cloning repo")
}
if err != nil && strings.Contains(err.Error(), "cannot read hash, pkt-line too short") {
log.WithField("repo", safeRepo).WithError(err).Error("error cloning repo")
}
return
}
func CloneRepoUsingUnauthenticated(url string) (clonePath string, repo *git.Repository, err error) {
cloneOptions := &git.CloneOptions{
URL: url,
}
clonePath, err = ioutil.TempDir(os.TempDir(), "trufflehog")
if err != nil {
return
}
repo, err = git.PlainClone(clonePath, false, cloneOptions)
safeRepo, err := stripPassword(url)
if err != nil {
err = errors.New(err)
return
}
if _, ok := err.(*os.PathError); ok {
log.WithField("repo", safeRepo).WithError(err).Error("error cloning repo")
}
if err != nil && strings.Contains(err.Error(), "cannot read hash, pkt-line too short") {
log.WithField("repo", safeRepo).WithError(err).Error("error cloning repo")
}
return
}
func (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, opts *git.LogOptions, untilCommit *object.Commit, chunksChan chan *sources.Chunk) error {
wg := &sync.WaitGroup{}
remote, err := repo.Remote("origin")
if err != nil {
return errors.New(err)
}
safeRepo, err := stripPassword(remote.Config().URLs[0])
if err != nil {
return errors.New(err)
}
commitIter, err := repo.Log(opts)
if err != nil {
return errors.New(err)
}
scanOneCommit := false
if untilCommit != nil && opts.From.String() == untilCommit.Hash.String() {
// Our head and base commits are the same, so scan the one commit
scanOneCommit = true
}
breakIteration := false
err = commitIter.ForEach(func(commit *object.Commit) error {
if breakIteration {
return storer.ErrStop
}
if scanOneCommit {
breakIteration = true
}
if untilCommit != nil && commit.Hash == untilCommit.Hash && !scanOneCommit {
return storer.ErrStop
}
// err := s.sem.Acquire(ctx, 1)
// if err != nil {
// return err
// }
// wg.Add(1)
// go func(wg *sync.WaitGroup) {
// defer wg.Done()
// defer s.sem.Release(1)
err = s.scanCommitPatches(ctx, repo, commit, chunksChan)
if err != nil {
switch e := err.Error(); {
case strings.Contains(e, "operation canceled"):
log.WithError(err).
WithField("repo", safeRepo).
WithField("commit", commit.Hash.String()).
Warn("commit took too long to compute")
return nil
case strings.Contains(e, "packfile not found"):
log.WithError(err).WithField("repo", safeRepo).Warn("invalid commit reference while scanning commit")
return nil
default:
log.WithError(err).WithField("repo", safeRepo).Error("unhandled error scanning commit")
return err
}
}
// }(wg)
return nil
})
if err != nil {
// https://github.com/src-d/go-git/issues/879
if strings.Contains(err.Error(), "object not found") {
log.WithError(err).Error("known issue: probably caused by a dangling reference in the repo")
} else {
return errors.New(err)
}
}
// Also scan any unstaged changes in the working tree of the repo
_, err = repo.Head()
if err == nil || err == plumbing.ErrReferenceNotFound {
wt, err := repo.Worktree()
if err != nil {
log.WithError(err).Error("error obtaining repo worktree")
return err
}
status, err := wt.Status()
if err != nil {
log.WithError(err).Error("error obtaining worktree status")
return err
}
for fh := range status {
metadata := s.sourceMetadataFunc(
fh, "unstaged", "unstaged", safeRepo,
)
fileBuf := bytes.NewBuffer(nil)
fileHandle, err := wt.Filesystem.Open(fh)
if err != nil {
continue
}
defer fileHandle.Close()
_, err = io.Copy(fileBuf, fileHandle)
if err != nil {
continue
}
chunksChan <- &sources.Chunk{
SourceType: s.sourceType,
SourceName: s.sourceName,
SourceID: s.sourceID,
Data: fileBuf.Bytes(),
SourceMetadata: metadata,
Verify: s.verify,
}
}
}
wg.Wait()
return nil
}
//GenerateLink crafts a link to the specific file from a commit. This works in most major git providers (Github/Gitlab)
func GenerateLink(repo, commit, file string) string {
//bitbucket links are commits not commit...
if strings.Contains(repo, "bitbucket.org/") {
return repo[:len(repo)-4] + "/commits/" + commit
}
link := repo[:len(repo)-4] + "/blob/" + commit + "/" + file
if file == "" {
link = repo[:len(repo)-4] + "/commit/" + commit
}
return link
}
func (s *Git) scanCommitPatches(ctx context.Context, repo *git.Repository, commit *object.Commit, chunksChan chan *sources.Chunk) error {
defer func() {
if err := recover(); err != nil {
return
}
}()
// If there are no parents, just scan all files present in the commit
//log.Debugf("scanning: %v : %s", repo, commit.Hash)
if len(commit.ParentHashes) == 0 {
err := s.scanFilesForCommit(ctx, repo, commit, chunksChan)
if err != nil {
return errors.New(err)
}
return nil
}
parent, err := commit.Parent(0)
if err != nil {
return errors.New(err)
}
remote, err := repo.Remote("origin")
if err != nil {
return errors.New(err)
}
safeRepo, err := stripPassword(remote.Config().URLs[0])
if err != nil {
return errors.New(err)
}
patchCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
patch, err := parent.PatchContext(patchCtx, commit)
if err != nil {
if errors.Is(context.DeadlineExceeded, err) {
return nil
}
return errors.New(err)
}
email := commit.Author.Email
commitHash := commit.Hash.String()
for _, file := range patch.FilePatches() {
bf, f := file.Files()
filename := f.Path()
if filename == "" {
filename = bf.Path()
}
metadata := s.sourceMetadataFunc(
filename, email, commitHash, safeRepo,
)
chunk := bytes.NewBuffer(nil)
// This makes a chunk for every section of the diff, so lots of little changes in a file can produce a lot of chunks.
for _, patchChunk := range file.Chunks() {
if patchChunk.Type() != diff.Add {
continue
}
// I wonder if we can eliminate this string conversion
chunk.Write([]byte(patchChunk.Content()))
}
chunksChan <- &sources.Chunk{
SourceType: s.sourceType,
SourceName: s.sourceName,
SourceID: s.sourceID,
Data: chunk.Bytes(),
SourceMetadata: metadata,
Verify: s.verify,
}
}
return nil
}
func (s *Git) scanFilesForCommit(ctx context.Context, repo *git.Repository, commit *object.Commit, chunksChan chan *sources.Chunk) error {
fileIter, err := commit.Files()
if err != nil {
return errors.New(err)
}
remote, err := repo.Remote("origin")
if err != nil {
return errors.New(err)
}
safeRepo, err := stripPassword(remote.Config().URLs[0])
if err != nil {
return errors.New(err)
}
err = fileIter.ForEach(func(f *object.File) error {
isBinary, err := f.IsBinary()
if isBinary {
return nil
}
if err != nil {
return errors.New(err)
}
chunkStr, err := f.Contents()
if err != nil {
return errors.New(err)
}
chunksChan <- &sources.Chunk{
SourceType: s.sourceType,
SourceName: s.sourceName,
SourceID: s.sourceID,
Data: []byte(chunkStr),
SourceMetadata: s.sourceMetadataFunc(
f.Name, commit.Author.Email, commit.Hash.String(), safeRepo,
),
Verify: s.verify,
}
return nil
})
return err
}
func stripPassword(u string) (string, error) {
if strings.HasPrefix(u, "git@") {
return u, nil
}
repoURL, err := url.Parse(u)
if err != nil {
return "", errors.WrapPrefix(err, "repo remote cannot be sanitized as URI", 0)
}
_, passSet := repoURL.User.Password()
if passSet {
return strings.Replace(repoURL.String(), repoURL.User.String()+"@", repoURL.User.Username()+":***@", 1), nil
}
return repoURL.String(), nil
}
func TryAdditionalBaseRefs(repo *git.Repository, base string) (*plumbing.Hash, error) {
revisionPrefixes := []string{
"",
"refs/heads/",
"refs/remotes/origin/",
}
for _, prefix := range revisionPrefixes {
outHash, err := repo.ResolveRevision(plumbing.Revision(prefix + base))
if err == plumbing.ErrReferenceNotFound {
continue
}
if err != nil {
return nil, err
}
return outHash, nil
}
return nil, fmt.Errorf("no base refs succeeded for base: %q", base)
}

316
pkg/sources/git/git_test.go Normal file
View file

@ -0,0 +1,316 @@
package git
import (
"bytes"
"context"
"strings"
"testing"
"github.com/kylelemons/godebug/pretty"
log "github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/pb/credentialspb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sources"
"google.golang.org/protobuf/types/known/anypb"
)
func TestSource_Scan(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
type init struct {
name string
verify bool
connection *sourcespb.Git
}
tests := []struct {
name string
init init
wantChunk *sources.Chunk
wantErr bool
}{
{
name: "local repo",
init: init{
name: "this repo",
connection: &sourcespb.Git{
Directories: []string{"../../../../"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,
SourceName: "this repo",
Verify: false,
},
wantErr: false,
},
{
name: "remote repo, unauthenticated",
init: init{
name: "test source",
connection: &sourcespb.Git{
Repositories: []string{"https://github.com/dustin-decker/secretsandstuff.git"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,
SourceName: "test source",
Verify: false,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
return
}
chunksCh := make(chan *sources.Chunk, 1)
go func() {
s.Chunks(ctx, chunksCh)
}()
gotChunk := <-chunksCh
gotChunk.Data = nil
// Commits don't come in a deterministic order, so remove metadata comparison
gotChunk.SourceMetadata = nil
if diff := pretty.Compare(gotChunk, tt.wantChunk); diff != "" {
t.Errorf("Source.Chunks() %s diff: (-got +want)\n%s", tt.name, diff)
t.Errorf("Data: %s", string(gotChunk.Data))
}
})
}
}
func Test_generateLink(t *testing.T) {
type args struct {
repo string
commit string
file string
}
tests := []struct {
name string
args args
want string
}{
{
name: "test link gen",
args: args{
repo: "https://github.com/trufflesec-julian/confluence-go-api.git",
commit: "047b4a2ba42fc5b6c0bd535c5307434a666db5ec",
file: ".gitignore",
},
want: "https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore",
},
{
name: "test link gen - no file",
args: args{
repo: "https://github.com/trufflesec-julian/confluence-go-api.git",
commit: "047b4a2ba42fc5b6c0bd535c5307434a666db5ec",
},
want: "https://github.com/trufflesec-julian/confluence-go-api/commit/047b4a2ba42fc5b6c0bd535c5307434a666db5ec",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GenerateLink(tt.args.repo, tt.args.commit, tt.args.file); got != tt.want {
t.Errorf("generateLink() = %v, want %v", got, tt.want)
}
})
}
}
// We ran into an issue where upgrading a dependency caused the git patch chunking to break
// So this test exists to make sure that when something changes, we know about it.
func TestSource_Chunks_Integration(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
type init struct {
name string
verify bool
connection *sourcespb.Git
}
type byteCompare struct {
B []byte
Found bool
}
tests := []struct {
name string
init init
//verified
expectedChunkData map[string]*byteCompare
}{
{
name: "remote repo, unauthenticated",
init: init{
name: "test source",
connection: &sourcespb.Git{
Repositories: []string{"https://github.com/dustin-decker/secretsandstuff.git"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
expectedChunkData: map[string]*byteCompare{
"70001020fab32b1fcf2f1f0e5c66424eae649826-aws": {B: []byte("[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n")},
"a6f8aa55736d4a85be31a0048a4607396898647a-bump": {B: []byte("f\n")},
"73ab4713057944753f1bdeb80e757380e64c6b5b-bump": {B: []byte(" s \n")},
"2f251b8c1e72135a375b659951097ec7749d4af9-bump": {B: []byte(" \n")},
"90c75f884c65dc3638ca1610bd9844e668f213c2-aws": {B: []byte("this is the secret: [Default]\nAccess key Id: AKIAILE3JG6KMS3HZGCA\nSecret Access Key: 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\nokay thank you bye\n")},
"e6c8bbabd8796ea3cd85bfc2e55b27e0a491747f-bump": {B: []byte("oops \n")},
"735b52b0eb40610002bb1088e902bd61824eb305-bump": {B: []byte("oops\n")},
"ce62d79908803153ef6e145e042d3e80488ef747-bump": {B: []byte("\n")},
"27fbead3bf883cdb7de9d7825ed401f28f9398f1-slack": {B: []byte("yup, just did that\n\ngithub_lol: \"ffc7e0f9400fb6300167009e42d2f842cd7956e2\"\n\noh, goodness. there's another one!")},
"8afb0ecd4998b1179e428db5ebbcdc8221214432-slack": {B: []byte("oops might drop a slack token here\n\ngithub_secret=\"369963c1434c377428ca8531fbc46c0c43d037a0\"\n\nyup, just did that")},
"8fe6f04ef1839e3fc54b5147e3d0e0b7ab971bd5-aws": {B: []byte("blah blaj\n\nthis is the secret: AKIA2E0A8F3B244C9986\n\nokay thank you bye")},
"84e9c75e388ae3e866e121087ea2dd45a71068f2-aws": {B: []byte("this is the secret: [Default]\nAccess key Id: AKIAILE3JG6KMS3HZGCA\nSecret Access Key: 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\nokay thank you bye\n")},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)
if err != nil {
t.Fatal(err)
}
chunksCh := make(chan *sources.Chunk, 1)
go func() {
defer close(chunksCh)
err := s.Chunks(ctx, chunksCh)
if err != nil {
panic(err)
}
}()
for chunk := range chunksCh {
key := ""
switch meta := chunk.SourceMetadata.GetData().(type) {
case *source_metadatapb.MetaData_Git:
key = meta.Git.Commit + "-" + meta.Git.File
}
if expectedData, exists := tt.expectedChunkData[key]; !exists {
t.Errorf("A chunk exists that was not expected with key %q", key)
} else {
(*tt.expectedChunkData[key]).Found = true
if !bytes.Equal(chunk.Data, expectedData.B) {
t.Errorf("Got %q: %q, which was not expected", key, string(chunk.Data))
}
}
}
for key, expected := range tt.expectedChunkData {
if !expected.Found {
t.Errorf("Expected data with key %q not found", key)
}
}
})
}
}
func TestSource_Chunks_Edge_Cases(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
type init struct {
name string
verify bool
connection *sourcespb.Git
}
tests := []struct {
name string
init init
wantErr string
}{
{
name: "empty repo",
init: init{
name: "test source",
connection: &sourcespb.Git{
Repositories: []string{"https://github.com/git-fixtures/empty.git"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
wantErr: "remote",
},
{
name: "symlinks repo",
init: init{
name: "test source",
connection: &sourcespb.Git{
Repositories: []string{"https://github.com/git-fixtures/symlinks.git"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
},
{
name: "submodule repo",
init: init{
name: "test source",
connection: &sourcespb.Git{
Repositories: []string{"https://github.com/git-fixtures/submodule.git"},
Credential: &sourcespb.Git_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)
if err != nil {
t.Errorf("Source.Init() error = %v", err)
return
}
chunksCh := make(chan *sources.Chunk, 1)
go func() {
for chunk := range chunksCh {
chunk.Data = nil
}
}()
if err := s.Chunks(ctx, chunksCh); err != nil && !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("Source.Chunks() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -0,0 +1,513 @@
package github
import (
"context"
"fmt"
"math/rand"
"net/http"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/bradleyfalzon/ghinstallation"
"github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v41/github"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/common"
"github.com/trufflesecurity/trufflehog/pkg/giturl"
"github.com/trufflesecurity/trufflehog/pkg/sanitizer"
"github.com/trufflesecurity/trufflehog/pkg/sources"
"github.com/trufflesecurity/trufflehog/pkg/sources/git"
)
type Source struct {
name string
sourceId int64
jobId int64
verify bool
repos []string
orgs []string
members []string
git *git.Git
httpClient *http.Client
aCtx context.Context
sources.Progress
log *log.Entry
token string
conn *sourcespb.GitHub
}
// Ensure the Source satisfies the interface at compile time
var _ sources.Source = (*Source)(nil)
var endsWithGithub = regexp.MustCompile(`github.com/?$`)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
func (s *Source) Type() sourcespb.SourceType {
return sourcespb.SourceType_SOURCE_TYPE_GITHUB
}
func (s *Source) SourceID() int64 {
return s.sourceId
}
func (s *Source) JobID() int64 {
return s.jobId
}
func (s *Source) Token(ctx context.Context, installationClient *github.Client) (string, error) {
switch cred := s.conn.GetCredential().(type) {
case *sourcespb.GitHub_Unauthenticated:
// do nothing
case *sourcespb.GitHub_GithubApp:
id, err := strconv.ParseInt(cred.GithubApp.InstallationId, 10, 64)
if err != nil {
return "", errors.New(err)
}
token, _, err := installationClient.Apps.CreateInstallationToken(
ctx, id, &github.InstallationTokenOptions{})
if err != nil {
return "", errors.WrapPrefix(err, "unable to create installation token", 0)
}
return token.GetToken(), nil // TODO: multiple workers request this, track the TTL
case *sourcespb.GitHub_Token:
return cred.Token, nil
}
return "", errors.New("unhandled credential type for token fetch")
}
// Init returns an initialized GitHub source.
func (s *Source) Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error {
s.log = log.WithField("source", s.Type()).WithField("name", name)
s.aCtx = aCtx
s.name = name
s.sourceId = sourceId
s.jobId = jobId
s.verify = verify
s.httpClient = common.SaneHttpClient()
var conn sourcespb.GitHub
err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})
if err != nil {
errors.WrapPrefix(err, "error unmarshalling connection", 0)
}
s.conn = &conn
s.git = git.NewGit(s.Type(), s.JobID(), s.SourceID(), s.name, s.verify, runtime.NumCPU(),
func(file, email, commit, repository string) *source_metadatapb.MetaData {
return &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Commit: sanitizer.UTF8(commit),
File: sanitizer.UTF8(file),
Email: sanitizer.UTF8(email),
Repository: sanitizer.UTF8(repository),
Link: git.GenerateLink(repository, commit, file),
},
},
}
})
return nil
}
// Chunks emits chunks of bytes over a channel.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error {
apiEndpoint := s.conn.Endpoint
if len(s.conn.Endpoint) == 0 || endsWithGithub.MatchString(apiEndpoint) {
apiEndpoint = "https://api.github.com"
}
var installationClient *github.Client
s.repos = s.conn.Repositories
s.orgs = s.conn.Organizations
switch cred := s.conn.GetCredential().(type) {
case *sourcespb.GitHub_Unauthenticated:
apiClient := github.NewClient(s.httpClient)
if len(s.orgs) > 30 {
log.Warn("You may experience rate limiting due with the unauthenticated GitHub api. Consider using the authenticated org user scan feature instead")
}
for _, org := range s.orgs {
org = strings.TrimSpace(org)
if !strings.HasSuffix(org, ".git") {
s.paginateRepos(ctx, apiClient, org)
}
}
case *sourcespb.GitHub_Token:
// needed for clones
s.token = cred.Token
// needed to list repos
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: cred.Token},
)
tc := oauth2.NewClient(context.TODO(), ts)
var apiClient *github.Client
var err error
// If we're using public github, make a regular client.
// Otherwise make an enterprise client
if apiEndpoint == "https://api.github.com" {
apiClient = github.NewClient(tc)
} else {
apiClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, tc)
if err != nil {
return errors.New(err)
}
}
// TODO: this should support scanning users too
specificScope := false
if len(s.repos) > 0 {
specificScope = true
for i, repo := range s.repos {
if !strings.HasSuffix(repo, ".git") {
if repo, err := giturl.NormalizeGithubRepo(repo); err != nil {
// This wasn't formatted as expected, let the user know why that might be.
log.WithError(err).Warnf("Repo not in expected format, attempting to paginate repos instead.")
} else {
s.repos[i] = repo
}
s.paginateRepos(ctx, apiClient, repo)
}
}
}
if len(s.orgs) > 0 {
specificScope = true
for _, org := range s.orgs {
if !strings.HasSuffix(org, ".git") {
s.paginateRepos(ctx, apiClient, org)
}
}
}
user, _, err := apiClient.Users.Get(context.TODO(), "")
if err != nil {
return errors.New(err)
}
s.paginateGists(ctx, user.GetLogin(), chunksChan)
if !specificScope {
s.paginateRepos(ctx, apiClient, user.GetLogin())
// Scan for orgs is default with a token. GitHub App enumerates the repositories
// that were assigned to it in GitHub App settings.
s.paginateOrgs(ctx, apiClient, *user.Name)
}
case *sourcespb.GitHub_GithubApp:
installationID, err := strconv.ParseInt(cred.GithubApp.InstallationId, 10, 64)
if err != nil {
return errors.New(err)
}
appID, err := strconv.ParseInt(cred.GithubApp.AppId, 10, 64)
if err != nil {
return errors.New(err)
}
// This client is used for most APIs
itr, err := ghinstallation.New(
common.SaneHttpClient().Transport,
appID,
installationID,
[]byte(cred.GithubApp.PrivateKey))
if err != nil {
return errors.New(err)
}
itr.BaseURL = apiEndpoint
apiClient, err := github.NewEnterpriseClient(apiEndpoint, apiEndpoint, &http.Client{Transport: itr})
if err != nil {
return errors.New(err)
}
// This client is required to create installation tokens for cloning.. Otherwise the required JWT is not in the
// request for the token :/
appItr, err := ghinstallation.NewAppsTransport(
common.SaneHttpClient().Transport,
appID,
[]byte(cred.GithubApp.PrivateKey))
if err != nil {
return errors.New(err)
}
appItr.BaseURL = apiEndpoint
installationClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, &http.Client{Transport: appItr})
if err != nil {
return errors.New(err)
}
err = s.paginateApp(ctx, apiClient)
if err != nil {
return err
}
//check if we need to find user repos
if s.conn.ScanUsers {
err := s.paginateMembers(ctx, installationClient, apiClient)
if err != nil {
return err
}
log.Infof("Scanning repos from %v organization members.", len(s.members))
for _, member := range s.members {
//all org member's gists
s.paginateGists(ctx, member, chunksChan)
s.paginateRepos(ctx, apiClient, member)
}
}
default:
return errors.Errorf("Invalid configuration given for source. Name: %s, Type: %s", s.name, s.Type())
}
if _, ok := os.LookupEnv("DO_NOT_RANDOMIZE"); !ok {
//Randomize channel scan order on each scan
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(s.repos), func(i, j int) { s.repos[i], s.repos[j] = s.repos[j], s.repos[i] })
}
log.Infof("Found %v total repos to scan", len(s.repos))
for i, repoURL := range s.repos {
s.SetProgressComplete(i, len(s.repos), fmt.Sprintf("Repo: %s", repoURL))
if !strings.HasSuffix(repoURL, ".git") {
continue
}
if strings.Contains(repoURL, "DefinitelyTyped") {
continue
}
s.log.WithField("repo", repoURL).Debug("attempting to clone repo")
var path string
var repo *gogit.Repository
var err error
switch s.conn.GetCredential().(type) {
case *sourcespb.GitHub_Unauthenticated:
path, repo, err = git.CloneRepoUsingUnauthenticated(repoURL)
default:
var token string
token, err = s.Token(ctx, installationClient)
if err != nil {
return err
}
path, repo, err = git.CloneRepoUsingToken(token, repoURL, "clone")
}
defer os.RemoveAll(path)
if err != nil {
log.WithError(err).Warnf("unable to clone repo, continuing")
continue
}
err = s.git.ScanRepo(ctx, repo, &gogit.LogOptions{All: true}, nil, chunksChan)
if err != nil {
log.WithError(err).Warnf("unable to scan repo")
}
}
return nil
}
// handleRateLimit returns true if a rate limit was handled
//unauthed github has a rate limit of 60 requests per hour. This will likely only be exhausted if many users/orgs are scanned without auth
func handleRateLimit(err error) bool {
limit, ok := err.(*github.RateLimitError)
if !ok {
return false
}
log.WithField("retry-after", limit.Message).Debug("handling rate limit (5 minutes retry)")
time.Sleep(time.Minute * 5)
return true
}
func (s *Source) paginateReposByOrg(ctx context.Context, apiClient *github.Client, org string) {
opts := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
for {
someRepos, res, err := apiClient.Repositories.ListByOrg(ctx, org, opts)
if err == nil {
defer res.Body.Close()
}
if handled := handleRateLimit(err); handled {
continue
}
if len(someRepos) == 0 || err != nil {
break
}
for _, r := range someRepos {
s.repos = append(s.repos, r.GetCloneURL())
}
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
}
func (s *Source) paginateRepos(ctx context.Context, apiClient *github.Client, user string) {
opts := &github.RepositoryListOptions{
// Visibility: "all",
ListOptions: github.ListOptions{
PerPage: 50,
},
}
for {
someRepos, res, err := apiClient.Repositories.List(ctx, user, opts)
if err == nil {
defer res.Body.Close()
}
if handled := handleRateLimit(err); handled {
continue
}
if err != nil {
break
}
for _, r := range someRepos {
s.repos = append(s.repos, r.GetCloneURL())
}
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
}
func (s *Source) paginateGists(ctx context.Context, user string, chunksChan chan *sources.Chunk) {
apiClient := github.NewClient(s.httpClient)
gists, _, err := apiClient.Gists.List(ctx, user, &github.GistListOptions{})
if err != nil {
log.WithError(err).Warnf("Could not get gists for user %s", user)
return
}
for _, gist := range gists {
path, repo, err := git.CloneRepoUsingUnauthenticated(*gist.GitPullURL)
defer os.RemoveAll(path)
if err != nil {
log.WithError(err).Warnf("Could not get gist %s from user %s", *gist.HTMLURL, user)
continue
}
s.log.WithField("repo", *gist.HTMLURL).Debugf("attempting to clone gist from user %s", user)
scanCtx := context.Background()
err = s.git.ScanRepo(scanCtx, repo, &gogit.LogOptions{All: true}, nil, chunksChan)
if err != nil {
log.WithError(err).Warnf("Could not scan after clone: %s", *gist.HTMLURL)
continue
}
}
}
func (s *Source) paginateMembers(ctx context.Context, installationClient *github.Client, apiClient *github.Client) error {
opts := &github.ListOptions{
PerPage: 500,
}
optsOrg := &github.ListMembersOptions{
PublicOnly: false,
ListOptions: *opts,
}
installs, _, err := installationClient.Apps.ListInstallations(ctx, opts)
if err != nil {
log.WithError(err).Warn("Could not enumerate organizations using user")
return err
}
for _, org := range installs {
for {
members, res, err := apiClient.Organizations.ListMembers(ctx, *org.Account.Login, optsOrg)
if err == nil {
defer res.Body.Close()
}
if handled := handleRateLimit(err); handled {
continue
}
if err != nil || len(members) == 0 {
errText := "Could not list organization members: Please install on an organization. Otherwise, this is an older version of the Github app, please delete and re-add this source!"
log.WithError(err).Warnf(errText)
return errors.New(errText)
}
for _, m := range members {
usr := m.Login
if usr == nil || *usr == "" {
continue
}
s.members = append(s.members, *usr)
}
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
}
return nil
}
func (s *Source) paginateApp(ctx context.Context, apiClient *github.Client) error {
// Authenticated enumeration of repos
opts := &github.ListOptions{
PerPage: 100,
}
for {
someRepos, res, err := apiClient.Apps.ListRepos(ctx, opts)
if err == nil {
defer res.Body.Close()
}
if handled := handleRateLimit(err); handled {
continue
}
if err != nil {
return errors.WrapPrefix(err, "unable to list repositories", 0)
}
for _, r := range someRepos.Repositories {
s.repos = append(s.repos, r.GetCloneURL())
}
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
return nil
}
func (s *Source) paginateOrgs(ctx context.Context, apiClient *github.Client, user string) {
orgOpts := &github.ListOptions{}
orgs, _, err := apiClient.Organizations.List(ctx, "", orgOpts)
if err != nil {
log.WithError(err).Errorf("Could not list organizations for %s", user)
return
}
for _, org := range orgs {
var name string
if org.Name != nil {
name = *org.Name
} else if org.Login != nil {
name = *org.Login
} else {
continue
}
s.paginateReposByOrg(ctx, apiClient, name)
}
}

View file

@ -0,0 +1,406 @@
package github
import (
"context"
"encoding/base64"
"fmt"
"os"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/common"
"github.com/trufflesecurity/trufflehog/pkg/pb/credentialspb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/sources"
"google.golang.org/protobuf/types/known/anypb"
)
func TestSource_Scan(t *testing.T) {
os.Setenv("DO_NOT_RANDOMIZE", "true")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
defer cancel()
secret, err := common.GetTestSecret(ctx)
if err != nil {
t.Fatal(fmt.Errorf("failed to access secret: %v", err))
}
// For the personal access token test
githubToken := secret.MustGetField("GITHUB_TOKEN")
//For the NEW github app test (+Member enum)
githubPrivateKeyB64New := secret.MustGetField("GITHUB_PRIVATE_KEY_NEW")
githubPrivateKeyBytesNew, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64New)
if err != nil {
t.Fatal(err)
}
githubPrivateKeyNew := string(githubPrivateKeyBytesNew)
githubInstallationIDNew := secret.MustGetField("GITHUB_INSTALLATION_ID_NEW")
githubAppIDNew := secret.MustGetField("GITHUB_APP_ID_NEW")
//OLD app for breaking app change tests
githubPrivateKeyB64 := secret.MustGetField("GITHUB_PRIVATE_KEY")
githubPrivateKeyBytes, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64)
if err != nil {
t.Fatal(err)
}
githubPrivateKey := string(githubPrivateKeyBytes)
githubInstallationID := secret.MustGetField("GITHUB_INSTALLATION_ID")
githubAppID := secret.MustGetField("GITHUB_APP_ID")
type init struct {
name string
verify bool
connection *sourcespb.GitHub
}
tests := []struct {
name string
init init
wantChunk *sources.Chunk
wantErr bool
}{
{
name: "get an authenticated (token) chunk",
init: init{
name: "test source",
connection: &sourcespb.GitHub{
Repositories: []string{"https://github.com/dustin-decker/secretsandstuff.git"},
Credential: &sourcespb.GitHub_Token{
Token: githubToken,
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://gist.github.com/be45ad1ebabe98482d9c0bb80c07c619.git",
},
},
},
Verify: false,
},
wantErr: false,
},
// {
// name: "get an authenticated (token) chunk with specific 'org' enum",
// init: init{
// name: "test source",
// connection: &sourcespb.GitHub{
// Repositories: []string{"https://github.com/dustin-decker/"},
// Credential: &sourcespb.GitHub_Token{
// Token: githubToken,
// },
// },
// },
// wantChunk: &sources.Chunk{
// SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
// SourceName: "test source",
// SourceMetadata: &source_metadatapb.MetaData{
// Data: &source_metadatapb.MetaData_Github{
// Github: &source_metadatapb.Github{
// Repository: "https://github.com/dustin-decker/secretsandstuff.git",
// },
// },
// },
// Verify: false,
// },
// wantErr: false,
// },
{
name: "get an authenticated (token) chunk with enumeration",
//Enum cannot be restricted w/ token
init: init{
name: "test source",
connection: &sourcespb.GitHub{
Credential: &sourcespb.GitHub_Token{
Token: githubToken,
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://gist.github.com/be45ad1ebabe98482d9c0bb80c07c619.git",
},
},
},
Verify: false,
},
wantErr: false,
},
{
name: "get an authenticated (old app) chunk w/ enum",
init: init{
name: "test source",
connection: &sourcespb.GitHub{
ScanUsers: false,
Credential: &sourcespb.GitHub_GithubApp{
GithubApp: &credentialspb.GitHubApp{
PrivateKey: githubPrivateKey,
InstallationId: githubInstallationID,
AppId: githubAppID,
},
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://github.com/dustin-decker/dockerfiles.git",
},
},
},
Verify: false,
},
wantErr: false,
},
{
name: "get an unauthenticated org chunk with enumeration",
init: init{
name: "test source",
connection: &sourcespb.GitHub{
Organizations: []string{"trufflesecurity"},
Credential: &sourcespb.GitHub_Unauthenticated{},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://github.com/trufflesecurity/driftwood.git",
},
},
},
Verify: false,
},
wantErr: false,
},
{
name: "installed app on org w/ enum",
init: init{
name: "test source",
connection: &sourcespb.GitHub{
ScanUsers: true,
Credential: &sourcespb.GitHub_GithubApp{
GithubApp: &credentialspb.GitHubApp{
PrivateKey: githubPrivateKeyNew,
InstallationId: githubInstallationIDNew,
AppId: githubAppIDNew,
},
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://gist.github.com/be45ad1ebabe98482d9c0bb80c07c619.git",
},
},
},
Verify: false,
},
wantErr: false,
},
// {
// name: "early termination of org/users",
// init: init{
// name: "test source",
// connection: &sourcespb.GitHub{
// Repositories: strings.Split(testAccts, "\n"),
// Credential: &sourcespb.GitHub_Unauthenticated{},
// },
// },
// wantChunk: &sources.Chunk{
// SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
// SourceName: "test source",
// SourceMetadata: &source_metadatapb.MetaData{
// Data: &source_metadatapb.MetaData_Github{
// Github: &source_metadatapb.Github{
// Repository: "https://gist.github.com/be45ad1ebabe98482d9c0bb80c07c619.git",
// },
// },
// },
// Verify: false,
// },
// wantErr: false,
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
//uncomment for windows Testing
log.SetFormatter(&log.TextFormatter{ForceColors: true})
log.SetOutput(colorable.NewColorableStdout())
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
return
}
chunksCh := make(chan *sources.Chunk, 5)
go func() {
err = s.Chunks(ctx, chunksCh)
if (err != nil) != tt.wantErr {
if ctx.Err() != nil {
return
}
t.Errorf("Source.Chunks() error = %v, wantErr %v", err, tt.wantErr)
return
}
}()
gotChunk := <-chunksCh
if diff := pretty.Compare(gotChunk.SourceMetadata.GetGithub().Repository, tt.wantChunk.SourceMetadata.GetGithub().Repository); diff != "" {
t.Errorf("Source.Chunks() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func TestSource_paginateGists(t *testing.T) {
os.Setenv("DO_NOT_RANDOMIZE", "true")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
secret, err := common.GetTestSecret(ctx)
if err != nil {
t.Fatal(fmt.Errorf("failed to access secret: %v", err))
}
//For the NEW github app test (+Member enum)
githubPrivateKeyB64New := secret.MustGetField("GITHUB_PRIVATE_KEY_NEW")
githubPrivateKeyBytesNew, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64New)
if err != nil {
t.Fatal(err)
}
githubPrivateKeyNew := string(githubPrivateKeyBytesNew)
githubInstallationIDNew := secret.MustGetField("GITHUB_INSTALLATION_ID_NEW")
githubAppIDNew := secret.MustGetField("GITHUB_APP_ID_NEW")
type init struct {
name string
verify bool
connection *sourcespb.GitHub
}
tests := []struct {
name string
init init
wantChunk *sources.Chunk
wantErr bool
}{
{
name: "get gist secret",
init: init{
name: "test source",
connection: &sourcespb.GitHub{
Credential: &sourcespb.GitHub_GithubApp{
GithubApp: &credentialspb.GitHubApp{
PrivateKey: githubPrivateKeyNew,
InstallationId: githubInstallationIDNew,
AppId: githubAppIDNew,
},
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,
SourceName: "test source",
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://gist.github.com/be45ad1ebabe98482d9c0bb80c07c619.git",
},
},
},
Verify: false,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
//uncomment for windows Testing
log.SetFormatter(&log.TextFormatter{ForceColors: true})
log.SetOutput(colorable.NewColorableStdout())
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
return
}
chunksCh := make(chan *sources.Chunk, 5)
go func() {
s.paginateGists(ctx, "dustin-decker", chunksCh)
}()
gotChunk := <-chunksCh
if diff := pretty.Compare(gotChunk.SourceMetadata.GetGithub().Repository, tt.wantChunk.SourceMetadata.GetGithub().Repository); diff != "" {
t.Errorf("Source.Chunks() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
// func TestSource_paginateRepos(t *testing.T) {
// type args struct {
// ctx context.Context
// apiClient *github.Client
// }
// tests := []struct {
// name string
// org string
// args args
// }{
// {
// org: "fakeNetflix",
// args: args{
// ctx: context.Background(),
// apiClient: github.NewClient(common.SaneHttpClient()),
// },
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// s := &Source{httpClient: common.SaneHttpClient()}
// s.paginateRepos(tt.args.ctx, tt.args.apiClient, tt.org)
// if len(s.repos) < 101 {
// t.Errorf("expected > 100 repos, got %d", len(s.repos))
// }
// })
// }
// }

View file

@ -0,0 +1,293 @@
package gitlab
import (
"context"
"fmt"
"net/url"
"os"
"runtime"
"github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5"
log "github.com/sirupsen/logrus"
"github.com/xanzy/go-gitlab"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/pkg/giturl"
"github.com/trufflesecurity/trufflehog/pkg/sanitizer"
"github.com/trufflesecurity/trufflehog/pkg/sources"
"github.com/trufflesecurity/trufflehog/pkg/sources/git"
)
type Source struct {
name string
sourceId int64
jobId int64
verify bool
authMethod string
user string
password string
token string
url string
repos []string
git *git.Git
aCtx context.Context
sources.Progress
}
// Ensure the Source satisfies the interface at compile time
var _ sources.Source = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
func (s *Source) Type() sourcespb.SourceType {
return sourcespb.SourceType_SOURCE_TYPE_GITLAB
}
func (s *Source) SourceID() int64 {
return s.sourceId
}
func (s *Source) JobID() int64 {
return s.jobId
}
// Init returns an initialized Gitlab source.
func (s *Source) Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error {
s.aCtx = aCtx
s.name = name
s.sourceId = sourceId
s.jobId = jobId
s.verify = verify
var conn sourcespb.GitLab
err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})
if err != nil {
errors.WrapPrefix(err, "error unmarshalling connection", 0)
}
s.repos = conn.Repositories
s.url = conn.Endpoint
switch cred := conn.GetCredential().(type) {
case *sourcespb.GitLab_Token:
s.authMethod = "TOKEN"
s.token = cred.Token
case *sourcespb.GitLab_Oauth:
s.authMethod = "OAUTH"
s.token = cred.Oauth.RefreshToken
// TODO: is it okay if there is no client id and secret? Might be an issue when marshalling config to proto
case *sourcespb.GitLab_BasicAuth:
s.authMethod = "BASIC_AUTH"
s.user = cred.BasicAuth.Username
s.password = cred.BasicAuth.Password
default:
return errors.Errorf("Invalid configuration given for source. Name: %s, Type: %s", name, s.Type())
}
if len(s.url) == 0 {
//assuming not custom gitlab url
s.url = "https://gitlab.com/"
}
s.git = git.NewGit(s.Type(), s.JobID(), s.SourceID(), s.name, s.verify, runtime.NumCPU(),
func(file, email, commit, repository string) *source_metadatapb.MetaData {
return &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Gitlab{
Gitlab: &source_metadatapb.Gitlab{
Commit: sanitizer.UTF8(commit),
File: sanitizer.UTF8(file),
Email: sanitizer.UTF8(email),
Repository: sanitizer.UTF8(repository),
Link: git.GenerateLink(repository, commit, file),
},
},
}
})
return nil
}
func (s *Source) newClient() (*gitlab.Client, error) {
// initialize a new api instance
switch s.authMethod {
case "OAUTH":
apiClient, err := gitlab.NewOAuthClient(s.token, gitlab.WithBaseURL(s.url))
if err != nil {
return nil, fmt.Errorf("could not authenticate to Gitlab instance %s via OAUTH. Error: %v", s.url, err)
}
return apiClient, nil
case "BASIC_AUTH":
apiClient, err := gitlab.NewBasicAuthClient(s.user, s.password, gitlab.WithBaseURL(s.url))
if err != nil {
return nil, fmt.Errorf("could not authenticate to Gitlab instance %s via BASICAUTH. Error: %v", s.url, err)
}
return apiClient, nil
case "TOKEN":
apiClient, err := gitlab.NewOAuthClient(s.token, gitlab.WithBaseURL(s.url))
if err != nil {
return nil, fmt.Errorf("could not authenticate to Gitlab instance %s via TOKEN Auth. Error: %v", s.url, err)
}
return apiClient, nil
default:
return nil, errors.New("Could not determine authMethod specified for GitLab")
}
}
func (s *Source) getAllProjects(apiClient *gitlab.Client) ([]*gitlab.Project, error) {
// projects without repo will get user projects, groups projects, and subgroup projects.
user, _, err := apiClient.Users.CurrentUser()
//TODO what happens if the user is anonymous
if err != nil {
return nil, errors.Errorf("unable to authenticate using: %s", s.authMethod)
}
//when bool pointers are req'd
//yes := true
no := false
projectQuery := &gitlab.ListProjectsOptions{}
projects, _, err := apiClient.Projects.ListUserProjects(user.ID, projectQuery)
if err != nil {
return nil, errors.Errorf("received error on listing projects: %s\n", err)
}
groups, _, err := apiClient.Groups.ListGroups(&gitlab.ListGroupsOptions{AllAvailable: &no})
if err != nil {
return nil, errors.Errorf("received error on listing projects: %s\n", err)
}
for _, group := range groups {
grpPrjs, _, err := apiClient.Groups.ListGroupProjects(group.ID, &gitlab.ListGroupProjectsOptions{})
if err != nil {
return nil, errors.Errorf("received error on listing projects: %s\n", err)
}
projects = append(projects, grpPrjs...)
subgroups, _, err := apiClient.Groups.ListSubgroups(group.ID, &gitlab.ListSubgroupsOptions{AllAvailable: &no})
if err != nil {
log.Debugf("could not retrieve subgroups from %s", group.Name)
continue
}
for _, subgroup := range subgroups {
projects = append(projects, subgroup.Projects...)
}
}
return projects, nil
}
func (s *Source) getRepos(apiClient *gitlab.Client) ([]*url.URL, []error) {
//is repo defined?
var validRepos []*url.URL
var errs []error
if len(s.repos) > 0 {
for _, prj := range s.repos {
repo, err := giturl.NormalizeGitlabRepo(prj)
if err != nil {
errs = append(errs, errors.WrapPrefix(err, fmt.Sprintf("unable to normalize gitlab repo url %s", prj), 0))
}
// The repo normalization has already successfully parsed the URL at this point, so we can ignore the error.
u, _ := url.ParseRequestURI(repo)
validRepos = append(validRepos, u)
}
return validRepos, errs
}
return nil, nil
}
func (s *Source) scanRepos(ctx context.Context, chunksChan chan *sources.Chunk, repos []*url.URL) []error {
var errors []error
if s.authMethod == "UNAUTHENTICATED" {
for i, u := range repos {
s.SetProgressComplete(i, len(repos), fmt.Sprintf("Repo: %s", u))
if len(u.String()) == 0 {
continue
}
path, repo, err := git.CloneRepoUsingUnauthenticated(u.String())
defer os.RemoveAll(path)
if err != nil {
errors = append(errors, err)
continue
}
err = s.git.ScanRepo(ctx, repo, &gogit.LogOptions{All: true}, nil, chunksChan)
if err != nil {
errors = append(errors, err)
continue
}
}
} else {
for i, u := range repos {
s.SetProgressComplete(i, len(repos), fmt.Sprintf("Repo: %s", u))
if len(u.String()) == 0 {
continue
}
path, repo, err := git.CloneRepoUsingToken(s.token, u.String(), s.user)
defer os.RemoveAll(path)
if err != nil {
errors = append(errors, err)
continue
}
err = s.git.ScanRepo(ctx, repo, &gogit.LogOptions{All: true}, nil, chunksChan)
if err != nil {
errors = append(errors, err)
continue
}
}
}
return errors
}
// Chunks emits chunks of bytes over a channel.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error {
// start client
apiClient, err := s.newClient()
if err != nil {
return errors.New(err)
}
// get repo within target
repos, errs := s.getRepos(apiClient)
for _, repoErr := range errs {
log.WithError(repoErr).Warn("error getting repo")
}
// get all repos if not specified
if repos == nil {
projects, err := s.getAllProjects(apiClient)
if err != nil {
return errors.New(err)
}
// turn projects into URLs for Git cloner
for _, prj := range projects {
u, err := url.Parse(prj.HTTPURLToRepo)
if err != nil {
fmt.Printf("could not parse url given by project: %s", prj.HTTPURLToRepo)
}
repos = append(repos, u)
}
if repos == nil {
return errors.Errorf("unable to discover any repos")
}
}
errs = s.scanRepos(ctx, chunksChan, repos)
for _, err := range errs {
log.WithError(err).WithFields(
log.Fields{
"source_name": s.name,
"source_type": s.Type(),
"repos": repos,
},
).Error("error scanning repo")
}
return nil
}

View file

@ -0,0 +1,110 @@
package gitlab
import (
"context"
"fmt"
"testing"
"github.com/kylelemons/godebug/pretty"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
log "github.com/sirupsen/logrus"
"github.com/trufflesecurity/trufflehog/pkg/common"
"github.com/trufflesecurity/trufflehog/pkg/sources"
)
func TestSource_Scan(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{ForceColors: true})
secret, err := common.GetTestSecret(ctx)
if err != nil {
t.Fatal(fmt.Errorf("failed to access secret: %v", err))
}
token := secret.MustGetField("GITLAB_TOKEN")
type init struct {
name string
verify bool
connection *sourcespb.GitLab
}
tests := []struct {
name string
init init
wantChunk *sources.Chunk
wantErr bool
}{
{
name: "token auth, enumerate repo",
init: init{
name: "test source",
connection: &sourcespb.GitLab{
Credential: &sourcespb.GitLab_Token{
Token: token,
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,
SourceName: "test source",
Verify: false,
},
wantErr: false,
},
{
name: "token auth, scoped repo",
init: init{
name: "test source scoped",
connection: &sourcespb.GitLab{
Repositories: []string{"https://gitlab.com/testermctestface/testy.git"},
Credential: &sourcespb.GitLab_Token{
Token: token,
},
},
},
wantChunk: &sources.Chunk{
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,
SourceName: "test source scoped",
Verify: false,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
log.SetLevel(log.DebugLevel)
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 10)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
return
}
chunksCh := make(chan *sources.Chunk, 1)
go func() {
err = s.Chunks(ctx, chunksCh)
if (err != nil) != tt.wantErr {
t.Errorf("Source.Chunks() error = %v, wantErr %v", err, tt.wantErr)
return
}
}()
gotChunk := <-chunksCh
// Commits don't come in a deterministic order, so remove metadata comparison
gotChunk.Data = nil
gotChunk.SourceMetadata = nil
if diff := pretty.Compare(gotChunk, tt.wantChunk); diff != "" {
t.Errorf("Source.Chunks() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}

72
pkg/sources/sources.go Normal file
View file

@ -0,0 +1,72 @@
package sources
import (
"context"
"sync"
"github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb"
"google.golang.org/protobuf/types/known/anypb"
)
// Chunk contains data to be decoded and scanned along with context on where it came from.
type Chunk struct {
// SourceName is the name of the Source that produced the chunk.
SourceName string
// SourceID is the ID of the source that the Chunk originated from.
SourceID int64
// SourceType is the type of Source that produced the chunk.
SourceType sourcespb.SourceType
// SourceMetadata holds the context of where the Chunk was found.
SourceMetadata *source_metadatapb.MetaData
// Data is the data to decode and scan.
Data []byte
// Verify specifies whether any secrets in the Chunk should be verified.
Verify bool
}
// Source defines the interface required to implement a source chunker.
type Source interface {
// Type returns the source type, used for matching against configuration and jobs.
Type() sourcespb.SourceType
// SourceID returns the initialized source ID used for tracking relationships in the DB.
SourceID() int64
// JobID returns the initialized job ID used for tracking relationships in the DB.
JobID() int64
// Init initializes the source.
Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error
// Chunks emits data over a channel that is decoded and scanned for secrets.
Chunks(ctx context.Context, chunksChan chan *Chunk) error
// Completion Percentage for Scanned Source
GetProgress() *Progress
}
// PercentComplete is used to update job completion percentages across sources
type Progress struct {
mut sync.Mutex
PercentComplete int64
Message string
SectionsCompleted int32
SectionsRemaining int32
}
// SetProgressComplete sets job complete percentage based on passed in scope array of highest level objects. i is the current iteration in the loop of target scope, scope should be len(scoped_items)
func (p *Progress) SetProgressComplete(i, scope int, message string) {
p.mut.Lock()
defer p.mut.Unlock()
if p == nil {
p = &Progress{}
}
p.Message = message
p.SectionsCompleted = int32(i)
p.SectionsRemaining = int32(scope)
p.PercentComplete = int64((float64(i) / float64(scope)) * 100)
}
//GetProgressComplete gets job completion percentage for metrics reporting
func (p *Progress) GetProgress() *Progress {
p.mut.Lock()
defer p.mut.Unlock()
return p
}

56
proto/credentials.proto Normal file
View file

@ -0,0 +1,56 @@
syntax = "proto3";
package credentials;
option go_package = "github.com/trufflesecurity/trufflehog/pkg/pb/credentialspb";
message Unauthenticated {}
message CloudEnvironment {}
message BasicAuth {
string username = 1;
string password = 2;
}
message ClientCrednetials {
string tenant_id = 1;
string client_id =2;
string client_secret=3;
}
message ClientCertificate {
string tenant_id = 1;
string client_id =2;
string certificate_path =3;
string certificate_password =4;
}
message Oauth2 {
string refresh_token = 1;
string client_id = 2;
string client_secret = 3;
}
message KeySecret {
string key = 1;
string secret = 2;
}
message AWS {
string key = 1;
string secret = 2;
string region = 3;
}
message SES {
AWS creds = 1;
string sender = 2;
repeated string recipients = 3;
}
message GitHubApp {
string private_key = 1;
string installation_id = 2;
string app_id = 3;
}

778
proto/detectors.proto Normal file
View file

@ -0,0 +1,778 @@
syntax = "proto3";
package detectors;
option go_package = "github.com/trufflesecurity/trufflehog/pkg/pb/detectorspb";
enum DetectorType {
Alibaba = 0;
AMQP = 1;
AWS = 2;
Azure = 3;
Circle = 4;
Coinbase = 5;
GCP = 6;
Generic = 7;
Github = 8;
Gitlab = 9;
JDBC = 10;
RazorPay = 11;
SendGrid = 12;
Slack = 13;
Square = 14;
PrivateKey = 15;
Stripe = 16;
URI = 17;
Dropbox = 18;
Heroku = 19;
Mailchimp = 20;
Okta = 21;
OneLogin = 22;
PivotalTracker = 23;
SquareApp = 25;
Twilio = 26;
Test = 27;
TravisCI = 29;
SlackWebhook = 30;
PaypalOauth = 31;
PagerDutyApiKey = 32;
Firebase = 33;
Mailgun = 34;
HubSpot = 35;
GitHubApp = 36;
CircleCI = 37;
WpEngine = 38;
DatadogToken = 39;
FacebookOAuth = 40;
AsanaPersonalAccessToken = 41;
AmplitudeApiKey = 42;
BitLyAccessToken = 43;
CalendlyApiKey = 44;
ZapierWebhook = 45;
YoutubeApiKey = 46;
SalesforceOauth2 = 47;
TwitterApiSecret = 48;
NpmToken = 49;
NewRelicPersonalApiKey = 50;
AirtableApiKey = 51;
AkamaiToken = 52;
AmazonMWS = 53;
KubeConfig = 54;
Auth0oauth = 55;
Bitfinex = 56;
Clarifai = 57;
CloudflareGlobalApiKey = 58;
CloudflareCaKey = 59;
Confluent = 60;
ContentfulDelivery = 61; // Didn't do
DatabricksToken = 62;
DigitalOceanSpaces = 63; // Didn't do
DigitalOceanToken = 64;
DiscordBotToken = 65;
DiscordWebhook = 66;
EtsyApiKey = 67;
FastlyPersonalToken = 68;
GoogleOauth2 = 69;
ReCAPTCHA = 70; // Didn't do
GoogleApiKey = 71; // Didn't do
Hunter = 72;
IbmCloudUserKey = 73;
Netlify = 74;
Vonage = 75;
EquinixOauth = 76;
Paystack = 77;
PlaidToken = 78;
PlaidKey = 79;
Plivo = 80;
Postmark = 81;
PubNubPublishKey = 82;
PubNubSubscriptionKey = 83;
PusherChannelKey = 84;
ScalewayKey = 85;
SendinBlueV2 = 86;
SentryToken = 87;
ShodanKey = 88;
SnykKey = 89;
SpotifyKey = 90;
TelegramBotToken = 91;
TencentCloudKey = 92;
TerraformCloudPersonalToken = 93;
TrelloApiKey = 94;
ZendeskApi = 95;
MaxMindLicense = 96;
AirtableMetadataApiKey = 97;
AsanaOauth = 98;
RapidApi = 99;
CloudflareApiToken = 100;
Webex = 101;
FirebaseCloudMessaging = 102;
ContentfulPersonalAccessToken = 103;
MapBox = 104;
MailJetBasicAuth = 105;
MailJetSMS = 106;
HubSpotApiKey = 107;
HubSpotOauth = 108;
SslMate = 109;
Auth0ManagementApiToken = 110;
MessageBird = 111;
ElasticEmail = 112;
FigmaPersonalAccessToken = 113;
MicrosoftTeamsWebhook = 114;
GitHubOld = 115;
VultrApiKey = 116;
Pepipost = 117;
Postman = 118;
CloudsightKey = 119;
JiraToken = 120;
NexmoApiKey = 121;
SegmentApiKey = 122;
SumoLogicKey = 123;
PushBulletApiKey = 124;
AirbrakeProjectKey = 125;
AirbrakeUserKey = 126;
PendoIntegrationKey = 127;
SplunkOberservabilityToken = 128;
LokaliseToken = 129;
Calendarific = 130;
Jumpcloud = 131;
IpStack = 133;
Notion = 134;
DroneCI = 135;
AdobeIO = 136;
TwelveData = 137;
D7Network = 138;
ScrapingBee = 139;
KeenIO = 140;
Wakatime = 141;
Buildkite = 142;
Verimail = 143;
Zerobounce = 144;
Mailboxlayer = 145;
Fastspring = 146;
Paddle = 147;
Sellfy = 148;
FixerIO = 149;
ButterCMS = 150;
Taxjar = 151;
Avalara = 152;
Helpscout = 153;
ElasticPath = 154;
Zeplin = 155;
Intercom = 156;
Mailmodo = 157;
CannyIo = 158;
Pipedrive = 159;
Vercel = 160;
PosthogApp = 161;
SinchMessage = 162;
Ayrshare = 163;
HelpCrunch = 164;
LiveAgent = 165;
Beamer = 166;
WeChatAppKey = 167;
LineMessaging = 168;
UberServerToken = 169;
AlgoliaAdminKey = 170;
FullContact = 171;
Mandrill = 172;
Flutterwave = 173;
MattermostPersonalToken = 174;
Cloudant = 175;
LineNotify = 176;
LinearAPI = 177;
Ubidots = 178;
Anypoint = 179;
Dwolla = 180;
ArtifactoryAccessToken = 181;
Surge = 182;
Sparkpost = 183;
GoCardless = 184;
Codacy = 185;
Kraken = 186;
Checkout = 187;
Kairos = 188;
ClockworkSMS = 189;
Atlassian = 190;
LaunchDarkly = 191;
Coveralls = 192;
Linode = 193;
WePay = 194;
PlanetScale = 195;
Doppler = 196;
Agora = 197;
Samsara = 198;
FrameIO = 199;
RubyGems = 200;
OpenAI = 201;
SurveySparrow = 202;
Simvoly = 203;
Survicate = 204;
Omnisend = 205;
Groovehq = 206;
Newsapi = 207;
Chatbot = 208;
ClickSendsms = 209;
Getgist = 210;
CustomerIO = 211;
ApiDeck = 212;
Nftport = 213;
Copper = 214;
Close = 215;
Myfreshworks = 216;
Salesflare = 217;
Webflow = 218;
Duda = 219;
Yext = 220;
ContentStack = 221;
Storyblok = 222;
GraphCMS = 223;
Checkmarket = 224;
Convertkit = 225;
CustomerGuru = 226;
Kaleyra = 227;
Mailerlite = 228;
Qualaroo = 229;
SatismeterProjectkey = 230;
SatismeterWritekey = 231;
Simplesat = 232;
SurveyAnyplace = 233;
SurveyBot = 234;
Webengage = 235;
ZonkaFeedback = 236;
Delighted = 237;
Feedier = 238;
Abbysale = 239;
Magnetic = 240;
Nytimes = 241;
Polygon = 242;
Powrbot = 243;
ProspectIO = 244;
Skrappio = 245;
Monday = 246;
Smartsheets = 247;
Wrike = 248;
Float = 249;
Imagekit = 250;
Integromat = 251;
Salesblink = 252;
Bored = 253;
Campayn = 254;
Clinchpad = 255;
CompanyHub = 256;
Debounce = 257;
Dyspatch = 258;
Guardianapi = 259;
Harvest = 260;
Moosend = 261;
OpenWeather = 262;
Siteleaf = 263;
Squarespace = 264;
FlowFlu = 265;
Nimble = 266;
LessAnnoyingCRM = 267;
Nethunt = 268;
Apptivo = 269;
CapsuleCRM = 270;
Insightly = 271;
Kylas = 272;
OnepageCRM = 273;
User = 274;
ProspectCRM = 275;
ReallySimpleSystems = 276;
Airship = 277;
Artsy = 278;
Yandex = 279;
Clockify = 280;
Dnscheck = 281;
EasyInsight = 282;
Ethplorer = 283;
Everhour = 284;
Fulcrum = 285;
GeoIpifi = 286;
Jotform = 287;
Refiner = 288;
Timezoneapi = 289;
TogglTrack = 290;
Vpnapi = 291;
Workstack = 292;
Apollo = 293;
Eversign = 294;
Juro = 295;
KarmaCRM = 296;
Metrilo = 297;
Pandadoc = 298;
RevampCRM = 299;
Salescookie = 300;
Alconost = 301;
Blogger = 302;
Accuweather = 303;
Opengraphr = 304;
Rawg = 305;
Riotgames = 306;
RoninApp = 307;
Stormglass = 308;
Tomtom = 309;
Twitch = 310;
Documo = 311;
Cloudways = 312;
Veevavault = 313;
KiteConnect = 314;
ShopeeOpenPlatform = 315;
TeamViewer = 316;
Bulbul = 317;
CentralStationCRM = 318;
Teamgate = 319;
Axonaut = 320;
Tyntec = 321;
Appcues = 322;
Autoklose = 323;
Cloudplan = 324;
Dotmailer = 325;
GetEmail = 326;
GetEmails = 327;
Kontent = 328;
Leadfeeder = 329;
Raven = 330;
RocketReach = 331;
Uplead = 332;
Brandfetch = 333;
Clearbit = 334;
Crowdin = 335;
Mapquest = 336;
Noticeable = 337;
Onbuka = 338;
Todoist = 339;
Storychief = 340;
LinkedIn = 341;
YouSign = 342;
Docker = 343;
Telesign = 344;
Spoonacular = 345;
Aerisweather = 346;
Alphavantage = 347;
Imgur = 348;
Imagga = 349;
SMSApi = 350;
Distribusion = 351;
Blablabus = 352;
WordsApi = 353;
Currencylayer = 354;
Html2Pdf = 355;
IPGeolocation = 356;
Owlbot = 357;
Cloudmersive = 358;
Dynalist = 359;
ExchangeRateAPI = 360;
HolidayAPI = 361;
Ipapi = 362;
Marketstack = 363;
Nutritionix = 364;
Swell = 365;
ClickupPersonalToken = 366;
Nitro = 367;
Rev = 368;
RunRunIt = 369;
Typeform = 370;
Mixpanel = 371;
Tradier = 372;
Verifier = 373;
Vouchery = 374;
Alegra = 375;
Audd = 376;
Baremetrics = 377;
Coinlib = 378;
ExchangeRatesAPI = 379;
CurrencyScoop = 380;
FXMarket = 381;
CurrencyCloud = 382;
GetGeoAPI = 383;
Abstract = 384;
Billomat = 385;
Dovico = 386;
Bitbar = 387;
Bugsnag = 388;
AssemblyAI = 389;
AdafruitIO = 390;
Apify = 391;
CoinGecko = 392;
CryptoCompare = 393;
Fullstory = 394;
HelloSign = 395;
Loyverse = 396;
NetCore = 397;
SauceLabs = 398;
AlienVault = 399;
Apiflash = 401;
Coinlayer = 402;
CurrentsAPI = 403;
DataGov = 404;
Enigma = 405;
FinancialModelingPrep = 406;
Geocodio = 407;
HereAPI = 408;
Macaddress = 409;
OOPSpam = 410;
ProtocolsIO = 411;
ScraperAPI = 412;
SecurityTrails = 413;
TomorrowIO = 414;
WorldCoinIndex = 415;
FacePlusPlus = 416;
Voicegain = 417;
Deepgram = 418;
VisualCrossing = 419;
Finnhub = 420;
Tiingo = 421;
RingCentral = 422;
Finage = 423;
Edamam = 424;
HypeAuditor = 425;
Gengo = 426;
Front = 427;
Fleetbase = 428;
Bubble = 429;
Bannerbear = 430;
Adzuna = 431;
BitcoinAverage = 432;
CommerceJS = 433;
DetectLanguage = 434;
FakeJSON = 435;
Graphhopper = 436;
Lexigram = 437;
LinkPreview = 438;
Numverify = 439;
ProxyCrawl = 440;
ZipCodeAPI = 441;
Cometchat = 442;
Keygen = 443;
Mixcloud = 444;
TatumIO = 445;
Tmetric = 446;
Lastfm = 447;
Browshot = 448;
JSONbin = 449;
LocationIQ = 450;
ScreenshotAPI = 451;
WeatherStack = 452;
Amadeus = 453;
FourSquare = 454;
Flickr = 455;
ClickHelp = 456;
Ambee = 457;
Api2Cart = 458;
Hypertrack = 459;
KakaoTalk = 460;
RiteKit = 461;
Shutterstock = 462;
Text2Data = 463;
YouNeedABudget = 464;
Cricket = 465;
Filestack = 466;
Gyazo = 467;
Mavenlink = 468;
Sheety = 469;
Sportsmonk = 470;
Stockdata = 471;
Unsplash = 472;
Allsports = 473;
CalorieNinja = 474;
WalkScore = 475;
Strava = 476;
Cicero = 477;
IPQuality = 478;
ParallelDots = 479;
Roaring = 480;
Mailsac = 481;
Whoxy = 482;
WorldWeather = 483;
ApiFonica = 484;
Aylien = 485;
Geocode = 486;
IconFinder = 487;
Ipify = 488;
LanguageLayer = 489;
Lob = 490;
OnWaterIO = 491;
Pastebin = 492;
PdfLayer = 493;
Pixabay = 494;
ReadMe = 495;
VatLayer = 496;
VirusTotal = 497;
AirVisual = 498;
Currencyfreaks = 499;
Duffel = 500;
FlatIO = 501;
M3o = 502;
Mesibo = 503;
Openuv = 504;
Snipcart = 505;
Besttime = 506;
Happyscribe = 507;
Humanity = 508;
Impala = 509;
Loginradius = 510;
AutoPilot = 511;
Bitmex = 512;
ClustDoc = 513;
Messari = 514;
PdfShift = 515;
Poloniex = 516;
RestpackHtmlToPdfAPI = 517;
RestpackScreenshotAPI = 518;
ShutterstockOAuth = 519;
SkyBiometry = 520;
AbuseIPDB = 521;
AletheiaApi = 522;
BlitApp = 523;
Censys = 524;
Cloverly = 525;
CountryLayer = 526;
FileIO = 527;
FlightApi = 528;
Geoapify = 529;
IPinfoDB = 530;
MediaStack = 531;
NasdaqDataLink = 532;
OpenCageData = 533;
Paymongo = 534;
PositionStack = 535;
Rebrandly = 536;
ScreenshotLayer = 537;
Stytch = 538;
Unplugg = 539;
UPCDatabase = 540;
UserStack = 541;
Geocodify = 542;
Newscatcher = 543;
Nicereply = 544;
Partnerstack = 545;
Route4me = 546;
Scrapeowl = 547;
ScrapingDog = 548;
Streak = 549;
Veriphone = 550;
Webscraping = 551;
Zenscrape = 552;
Zenserp = 553;
CoinApi = 554;
Gitter = 555;
Host = 556;
Iexcloud = 557;
Restpack = 558;
ScraperBox = 559;
ScrapingAnt = 560;
SerpStack = 561;
SmartyStreets = 562;
TicketMaster = 563;
AviationStack = 564;
BombBomb = 565;
Commodities = 566;
Dfuse = 567;
EdenAI = 568;
Glassnode = 569;
Guru = 570;
Hive = 571;
Hiveage = 572;
Kickbox = 573;
Passbase = 574;
PostageApp = 575;
PureStake = 576;
Qubole = 577;
CarbonInterface = 578;
Intrinio = 579;
QuickMetrics = 580;
ScrapeStack = 581;
TechnicalAnalysisApi = 582;
Urlscan = 583;
BaseApiIO = 584;
DailyCO = 585;
TLy = 586;
Shortcut = 587;
Appfollow = 588;
Thinkific = 589;
Feedly = 590;
Stitchdata = 591;
Fetchrss = 592;
Signupgenius = 593;
Signaturit = 594;
Optimizely = 595;
OcrSpace = 596;
WeatherBit = 597;
BuddyNS = 598;
ZipAPI = 599;
ZipBooks = 600;
Onedesk = 601;
Bugherd = 602;
Blazemeter = 603;
Autodesk = 604;
Tru = 605;
UnifyID = 606;
Trimble = 607;
Smooch = 608;
Semaphore = 609;
Telnyx = 610;
Signalwire = 611;
Textmagic = 612;
Serphouse = 613;
Planyo = 614;
Simplybook = 615;
Vyte = 616;
Nylas = 617;
Squareup = 618;
Dandelion = 619;
DataFire = 620;
DeepAI = 621;
MeaningCloud = 622;
NeutrinoApi = 623;
Storecove = 624;
Shipday = 625;
Sentiment = 626;
StreamChatMessaging = 627;
TeamworkCRM = 628;
TeamworkDesk = 629;
TeamworkSpaces = 630;
TheOddsApi = 631;
Apacta = 632;
GetSandbox = 633;
Happi = 634;
Oanda = 635;
FastForex = 636;
APIMatic = 637;
VersionEye = 638;
EagleEyeNetworks = 639;
ThousandEyes = 640;
SelectPDF = 641;
Flightstats = 642;
ChecIO = 643;
Manifest = 644;
ApiScience = 645;
AppSynergy = 646;
Caflou = 647;
Caspio = 648;
ChecklyHQ = 649;
CloudElements = 650;
DronaHQ = 651;
Enablex = 652;
Fmfw = 653;
GoodDay = 654;
Luno = 655;
Meistertask = 656;
Mindmeister = 657;
PeopleDataLabs = 658;
ScraperSite = 659;
Scrapfly = 660;
SimplyNoted = 661;
TravelPayouts = 662;
WebScraper = 663;
Convier = 664;
Courier = 665;
Ditto = 666;
Findl = 667;
Lendflow = 668;
Moderation = 669;
Opendatasoft = 670;
Podio = 671;
Rockset = 672;
Rownd = 673;
Shotstack = 674;
Swiftype = 675;
Twitter = 676;
Honey = 677;
Freshdesk = 678;
Upwave = 679;
Fountain = 680;
Freshbooks = 681;
Mite = 682;
Deputy = 683;
Beebole = 684;
Cashboard = 685;
Kanban = 686;
Worksnaps = 687;
MyIntervals = 688;
InvoiceOcean = 689;
Sherpadesk = 690;
Mrticktock = 691;
Chatfule = 692;
Aeroworkflow = 693;
Emailoctopus = 694;
Fusebill = 695;
Geckoboard = 696;
Gosquared = 697;
Moonclerck = 698;
Paymoapp = 699;
Mixmax = 700;
Processst = 701;
Repairshopr = 702;
Goshippo = 703;
Sigopt = 704;
Sugester = 705;
Viewneo = 706;
BoostNote = 707;
CaptainData = 708;
Checkvist = 709;
Cliengo = 710;
Cloze = 711;
FormIO = 712;
FormBucket = 713;
GoCanvas = 714;
MadKudu = 715;
NozbeTeams = 716;
Papyrs = 717;
SuperNotesAPI = 718;
Tallyfy = 719;
ZenkitAPI = 720;
CloudImage = 721;
UploadCare = 722;
Borgbase = 723;
Pipedream = 724;
Sirv = 725;
Diffbot = 726;
EightxEight = 727;
Sendoso = 728;
Printfection = 729;
Authorize = 730;
PandaScore = 731;
Paymo = 732;
AvazaPersonalAccessToken = 733;
PlanviewLeanKit = 734;
Livestorm = 735;
KuCoin = 736;
MetaAPI = 737;
NiceHash = 738;
CexIO = 739;
Klipfolio = 740;
Dynatrace = 741;
MollieAPIKey = 742;
MollieAccessToken = 743;
BasisTheory = 744;
Nordigen = 745;
FlagsmithEnvironmentKey = 746;
FlagsmithToken = 747;
Mux = 748;
}
message Result {
int64 source_id = 2;
string redacted = 3;
bool verified = 4;
string hash = 5;
map<string, string> extra_data = 6;
StructuredData structured_data = 7;
}
message StructuredData {
repeated TlsPrivateKey tls_private_key = 1;
repeated GitHubSSHKey github_ssh_key = 2;
}
message TlsPrivateKey {
string certificate_fingerprint = 1;
string verification_url = 2;
int64 expiration_timestamp = 3;
}
message GitHubSSHKey {
string user = 1;
string public_key_fingerprint = 2;
}

189
proto/source_metadata.proto Normal file
View file

@ -0,0 +1,189 @@
syntax = "proto3";
package source_metadata;
option go_package = "github.com/trufflesecurity/trufflehog/pkg/pb/source_metadatapb";
message Azure {
string container = 1;
string file = 2;
string uploaded = 3;
string link = 4;
string email = 5;
}
message Bitbucket {
string file = 1;
string repository = 2;
string workspace = 3;
string snippet_id = 4;
string title = 5;
string commit = 6;
string email = 7;
string link = 8;
}
message Buildkite {
string org = 1;
string pipeline = 2;
string link = 3;
string email = 4;
int64 build_number = 5;
}
message CircleCI {
string vcs_type = 1;
string username = 2;
string repository = 3;
int64 build_number = 4;
string build_step = 5;
string link = 6;
string email = 7;
}
message Confluence {
string page = 1;
string space = 2;
string version = 3;
string link = 4;
string email = 5;
}
message Dockerhub {
string file = 1;
string image = 2;
string layer = 3;
string tag = 4;
string link = 5;
string email = 6;
}
message ECR {
string file = 1;
string layer = 2;
string image = 3;
string registry = 4;
string region = 5;
string link = 6;
string email = 7;
}
message Filesystem {
string file = 1;
string link = 2;
string email = 3;
}
message Git {
string commit = 1;
string file = 2;
string email = 3;
string repository = 4;
}
message Github {
string link = 1;
string username = 2;
string repository = 3;
string commit = 4;
string email = 5;
string file = 6;
}
message Gitlab {
string commit = 1;
string file = 2;
string link = 3;
string email = 4;
string repository = 5;
}
message GCS {
string bucket = 1;
string file = 2;
string link = 3;
string email = 4;
}
message Jira {
string issue = 1;
string author = 2;
string link = 3;
string location = 4;
string email = 5;
}
message NPM {
string file = 1;
string package = 2;
string release = 3;
string link = 4;
string email = 5;
}
message PyPi {
string file = 1;
string package = 2;
string release = 3;
string link = 4;
string email = 5;
}
message S3 {
string bucket = 1;
string file = 2;
string link = 3;
string email = 5;
}
message Slack {
string channel_id = 1;
string channel_name = 2;
string timestamp = 3;
string user_id = 4;
string link = 5;
string file = 6;
string email = 7;
}
message Gerrit {
string commit = 1;
string file = 2;
string email = 3;
string project = 4; // projects are what Gerrit calls repositories
}
message Test {
string file = 1;
}
message Jenkins {
string project_name = 1;
int64 build_number = 2;
string link = 3;
}
message MetaData {
oneof data {
Azure azure = 1;
Bitbucket bitbucket = 2;
CircleCI circleci = 3;
Confluence confluence = 4;
Dockerhub dockerhub = 5;
ECR ecr = 6;
GCS gcs = 7;
Github github = 8;
Gitlab gitlab = 9;
Jira jira = 10;
NPM npm = 11;
PyPi pypi = 12;
S3 s3 = 13;
Slack slack = 14;
Filesystem filesystem = 15;
Git git = 16;
Test test = 17;
Buildkite buildkite = 18;
Gerrit gerrit = 19;
Jenkins jenkins = 20;
}
}

195
proto/sources.proto Normal file
View file

@ -0,0 +1,195 @@
syntax = "proto3";
package sources;
option go_package = "github.com/trufflesecurity/trufflehog/pkg/pb/sourcespb";
import "validate/validate.proto";
import "credentials.proto";
import "google/protobuf/duration.proto";
enum SourceType {
SOURCE_TYPE_AZURE_STORAGE = 0;
SOURCE_TYPE_BITBUCKET = 1;
SOURCE_TYPE_CIRCLECI = 2;
SOURCE_TYPE_CONFLUENCE = 3;
SOURCE_TYPE_DOCKERHUB_IMAGES = 4;
SOURCE_TYPE_ECR = 5;
SOURCE_TYPE_GCS = 6;
SOURCE_TYPE_GITHUB = 7;
SOURCE_TYPE_PUBLIC_GIT = 8;
SOURCE_TYPE_GITLAB = 9;
SOURCE_TYPE_JIRA = 10;
SOURCE_TYPE_NPM_UNAUTHD_PACKAGES = 11;
SOURCE_TYPE_PYPI_UNAUTHD_PACKAGES = 12;
SOURCE_TYPE_S3 = 13;
SOURCE_TYPE_SLACK = 14;
SOURCE_TYPE_FILESYSTEM = 15;
SOURCE_TYPE_GIT = 16;
SOURCE_TYPE_TEST = 17;
SOURCE_TYPE_S3_UNAUTHED = 18;
SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG = 19;
SOURCE_TYPE_BUILDKITE = 20;
SOURCE_TYPE_GERRIT = 21;
SOURCE_TYPE_JENKINS = 22;
}
message AzureStorage {
oneof credential {
string connection_string = 1;
credentials.BasicAuth basic_auth = 2;
string client_certificate = 3;
credentials.Unauthenticated unauthenticated = 4;
}
repeated string storage_containers = 5;
}
message Bitbucket {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
string token = 2;
credentials.Oauth2 oauth = 3;
credentials.BasicAuth basic_auth = 4;
}
repeated string repositories = 5;
}
message CircleCI {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
string token = 2;
}
}
message Confluence {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
credentials.Unauthenticated unauthenticated = 2;
credentials.BasicAuth basic_auth = 3;
string token = 4;
}
}
message DockerHub {
oneof credential {
credentials.Unauthenticated unauthenticated = 1;
}
repeated string repositories = 2;
}
message ECR {
oneof credential {
credentials.KeySecret access_key = 1;
}
repeated string registries = 2;
}
message Filesystem{
repeated string directories = 1;
}
message GCS {
oneof credential {
string json_sa = 1;
}
repeated string buckets = 2;
}
message Git {
oneof credential {
credentials.BasicAuth basic_auth = 1;
credentials.Unauthenticated unauthenticated = 2;
}
repeated string directories = 3;
repeated string repositories = 4;
}
message GitLab {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
string token = 2;
credentials.Oauth2 oauth = 3;
credentials.BasicAuth basic_auth = 4;
}
repeated string repositories = 5;
}
message GitHub {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
credentials.GitHubApp github_app = 2;
string token = 3;
credentials.Unauthenticated unauthenticated = 4;
}
repeated string repositories = 5;
repeated string organizations = 6;
bool scanUsers = 7;
}
message JIRA {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
credentials.BasicAuth basic_auth = 2;
credentials.Unauthenticated unauthenticated = 3;
credentials.Oauth2 oauth = 4;
}
repeated string projects = 5;
}
message NPMUnauthenticatedPackage {
oneof credential {
credentials.Unauthenticated unauthenticated = 1;
}
}
message PyPIUnauthenticatedPackage {
oneof credential {
credentials.Unauthenticated unauthenticated = 1;
}
}
message S3 {
oneof credential {
credentials.KeySecret access_key = 1;
credentials.Unauthenticated unauthenticated = 2;
credentials.CloudEnvironment cloud_environment = 4;
}
repeated string buckets = 3;
}
message Slack {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
string token = 2;
}
repeated string channels = 3;
}
message Test{}
message Buildkite {
oneof credential {
string token = 1;
}
}
message Gerrit {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
credentials.BasicAuth basic_auth = 2;
credentials.Unauthenticated unauthenticated = 3;
}
repeated string projects = 4;
}
message Jenkins {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
credentials.BasicAuth basic_auth = 2;
}
}

32
scripts/gen_proto.sh Executable file
View file

@ -0,0 +1,32 @@
#!/bin/bash
set -eux
protoc -I proto/ \
-I ${GOPATH}/src \
-I /usr/local/include \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=plugins=grpc:./pkg/pb/credentialspb --go_opt=paths=source_relative \
--validate_out="lang=go,paths=source_relative:./pkg/pb/credentialspb" \
proto/credentials.proto
protoc -I proto/ \
-I ${GOPATH}/src \
-I /usr/local/include \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=plugins=grpc:./pkg/pb/sourcespb --go_opt=paths=source_relative \
--validate_out="lang=go,paths=source_relative:./pkg/pb/sourcespb" \
proto/sources.proto
protoc -I proto/ \
-I ${GOPATH}/src \
-I /usr/local/include \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=plugins=grpc:./pkg/pb/detectorspb --go_opt=paths=source_relative \
--validate_out="lang=go,paths=source_relative:./pkg/pb/detectorspb" \
proto/detectors.proto
protoc -I proto/ \
-I ${GOPATH}/src \
-I /usr/local/include \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=plugins=grpc:./pkg/pb/source_metadatapb --go_opt=paths=source_relative \
--validate_out="lang=go,paths=source_relative:./pkg/pb/source_metadatapb" \
proto/source_metadata.proto