mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-10 07:04:24 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
6294b10983
62 changed files with 4190 additions and 266 deletions
26
README.md
26
README.md
|
@ -309,11 +309,33 @@ The following command will enumerate deleted and hidden commits on a GitHub repo
|
|||
trufflehog github-experimental --repo https://github.com/<USER>/<REPO>.git --object-discovery
|
||||
```
|
||||
|
||||
In addition to the normal TruffleHog output, the `--object-discovery` flag creates two files in a new `$HOME/.trufflehog` directory: `valid_hidden.txt` and `invalid.txt`. These are used to track state during commit enumeration, as well as to provide users with a complete list of all hidden and deleted commits (`valid_hidden.txt`). If you'd like to automatically remove these files after scanning, please add the flag `--delete-cached-data`.
|
||||
In addition to the normal TruffleHog output, the `--object-discovery` flag creates two files in a new `$HOME/.trufflehog` directory: `valid_hidden.txt` and `invalid.txt`. These are used to track state during commit enumeration, as well as to provide users with a complete list of all hidden and deleted commits (`valid_hidden.txt`). If you'd like to automatically remove these files after scanning, please add the flag `--delete-cached-data`.
|
||||
|
||||
**Note**: Enumerating all valid commits on a repository using this method takes between 20 minutes and a few hours, depending on the size of your repository. We added a progress bar to keep you updated on how long the enumeration will take. The actual secret scanning runs extremely fast.
|
||||
|
||||
For more information on Cross Fork Object References, please [read our blog post](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github).
|
||||
For more information on Cross Fork Object References, please [read our blog post](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github).
|
||||
|
||||
## 16. Scan Hugging Face
|
||||
|
||||
### Scan a Hugging Face Model, Dataset or Space
|
||||
|
||||
```bash
|
||||
trufflehog huggingface --model <model_id> --space <space_id> --dataset <dataset_id>
|
||||
```
|
||||
|
||||
### Scan all Models, Datasets and Spaces belonging to a Hugging Face Organization or User
|
||||
|
||||
```bash
|
||||
trufflehog huggingface --org <orgname> --user <username>
|
||||
```
|
||||
|
||||
(Optionally) When scanning an organization or user, you can skip an entire class of resources with `--skip-models`, `--skip-datasets`, `--skip-spaces` OR a particular resource with `--ignore-models <model_id>`, `--ignore-datasets <dataset_id>`, `--ignore-spaces <space_id>`.
|
||||
|
||||
### Scan Discussion and PR Comments
|
||||
|
||||
```bash
|
||||
trufflehog huggingface --model <model_id> --include-discussions --include-prs
|
||||
```
|
||||
|
||||
# :question: FAQ
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -141,7 +141,6 @@ require (
|
|||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.5 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/STARRY-S/zip v0.1.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 // indirect
|
||||
|
@ -165,7 +164,6 @@ require (
|
|||
github.com/charmbracelet/x/windows v0.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
|
|
104
go.sum
104
go.sum
|
@ -7,20 +7,10 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
|
|||
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.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY=
|
||||
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA=
|
||||
cloud.google.com/go/auth v0.8.0 h1:y8jUJLl/Fg+qNBWxP/Hox2ezJvjkrPb952PC1p0G6A4=
|
||||
cloud.google.com/go/auth v0.8.0/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
|
||||
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
|
||||
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
|
||||
cloud.google.com/go/auth v0.9.0 h1:cYhKl1JUhynmxjXfrk4qdPc6Amw7i+GC9VLflgT0p5M=
|
||||
cloud.google.com/go/auth v0.9.0/go.mod h1:2HsApZBr9zGZhC9QAXsYVYaWk8kNUt37uny+XVKi7wM=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
|
@ -28,18 +18,12 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
|
|||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
|
||||
cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg=
|
||||
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
|
||||
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
|
||||
cloud.google.com/go/longrunning v0.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk=
|
||||
cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4=
|
||||
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/secretmanager v1.13.5 h1:tXlHvpm97mFD0Lv50N4U4zlXfkoTNay3BmpNA/W7/oI=
|
||||
cloud.google.com/go/secretmanager v1.13.5/go.mod h1:/OeZ88l5Z6nBVilV0SXgv6XJ243KP2aIhSWRMrbvDCQ=
|
||||
cloud.google.com/go/secretmanager v1.13.6 h1:0ZEl/LuoB4xQsjVfQt3Gi/dZfOv36n4JmdPrMargzYs=
|
||||
cloud.google.com/go/secretmanager v1.13.6/go.mod h1:x2ySyOrqv3WGFRFn2Xk10iHmNmvmcEVSSqc30eb1bhw=
|
||||
cloud.google.com/go/secretmanager v1.14.0 h1:P2RRu2NEsQyOjplhUPvWKqzDXUKzwejHLuSUBHI8c4w=
|
||||
cloud.google.com/go/secretmanager v1.14.0/go.mod h1:q0hSFHzoW7eRgyYFH8trqEFavgrMeiJI4FETNN78vhM=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
|
@ -105,8 +89,6 @@ github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0
|
|||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38=
|
||||
github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/STARRY-S/zip v0.1.0 h1:eUER3jKmHKXjv+iy3BekLa+QnNSo1Lqz4eTzYBcGDqo=
|
||||
|
@ -170,22 +152,16 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
|||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
|
||||
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
|
||||
github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
|
||||
github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
|
||||
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
|
||||
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
|
||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
||||
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
|
||||
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
|
||||
|
@ -210,8 +186,6 @@ github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZ
|
|||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
|
||||
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
|
@ -250,14 +224,10 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
|
||||
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
|
@ -285,8 +255,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
|
@ -374,8 +342,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
|
|||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
|
||||
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -416,8 +384,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
|
||||
github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
|
||||
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
|
||||
github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4=
|
||||
|
@ -667,21 +633,13 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
|||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8=
|
||||
github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
|
@ -703,8 +661,6 @@ github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXI
|
|||
github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sendgrid/sendgrid-go v3.15.0+incompatible h1:oB6ujJD2aFcQRjmZLmmXiiUF9CBYKzsvYdPAS/71cSU=
|
||||
github.com/sendgrid/sendgrid-go v3.15.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
|
@ -752,28 +708,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
|||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tailscale/depaware v0.0.0-20240804103531-585336c3e1b3 h1:1wRadBXZ4ddC71FYpzDTS3weuj0IOgmeptZtgTDGU7g=
|
||||
github.com/tailscale/depaware v0.0.0-20240804103531-585336c3e1b3/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME=
|
||||
github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E=
|
||||
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
|
||||
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
|
||||
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.32.0 h1:nY9P1UKsKWoGuL01dbidgVupahMA3uIa3KbCaZgqsl4=
|
||||
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.32.0/go.mod h1:DcS1GwqZR18taQGusSQrW/nsIZOlgJzV2pSnlkcXBSY=
|
||||
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.33.0 h1:tVsooNzk7SgYDO1OnqeIgihDYiD/vSBNBqwqCfauIJY=
|
||||
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.33.0/go.mod h1:qmspvRf+Hx0iyqKQUmTg1jTNiO7HHGNrx98t/HksVfg=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.32.0 h1:DvmvHV1irfNIVBhixeTAcoaWCvmdkoNQxRmZisqic4E=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.32.0/go.mod h1:z0ZvM2V2iThZGrzEN6sddJpvnGhJd6O1O0FTFoZXmpk=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
|
||||
github.com/testcontainers/testcontainers-go/modules/mssql v0.32.0 h1:ZE22/n2X4bLUI3038ov1bcWSsluFIKQpjFYVU9E5tW8=
|
||||
github.com/testcontainers/testcontainers-go/modules/mssql v0.32.0/go.mod h1:1zk90NOHq3FZo2m4JFB3B2LrcVB2MNjgYjjL4o8XbBQ=
|
||||
github.com/testcontainers/testcontainers-go/modules/mssql v0.33.0 h1:gD4pHUPnEm5Bwup8KFdVmwXJLpyVy1hsp6bOXHAUlTA=
|
||||
github.com/testcontainers/testcontainers-go/modules/mssql v0.33.0/go.mod h1:HdgR2Q9SsGqohT6nhtU3tnG56iNGUV1Tr5If0QypZl0=
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.32.0 h1:6vjJOVJSWDTyNvQmB8EFTmv20ScquRWZa+pM1hZNodc=
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.32.0/go.mod h1:Q91G1jl4fSl75OICi+Bb6BQeU7LpKZaSfKvHOXRwPyI=
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.33.0 h1:1JN7YEEepTMJmGI2hW678IiiYoLM5HDp3vbCPmUokJ8=
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.33.0/go.mod h1:9tZZwRW5s3RaI5X0Wnc+GXNJFXqbkKmob2nBHbfA/5E=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0 h1:ZE4dTdswj3P0j71nL+pL0m2e5HTXJwPoIFr+DDgdPaU=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0/go.mod h1:njrNuyuoF2fjhVk6TG/R3Oeu82YwfYkbf5WVTyBXhV4=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 h1:c+Gt+XLJjqFAejgX4hSpnHIpC9eAhvgI/TFWL/PbrFI=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0/go.mod h1:I4DazHBoWDyf69ByOIyt3OdNjefiUx372459txOpQ3o=
|
||||
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
|
||||
|
@ -839,8 +783,6 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
|||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.einride.tech/aip v0.60.0 h1:h6bgabZ5BCfAptbGex8jbh3VvPBRLa6xq+pQ1CAjHYw=
|
||||
go.einride.tech/aip v0.60.0/go.mod h1:SdLbSbgSU60Xkb4TMkmsZEQPHeEWx0ikBoq5QnqZvdg=
|
||||
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
|
||||
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
|
||||
go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -849,30 +791,20 @@ 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.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
|
@ -907,8 +839,6 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
|||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -973,8 +903,6 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -1042,10 +970,7 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -1057,7 +982,6 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
|
@ -1074,14 +998,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -1133,12 +1053,6 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
|
|||
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.190.0 h1:ASM+IhLY1zljNdLu19W1jTmU6A+gMk6M46Wlur61s+Q=
|
||||
google.golang.org/api v0.190.0/go.mod h1:QIr6I9iedBLnfqoD6L6Vze1UvS5Hzj5r2aUBOaZnLHo=
|
||||
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
|
||||
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
|
||||
google.golang.org/api v0.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
|
||||
google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
|
||||
google.golang.org/api v0.193.0 h1:eOGDoJFsLU+HpCBaDJex2fWiYujAw9KbXgpOAMePoUs=
|
||||
google.golang.org/api v0.193.0/go.mod h1:Po3YMV1XZx+mTku3cfJrlIYR03wiGrCOsdpC67hjZvw=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
|
@ -1161,18 +1075,10 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
|
|||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY=
|
||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
|
||||
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 h1:oLiyxGgE+rt22duwci1+TG7bg2/L1LQsXwfjPlmuJA0=
|
||||
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142/go.mod h1:G11eXq53iI5Q+kyNOmCvnzBaxEA2Q/Ik5Tj7nqBE8j4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
@ -1185,8 +1091,6 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
|||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//go:generate generate_permissions permissions.yaml permissions.go bitbucket
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
|
@ -18,37 +19,33 @@ import (
|
|||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Bitbucket }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
var resource_name_map = map[string]string{
|
||||
"repo_access_token": "Repository",
|
||||
"project_access_token": "Project",
|
||||
"workspace_access_token": "Workspace",
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Type string
|
||||
OauthScopes []analyzers.Permission
|
||||
OauthScopes []string
|
||||
Repos []Repo
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
ID string `json:"uuid"`
|
||||
FullName string `json:"full_name"`
|
||||
RepoName string `json:"name"`
|
||||
Project struct {
|
||||
ID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
} `json:"project"`
|
||||
Workspace struct {
|
||||
ID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
} `json:"workspace"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
Owner struct {
|
||||
ID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
} `json:"owner"`
|
||||
Role string
|
||||
|
@ -58,7 +55,81 @@ type RepoJSON struct {
|
|||
Values []Repo `json:"values"`
|
||||
}
|
||||
|
||||
func getScopesAndType(cfg *config.Config, key string) (string, []analyzers.Permission, error) {
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Bitbucket }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, errors.New("key not found in credentialInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzerpb.AnalyzerType_Bitbucket,
|
||||
}
|
||||
|
||||
// add unbounded resources
|
||||
result.UnboundedResources = make([]analyzers.Resource, len(info.Repos))
|
||||
for i, repo := range info.Repos {
|
||||
result.UnboundedResources[i] = analyzers.Resource{
|
||||
Type: "repository",
|
||||
Name: repo.FullName,
|
||||
FullyQualifiedName: "bitbucket.com/repository/" + repo.ID,
|
||||
Parent: &analyzers.Resource{
|
||||
Type: "project",
|
||||
Name: repo.Project.Name,
|
||||
FullyQualifiedName: "bitbucket.com/project/" + repo.Project.ID,
|
||||
Parent: &analyzers.Resource{
|
||||
Type: "workspace",
|
||||
Name: repo.Workspace.Name,
|
||||
FullyQualifiedName: "bitbucket.com/workspace/" + repo.Workspace.ID,
|
||||
},
|
||||
},
|
||||
Metadata: map[string]any{
|
||||
"owner_id": repo.Owner.ID,
|
||||
"owner": repo.Owner.Username,
|
||||
"is_private": repo.IsPrivate,
|
||||
"role": repo.Role,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
credentialResource := &analyzers.Resource{
|
||||
Type: info.Type,
|
||||
Name: resource_name_map[info.Type],
|
||||
FullyQualifiedName: "bitbucket.com/credential/" + info.Type,
|
||||
Metadata: map[string]any{
|
||||
"type": credential_type_map[info.Type],
|
||||
},
|
||||
}
|
||||
|
||||
for _, scope := range info.OauthScopes {
|
||||
result.Bindings = append(result.Bindings, analyzers.Binding{
|
||||
Resource: *credentialResource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: scope,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func getScopesAndType(cfg *config.Config, key string) (string, []string, error) {
|
||||
// client
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
|
||||
|
@ -82,10 +153,7 @@ func getScopesAndType(cfg *config.Config, key string) (string, []analyzers.Permi
|
|||
credentialType := resp.Header.Get("x-credential-type")
|
||||
oauthScopes := resp.Header.Get("x-oauth-scopes")
|
||||
|
||||
var scopes []analyzers.Permission
|
||||
for _, scope := range strings.Split(oauthScopes, ", ") {
|
||||
scopes = append(scopes, analyzers.Permission{Value: scope})
|
||||
}
|
||||
scopes := strings.Split(oauthScopes, ", ")
|
||||
return credentialType, scopes, nil
|
||||
}
|
||||
|
||||
|
@ -183,13 +251,21 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func convertScopeToAnalyzerPermissions(scopes []string) []analyzers.Permission {
|
||||
permissions := make([]analyzers.Permission, len(scopes))
|
||||
for _, scope := range scopes {
|
||||
permissions = append(permissions, analyzers.Permission{Value: scope})
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
||||
info, err := AnalyzePermissions(cfg, key)
|
||||
if err != nil {
|
||||
color.Red("[x] Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
printScopes(info.Type, info.OauthScopes)
|
||||
printScopes(info.Type, convertScopeToAnalyzerPermissions(info.OauthScopes))
|
||||
printAccessibleRepositories(info.Repos)
|
||||
}
|
||||
|
||||
|
|
96
pkg/analyzer/analyzers/bitbucket/bitbucket_test.go
Normal file
96
pkg/analyzer/analyzers/bitbucket/bitbucket_test.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package bitbucket
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sid string
|
||||
key string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid Bitbucket key",
|
||||
key: testSecrets.MustGetField("BITBUCKET_ANALYZE_TOKEN"),
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.key, "sid": tt.sid})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = \n%s", gotIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
92
pkg/analyzer/analyzers/bitbucket/expected_output.json
Normal file
92
pkg/analyzer/analyzers/bitbucket/expected_output.json
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"AnalyzerType": 3,
|
||||
"Bindings": [
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Repository",
|
||||
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
|
||||
"Type": "repo_access_token",
|
||||
"Metadata": {
|
||||
"type": "Repository Access Token (Can access 1 repository)"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "pipeline",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Repository",
|
||||
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
|
||||
"Type": "repo_access_token",
|
||||
"Metadata": {
|
||||
"type": "Repository Access Token (Can access 1 repository)"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "pullrequest",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Repository",
|
||||
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
|
||||
"Type": "repo_access_token",
|
||||
"Metadata": {
|
||||
"type": "Repository Access Token (Can access 1 repository)"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "runner",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Repository",
|
||||
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
|
||||
"Type": "repo_access_token",
|
||||
"Metadata": {
|
||||
"type": "Repository Access Token (Can access 1 repository)"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "webhook",
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"UnboundedResources": [
|
||||
{
|
||||
"Name": "basit-trufflesec/repo1",
|
||||
"FullyQualifiedName": "bitbucket.com/repository/{8961ef70-000c-47ca-9348-5f9ecee875d6}",
|
||||
"Type": "repository",
|
||||
"Metadata": {
|
||||
"is_private": true,
|
||||
"owner": "basit-trufflesec",
|
||||
"owner_id": "{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
|
||||
"role": "admin"
|
||||
},
|
||||
"Parent": {
|
||||
"Name": "repo-analyzer",
|
||||
"FullyQualifiedName": "bitbucket.com/project/{8a693e10-087f-41fc-ba67-2d1414ab1c86}",
|
||||
"Type": "project",
|
||||
"Metadata": null,
|
||||
"Parent": {
|
||||
"Name": "basit-trufflesec",
|
||||
"FullyQualifiedName": "bitbucket.com/workspace/{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
|
||||
"Type": "workspace",
|
||||
"Metadata": null,
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Metadata": null
|
||||
}
|
131
pkg/analyzer/analyzers/bitbucket/permissions.go
Normal file
131
pkg/analyzer/analyzers/bitbucket/permissions.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
package bitbucket
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
Project Permission = iota
|
||||
ProjectAdmin Permission = iota
|
||||
Repository Permission = iota
|
||||
RepositoryWrite Permission = iota
|
||||
RepositoryAdmin Permission = iota
|
||||
RepositoryDelete Permission = iota
|
||||
Pullrequest Permission = iota
|
||||
PullrequestWrite Permission = iota
|
||||
Webhook Permission = iota
|
||||
Account Permission = iota
|
||||
Pipeline Permission = iota
|
||||
PipelineWrite Permission = iota
|
||||
PipelineVariable Permission = iota
|
||||
Runner Permission = iota
|
||||
RunnerWrite Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
Project: "project",
|
||||
ProjectAdmin: "project:admin",
|
||||
Repository: "repository",
|
||||
RepositoryWrite: "repository:write",
|
||||
RepositoryAdmin: "repository:admin",
|
||||
RepositoryDelete: "repository:delete",
|
||||
Pullrequest: "pullrequest",
|
||||
PullrequestWrite: "pullrequest:write",
|
||||
Webhook: "webhook",
|
||||
Account: "account",
|
||||
Pipeline: "pipeline",
|
||||
PipelineWrite: "pipeline:write",
|
||||
PipelineVariable: "pipeline:variable",
|
||||
Runner: "runner",
|
||||
RunnerWrite: "runner:write",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"project": Project,
|
||||
"project:admin": ProjectAdmin,
|
||||
"repository": Repository,
|
||||
"repository:write": RepositoryWrite,
|
||||
"repository:admin": RepositoryAdmin,
|
||||
"repository:delete": RepositoryDelete,
|
||||
"pullrequest": Pullrequest,
|
||||
"pullrequest:write": PullrequestWrite,
|
||||
"webhook": Webhook,
|
||||
"account": Account,
|
||||
"pipeline": Pipeline,
|
||||
"pipeline:write": PipelineWrite,
|
||||
"pipeline:variable": PipelineVariable,
|
||||
"runner": Runner,
|
||||
"runner:write": RunnerWrite,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
Project: 1,
|
||||
ProjectAdmin: 2,
|
||||
Repository: 3,
|
||||
RepositoryWrite: 4,
|
||||
RepositoryAdmin: 5,
|
||||
RepositoryDelete: 6,
|
||||
Pullrequest: 7,
|
||||
PullrequestWrite: 8,
|
||||
Webhook: 9,
|
||||
Account: 10,
|
||||
Pipeline: 11,
|
||||
PipelineWrite: 12,
|
||||
PipelineVariable: 13,
|
||||
Runner: 14,
|
||||
RunnerWrite: 15,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: Project,
|
||||
2: ProjectAdmin,
|
||||
3: Repository,
|
||||
4: RepositoryWrite,
|
||||
5: RepositoryAdmin,
|
||||
6: RepositoryDelete,
|
||||
7: Pullrequest,
|
||||
8: PullrequestWrite,
|
||||
9: Webhook,
|
||||
10: Account,
|
||||
11: Pipeline,
|
||||
12: PipelineWrite,
|
||||
13: PipelineVariable,
|
||||
14: Runner,
|
||||
15: RunnerWrite,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
16
pkg/analyzer/analyzers/bitbucket/permissions.yaml
Normal file
16
pkg/analyzer/analyzers/bitbucket/permissions.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
permissions:
|
||||
- project
|
||||
- project:admin
|
||||
- repository
|
||||
- repository:write
|
||||
- repository:admin
|
||||
- repository:delete
|
||||
- pullrequest
|
||||
- pullrequest:write
|
||||
- webhook
|
||||
- account
|
||||
- pipeline
|
||||
- pipeline:write
|
||||
- pipeline:variable
|
||||
- runner
|
||||
- runner:write
|
1
pkg/analyzer/analyzers/mailchimp/expected_output.json
Normal file
1
pkg/analyzer/analyzers/mailchimp/expected_output.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,9 @@
|
|||
//go:generate generate_permissions permissions.yaml permissions.go mailchimp
|
||||
package mailchimp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -26,11 +28,66 @@ type Analyzer struct {
|
|||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Mailchimp }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, errors.New("key not found in credentialInfo")
|
||||
}
|
||||
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzerpb.AnalyzerType_Mailchimp,
|
||||
Bindings: make([]analyzers.Binding, 0, len(StringToPermission)),
|
||||
UnboundedResources: make([]analyzers.Resource, 0, len(info.Domains.Domains)),
|
||||
}
|
||||
|
||||
accountResource := analyzers.Resource{
|
||||
Name: info.Metadata.AccountName,
|
||||
FullyQualifiedName: "mailchimp.com/account/" + info.Metadata.AccountID,
|
||||
Type: "account",
|
||||
Metadata: map[string]any{
|
||||
"email": info.Metadata.Email,
|
||||
"role": info.Metadata.Role,
|
||||
"member_since": info.Metadata.MemberSince,
|
||||
"pricing_plan": info.Metadata.PricingPlan,
|
||||
"account_timezone": info.Metadata.AccountTimezone,
|
||||
"last_login": info.Metadata.LastLogin,
|
||||
"total_subscribers": info.Metadata.TotalSubscribers,
|
||||
},
|
||||
}
|
||||
|
||||
for perm := range StringToPermission {
|
||||
result.Bindings = append(result.Bindings, analyzers.Binding{
|
||||
Resource: accountResource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: perm,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, domain := range info.Domains.Domains {
|
||||
result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
|
||||
Name: domain.Domain,
|
||||
FullyQualifiedName: "mailchimp.com/domain/" + domain.Domain,
|
||||
Type: "domain",
|
||||
Metadata: map[string]any{
|
||||
"verified": domain.Verified,
|
||||
"authenticated": domain.Authenticated,
|
||||
},
|
||||
Parent: &accountResource,
|
||||
})
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
type MetadataJSON struct {
|
||||
|
|
101
pkg/analyzer/analyzers/mailchimp/mailchimp_test.go
Normal file
101
pkg/analyzer/analyzers/mailchimp/mailchimp_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package mailchimp
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expected_output []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid Mailchimp key",
|
||||
key: testSecrets.MustGetField("MAILCHIMP"),
|
||||
want: string(expected_output),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// Bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.Marshal(got)
|
||||
// gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
391
pkg/analyzer/analyzers/mailchimp/permissions.go
Normal file
391
pkg/analyzer/analyzers/mailchimp/permissions.go
Normal file
|
@ -0,0 +1,391 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
package mailchimp
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
InviteUsers Permission = iota
|
||||
RevokeAccountAccess Permission = iota
|
||||
SetUserAccessLevel Permission = iota
|
||||
Require2FactorAuthentication Permission = iota
|
||||
ChangeBillingInformation Permission = iota
|
||||
ChangeCompanyOrganizationName Permission = iota
|
||||
AddOrAccessApiKeys Permission = iota
|
||||
CheckReconnectIntegrations Permission = iota
|
||||
ReferralProgram Permission = iota
|
||||
AccountExport Permission = iota
|
||||
CloseAccount Permission = iota
|
||||
AddFilesToContentStudio Permission = iota
|
||||
OptInToReceiveEmailsFromMailchimp Permission = iota
|
||||
CreateAudiences Permission = iota
|
||||
ViewAudiences Permission = iota
|
||||
AudienceExport Permission = iota
|
||||
AudienceImport Permission = iota
|
||||
AddContacts Permission = iota
|
||||
DeleteContacts Permission = iota
|
||||
ViewSegments Permission = iota
|
||||
EditAudienceSettings Permission = iota
|
||||
ArchiveContacts Permission = iota
|
||||
CreateOrImportTemplates Permission = iota
|
||||
EditTemplates Permission = iota
|
||||
CreateEmails Permission = iota
|
||||
EditEmails Permission = iota
|
||||
SendPublishEmails Permission = iota
|
||||
PauseUnpublishEmails Permission = iota
|
||||
DeleteEmails Permission = iota
|
||||
SubmitSmsMarketingApplication Permission = iota
|
||||
CreateSendSmsMmsMessages Permission = iota
|
||||
PurchaseSmsCredits Permission = iota
|
||||
ViewEmailReports Permission = iota
|
||||
ViewSmsReports Permission = iota
|
||||
ViewAbuseReports Permission = iota
|
||||
ViewEmailStatistics Permission = iota
|
||||
UseConversations Permission = iota
|
||||
ViewEmailRecipients Permission = iota
|
||||
TopLocations Permission = iota
|
||||
EmailContactDetails Permission = iota
|
||||
EmailOpenDetails Permission = iota
|
||||
ECommerceProductActivity Permission = iota
|
||||
DomainPerformance Permission = iota
|
||||
CreateYourWebsite Permission = iota
|
||||
PublishUnpublishYourWebsite Permission = iota
|
||||
ViewReport Permission = iota
|
||||
CreateALandingPage Permission = iota
|
||||
PublishUnpublishALandingPage Permission = iota
|
||||
ReplicateALandingPage Permission = iota
|
||||
VerifyADomain Permission = iota
|
||||
ConnectADomain Permission = iota
|
||||
CreateCustomerJourney Permission = iota
|
||||
ViewCustomerJourney Permission = iota
|
||||
EditCustomerJourney Permission = iota
|
||||
TurnOnPauseTurnBackOn Permission = iota
|
||||
ViewMessages Permission = iota
|
||||
LeaveComments Permission = iota
|
||||
SendMessages Permission = iota
|
||||
ToggleUserNotifications Permission = iota
|
||||
CreateSurvey Permission = iota
|
||||
EditSurvey Permission = iota
|
||||
PublishSurvey Permission = iota
|
||||
DeleteSurvey Permission = iota
|
||||
CreateForm Permission = iota
|
||||
EditForm Permission = iota
|
||||
PublishForm Permission = iota
|
||||
DeleteForm Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
InviteUsers: "invite_users",
|
||||
RevokeAccountAccess: "revoke_account_access",
|
||||
SetUserAccessLevel: "set_user_access_level",
|
||||
Require2FactorAuthentication: "require_2_factor_authentication",
|
||||
ChangeBillingInformation: "change_billing_information",
|
||||
ChangeCompanyOrganizationName: "change_company_organization_name",
|
||||
AddOrAccessApiKeys: "add_or_access_api_keys",
|
||||
CheckReconnectIntegrations: "check_reconnect_integrations",
|
||||
ReferralProgram: "referral_program",
|
||||
AccountExport: "account_export",
|
||||
CloseAccount: "close_account",
|
||||
AddFilesToContentStudio: "add_files_to_content_studio",
|
||||
OptInToReceiveEmailsFromMailchimp: "opt_in_to_receive_emails_from_mailchimp",
|
||||
CreateAudiences: "create_audiences",
|
||||
ViewAudiences: "view_audiences",
|
||||
AudienceExport: "audience_export",
|
||||
AudienceImport: "audience_import",
|
||||
AddContacts: "add_contacts",
|
||||
DeleteContacts: "delete_contacts",
|
||||
ViewSegments: "view_segments",
|
||||
EditAudienceSettings: "edit_audience_settings",
|
||||
ArchiveContacts: "archive_contacts",
|
||||
CreateOrImportTemplates: "create_or_import_templates",
|
||||
EditTemplates: "edit_templates",
|
||||
CreateEmails: "create_emails",
|
||||
EditEmails: "edit_emails",
|
||||
SendPublishEmails: "send_publish_emails",
|
||||
PauseUnpublishEmails: "pause_unpublish_emails",
|
||||
DeleteEmails: "delete_emails",
|
||||
SubmitSmsMarketingApplication: "submit_sms_marketing_application",
|
||||
CreateSendSmsMmsMessages: "create_send_sms_mms_messages",
|
||||
PurchaseSmsCredits: "purchase_sms_credits",
|
||||
ViewEmailReports: "view_email_reports",
|
||||
ViewSmsReports: "view_sms_reports",
|
||||
ViewAbuseReports: "view_abuse_reports",
|
||||
ViewEmailStatistics: "view_email_statistics",
|
||||
UseConversations: "use_conversations",
|
||||
ViewEmailRecipients: "view_email_recipients",
|
||||
TopLocations: "top_locations",
|
||||
EmailContactDetails: "email_contact_details",
|
||||
EmailOpenDetails: "email_open_details",
|
||||
ECommerceProductActivity: "e_commerce_product_activity",
|
||||
DomainPerformance: "domain_performance",
|
||||
CreateYourWebsite: "create_your_website",
|
||||
PublishUnpublishYourWebsite: "publish_unpublish_your_website",
|
||||
ViewReport: "view_report",
|
||||
CreateALandingPage: "create_a_landing_page",
|
||||
PublishUnpublishALandingPage: "publish_unpublish_a_landing_page",
|
||||
ReplicateALandingPage: "replicate_a_landing_page",
|
||||
VerifyADomain: "verify_a_domain",
|
||||
ConnectADomain: "connect_a_domain",
|
||||
CreateCustomerJourney: "create_customer_journey",
|
||||
ViewCustomerJourney: "view_customer_journey",
|
||||
EditCustomerJourney: "edit_customer_journey",
|
||||
TurnOnPauseTurnBackOn: "turn_on_pause_turn_back_on",
|
||||
ViewMessages: "view_messages",
|
||||
LeaveComments: "leave_comments",
|
||||
SendMessages: "send_messages",
|
||||
ToggleUserNotifications: "toggle_user_notifications",
|
||||
CreateSurvey: "create_survey",
|
||||
EditSurvey: "edit_survey",
|
||||
PublishSurvey: "publish_survey",
|
||||
DeleteSurvey: "delete_survey",
|
||||
CreateForm: "create_form",
|
||||
EditForm: "edit_form",
|
||||
PublishForm: "publish_form",
|
||||
DeleteForm: "delete_form",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"invite_users": InviteUsers,
|
||||
"revoke_account_access": RevokeAccountAccess,
|
||||
"set_user_access_level": SetUserAccessLevel,
|
||||
"require_2_factor_authentication": Require2FactorAuthentication,
|
||||
"change_billing_information": ChangeBillingInformation,
|
||||
"change_company_organization_name": ChangeCompanyOrganizationName,
|
||||
"add_or_access_api_keys": AddOrAccessApiKeys,
|
||||
"check_reconnect_integrations": CheckReconnectIntegrations,
|
||||
"referral_program": ReferralProgram,
|
||||
"account_export": AccountExport,
|
||||
"close_account": CloseAccount,
|
||||
"add_files_to_content_studio": AddFilesToContentStudio,
|
||||
"opt_in_to_receive_emails_from_mailchimp": OptInToReceiveEmailsFromMailchimp,
|
||||
"create_audiences": CreateAudiences,
|
||||
"view_audiences": ViewAudiences,
|
||||
"audience_export": AudienceExport,
|
||||
"audience_import": AudienceImport,
|
||||
"add_contacts": AddContacts,
|
||||
"delete_contacts": DeleteContacts,
|
||||
"view_segments": ViewSegments,
|
||||
"edit_audience_settings": EditAudienceSettings,
|
||||
"archive_contacts": ArchiveContacts,
|
||||
"create_or_import_templates": CreateOrImportTemplates,
|
||||
"edit_templates": EditTemplates,
|
||||
"create_emails": CreateEmails,
|
||||
"edit_emails": EditEmails,
|
||||
"send_publish_emails": SendPublishEmails,
|
||||
"pause_unpublish_emails": PauseUnpublishEmails,
|
||||
"delete_emails": DeleteEmails,
|
||||
"submit_sms_marketing_application": SubmitSmsMarketingApplication,
|
||||
"create_send_sms_mms_messages": CreateSendSmsMmsMessages,
|
||||
"purchase_sms_credits": PurchaseSmsCredits,
|
||||
"view_email_reports": ViewEmailReports,
|
||||
"view_sms_reports": ViewSmsReports,
|
||||
"view_abuse_reports": ViewAbuseReports,
|
||||
"view_email_statistics": ViewEmailStatistics,
|
||||
"use_conversations": UseConversations,
|
||||
"view_email_recipients": ViewEmailRecipients,
|
||||
"top_locations": TopLocations,
|
||||
"email_contact_details": EmailContactDetails,
|
||||
"email_open_details": EmailOpenDetails,
|
||||
"e_commerce_product_activity": ECommerceProductActivity,
|
||||
"domain_performance": DomainPerformance,
|
||||
"create_your_website": CreateYourWebsite,
|
||||
"publish_unpublish_your_website": PublishUnpublishYourWebsite,
|
||||
"view_report": ViewReport,
|
||||
"create_a_landing_page": CreateALandingPage,
|
||||
"publish_unpublish_a_landing_page": PublishUnpublishALandingPage,
|
||||
"replicate_a_landing_page": ReplicateALandingPage,
|
||||
"verify_a_domain": VerifyADomain,
|
||||
"connect_a_domain": ConnectADomain,
|
||||
"create_customer_journey": CreateCustomerJourney,
|
||||
"view_customer_journey": ViewCustomerJourney,
|
||||
"edit_customer_journey": EditCustomerJourney,
|
||||
"turn_on_pause_turn_back_on": TurnOnPauseTurnBackOn,
|
||||
"view_messages": ViewMessages,
|
||||
"leave_comments": LeaveComments,
|
||||
"send_messages": SendMessages,
|
||||
"toggle_user_notifications": ToggleUserNotifications,
|
||||
"create_survey": CreateSurvey,
|
||||
"edit_survey": EditSurvey,
|
||||
"publish_survey": PublishSurvey,
|
||||
"delete_survey": DeleteSurvey,
|
||||
"create_form": CreateForm,
|
||||
"edit_form": EditForm,
|
||||
"publish_form": PublishForm,
|
||||
"delete_form": DeleteForm,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
InviteUsers: 1,
|
||||
RevokeAccountAccess: 2,
|
||||
SetUserAccessLevel: 3,
|
||||
Require2FactorAuthentication: 4,
|
||||
ChangeBillingInformation: 5,
|
||||
ChangeCompanyOrganizationName: 6,
|
||||
AddOrAccessApiKeys: 7,
|
||||
CheckReconnectIntegrations: 8,
|
||||
ReferralProgram: 9,
|
||||
AccountExport: 10,
|
||||
CloseAccount: 11,
|
||||
AddFilesToContentStudio: 12,
|
||||
OptInToReceiveEmailsFromMailchimp: 13,
|
||||
CreateAudiences: 14,
|
||||
ViewAudiences: 15,
|
||||
AudienceExport: 16,
|
||||
AudienceImport: 17,
|
||||
AddContacts: 18,
|
||||
DeleteContacts: 19,
|
||||
ViewSegments: 20,
|
||||
EditAudienceSettings: 21,
|
||||
ArchiveContacts: 22,
|
||||
CreateOrImportTemplates: 23,
|
||||
EditTemplates: 24,
|
||||
CreateEmails: 25,
|
||||
EditEmails: 26,
|
||||
SendPublishEmails: 27,
|
||||
PauseUnpublishEmails: 28,
|
||||
DeleteEmails: 29,
|
||||
SubmitSmsMarketingApplication: 30,
|
||||
CreateSendSmsMmsMessages: 31,
|
||||
PurchaseSmsCredits: 32,
|
||||
ViewEmailReports: 33,
|
||||
ViewSmsReports: 34,
|
||||
ViewAbuseReports: 35,
|
||||
ViewEmailStatistics: 36,
|
||||
UseConversations: 37,
|
||||
ViewEmailRecipients: 38,
|
||||
TopLocations: 39,
|
||||
EmailContactDetails: 40,
|
||||
EmailOpenDetails: 41,
|
||||
ECommerceProductActivity: 42,
|
||||
DomainPerformance: 43,
|
||||
CreateYourWebsite: 44,
|
||||
PublishUnpublishYourWebsite: 45,
|
||||
ViewReport: 46,
|
||||
CreateALandingPage: 47,
|
||||
PublishUnpublishALandingPage: 48,
|
||||
ReplicateALandingPage: 49,
|
||||
VerifyADomain: 50,
|
||||
ConnectADomain: 51,
|
||||
CreateCustomerJourney: 52,
|
||||
ViewCustomerJourney: 53,
|
||||
EditCustomerJourney: 54,
|
||||
TurnOnPauseTurnBackOn: 55,
|
||||
ViewMessages: 56,
|
||||
LeaveComments: 57,
|
||||
SendMessages: 58,
|
||||
ToggleUserNotifications: 59,
|
||||
CreateSurvey: 60,
|
||||
EditSurvey: 61,
|
||||
PublishSurvey: 62,
|
||||
DeleteSurvey: 63,
|
||||
CreateForm: 64,
|
||||
EditForm: 65,
|
||||
PublishForm: 66,
|
||||
DeleteForm: 67,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: InviteUsers,
|
||||
2: RevokeAccountAccess,
|
||||
3: SetUserAccessLevel,
|
||||
4: Require2FactorAuthentication,
|
||||
5: ChangeBillingInformation,
|
||||
6: ChangeCompanyOrganizationName,
|
||||
7: AddOrAccessApiKeys,
|
||||
8: CheckReconnectIntegrations,
|
||||
9: ReferralProgram,
|
||||
10: AccountExport,
|
||||
11: CloseAccount,
|
||||
12: AddFilesToContentStudio,
|
||||
13: OptInToReceiveEmailsFromMailchimp,
|
||||
14: CreateAudiences,
|
||||
15: ViewAudiences,
|
||||
16: AudienceExport,
|
||||
17: AudienceImport,
|
||||
18: AddContacts,
|
||||
19: DeleteContacts,
|
||||
20: ViewSegments,
|
||||
21: EditAudienceSettings,
|
||||
22: ArchiveContacts,
|
||||
23: CreateOrImportTemplates,
|
||||
24: EditTemplates,
|
||||
25: CreateEmails,
|
||||
26: EditEmails,
|
||||
27: SendPublishEmails,
|
||||
28: PauseUnpublishEmails,
|
||||
29: DeleteEmails,
|
||||
30: SubmitSmsMarketingApplication,
|
||||
31: CreateSendSmsMmsMessages,
|
||||
32: PurchaseSmsCredits,
|
||||
33: ViewEmailReports,
|
||||
34: ViewSmsReports,
|
||||
35: ViewAbuseReports,
|
||||
36: ViewEmailStatistics,
|
||||
37: UseConversations,
|
||||
38: ViewEmailRecipients,
|
||||
39: TopLocations,
|
||||
40: EmailContactDetails,
|
||||
41: EmailOpenDetails,
|
||||
42: ECommerceProductActivity,
|
||||
43: DomainPerformance,
|
||||
44: CreateYourWebsite,
|
||||
45: PublishUnpublishYourWebsite,
|
||||
46: ViewReport,
|
||||
47: CreateALandingPage,
|
||||
48: PublishUnpublishALandingPage,
|
||||
49: ReplicateALandingPage,
|
||||
50: VerifyADomain,
|
||||
51: ConnectADomain,
|
||||
52: CreateCustomerJourney,
|
||||
53: ViewCustomerJourney,
|
||||
54: EditCustomerJourney,
|
||||
55: TurnOnPauseTurnBackOn,
|
||||
56: ViewMessages,
|
||||
57: LeaveComments,
|
||||
58: SendMessages,
|
||||
59: ToggleUserNotifications,
|
||||
60: CreateSurvey,
|
||||
61: EditSurvey,
|
||||
62: PublishSurvey,
|
||||
63: DeleteSurvey,
|
||||
64: CreateForm,
|
||||
65: EditForm,
|
||||
66: PublishForm,
|
||||
67: DeleteForm,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
68
pkg/analyzer/analyzers/mailchimp/permissions.yaml
Normal file
68
pkg/analyzer/analyzers/mailchimp/permissions.yaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
permissions:
|
||||
- invite_users
|
||||
- revoke_account_access
|
||||
- set_user_access_level
|
||||
- require_2_factor_authentication
|
||||
- change_billing_information
|
||||
- change_company_organization_name
|
||||
- add_or_access_api_keys
|
||||
- check_reconnect_integrations
|
||||
- referral_program
|
||||
- account_export
|
||||
- close_account
|
||||
- add_files_to_content_studio
|
||||
- opt_in_to_receive_emails_from_mailchimp
|
||||
- create_audiences
|
||||
- view_audiences
|
||||
- audience_export
|
||||
- audience_import
|
||||
- add_contacts
|
||||
- delete_contacts
|
||||
- view_segments
|
||||
- edit_audience_settings
|
||||
- archive_contacts
|
||||
- create_or_import_templates
|
||||
- edit_templates
|
||||
- create_emails
|
||||
- edit_emails
|
||||
- send_publish_emails
|
||||
- pause_unpublish_emails
|
||||
- delete_emails
|
||||
- submit_sms_marketing_application
|
||||
- create_send_sms_mms_messages
|
||||
- purchase_sms_credits
|
||||
- view_email_reports
|
||||
- view_sms_reports
|
||||
- view_abuse_reports
|
||||
- view_email_statistics
|
||||
- use_conversations
|
||||
- view_email_recipients
|
||||
- top_locations
|
||||
- email_contact_details
|
||||
- email_open_details
|
||||
- e_commerce_product_activity
|
||||
- domain_performance
|
||||
- create_your_website
|
||||
- publish_unpublish_your_website
|
||||
- view_report
|
||||
- create_a_landing_page
|
||||
- publish_unpublish_a_landing_page
|
||||
- replicate_a_landing_page
|
||||
- verify_a_domain
|
||||
- connect_a_domain
|
||||
- create_customer_journey
|
||||
- view_customer_journey
|
||||
- edit_customer_journey
|
||||
- turn_on_pause_turn_back_on
|
||||
- view_messages
|
||||
- leave_comments
|
||||
- send_messages
|
||||
- toggle_user_notifications
|
||||
- create_survey
|
||||
- edit_survey
|
||||
- publish_survey
|
||||
- delete_survey
|
||||
- create_form
|
||||
- edit_form
|
||||
- publish_form
|
||||
- delete_form
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate generate_permissions permissions.yaml permissions.go opsgenie
|
||||
|
||||
package opsgenie
|
||||
|
||||
import (
|
||||
|
@ -14,8 +16,79 @@ import (
|
|||
"github.com/jedib0t/go-pretty/table"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Opsgenie }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing key in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzerpb.AnalyzerType_Opsgenie,
|
||||
Metadata: nil,
|
||||
Bindings: make([]analyzers.Binding, len(info.Permissions)),
|
||||
UnboundedResources: make([]analyzers.Resource, len(info.Users)),
|
||||
}
|
||||
|
||||
// Opsgenie has API integrations, so the key does not belong
|
||||
// to a particular user or account, it itself is a resource
|
||||
resource := analyzers.Resource{
|
||||
Name: "Opsgenie API Integration Key",
|
||||
FullyQualifiedName: "Opsgenie API Integration Key",
|
||||
Type: "API Key",
|
||||
Metadata: map[string]any{
|
||||
"expires": "never",
|
||||
},
|
||||
}
|
||||
|
||||
for idx, permission := range info.Permissions {
|
||||
result.Bindings[idx] = analyzers.Binding{
|
||||
Resource: resource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: permission,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// We can find list of users in the current account
|
||||
// if the API key has Configuration Access, so these can be
|
||||
// unbounded resources
|
||||
for idx, user := range info.Users {
|
||||
result.UnboundedResources[idx] = analyzers.Resource{
|
||||
Name: user.FullName,
|
||||
FullyQualifiedName: user.Username,
|
||||
Type: "user",
|
||||
Metadata: map[string]any{
|
||||
"username": user.Username,
|
||||
"role": user.Role.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
//go:embed scopes.json
|
||||
var scopesConfig []byte
|
||||
|
||||
|
@ -195,7 +268,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
|||
|
||||
info.Permissions = permissions
|
||||
|
||||
if contains(permissions, "Configuration Access") {
|
||||
if contains(permissions, "configuration_access") {
|
||||
users, err := getUserList(cfg, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting user list: %w", err)
|
||||
|
|
158
pkg/analyzer/analyzers/opsgenie/opsgenie_test.go
Normal file
158
pkg/analyzer/analyzers/opsgenie/opsgenie_test.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
package opsgenie
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
key := testSecrets.MustGetField("OPSGENIE")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid Opsgenie API key",
|
||||
key: key,
|
||||
want: `{
|
||||
"AnalyzerType": 11,
|
||||
"Bindings": [
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Opsgenie API Integration Key",
|
||||
"FullyQualifiedName": "Opsgenie API Integration Key",
|
||||
"Type": "API Key",
|
||||
"Metadata": {
|
||||
"expires": "never"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "configuration_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Opsgenie API Integration Key",
|
||||
"FullyQualifiedName": "Opsgenie API Integration Key",
|
||||
"Type": "API Key",
|
||||
"Metadata": {
|
||||
"expires": "never"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Opsgenie API Integration Key",
|
||||
"FullyQualifiedName": "Opsgenie API Integration Key",
|
||||
"Type": "API Key",
|
||||
"Metadata": {
|
||||
"expires": "never"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "delete",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Opsgenie API Integration Key",
|
||||
"FullyQualifiedName": "Opsgenie API Integration Key",
|
||||
"Type": "API Key",
|
||||
"Metadata": {
|
||||
"expires": "never"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "create_and_update",
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"UnboundedResources": [
|
||||
{
|
||||
"Name": "John Scanner",
|
||||
"FullyQualifiedName": "secretscanner02@zohomail.com",
|
||||
"Type": "user",
|
||||
"Metadata": {
|
||||
"role": "Owner",
|
||||
"username": "secretscanner02@zohomail.com"
|
||||
},
|
||||
"Parent": null
|
||||
}
|
||||
],
|
||||
"Metadata": null
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
76
pkg/analyzer/analyzers/opsgenie/permissions.go
Normal file
76
pkg/analyzer/analyzers/opsgenie/permissions.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
package opsgenie
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
ConfigurationAccess Permission = iota
|
||||
Read Permission = iota
|
||||
Delete Permission = iota
|
||||
CreateAndUpdate Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
ConfigurationAccess: "configuration_access",
|
||||
Read: "read",
|
||||
Delete: "delete",
|
||||
CreateAndUpdate: "create_and_update",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"configuration_access": ConfigurationAccess,
|
||||
"read": Read,
|
||||
"delete": Delete,
|
||||
"create_and_update": CreateAndUpdate,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
ConfigurationAccess: 1,
|
||||
Read: 2,
|
||||
Delete: 3,
|
||||
CreateAndUpdate: 4,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: ConfigurationAccess,
|
||||
2: Read,
|
||||
3: Delete,
|
||||
4: CreateAndUpdate,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
5
pkg/analyzer/analyzers/opsgenie/permissions.yaml
Normal file
5
pkg/analyzer/analyzers/opsgenie/permissions.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
permissions:
|
||||
- configuration_access
|
||||
- read
|
||||
- delete
|
||||
- create_and_update
|
|
@ -1,6 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name": "Configuration Access",
|
||||
"name": "configuration_access",
|
||||
"test": {
|
||||
"endpoint": "https://api.opsgenie.com/v2/account",
|
||||
"method": "GET",
|
||||
|
@ -9,7 +9,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "Read",
|
||||
"name": "read",
|
||||
"test": {
|
||||
"endpoint": "https://api.opsgenie.com/v2/alerts",
|
||||
"method": "GET",
|
||||
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "Delete",
|
||||
"name": "delete",
|
||||
"test": {
|
||||
"endpoint": "https://api.opsgenie.com/v2/alerts/`nowaythiscanexist",
|
||||
"method": "DELETE",
|
||||
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "Create and Update",
|
||||
"name": "create_and_update",
|
||||
"test": {
|
||||
"endpoint": "https://api.opsgenie.com/v2/alerts/`nowaycanthisexist/message",
|
||||
"method": "PUT",
|
||||
|
|
76
pkg/analyzer/analyzers/postman/expected_output.json
Normal file
76
pkg/analyzer/analyzers/postman/expected_output.json
Normal file
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"AnalyzerType": 13,
|
||||
"Bindings": [
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "rendy",
|
||||
"FullyQualifiedName": "rendyplayground@gmail.com",
|
||||
"Type": "user",
|
||||
"Metadata": {
|
||||
"email": "rendyplayground@gmail.com",
|
||||
"role": "user",
|
||||
"team_domain": "",
|
||||
"team_name": "",
|
||||
"username": "rendyplayground"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "usage_data:view",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "rendy",
|
||||
"FullyQualifiedName": "rendyplayground@gmail.com",
|
||||
"Type": "user",
|
||||
"Metadata": {
|
||||
"email": "rendyplayground@gmail.com",
|
||||
"role": "user",
|
||||
"team_domain": "",
|
||||
"team_name": "",
|
||||
"username": "rendyplayground"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "team_workspaces:create",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "rendy",
|
||||
"FullyQualifiedName": "rendyplayground@gmail.com",
|
||||
"Type": "user",
|
||||
"Metadata": {
|
||||
"email": "rendyplayground@gmail.com",
|
||||
"role": "user",
|
||||
"team_domain": "",
|
||||
"team_name": "",
|
||||
"username": "rendyplayground"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "team_workspaces:view",
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"UnboundedResources": [
|
||||
{
|
||||
"Name": "My Workspace",
|
||||
"FullyQualifiedName": "4d06fc0c-6402-4a26-857d-80787b10eabf",
|
||||
"Type": "workspace",
|
||||
"Metadata": {
|
||||
"id": "4d06fc0c-6402-4a26-857d-80787b10eabf",
|
||||
"type": "personal",
|
||||
"visibility": "personal"
|
||||
},
|
||||
"Parent": null
|
||||
}
|
||||
],
|
||||
"Metadata": null
|
||||
}
|
181
pkg/analyzer/analyzers/postman/permissions.go
Normal file
181
pkg/analyzer/analyzers/postman/permissions.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
package postman
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
NoAccess Permission = iota
|
||||
UserAdd Permission = iota
|
||||
UserRemove Permission = iota
|
||||
TeamAdminManage Permission = iota
|
||||
TeamDevelopersManage Permission = iota
|
||||
SsoManage Permission = iota
|
||||
CustomDomainAdd Permission = iota
|
||||
CustomDomainEdit Permission = iota
|
||||
CustomDomainRemove Permission = iota
|
||||
AuditLogsView Permission = iota
|
||||
UsageDataView Permission = iota
|
||||
BillingMembersManage Permission = iota
|
||||
PaymentManage Permission = iota
|
||||
PlanUpdate Permission = iota
|
||||
TeamWorkspacesView Permission = iota
|
||||
TeamWorkspacesCreate Permission = iota
|
||||
TeamPublicProfileEnable Permission = iota
|
||||
TeamPrivateApiNetworkManage Permission = iota
|
||||
ParternerWorkspaceView Permission = iota
|
||||
ParternerWorkspaceManage Permission = iota
|
||||
ParternerWorkspaceVisibilityManage Permission = iota
|
||||
PartnersManage Permission = iota
|
||||
FlowAdd Permission = iota
|
||||
FlowEdit Permission = iota
|
||||
FlowRun Permission = iota
|
||||
FlowPublish Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
UserAdd: "user:add",
|
||||
UserRemove: "user:remove",
|
||||
TeamAdminManage: "team_admin:manage",
|
||||
TeamDevelopersManage: "team_developers:manage",
|
||||
SsoManage: "sso:manage",
|
||||
CustomDomainAdd: "custom_domain:add",
|
||||
CustomDomainEdit: "custom_domain:edit",
|
||||
CustomDomainRemove: "custom_domain:remove",
|
||||
AuditLogsView: "audit_logs:view",
|
||||
UsageDataView: "usage_data:view",
|
||||
BillingMembersManage: "billing_members:manage",
|
||||
PaymentManage: "payment:manage",
|
||||
PlanUpdate: "plan:update",
|
||||
TeamWorkspacesView: "team_workspaces:view",
|
||||
TeamWorkspacesCreate: "team_workspaces:create",
|
||||
TeamPublicProfileEnable: "team_public_profile:enable",
|
||||
TeamPrivateApiNetworkManage: "team_private_api_network:manage",
|
||||
ParternerWorkspaceView: "parterner_workspace:view",
|
||||
ParternerWorkspaceManage: "parterner_workspace:manage",
|
||||
ParternerWorkspaceVisibilityManage: "parterner_workspace_visibility:manage",
|
||||
PartnersManage: "partners:manage",
|
||||
FlowAdd: "flow:add",
|
||||
FlowEdit: "flow:edit",
|
||||
FlowRun: "flow:run",
|
||||
FlowPublish: "flow:publish",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"user:add": UserAdd,
|
||||
"user:remove": UserRemove,
|
||||
"team_admin:manage": TeamAdminManage,
|
||||
"team_developers:manage": TeamDevelopersManage,
|
||||
"sso:manage": SsoManage,
|
||||
"custom_domain:add": CustomDomainAdd,
|
||||
"custom_domain:edit": CustomDomainEdit,
|
||||
"custom_domain:remove": CustomDomainRemove,
|
||||
"audit_logs:view": AuditLogsView,
|
||||
"usage_data:view": UsageDataView,
|
||||
"billing_members:manage": BillingMembersManage,
|
||||
"payment:manage": PaymentManage,
|
||||
"plan:update": PlanUpdate,
|
||||
"team_workspaces:view": TeamWorkspacesView,
|
||||
"team_workspaces:create": TeamWorkspacesCreate,
|
||||
"team_public_profile:enable": TeamPublicProfileEnable,
|
||||
"team_private_api_network:manage": TeamPrivateApiNetworkManage,
|
||||
"parterner_workspace:view": ParternerWorkspaceView,
|
||||
"parterner_workspace:manage": ParternerWorkspaceManage,
|
||||
"parterner_workspace_visibility:manage": ParternerWorkspaceVisibilityManage,
|
||||
"partners:manage": PartnersManage,
|
||||
"flow:add": FlowAdd,
|
||||
"flow:edit": FlowEdit,
|
||||
"flow:run": FlowRun,
|
||||
"flow:publish": FlowPublish,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
UserAdd: 0,
|
||||
UserRemove: 1,
|
||||
TeamAdminManage: 2,
|
||||
TeamDevelopersManage: 3,
|
||||
SsoManage: 4,
|
||||
CustomDomainAdd: 5,
|
||||
CustomDomainEdit: 6,
|
||||
CustomDomainRemove: 7,
|
||||
AuditLogsView: 8,
|
||||
UsageDataView: 9,
|
||||
BillingMembersManage: 10,
|
||||
PaymentManage: 11,
|
||||
PlanUpdate: 12,
|
||||
TeamWorkspacesView: 13,
|
||||
TeamWorkspacesCreate: 14,
|
||||
TeamPublicProfileEnable: 15,
|
||||
TeamPrivateApiNetworkManage: 16,
|
||||
ParternerWorkspaceView: 17,
|
||||
ParternerWorkspaceManage: 18,
|
||||
ParternerWorkspaceVisibilityManage: 19,
|
||||
PartnersManage: 20,
|
||||
FlowAdd: 21,
|
||||
FlowEdit: 22,
|
||||
FlowRun: 23,
|
||||
FlowPublish: 24,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
0: UserAdd,
|
||||
1: UserRemove,
|
||||
2: TeamAdminManage,
|
||||
3: TeamDevelopersManage,
|
||||
4: SsoManage,
|
||||
5: CustomDomainAdd,
|
||||
6: CustomDomainEdit,
|
||||
7: CustomDomainRemove,
|
||||
8: AuditLogsView,
|
||||
9: UsageDataView,
|
||||
10: BillingMembersManage,
|
||||
11: PaymentManage,
|
||||
12: PlanUpdate,
|
||||
13: TeamWorkspacesView,
|
||||
14: TeamWorkspacesCreate,
|
||||
15: TeamPublicProfileEnable,
|
||||
16: TeamPrivateApiNetworkManage,
|
||||
17: ParternerWorkspaceView,
|
||||
18: ParternerWorkspaceManage,
|
||||
19: ParternerWorkspaceVisibilityManage,
|
||||
20: PartnersManage,
|
||||
21: FlowAdd,
|
||||
22: FlowEdit,
|
||||
23: FlowRun,
|
||||
24: FlowPublish,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
27
pkg/analyzer/analyzers/postman/permissions.yaml
Normal file
27
pkg/analyzer/analyzers/postman/permissions.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
permissions:
|
||||
- user:add
|
||||
- user:remove
|
||||
- team_admin:manage
|
||||
- team_developers:manage
|
||||
- sso:manage
|
||||
- custom_domain:add
|
||||
- custom_domain:edit
|
||||
- custom_domain:remove
|
||||
- audit_logs:view
|
||||
- usage_data:view
|
||||
- billing_members:manage
|
||||
- payment:manage
|
||||
- plan:update
|
||||
- team_workspaces:view
|
||||
- team_workspaces:create
|
||||
- team_public_profile:enable
|
||||
- team_private_api_network:manage
|
||||
- parterner_workspace:view
|
||||
- parterner_workspace:manage
|
||||
- parterner_workspace_visibility:manage
|
||||
- partners:manage
|
||||
- flow:add
|
||||
- flow:edit
|
||||
- flow:run
|
||||
- flow:publish
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
//go:generate generate_permissions permissions.yaml permissions.go postman
|
||||
package postman
|
||||
|
||||
import (
|
||||
|
@ -5,13 +6,109 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/table"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Postman }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing key in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzerpb.AnalyzerType_Postman,
|
||||
Metadata: nil,
|
||||
UnboundedResources: []analyzers.Resource{},
|
||||
}
|
||||
|
||||
resource := analyzers.Resource{
|
||||
Name: info.User.User.FullName,
|
||||
FullyQualifiedName: info.User.User.Email,
|
||||
Type: "user",
|
||||
Metadata: map[string]any{
|
||||
"role": strings.Join(info.User.User.Roles, ","),
|
||||
"username": info.User.User.Username,
|
||||
"email": info.User.User.Email,
|
||||
"team_name": info.User.User.TeamName,
|
||||
"team_domain": info.User.User.TeamDomain,
|
||||
},
|
||||
}
|
||||
|
||||
permissions := bakePermissions(info.User.User.Roles)
|
||||
|
||||
// bind all permissions with resources
|
||||
result.Bindings = analyzers.BindAllPermissions(resource, permissions...)
|
||||
|
||||
for _, workspace := range info.Workspace.Workspaces {
|
||||
result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
|
||||
Name: workspace.Name,
|
||||
FullyQualifiedName: workspace.ID,
|
||||
Type: "workspace",
|
||||
Metadata: map[string]any{
|
||||
"id": workspace.ID,
|
||||
"type": workspace.Type,
|
||||
"visibility": workspace.Visibility,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func bakePermissions(roles []string) []analyzers.Permission {
|
||||
permissionMap := map[Permission]struct{}{}
|
||||
|
||||
for _, role := range roles {
|
||||
permissions, ok := rolePermission[role]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, permission := range permissions {
|
||||
permissionMap[permission] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
permissions := make([]analyzers.Permission, 0, len(permissionMap))
|
||||
for perm := range permissionMap {
|
||||
permStr, err := perm.ToString()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
permissions = append(permissions, analyzers.Permission{
|
||||
Value: permStr,
|
||||
Parent: nil,
|
||||
})
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
type UserInfoJSON struct {
|
||||
User struct {
|
||||
Username string `json:"username"`
|
||||
|
|
100
pkg/analyzer/analyzers/postman/postman_test.go
Normal file
100
pkg/analyzer/analyzers/postman/postman_test.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package postman
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid Postman key",
|
||||
key: testSecrets.MustGetField("POSTMAN_TOKEN"),
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
|
@ -11,3 +11,93 @@ var roleDescriptions = map[string]string{
|
|||
"guest": "Views collections and sends requests in collections that have been shared with them. This role can't be directly assigned to a user.",
|
||||
"flow-editor": "(Basic and Professional plans only) - Can create, edit, run, and publish Postman Flows.",
|
||||
}
|
||||
|
||||
var rolePermission = map[string][]Permission{
|
||||
"super-admin": {
|
||||
UserAdd,
|
||||
UserRemove,
|
||||
TeamAdminManage,
|
||||
TeamDevelopersManage,
|
||||
SsoManage,
|
||||
CustomDomainAdd,
|
||||
CustomDomainEdit,
|
||||
CustomDomainRemove,
|
||||
AuditLogsView,
|
||||
UsageDataView,
|
||||
BillingMembersManage,
|
||||
PaymentManage,
|
||||
PlanUpdate,
|
||||
TeamWorkspacesView,
|
||||
TeamWorkspacesCreate,
|
||||
TeamPublicProfileEnable,
|
||||
TeamPrivateApiNetworkManage,
|
||||
PartnersManage,
|
||||
ParternerWorkspaceManage,
|
||||
ParternerWorkspaceView,
|
||||
ParternerWorkspaceVisibilityManage,
|
||||
FlowAdd,
|
||||
FlowEdit,
|
||||
FlowRun,
|
||||
FlowPublish,
|
||||
},
|
||||
"admin": {
|
||||
UserAdd,
|
||||
UserRemove,
|
||||
TeamAdminManage,
|
||||
TeamDevelopersManage,
|
||||
SsoManage,
|
||||
CustomDomainAdd,
|
||||
CustomDomainEdit,
|
||||
CustomDomainRemove,
|
||||
AuditLogsView,
|
||||
UsageDataView,
|
||||
BillingMembersManage,
|
||||
TeamPublicProfileEnable,
|
||||
PartnersManage,
|
||||
ParternerWorkspaceManage,
|
||||
ParternerWorkspaceView,
|
||||
ParternerWorkspaceVisibilityManage,
|
||||
FlowAdd,
|
||||
FlowEdit,
|
||||
FlowRun,
|
||||
FlowPublish,
|
||||
},
|
||||
"billing": {
|
||||
UsageDataView,
|
||||
BillingMembersManage,
|
||||
PaymentManage,
|
||||
PlanUpdate,
|
||||
},
|
||||
"user": {
|
||||
UsageDataView,
|
||||
TeamWorkspacesCreate,
|
||||
TeamWorkspacesView,
|
||||
},
|
||||
"community-manager": {
|
||||
CustomDomainAdd,
|
||||
CustomDomainEdit,
|
||||
AuditLogsView,
|
||||
UsageDataView,
|
||||
TeamWorkspacesView,
|
||||
TeamWorkspacesCreate,
|
||||
TeamPublicProfileEnable,
|
||||
},
|
||||
"partner-manager": {
|
||||
PartnersManage,
|
||||
ParternerWorkspaceManage,
|
||||
ParternerWorkspaceView,
|
||||
ParternerWorkspaceVisibilityManage,
|
||||
},
|
||||
"partner": {
|
||||
ParternerWorkspaceView,
|
||||
},
|
||||
"guest": {
|
||||
TeamWorkspacesView,
|
||||
},
|
||||
"flow-editor": {
|
||||
FlowAdd,
|
||||
FlowEdit,
|
||||
FlowRun,
|
||||
FlowPublish,
|
||||
},
|
||||
}
|
||||
|
|
991
pkg/analyzer/analyzers/sendgrid/permissions.go
Normal file
991
pkg/analyzer/analyzers/sendgrid/permissions.go
Normal file
|
@ -0,0 +1,991 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
package sendgrid
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
AccessSettingsActivityRead Permission = iota
|
||||
AccessSettingsWhitelistCreate Permission = iota
|
||||
AccessSettingsWhitelistDelete Permission = iota
|
||||
AccessSettingsWhitelistRead Permission = iota
|
||||
AccessSettingsWhitelistUpdate Permission = iota
|
||||
AlertsCreate Permission = iota
|
||||
AlertsDelete Permission = iota
|
||||
AlertsRead Permission = iota
|
||||
AlertsUpdate Permission = iota
|
||||
ApiKeysCreate Permission = iota
|
||||
ApiKeysDelete Permission = iota
|
||||
ApiKeysRead Permission = iota
|
||||
ApiKeysUpdate Permission = iota
|
||||
AsmGroupsCreate Permission = iota
|
||||
AsmGroupsDelete Permission = iota
|
||||
AsmGroupsRead Permission = iota
|
||||
AsmGroupsUpdate Permission = iota
|
||||
BillingCreate Permission = iota
|
||||
BillingDelete Permission = iota
|
||||
BillingRead Permission = iota
|
||||
BillingUpdate Permission = iota
|
||||
BrowsersStatsRead Permission = iota
|
||||
CategoriesCreate Permission = iota
|
||||
CategoriesDelete Permission = iota
|
||||
CategoriesRead Permission = iota
|
||||
CategoriesStatsRead Permission = iota
|
||||
CategoriesStatsSumsRead Permission = iota
|
||||
CategoriesUpdate Permission = iota
|
||||
ClientsDesktopStatsRead Permission = iota
|
||||
ClientsPhoneStatsRead Permission = iota
|
||||
ClientsStatsRead Permission = iota
|
||||
ClientsTabletStatsRead Permission = iota
|
||||
ClientsWebmailStatsRead Permission = iota
|
||||
DevicesStatsRead Permission = iota
|
||||
EmailActivityRead Permission = iota
|
||||
GeoStatsRead Permission = iota
|
||||
IpsAssignedRead Permission = iota
|
||||
IpsPoolsCreate Permission = iota
|
||||
IpsPoolsDelete Permission = iota
|
||||
IpsPoolsIpsCreate Permission = iota
|
||||
IpsPoolsIpsDelete Permission = iota
|
||||
IpsPoolsIpsRead Permission = iota
|
||||
IpsPoolsIpsUpdate Permission = iota
|
||||
IpsPoolsRead Permission = iota
|
||||
IpsPoolsUpdate Permission = iota
|
||||
IpsRead Permission = iota
|
||||
IpsWarmupCreate Permission = iota
|
||||
IpsWarmupDelete Permission = iota
|
||||
IpsWarmupRead Permission = iota
|
||||
IpsWarmupUpdate Permission = iota
|
||||
MailSettingsAddressWhitelistRead Permission = iota
|
||||
MailSettingsAddressWhitelistUpdate Permission = iota
|
||||
MailSettingsBouncePurgeRead Permission = iota
|
||||
MailSettingsBouncePurgeUpdate Permission = iota
|
||||
MailSettingsFooterRead Permission = iota
|
||||
MailSettingsFooterUpdate Permission = iota
|
||||
MailSettingsForwardBounceRead Permission = iota
|
||||
MailSettingsForwardBounceUpdate Permission = iota
|
||||
MailSettingsForwardSpamRead Permission = iota
|
||||
MailSettingsForwardSpamUpdate Permission = iota
|
||||
MailSettingsPlainContentRead Permission = iota
|
||||
MailSettingsPlainContentUpdate Permission = iota
|
||||
MailSettingsRead Permission = iota
|
||||
MailSettingsTemplateRead Permission = iota
|
||||
MailSettingsTemplateUpdate Permission = iota
|
||||
MailBatchCreate Permission = iota
|
||||
MailBatchDelete Permission = iota
|
||||
MailBatchRead Permission = iota
|
||||
MailBatchUpdate Permission = iota
|
||||
MailSend Permission = iota
|
||||
MailboxProvidersStatsRead Permission = iota
|
||||
MarketingCampaignsCreate Permission = iota
|
||||
MarketingCampaignsDelete Permission = iota
|
||||
MarketingCampaignsRead Permission = iota
|
||||
MarketingCampaignsUpdate Permission = iota
|
||||
PartnerSettingsNewRelicRead Permission = iota
|
||||
PartnerSettingsNewRelicUpdate Permission = iota
|
||||
PartnerSettingsRead Permission = iota
|
||||
StatsGlobalRead Permission = iota
|
||||
StatsRead Permission = iota
|
||||
SubusersCreate Permission = iota
|
||||
SubusersCreditsCreate Permission = iota
|
||||
SubusersCreditsDelete Permission = iota
|
||||
SubusersCreditsRead Permission = iota
|
||||
SubusersCreditsRemainingCreate Permission = iota
|
||||
SubusersCreditsRemainingDelete Permission = iota
|
||||
SubusersCreditsRemainingRead Permission = iota
|
||||
SubusersCreditsRemainingUpdate Permission = iota
|
||||
SubusersCreditsUpdate Permission = iota
|
||||
SubusersDelete Permission = iota
|
||||
SubusersMonitorCreate Permission = iota
|
||||
SubusersMonitorDelete Permission = iota
|
||||
SubusersMonitorRead Permission = iota
|
||||
SubusersMonitorUpdate Permission = iota
|
||||
SubusersRead Permission = iota
|
||||
SubusersReputationsRead Permission = iota
|
||||
SubusersStatsMonthlyRead Permission = iota
|
||||
SubusersStatsRead Permission = iota
|
||||
SubusersStatsSumsRead Permission = iota
|
||||
SubusersSummaryRead Permission = iota
|
||||
SubusersUpdate Permission = iota
|
||||
SuppressionBlocksCreate Permission = iota
|
||||
SuppressionBlocksDelete Permission = iota
|
||||
SuppressionBlocksRead Permission = iota
|
||||
SuppressionBlocksUpdate Permission = iota
|
||||
SuppressionBouncesCreate Permission = iota
|
||||
SuppressionBouncesDelete Permission = iota
|
||||
SuppressionBouncesRead Permission = iota
|
||||
SuppressionBouncesUpdate Permission = iota
|
||||
SuppressionCreate Permission = iota
|
||||
SuppressionDelete Permission = iota
|
||||
SuppressionInvalidEmailsCreate Permission = iota
|
||||
SuppressionInvalidEmailsDelete Permission = iota
|
||||
SuppressionInvalidEmailsRead Permission = iota
|
||||
SuppressionInvalidEmailsUpdate Permission = iota
|
||||
SuppressionRead Permission = iota
|
||||
SuppressionSpamReportsCreate Permission = iota
|
||||
SuppressionSpamReportsDelete Permission = iota
|
||||
SuppressionSpamReportsRead Permission = iota
|
||||
SuppressionSpamReportsUpdate Permission = iota
|
||||
SuppressionUnsubscribesCreate Permission = iota
|
||||
SuppressionUnsubscribesDelete Permission = iota
|
||||
SuppressionUnsubscribesRead Permission = iota
|
||||
SuppressionUnsubscribesUpdate Permission = iota
|
||||
SuppressionUpdate Permission = iota
|
||||
TeammatesCreate Permission = iota
|
||||
TeammatesRead Permission = iota
|
||||
TeammatesUpdate Permission = iota
|
||||
TeammatesDelete Permission = iota
|
||||
TemplatesCreate Permission = iota
|
||||
TemplatesDelete Permission = iota
|
||||
TemplatesRead Permission = iota
|
||||
TemplatesUpdate Permission = iota
|
||||
TemplatesVersionsActivateCreate Permission = iota
|
||||
TemplatesVersionsActivateDelete Permission = iota
|
||||
TemplatesVersionsActivateRead Permission = iota
|
||||
TemplatesVersionsActivateUpdate Permission = iota
|
||||
TemplatesVersionsCreate Permission = iota
|
||||
TemplatesVersionsDelete Permission = iota
|
||||
TemplatesVersionsRead Permission = iota
|
||||
TemplatesVersionsUpdate Permission = iota
|
||||
TrackingSettingsClickRead Permission = iota
|
||||
TrackingSettingsClickUpdate Permission = iota
|
||||
TrackingSettingsGoogleAnalyticsRead Permission = iota
|
||||
TrackingSettingsGoogleAnalyticsUpdate Permission = iota
|
||||
TrackingSettingsOpenRead Permission = iota
|
||||
TrackingSettingsOpenUpdate Permission = iota
|
||||
TrackingSettingsRead Permission = iota
|
||||
TrackingSettingsSubscriptionRead Permission = iota
|
||||
TrackingSettingsSubscriptionUpdate Permission = iota
|
||||
UserAccountRead Permission = iota
|
||||
UserCreditsRead Permission = iota
|
||||
UserEmailCreate Permission = iota
|
||||
UserEmailDelete Permission = iota
|
||||
UserEmailRead Permission = iota
|
||||
UserEmailUpdate Permission = iota
|
||||
UserMultifactorAuthenticationCreate Permission = iota
|
||||
UserMultifactorAuthenticationDelete Permission = iota
|
||||
UserMultifactorAuthenticationRead Permission = iota
|
||||
UserMultifactorAuthenticationUpdate Permission = iota
|
||||
UserPasswordRead Permission = iota
|
||||
UserPasswordUpdate Permission = iota
|
||||
UserProfileRead Permission = iota
|
||||
UserProfileUpdate Permission = iota
|
||||
UserScheduledSendsCreate Permission = iota
|
||||
UserScheduledSendsDelete Permission = iota
|
||||
UserScheduledSendsRead Permission = iota
|
||||
UserScheduledSendsUpdate Permission = iota
|
||||
UserSettingsEnforcedTlsRead Permission = iota
|
||||
UserSettingsEnforcedTlsUpdate Permission = iota
|
||||
UserTimezoneRead Permission = iota
|
||||
UserUsernameRead Permission = iota
|
||||
UserUsernameUpdate Permission = iota
|
||||
UserWebhooksEventSettingsRead Permission = iota
|
||||
UserWebhooksEventSettingsUpdate Permission = iota
|
||||
UserWebhooksEventTestCreate Permission = iota
|
||||
UserWebhooksEventTestRead Permission = iota
|
||||
UserWebhooksEventTestUpdate Permission = iota
|
||||
UserWebhooksParseSettingsCreate Permission = iota
|
||||
UserWebhooksParseSettingsDelete Permission = iota
|
||||
UserWebhooksParseSettingsRead Permission = iota
|
||||
UserWebhooksParseSettingsUpdate Permission = iota
|
||||
UserWebhooksParseStatsRead Permission = iota
|
||||
WhitelabelCreate Permission = iota
|
||||
WhitelabelDelete Permission = iota
|
||||
WhitelabelRead Permission = iota
|
||||
WhitelabelUpdate Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
AccessSettingsActivityRead: "access_settings.activity.read",
|
||||
AccessSettingsWhitelistCreate: "access_settings.whitelist.create",
|
||||
AccessSettingsWhitelistDelete: "access_settings.whitelist.delete",
|
||||
AccessSettingsWhitelistRead: "access_settings.whitelist.read",
|
||||
AccessSettingsWhitelistUpdate: "access_settings.whitelist.update",
|
||||
AlertsCreate: "alerts.create",
|
||||
AlertsDelete: "alerts.delete",
|
||||
AlertsRead: "alerts.read",
|
||||
AlertsUpdate: "alerts.update",
|
||||
ApiKeysCreate: "api_keys.create",
|
||||
ApiKeysDelete: "api_keys.delete",
|
||||
ApiKeysRead: "api_keys.read",
|
||||
ApiKeysUpdate: "api_keys.update",
|
||||
AsmGroupsCreate: "asm.groups.create",
|
||||
AsmGroupsDelete: "asm.groups.delete",
|
||||
AsmGroupsRead: "asm.groups.read",
|
||||
AsmGroupsUpdate: "asm.groups.update",
|
||||
BillingCreate: "billing.create",
|
||||
BillingDelete: "billing.delete",
|
||||
BillingRead: "billing.read",
|
||||
BillingUpdate: "billing.update",
|
||||
BrowsersStatsRead: "browsers.stats.read",
|
||||
CategoriesCreate: "categories.create",
|
||||
CategoriesDelete: "categories.delete",
|
||||
CategoriesRead: "categories.read",
|
||||
CategoriesStatsRead: "categories.stats.read",
|
||||
CategoriesStatsSumsRead: "categories.stats.sums.read",
|
||||
CategoriesUpdate: "categories.update",
|
||||
ClientsDesktopStatsRead: "clients.desktop.stats.read",
|
||||
ClientsPhoneStatsRead: "clients.phone.stats.read",
|
||||
ClientsStatsRead: "clients.stats.read",
|
||||
ClientsTabletStatsRead: "clients.tablet.stats.read",
|
||||
ClientsWebmailStatsRead: "clients.webmail.stats.read",
|
||||
DevicesStatsRead: "devices.stats.read",
|
||||
EmailActivityRead: "email_activity.read",
|
||||
GeoStatsRead: "geo.stats.read",
|
||||
IpsAssignedRead: "ips.assigned.read",
|
||||
IpsPoolsCreate: "ips.pools.create",
|
||||
IpsPoolsDelete: "ips.pools.delete",
|
||||
IpsPoolsIpsCreate: "ips.pools.ips.create",
|
||||
IpsPoolsIpsDelete: "ips.pools.ips.delete",
|
||||
IpsPoolsIpsRead: "ips.pools.ips.read",
|
||||
IpsPoolsIpsUpdate: "ips.pools.ips.update",
|
||||
IpsPoolsRead: "ips.pools.read",
|
||||
IpsPoolsUpdate: "ips.pools.update",
|
||||
IpsRead: "ips.read",
|
||||
IpsWarmupCreate: "ips.warmup.create",
|
||||
IpsWarmupDelete: "ips.warmup.delete",
|
||||
IpsWarmupRead: "ips.warmup.read",
|
||||
IpsWarmupUpdate: "ips.warmup.update",
|
||||
MailSettingsAddressWhitelistRead: "mail_settings.address_whitelist.read",
|
||||
MailSettingsAddressWhitelistUpdate: "mail_settings.address_whitelist.update",
|
||||
MailSettingsBouncePurgeRead: "mail_settings.bounce_purge.read",
|
||||
MailSettingsBouncePurgeUpdate: "mail_settings.bounce_purge.update",
|
||||
MailSettingsFooterRead: "mail_settings.footer.read",
|
||||
MailSettingsFooterUpdate: "mail_settings.footer.update",
|
||||
MailSettingsForwardBounceRead: "mail_settings.forward_bounce.read",
|
||||
MailSettingsForwardBounceUpdate: "mail_settings.forward_bounce.update",
|
||||
MailSettingsForwardSpamRead: "mail_settings.forward_spam.read",
|
||||
MailSettingsForwardSpamUpdate: "mail_settings.forward_spam.update",
|
||||
MailSettingsPlainContentRead: "mail_settings.plain_content.read",
|
||||
MailSettingsPlainContentUpdate: "mail_settings.plain_content.update",
|
||||
MailSettingsRead: "mail_settings.read",
|
||||
MailSettingsTemplateRead: "mail_settings.template.read",
|
||||
MailSettingsTemplateUpdate: "mail_settings.template.update",
|
||||
MailBatchCreate: "mail.batch.create",
|
||||
MailBatchDelete: "mail.batch.delete",
|
||||
MailBatchRead: "mail.batch.read",
|
||||
MailBatchUpdate: "mail.batch.update",
|
||||
MailSend: "mail.send",
|
||||
MailboxProvidersStatsRead: "mailbox_providers.stats.read",
|
||||
MarketingCampaignsCreate: "marketing_campaigns.create",
|
||||
MarketingCampaignsDelete: "marketing_campaigns.delete",
|
||||
MarketingCampaignsRead: "marketing_campaigns.read",
|
||||
MarketingCampaignsUpdate: "marketing_campaigns.update",
|
||||
PartnerSettingsNewRelicRead: "partner_settings.new_relic.read",
|
||||
PartnerSettingsNewRelicUpdate: "partner_settings.new_relic.update",
|
||||
PartnerSettingsRead: "partner_settings.read",
|
||||
StatsGlobalRead: "stats.global.read",
|
||||
StatsRead: "stats.read",
|
||||
SubusersCreate: "subusers.create",
|
||||
SubusersCreditsCreate: "subusers.credits.create",
|
||||
SubusersCreditsDelete: "subusers.credits.delete",
|
||||
SubusersCreditsRead: "subusers.credits.read",
|
||||
SubusersCreditsRemainingCreate: "subusers.credits.remaining.create",
|
||||
SubusersCreditsRemainingDelete: "subusers.credits.remaining.delete",
|
||||
SubusersCreditsRemainingRead: "subusers.credits.remaining.read",
|
||||
SubusersCreditsRemainingUpdate: "subusers.credits.remaining.update",
|
||||
SubusersCreditsUpdate: "subusers.credits.update",
|
||||
SubusersDelete: "subusers.delete",
|
||||
SubusersMonitorCreate: "subusers.monitor.create",
|
||||
SubusersMonitorDelete: "subusers.monitor.delete",
|
||||
SubusersMonitorRead: "subusers.monitor.read",
|
||||
SubusersMonitorUpdate: "subusers.monitor.update",
|
||||
SubusersRead: "subusers.read",
|
||||
SubusersReputationsRead: "subusers.reputations.read",
|
||||
SubusersStatsMonthlyRead: "subusers.stats.monthly.read",
|
||||
SubusersStatsRead: "subusers.stats.read",
|
||||
SubusersStatsSumsRead: "subusers.stats.sums.read",
|
||||
SubusersSummaryRead: "subusers.summary.read",
|
||||
SubusersUpdate: "subusers.update",
|
||||
SuppressionBlocksCreate: "suppression.blocks.create",
|
||||
SuppressionBlocksDelete: "suppression.blocks.delete",
|
||||
SuppressionBlocksRead: "suppression.blocks.read",
|
||||
SuppressionBlocksUpdate: "suppression.blocks.update",
|
||||
SuppressionBouncesCreate: "suppression.bounces.create",
|
||||
SuppressionBouncesDelete: "suppression.bounces.delete",
|
||||
SuppressionBouncesRead: "suppression.bounces.read",
|
||||
SuppressionBouncesUpdate: "suppression.bounces.update",
|
||||
SuppressionCreate: "suppression.create",
|
||||
SuppressionDelete: "suppression.delete",
|
||||
SuppressionInvalidEmailsCreate: "suppression.invalid_emails.create",
|
||||
SuppressionInvalidEmailsDelete: "suppression.invalid_emails.delete",
|
||||
SuppressionInvalidEmailsRead: "suppression.invalid_emails.read",
|
||||
SuppressionInvalidEmailsUpdate: "suppression.invalid_emails.update",
|
||||
SuppressionRead: "suppression.read",
|
||||
SuppressionSpamReportsCreate: "suppression.spam_reports.create",
|
||||
SuppressionSpamReportsDelete: "suppression.spam_reports.delete",
|
||||
SuppressionSpamReportsRead: "suppression.spam_reports.read",
|
||||
SuppressionSpamReportsUpdate: "suppression.spam_reports.update",
|
||||
SuppressionUnsubscribesCreate: "suppression.unsubscribes.create",
|
||||
SuppressionUnsubscribesDelete: "suppression.unsubscribes.delete",
|
||||
SuppressionUnsubscribesRead: "suppression.unsubscribes.read",
|
||||
SuppressionUnsubscribesUpdate: "suppression.unsubscribes.update",
|
||||
SuppressionUpdate: "suppression.update",
|
||||
TeammatesCreate: "teammates.create",
|
||||
TeammatesRead: "teammates.read",
|
||||
TeammatesUpdate: "teammates.update",
|
||||
TeammatesDelete: "teammates.delete",
|
||||
TemplatesCreate: "templates.create",
|
||||
TemplatesDelete: "templates.delete",
|
||||
TemplatesRead: "templates.read",
|
||||
TemplatesUpdate: "templates.update",
|
||||
TemplatesVersionsActivateCreate: "templates.versions.activate.create",
|
||||
TemplatesVersionsActivateDelete: "templates.versions.activate.delete",
|
||||
TemplatesVersionsActivateRead: "templates.versions.activate.read",
|
||||
TemplatesVersionsActivateUpdate: "templates.versions.activate.update",
|
||||
TemplatesVersionsCreate: "templates.versions.create",
|
||||
TemplatesVersionsDelete: "templates.versions.delete",
|
||||
TemplatesVersionsRead: "templates.versions.read",
|
||||
TemplatesVersionsUpdate: "templates.versions.update",
|
||||
TrackingSettingsClickRead: "tracking_settings.click.read",
|
||||
TrackingSettingsClickUpdate: "tracking_settings.click.update",
|
||||
TrackingSettingsGoogleAnalyticsRead: "tracking_settings.google_analytics.read",
|
||||
TrackingSettingsGoogleAnalyticsUpdate: "tracking_settings.google_analytics.update",
|
||||
TrackingSettingsOpenRead: "tracking_settings.open.read",
|
||||
TrackingSettingsOpenUpdate: "tracking_settings.open.update",
|
||||
TrackingSettingsRead: "tracking_settings.read",
|
||||
TrackingSettingsSubscriptionRead: "tracking_settings.subscription.read",
|
||||
TrackingSettingsSubscriptionUpdate: "tracking_settings.subscription.update",
|
||||
UserAccountRead: "user.account.read",
|
||||
UserCreditsRead: "user.credits.read",
|
||||
UserEmailCreate: "user.email.create",
|
||||
UserEmailDelete: "user.email.delete",
|
||||
UserEmailRead: "user.email.read",
|
||||
UserEmailUpdate: "user.email.update",
|
||||
UserMultifactorAuthenticationCreate: "user.multifactor_authentication.create",
|
||||
UserMultifactorAuthenticationDelete: "user.multifactor_authentication.delete",
|
||||
UserMultifactorAuthenticationRead: "user.multifactor_authentication.read",
|
||||
UserMultifactorAuthenticationUpdate: "user.multifactor_authentication.update",
|
||||
UserPasswordRead: "user.password.read",
|
||||
UserPasswordUpdate: "user.password.update",
|
||||
UserProfileRead: "user.profile.read",
|
||||
UserProfileUpdate: "user.profile.update",
|
||||
UserScheduledSendsCreate: "user.scheduled_sends.create",
|
||||
UserScheduledSendsDelete: "user.scheduled_sends.delete",
|
||||
UserScheduledSendsRead: "user.scheduled_sends.read",
|
||||
UserScheduledSendsUpdate: "user.scheduled_sends.update",
|
||||
UserSettingsEnforcedTlsRead: "user.settings.enforced_tls.read",
|
||||
UserSettingsEnforcedTlsUpdate: "user.settings.enforced_tls.update",
|
||||
UserTimezoneRead: "user.timezone.read",
|
||||
UserUsernameRead: "user.username.read",
|
||||
UserUsernameUpdate: "user.username.update",
|
||||
UserWebhooksEventSettingsRead: "user.webhooks.event.settings.read",
|
||||
UserWebhooksEventSettingsUpdate: "user.webhooks.event.settings.update",
|
||||
UserWebhooksEventTestCreate: "user.webhooks.event.test.create",
|
||||
UserWebhooksEventTestRead: "user.webhooks.event.test.read",
|
||||
UserWebhooksEventTestUpdate: "user.webhooks.event.test.update",
|
||||
UserWebhooksParseSettingsCreate: "user.webhooks.parse.settings.create",
|
||||
UserWebhooksParseSettingsDelete: "user.webhooks.parse.settings.delete",
|
||||
UserWebhooksParseSettingsRead: "user.webhooks.parse.settings.read",
|
||||
UserWebhooksParseSettingsUpdate: "user.webhooks.parse.settings.update",
|
||||
UserWebhooksParseStatsRead: "user.webhooks.parse.stats.read",
|
||||
WhitelabelCreate: "whitelabel.create",
|
||||
WhitelabelDelete: "whitelabel.delete",
|
||||
WhitelabelRead: "whitelabel.read",
|
||||
WhitelabelUpdate: "whitelabel.update",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"access_settings.activity.read": AccessSettingsActivityRead,
|
||||
"access_settings.whitelist.create": AccessSettingsWhitelistCreate,
|
||||
"access_settings.whitelist.delete": AccessSettingsWhitelistDelete,
|
||||
"access_settings.whitelist.read": AccessSettingsWhitelistRead,
|
||||
"access_settings.whitelist.update": AccessSettingsWhitelistUpdate,
|
||||
"alerts.create": AlertsCreate,
|
||||
"alerts.delete": AlertsDelete,
|
||||
"alerts.read": AlertsRead,
|
||||
"alerts.update": AlertsUpdate,
|
||||
"api_keys.create": ApiKeysCreate,
|
||||
"api_keys.delete": ApiKeysDelete,
|
||||
"api_keys.read": ApiKeysRead,
|
||||
"api_keys.update": ApiKeysUpdate,
|
||||
"asm.groups.create": AsmGroupsCreate,
|
||||
"asm.groups.delete": AsmGroupsDelete,
|
||||
"asm.groups.read": AsmGroupsRead,
|
||||
"asm.groups.update": AsmGroupsUpdate,
|
||||
"billing.create": BillingCreate,
|
||||
"billing.delete": BillingDelete,
|
||||
"billing.read": BillingRead,
|
||||
"billing.update": BillingUpdate,
|
||||
"browsers.stats.read": BrowsersStatsRead,
|
||||
"categories.create": CategoriesCreate,
|
||||
"categories.delete": CategoriesDelete,
|
||||
"categories.read": CategoriesRead,
|
||||
"categories.stats.read": CategoriesStatsRead,
|
||||
"categories.stats.sums.read": CategoriesStatsSumsRead,
|
||||
"categories.update": CategoriesUpdate,
|
||||
"clients.desktop.stats.read": ClientsDesktopStatsRead,
|
||||
"clients.phone.stats.read": ClientsPhoneStatsRead,
|
||||
"clients.stats.read": ClientsStatsRead,
|
||||
"clients.tablet.stats.read": ClientsTabletStatsRead,
|
||||
"clients.webmail.stats.read": ClientsWebmailStatsRead,
|
||||
"devices.stats.read": DevicesStatsRead,
|
||||
"email_activity.read": EmailActivityRead,
|
||||
"geo.stats.read": GeoStatsRead,
|
||||
"ips.assigned.read": IpsAssignedRead,
|
||||
"ips.pools.create": IpsPoolsCreate,
|
||||
"ips.pools.delete": IpsPoolsDelete,
|
||||
"ips.pools.ips.create": IpsPoolsIpsCreate,
|
||||
"ips.pools.ips.delete": IpsPoolsIpsDelete,
|
||||
"ips.pools.ips.read": IpsPoolsIpsRead,
|
||||
"ips.pools.ips.update": IpsPoolsIpsUpdate,
|
||||
"ips.pools.read": IpsPoolsRead,
|
||||
"ips.pools.update": IpsPoolsUpdate,
|
||||
"ips.read": IpsRead,
|
||||
"ips.warmup.create": IpsWarmupCreate,
|
||||
"ips.warmup.delete": IpsWarmupDelete,
|
||||
"ips.warmup.read": IpsWarmupRead,
|
||||
"ips.warmup.update": IpsWarmupUpdate,
|
||||
"mail_settings.address_whitelist.read": MailSettingsAddressWhitelistRead,
|
||||
"mail_settings.address_whitelist.update": MailSettingsAddressWhitelistUpdate,
|
||||
"mail_settings.bounce_purge.read": MailSettingsBouncePurgeRead,
|
||||
"mail_settings.bounce_purge.update": MailSettingsBouncePurgeUpdate,
|
||||
"mail_settings.footer.read": MailSettingsFooterRead,
|
||||
"mail_settings.footer.update": MailSettingsFooterUpdate,
|
||||
"mail_settings.forward_bounce.read": MailSettingsForwardBounceRead,
|
||||
"mail_settings.forward_bounce.update": MailSettingsForwardBounceUpdate,
|
||||
"mail_settings.forward_spam.read": MailSettingsForwardSpamRead,
|
||||
"mail_settings.forward_spam.update": MailSettingsForwardSpamUpdate,
|
||||
"mail_settings.plain_content.read": MailSettingsPlainContentRead,
|
||||
"mail_settings.plain_content.update": MailSettingsPlainContentUpdate,
|
||||
"mail_settings.read": MailSettingsRead,
|
||||
"mail_settings.template.read": MailSettingsTemplateRead,
|
||||
"mail_settings.template.update": MailSettingsTemplateUpdate,
|
||||
"mail.batch.create": MailBatchCreate,
|
||||
"mail.batch.delete": MailBatchDelete,
|
||||
"mail.batch.read": MailBatchRead,
|
||||
"mail.batch.update": MailBatchUpdate,
|
||||
"mail.send": MailSend,
|
||||
"mailbox_providers.stats.read": MailboxProvidersStatsRead,
|
||||
"marketing_campaigns.create": MarketingCampaignsCreate,
|
||||
"marketing_campaigns.delete": MarketingCampaignsDelete,
|
||||
"marketing_campaigns.read": MarketingCampaignsRead,
|
||||
"marketing_campaigns.update": MarketingCampaignsUpdate,
|
||||
"partner_settings.new_relic.read": PartnerSettingsNewRelicRead,
|
||||
"partner_settings.new_relic.update": PartnerSettingsNewRelicUpdate,
|
||||
"partner_settings.read": PartnerSettingsRead,
|
||||
"stats.global.read": StatsGlobalRead,
|
||||
"stats.read": StatsRead,
|
||||
"subusers.create": SubusersCreate,
|
||||
"subusers.credits.create": SubusersCreditsCreate,
|
||||
"subusers.credits.delete": SubusersCreditsDelete,
|
||||
"subusers.credits.read": SubusersCreditsRead,
|
||||
"subusers.credits.remaining.create": SubusersCreditsRemainingCreate,
|
||||
"subusers.credits.remaining.delete": SubusersCreditsRemainingDelete,
|
||||
"subusers.credits.remaining.read": SubusersCreditsRemainingRead,
|
||||
"subusers.credits.remaining.update": SubusersCreditsRemainingUpdate,
|
||||
"subusers.credits.update": SubusersCreditsUpdate,
|
||||
"subusers.delete": SubusersDelete,
|
||||
"subusers.monitor.create": SubusersMonitorCreate,
|
||||
"subusers.monitor.delete": SubusersMonitorDelete,
|
||||
"subusers.monitor.read": SubusersMonitorRead,
|
||||
"subusers.monitor.update": SubusersMonitorUpdate,
|
||||
"subusers.read": SubusersRead,
|
||||
"subusers.reputations.read": SubusersReputationsRead,
|
||||
"subusers.stats.monthly.read": SubusersStatsMonthlyRead,
|
||||
"subusers.stats.read": SubusersStatsRead,
|
||||
"subusers.stats.sums.read": SubusersStatsSumsRead,
|
||||
"subusers.summary.read": SubusersSummaryRead,
|
||||
"subusers.update": SubusersUpdate,
|
||||
"suppression.blocks.create": SuppressionBlocksCreate,
|
||||
"suppression.blocks.delete": SuppressionBlocksDelete,
|
||||
"suppression.blocks.read": SuppressionBlocksRead,
|
||||
"suppression.blocks.update": SuppressionBlocksUpdate,
|
||||
"suppression.bounces.create": SuppressionBouncesCreate,
|
||||
"suppression.bounces.delete": SuppressionBouncesDelete,
|
||||
"suppression.bounces.read": SuppressionBouncesRead,
|
||||
"suppression.bounces.update": SuppressionBouncesUpdate,
|
||||
"suppression.create": SuppressionCreate,
|
||||
"suppression.delete": SuppressionDelete,
|
||||
"suppression.invalid_emails.create": SuppressionInvalidEmailsCreate,
|
||||
"suppression.invalid_emails.delete": SuppressionInvalidEmailsDelete,
|
||||
"suppression.invalid_emails.read": SuppressionInvalidEmailsRead,
|
||||
"suppression.invalid_emails.update": SuppressionInvalidEmailsUpdate,
|
||||
"suppression.read": SuppressionRead,
|
||||
"suppression.spam_reports.create": SuppressionSpamReportsCreate,
|
||||
"suppression.spam_reports.delete": SuppressionSpamReportsDelete,
|
||||
"suppression.spam_reports.read": SuppressionSpamReportsRead,
|
||||
"suppression.spam_reports.update": SuppressionSpamReportsUpdate,
|
||||
"suppression.unsubscribes.create": SuppressionUnsubscribesCreate,
|
||||
"suppression.unsubscribes.delete": SuppressionUnsubscribesDelete,
|
||||
"suppression.unsubscribes.read": SuppressionUnsubscribesRead,
|
||||
"suppression.unsubscribes.update": SuppressionUnsubscribesUpdate,
|
||||
"suppression.update": SuppressionUpdate,
|
||||
"teammates.create": TeammatesCreate,
|
||||
"teammates.read": TeammatesRead,
|
||||
"teammates.update": TeammatesUpdate,
|
||||
"teammates.delete": TeammatesDelete,
|
||||
"templates.create": TemplatesCreate,
|
||||
"templates.delete": TemplatesDelete,
|
||||
"templates.read": TemplatesRead,
|
||||
"templates.update": TemplatesUpdate,
|
||||
"templates.versions.activate.create": TemplatesVersionsActivateCreate,
|
||||
"templates.versions.activate.delete": TemplatesVersionsActivateDelete,
|
||||
"templates.versions.activate.read": TemplatesVersionsActivateRead,
|
||||
"templates.versions.activate.update": TemplatesVersionsActivateUpdate,
|
||||
"templates.versions.create": TemplatesVersionsCreate,
|
||||
"templates.versions.delete": TemplatesVersionsDelete,
|
||||
"templates.versions.read": TemplatesVersionsRead,
|
||||
"templates.versions.update": TemplatesVersionsUpdate,
|
||||
"tracking_settings.click.read": TrackingSettingsClickRead,
|
||||
"tracking_settings.click.update": TrackingSettingsClickUpdate,
|
||||
"tracking_settings.google_analytics.read": TrackingSettingsGoogleAnalyticsRead,
|
||||
"tracking_settings.google_analytics.update": TrackingSettingsGoogleAnalyticsUpdate,
|
||||
"tracking_settings.open.read": TrackingSettingsOpenRead,
|
||||
"tracking_settings.open.update": TrackingSettingsOpenUpdate,
|
||||
"tracking_settings.read": TrackingSettingsRead,
|
||||
"tracking_settings.subscription.read": TrackingSettingsSubscriptionRead,
|
||||
"tracking_settings.subscription.update": TrackingSettingsSubscriptionUpdate,
|
||||
"user.account.read": UserAccountRead,
|
||||
"user.credits.read": UserCreditsRead,
|
||||
"user.email.create": UserEmailCreate,
|
||||
"user.email.delete": UserEmailDelete,
|
||||
"user.email.read": UserEmailRead,
|
||||
"user.email.update": UserEmailUpdate,
|
||||
"user.multifactor_authentication.create": UserMultifactorAuthenticationCreate,
|
||||
"user.multifactor_authentication.delete": UserMultifactorAuthenticationDelete,
|
||||
"user.multifactor_authentication.read": UserMultifactorAuthenticationRead,
|
||||
"user.multifactor_authentication.update": UserMultifactorAuthenticationUpdate,
|
||||
"user.password.read": UserPasswordRead,
|
||||
"user.password.update": UserPasswordUpdate,
|
||||
"user.profile.read": UserProfileRead,
|
||||
"user.profile.update": UserProfileUpdate,
|
||||
"user.scheduled_sends.create": UserScheduledSendsCreate,
|
||||
"user.scheduled_sends.delete": UserScheduledSendsDelete,
|
||||
"user.scheduled_sends.read": UserScheduledSendsRead,
|
||||
"user.scheduled_sends.update": UserScheduledSendsUpdate,
|
||||
"user.settings.enforced_tls.read": UserSettingsEnforcedTlsRead,
|
||||
"user.settings.enforced_tls.update": UserSettingsEnforcedTlsUpdate,
|
||||
"user.timezone.read": UserTimezoneRead,
|
||||
"user.username.read": UserUsernameRead,
|
||||
"user.username.update": UserUsernameUpdate,
|
||||
"user.webhooks.event.settings.read": UserWebhooksEventSettingsRead,
|
||||
"user.webhooks.event.settings.update": UserWebhooksEventSettingsUpdate,
|
||||
"user.webhooks.event.test.create": UserWebhooksEventTestCreate,
|
||||
"user.webhooks.event.test.read": UserWebhooksEventTestRead,
|
||||
"user.webhooks.event.test.update": UserWebhooksEventTestUpdate,
|
||||
"user.webhooks.parse.settings.create": UserWebhooksParseSettingsCreate,
|
||||
"user.webhooks.parse.settings.delete": UserWebhooksParseSettingsDelete,
|
||||
"user.webhooks.parse.settings.read": UserWebhooksParseSettingsRead,
|
||||
"user.webhooks.parse.settings.update": UserWebhooksParseSettingsUpdate,
|
||||
"user.webhooks.parse.stats.read": UserWebhooksParseStatsRead,
|
||||
"whitelabel.create": WhitelabelCreate,
|
||||
"whitelabel.delete": WhitelabelDelete,
|
||||
"whitelabel.read": WhitelabelRead,
|
||||
"whitelabel.update": WhitelabelUpdate,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
AccessSettingsActivityRead: 1,
|
||||
AccessSettingsWhitelistCreate: 2,
|
||||
AccessSettingsWhitelistDelete: 3,
|
||||
AccessSettingsWhitelistRead: 4,
|
||||
AccessSettingsWhitelistUpdate: 5,
|
||||
AlertsCreate: 6,
|
||||
AlertsDelete: 7,
|
||||
AlertsRead: 8,
|
||||
AlertsUpdate: 9,
|
||||
ApiKeysCreate: 10,
|
||||
ApiKeysDelete: 11,
|
||||
ApiKeysRead: 12,
|
||||
ApiKeysUpdate: 13,
|
||||
AsmGroupsCreate: 14,
|
||||
AsmGroupsDelete: 15,
|
||||
AsmGroupsRead: 16,
|
||||
AsmGroupsUpdate: 17,
|
||||
BillingCreate: 18,
|
||||
BillingDelete: 19,
|
||||
BillingRead: 20,
|
||||
BillingUpdate: 21,
|
||||
BrowsersStatsRead: 22,
|
||||
CategoriesCreate: 23,
|
||||
CategoriesDelete: 24,
|
||||
CategoriesRead: 25,
|
||||
CategoriesStatsRead: 26,
|
||||
CategoriesStatsSumsRead: 27,
|
||||
CategoriesUpdate: 28,
|
||||
ClientsDesktopStatsRead: 29,
|
||||
ClientsPhoneStatsRead: 30,
|
||||
ClientsStatsRead: 31,
|
||||
ClientsTabletStatsRead: 32,
|
||||
ClientsWebmailStatsRead: 33,
|
||||
DevicesStatsRead: 34,
|
||||
EmailActivityRead: 35,
|
||||
GeoStatsRead: 36,
|
||||
IpsAssignedRead: 37,
|
||||
IpsPoolsCreate: 38,
|
||||
IpsPoolsDelete: 39,
|
||||
IpsPoolsIpsCreate: 40,
|
||||
IpsPoolsIpsDelete: 41,
|
||||
IpsPoolsIpsRead: 42,
|
||||
IpsPoolsIpsUpdate: 43,
|
||||
IpsPoolsRead: 44,
|
||||
IpsPoolsUpdate: 45,
|
||||
IpsRead: 46,
|
||||
IpsWarmupCreate: 47,
|
||||
IpsWarmupDelete: 48,
|
||||
IpsWarmupRead: 49,
|
||||
IpsWarmupUpdate: 50,
|
||||
MailSettingsAddressWhitelistRead: 51,
|
||||
MailSettingsAddressWhitelistUpdate: 52,
|
||||
MailSettingsBouncePurgeRead: 53,
|
||||
MailSettingsBouncePurgeUpdate: 54,
|
||||
MailSettingsFooterRead: 55,
|
||||
MailSettingsFooterUpdate: 56,
|
||||
MailSettingsForwardBounceRead: 57,
|
||||
MailSettingsForwardBounceUpdate: 58,
|
||||
MailSettingsForwardSpamRead: 59,
|
||||
MailSettingsForwardSpamUpdate: 60,
|
||||
MailSettingsPlainContentRead: 61,
|
||||
MailSettingsPlainContentUpdate: 62,
|
||||
MailSettingsRead: 63,
|
||||
MailSettingsTemplateRead: 64,
|
||||
MailSettingsTemplateUpdate: 65,
|
||||
MailBatchCreate: 66,
|
||||
MailBatchDelete: 67,
|
||||
MailBatchRead: 68,
|
||||
MailBatchUpdate: 69,
|
||||
MailSend: 70,
|
||||
MailboxProvidersStatsRead: 71,
|
||||
MarketingCampaignsCreate: 72,
|
||||
MarketingCampaignsDelete: 73,
|
||||
MarketingCampaignsRead: 74,
|
||||
MarketingCampaignsUpdate: 75,
|
||||
PartnerSettingsNewRelicRead: 76,
|
||||
PartnerSettingsNewRelicUpdate: 77,
|
||||
PartnerSettingsRead: 78,
|
||||
StatsGlobalRead: 79,
|
||||
StatsRead: 80,
|
||||
SubusersCreate: 81,
|
||||
SubusersCreditsCreate: 82,
|
||||
SubusersCreditsDelete: 83,
|
||||
SubusersCreditsRead: 84,
|
||||
SubusersCreditsRemainingCreate: 85,
|
||||
SubusersCreditsRemainingDelete: 86,
|
||||
SubusersCreditsRemainingRead: 87,
|
||||
SubusersCreditsRemainingUpdate: 88,
|
||||
SubusersCreditsUpdate: 89,
|
||||
SubusersDelete: 90,
|
||||
SubusersMonitorCreate: 91,
|
||||
SubusersMonitorDelete: 92,
|
||||
SubusersMonitorRead: 93,
|
||||
SubusersMonitorUpdate: 94,
|
||||
SubusersRead: 95,
|
||||
SubusersReputationsRead: 96,
|
||||
SubusersStatsMonthlyRead: 97,
|
||||
SubusersStatsRead: 98,
|
||||
SubusersStatsSumsRead: 99,
|
||||
SubusersSummaryRead: 100,
|
||||
SubusersUpdate: 101,
|
||||
SuppressionBlocksCreate: 102,
|
||||
SuppressionBlocksDelete: 103,
|
||||
SuppressionBlocksRead: 104,
|
||||
SuppressionBlocksUpdate: 105,
|
||||
SuppressionBouncesCreate: 106,
|
||||
SuppressionBouncesDelete: 107,
|
||||
SuppressionBouncesRead: 108,
|
||||
SuppressionBouncesUpdate: 109,
|
||||
SuppressionCreate: 110,
|
||||
SuppressionDelete: 111,
|
||||
SuppressionInvalidEmailsCreate: 112,
|
||||
SuppressionInvalidEmailsDelete: 113,
|
||||
SuppressionInvalidEmailsRead: 114,
|
||||
SuppressionInvalidEmailsUpdate: 115,
|
||||
SuppressionRead: 116,
|
||||
SuppressionSpamReportsCreate: 117,
|
||||
SuppressionSpamReportsDelete: 118,
|
||||
SuppressionSpamReportsRead: 119,
|
||||
SuppressionSpamReportsUpdate: 120,
|
||||
SuppressionUnsubscribesCreate: 121,
|
||||
SuppressionUnsubscribesDelete: 122,
|
||||
SuppressionUnsubscribesRead: 123,
|
||||
SuppressionUnsubscribesUpdate: 124,
|
||||
SuppressionUpdate: 125,
|
||||
TeammatesCreate: 126,
|
||||
TeammatesRead: 127,
|
||||
TeammatesUpdate: 128,
|
||||
TeammatesDelete: 129,
|
||||
TemplatesCreate: 130,
|
||||
TemplatesDelete: 131,
|
||||
TemplatesRead: 132,
|
||||
TemplatesUpdate: 133,
|
||||
TemplatesVersionsActivateCreate: 134,
|
||||
TemplatesVersionsActivateDelete: 135,
|
||||
TemplatesVersionsActivateRead: 136,
|
||||
TemplatesVersionsActivateUpdate: 137,
|
||||
TemplatesVersionsCreate: 138,
|
||||
TemplatesVersionsDelete: 139,
|
||||
TemplatesVersionsRead: 140,
|
||||
TemplatesVersionsUpdate: 141,
|
||||
TrackingSettingsClickRead: 142,
|
||||
TrackingSettingsClickUpdate: 143,
|
||||
TrackingSettingsGoogleAnalyticsRead: 144,
|
||||
TrackingSettingsGoogleAnalyticsUpdate: 145,
|
||||
TrackingSettingsOpenRead: 146,
|
||||
TrackingSettingsOpenUpdate: 147,
|
||||
TrackingSettingsRead: 148,
|
||||
TrackingSettingsSubscriptionRead: 149,
|
||||
TrackingSettingsSubscriptionUpdate: 150,
|
||||
UserAccountRead: 151,
|
||||
UserCreditsRead: 152,
|
||||
UserEmailCreate: 153,
|
||||
UserEmailDelete: 154,
|
||||
UserEmailRead: 155,
|
||||
UserEmailUpdate: 156,
|
||||
UserMultifactorAuthenticationCreate: 157,
|
||||
UserMultifactorAuthenticationDelete: 158,
|
||||
UserMultifactorAuthenticationRead: 159,
|
||||
UserMultifactorAuthenticationUpdate: 160,
|
||||
UserPasswordRead: 161,
|
||||
UserPasswordUpdate: 162,
|
||||
UserProfileRead: 163,
|
||||
UserProfileUpdate: 164,
|
||||
UserScheduledSendsCreate: 165,
|
||||
UserScheduledSendsDelete: 166,
|
||||
UserScheduledSendsRead: 167,
|
||||
UserScheduledSendsUpdate: 168,
|
||||
UserSettingsEnforcedTlsRead: 169,
|
||||
UserSettingsEnforcedTlsUpdate: 170,
|
||||
UserTimezoneRead: 171,
|
||||
UserUsernameRead: 172,
|
||||
UserUsernameUpdate: 173,
|
||||
UserWebhooksEventSettingsRead: 174,
|
||||
UserWebhooksEventSettingsUpdate: 175,
|
||||
UserWebhooksEventTestCreate: 176,
|
||||
UserWebhooksEventTestRead: 177,
|
||||
UserWebhooksEventTestUpdate: 178,
|
||||
UserWebhooksParseSettingsCreate: 179,
|
||||
UserWebhooksParseSettingsDelete: 180,
|
||||
UserWebhooksParseSettingsRead: 181,
|
||||
UserWebhooksParseSettingsUpdate: 182,
|
||||
UserWebhooksParseStatsRead: 183,
|
||||
WhitelabelCreate: 184,
|
||||
WhitelabelDelete: 185,
|
||||
WhitelabelRead: 186,
|
||||
WhitelabelUpdate: 187,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: AccessSettingsActivityRead,
|
||||
2: AccessSettingsWhitelistCreate,
|
||||
3: AccessSettingsWhitelistDelete,
|
||||
4: AccessSettingsWhitelistRead,
|
||||
5: AccessSettingsWhitelistUpdate,
|
||||
6: AlertsCreate,
|
||||
7: AlertsDelete,
|
||||
8: AlertsRead,
|
||||
9: AlertsUpdate,
|
||||
10: ApiKeysCreate,
|
||||
11: ApiKeysDelete,
|
||||
12: ApiKeysRead,
|
||||
13: ApiKeysUpdate,
|
||||
14: AsmGroupsCreate,
|
||||
15: AsmGroupsDelete,
|
||||
16: AsmGroupsRead,
|
||||
17: AsmGroupsUpdate,
|
||||
18: BillingCreate,
|
||||
19: BillingDelete,
|
||||
20: BillingRead,
|
||||
21: BillingUpdate,
|
||||
22: BrowsersStatsRead,
|
||||
23: CategoriesCreate,
|
||||
24: CategoriesDelete,
|
||||
25: CategoriesRead,
|
||||
26: CategoriesStatsRead,
|
||||
27: CategoriesStatsSumsRead,
|
||||
28: CategoriesUpdate,
|
||||
29: ClientsDesktopStatsRead,
|
||||
30: ClientsPhoneStatsRead,
|
||||
31: ClientsStatsRead,
|
||||
32: ClientsTabletStatsRead,
|
||||
33: ClientsWebmailStatsRead,
|
||||
34: DevicesStatsRead,
|
||||
35: EmailActivityRead,
|
||||
36: GeoStatsRead,
|
||||
37: IpsAssignedRead,
|
||||
38: IpsPoolsCreate,
|
||||
39: IpsPoolsDelete,
|
||||
40: IpsPoolsIpsCreate,
|
||||
41: IpsPoolsIpsDelete,
|
||||
42: IpsPoolsIpsRead,
|
||||
43: IpsPoolsIpsUpdate,
|
||||
44: IpsPoolsRead,
|
||||
45: IpsPoolsUpdate,
|
||||
46: IpsRead,
|
||||
47: IpsWarmupCreate,
|
||||
48: IpsWarmupDelete,
|
||||
49: IpsWarmupRead,
|
||||
50: IpsWarmupUpdate,
|
||||
51: MailSettingsAddressWhitelistRead,
|
||||
52: MailSettingsAddressWhitelistUpdate,
|
||||
53: MailSettingsBouncePurgeRead,
|
||||
54: MailSettingsBouncePurgeUpdate,
|
||||
55: MailSettingsFooterRead,
|
||||
56: MailSettingsFooterUpdate,
|
||||
57: MailSettingsForwardBounceRead,
|
||||
58: MailSettingsForwardBounceUpdate,
|
||||
59: MailSettingsForwardSpamRead,
|
||||
60: MailSettingsForwardSpamUpdate,
|
||||
61: MailSettingsPlainContentRead,
|
||||
62: MailSettingsPlainContentUpdate,
|
||||
63: MailSettingsRead,
|
||||
64: MailSettingsTemplateRead,
|
||||
65: MailSettingsTemplateUpdate,
|
||||
66: MailBatchCreate,
|
||||
67: MailBatchDelete,
|
||||
68: MailBatchRead,
|
||||
69: MailBatchUpdate,
|
||||
70: MailSend,
|
||||
71: MailboxProvidersStatsRead,
|
||||
72: MarketingCampaignsCreate,
|
||||
73: MarketingCampaignsDelete,
|
||||
74: MarketingCampaignsRead,
|
||||
75: MarketingCampaignsUpdate,
|
||||
76: PartnerSettingsNewRelicRead,
|
||||
77: PartnerSettingsNewRelicUpdate,
|
||||
78: PartnerSettingsRead,
|
||||
79: StatsGlobalRead,
|
||||
80: StatsRead,
|
||||
81: SubusersCreate,
|
||||
82: SubusersCreditsCreate,
|
||||
83: SubusersCreditsDelete,
|
||||
84: SubusersCreditsRead,
|
||||
85: SubusersCreditsRemainingCreate,
|
||||
86: SubusersCreditsRemainingDelete,
|
||||
87: SubusersCreditsRemainingRead,
|
||||
88: SubusersCreditsRemainingUpdate,
|
||||
89: SubusersCreditsUpdate,
|
||||
90: SubusersDelete,
|
||||
91: SubusersMonitorCreate,
|
||||
92: SubusersMonitorDelete,
|
||||
93: SubusersMonitorRead,
|
||||
94: SubusersMonitorUpdate,
|
||||
95: SubusersRead,
|
||||
96: SubusersReputationsRead,
|
||||
97: SubusersStatsMonthlyRead,
|
||||
98: SubusersStatsRead,
|
||||
99: SubusersStatsSumsRead,
|
||||
100: SubusersSummaryRead,
|
||||
101: SubusersUpdate,
|
||||
102: SuppressionBlocksCreate,
|
||||
103: SuppressionBlocksDelete,
|
||||
104: SuppressionBlocksRead,
|
||||
105: SuppressionBlocksUpdate,
|
||||
106: SuppressionBouncesCreate,
|
||||
107: SuppressionBouncesDelete,
|
||||
108: SuppressionBouncesRead,
|
||||
109: SuppressionBouncesUpdate,
|
||||
110: SuppressionCreate,
|
||||
111: SuppressionDelete,
|
||||
112: SuppressionInvalidEmailsCreate,
|
||||
113: SuppressionInvalidEmailsDelete,
|
||||
114: SuppressionInvalidEmailsRead,
|
||||
115: SuppressionInvalidEmailsUpdate,
|
||||
116: SuppressionRead,
|
||||
117: SuppressionSpamReportsCreate,
|
||||
118: SuppressionSpamReportsDelete,
|
||||
119: SuppressionSpamReportsRead,
|
||||
120: SuppressionSpamReportsUpdate,
|
||||
121: SuppressionUnsubscribesCreate,
|
||||
122: SuppressionUnsubscribesDelete,
|
||||
123: SuppressionUnsubscribesRead,
|
||||
124: SuppressionUnsubscribesUpdate,
|
||||
125: SuppressionUpdate,
|
||||
126: TeammatesCreate,
|
||||
127: TeammatesRead,
|
||||
128: TeammatesUpdate,
|
||||
129: TeammatesDelete,
|
||||
130: TemplatesCreate,
|
||||
131: TemplatesDelete,
|
||||
132: TemplatesRead,
|
||||
133: TemplatesUpdate,
|
||||
134: TemplatesVersionsActivateCreate,
|
||||
135: TemplatesVersionsActivateDelete,
|
||||
136: TemplatesVersionsActivateRead,
|
||||
137: TemplatesVersionsActivateUpdate,
|
||||
138: TemplatesVersionsCreate,
|
||||
139: TemplatesVersionsDelete,
|
||||
140: TemplatesVersionsRead,
|
||||
141: TemplatesVersionsUpdate,
|
||||
142: TrackingSettingsClickRead,
|
||||
143: TrackingSettingsClickUpdate,
|
||||
144: TrackingSettingsGoogleAnalyticsRead,
|
||||
145: TrackingSettingsGoogleAnalyticsUpdate,
|
||||
146: TrackingSettingsOpenRead,
|
||||
147: TrackingSettingsOpenUpdate,
|
||||
148: TrackingSettingsRead,
|
||||
149: TrackingSettingsSubscriptionRead,
|
||||
150: TrackingSettingsSubscriptionUpdate,
|
||||
151: UserAccountRead,
|
||||
152: UserCreditsRead,
|
||||
153: UserEmailCreate,
|
||||
154: UserEmailDelete,
|
||||
155: UserEmailRead,
|
||||
156: UserEmailUpdate,
|
||||
157: UserMultifactorAuthenticationCreate,
|
||||
158: UserMultifactorAuthenticationDelete,
|
||||
159: UserMultifactorAuthenticationRead,
|
||||
160: UserMultifactorAuthenticationUpdate,
|
||||
161: UserPasswordRead,
|
||||
162: UserPasswordUpdate,
|
||||
163: UserProfileRead,
|
||||
164: UserProfileUpdate,
|
||||
165: UserScheduledSendsCreate,
|
||||
166: UserScheduledSendsDelete,
|
||||
167: UserScheduledSendsRead,
|
||||
168: UserScheduledSendsUpdate,
|
||||
169: UserSettingsEnforcedTlsRead,
|
||||
170: UserSettingsEnforcedTlsUpdate,
|
||||
171: UserTimezoneRead,
|
||||
172: UserUsernameRead,
|
||||
173: UserUsernameUpdate,
|
||||
174: UserWebhooksEventSettingsRead,
|
||||
175: UserWebhooksEventSettingsUpdate,
|
||||
176: UserWebhooksEventTestCreate,
|
||||
177: UserWebhooksEventTestRead,
|
||||
178: UserWebhooksEventTestUpdate,
|
||||
179: UserWebhooksParseSettingsCreate,
|
||||
180: UserWebhooksParseSettingsDelete,
|
||||
181: UserWebhooksParseSettingsRead,
|
||||
182: UserWebhooksParseSettingsUpdate,
|
||||
183: UserWebhooksParseStatsRead,
|
||||
184: WhitelabelCreate,
|
||||
185: WhitelabelDelete,
|
||||
186: WhitelabelRead,
|
||||
187: WhitelabelUpdate,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
188
pkg/analyzer/analyzers/sendgrid/permissions.yaml
Normal file
188
pkg/analyzer/analyzers/sendgrid/permissions.yaml
Normal file
|
@ -0,0 +1,188 @@
|
|||
permissions:
|
||||
- access_settings.activity.read
|
||||
- access_settings.whitelist.create
|
||||
- access_settings.whitelist.delete
|
||||
- access_settings.whitelist.read
|
||||
- access_settings.whitelist.update
|
||||
- alerts.create
|
||||
- alerts.delete
|
||||
- alerts.read
|
||||
- alerts.update
|
||||
- api_keys.create
|
||||
- api_keys.delete
|
||||
- api_keys.read
|
||||
- api_keys.update
|
||||
- asm.groups.create
|
||||
- asm.groups.delete
|
||||
- asm.groups.read
|
||||
- asm.groups.update
|
||||
- billing.create
|
||||
- billing.delete
|
||||
- billing.read
|
||||
- billing.update
|
||||
- browsers.stats.read
|
||||
- categories.create
|
||||
- categories.delete
|
||||
- categories.read
|
||||
- categories.stats.read
|
||||
- categories.stats.sums.read
|
||||
- categories.update
|
||||
- clients.desktop.stats.read
|
||||
- clients.phone.stats.read
|
||||
- clients.stats.read
|
||||
- clients.tablet.stats.read
|
||||
- clients.webmail.stats.read
|
||||
- devices.stats.read
|
||||
- email_activity.read
|
||||
- geo.stats.read
|
||||
- ips.assigned.read
|
||||
- ips.pools.create
|
||||
- ips.pools.delete
|
||||
- ips.pools.ips.create
|
||||
- ips.pools.ips.delete
|
||||
- ips.pools.ips.read
|
||||
- ips.pools.ips.update
|
||||
- ips.pools.read
|
||||
- ips.pools.update
|
||||
- ips.read
|
||||
- ips.warmup.create
|
||||
- ips.warmup.delete
|
||||
- ips.warmup.read
|
||||
- ips.warmup.update
|
||||
- mail_settings.address_whitelist.read
|
||||
- mail_settings.address_whitelist.update
|
||||
- mail_settings.bounce_purge.read
|
||||
- mail_settings.bounce_purge.update
|
||||
- mail_settings.footer.read
|
||||
- mail_settings.footer.update
|
||||
- mail_settings.forward_bounce.read
|
||||
- mail_settings.forward_bounce.update
|
||||
- mail_settings.forward_spam.read
|
||||
- mail_settings.forward_spam.update
|
||||
- mail_settings.plain_content.read
|
||||
- mail_settings.plain_content.update
|
||||
- mail_settings.read
|
||||
- mail_settings.template.read
|
||||
- mail_settings.template.update
|
||||
- mail.batch.create
|
||||
- mail.batch.delete
|
||||
- mail.batch.read
|
||||
- mail.batch.update
|
||||
- mail.send
|
||||
- mailbox_providers.stats.read
|
||||
- marketing_campaigns.create
|
||||
- marketing_campaigns.delete
|
||||
- marketing_campaigns.read
|
||||
- marketing_campaigns.update
|
||||
- partner_settings.new_relic.read
|
||||
- partner_settings.new_relic.update
|
||||
- partner_settings.read
|
||||
- stats.global.read
|
||||
- stats.read
|
||||
- subusers.create
|
||||
- subusers.credits.create
|
||||
- subusers.credits.delete
|
||||
- subusers.credits.read
|
||||
- subusers.credits.remaining.create
|
||||
- subusers.credits.remaining.delete
|
||||
- subusers.credits.remaining.read
|
||||
- subusers.credits.remaining.update
|
||||
- subusers.credits.update
|
||||
- subusers.delete
|
||||
- subusers.monitor.create
|
||||
- subusers.monitor.delete
|
||||
- subusers.monitor.read
|
||||
- subusers.monitor.update
|
||||
- subusers.read
|
||||
- subusers.reputations.read
|
||||
- subusers.stats.monthly.read
|
||||
- subusers.stats.read
|
||||
- subusers.stats.sums.read
|
||||
- subusers.summary.read
|
||||
- subusers.update
|
||||
- suppression.blocks.create
|
||||
- suppression.blocks.delete
|
||||
- suppression.blocks.read
|
||||
- suppression.blocks.update
|
||||
- suppression.bounces.create
|
||||
- suppression.bounces.delete
|
||||
- suppression.bounces.read
|
||||
- suppression.bounces.update
|
||||
- suppression.create
|
||||
- suppression.delete
|
||||
- suppression.invalid_emails.create
|
||||
- suppression.invalid_emails.delete
|
||||
- suppression.invalid_emails.read
|
||||
- suppression.invalid_emails.update
|
||||
- suppression.read
|
||||
- suppression.spam_reports.create
|
||||
- suppression.spam_reports.delete
|
||||
- suppression.spam_reports.read
|
||||
- suppression.spam_reports.update
|
||||
- suppression.unsubscribes.create
|
||||
- suppression.unsubscribes.delete
|
||||
- suppression.unsubscribes.read
|
||||
- suppression.unsubscribes.update
|
||||
- suppression.update
|
||||
- teammates.create
|
||||
- teammates.read
|
||||
- teammates.update
|
||||
- teammates.delete
|
||||
- templates.create
|
||||
- templates.delete
|
||||
- templates.read
|
||||
- templates.update
|
||||
- templates.versions.activate.create
|
||||
- templates.versions.activate.delete
|
||||
- templates.versions.activate.read
|
||||
- templates.versions.activate.update
|
||||
- templates.versions.create
|
||||
- templates.versions.delete
|
||||
- templates.versions.read
|
||||
- templates.versions.update
|
||||
- tracking_settings.click.read
|
||||
- tracking_settings.click.update
|
||||
- tracking_settings.google_analytics.read
|
||||
- tracking_settings.google_analytics.update
|
||||
- tracking_settings.open.read
|
||||
- tracking_settings.open.update
|
||||
- tracking_settings.read
|
||||
- tracking_settings.subscription.read
|
||||
- tracking_settings.subscription.update
|
||||
- user.account.read
|
||||
- user.credits.read
|
||||
- user.email.create
|
||||
- user.email.delete
|
||||
- user.email.read
|
||||
- user.email.update
|
||||
- user.multifactor_authentication.create
|
||||
- user.multifactor_authentication.delete
|
||||
- user.multifactor_authentication.read
|
||||
- user.multifactor_authentication.update
|
||||
- user.password.read
|
||||
- user.password.update
|
||||
- user.profile.read
|
||||
- user.profile.update
|
||||
- user.scheduled_sends.create
|
||||
- user.scheduled_sends.delete
|
||||
- user.scheduled_sends.read
|
||||
- user.scheduled_sends.update
|
||||
- user.settings.enforced_tls.read
|
||||
- user.settings.enforced_tls.update
|
||||
- user.timezone.read
|
||||
- user.username.read
|
||||
- user.username.update
|
||||
- user.webhooks.event.settings.read
|
||||
- user.webhooks.event.settings.update
|
||||
- user.webhooks.event.test.create
|
||||
- user.webhooks.event.test.read
|
||||
- user.webhooks.event.test.update
|
||||
- user.webhooks.parse.settings.create
|
||||
- user.webhooks.parse.settings.delete
|
||||
- user.webhooks.parse.settings.read
|
||||
- user.webhooks.parse.settings.update
|
||||
- user.webhooks.parse.stats.read
|
||||
- whitelabel.create
|
||||
- whitelabel.delete
|
||||
- whitelabel.read
|
||||
- whitelabel.update
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate generate_permissions permissions.yaml permissions.go sendgrid
|
||||
|
||||
package sendgrid
|
||||
|
||||
import (
|
||||
|
@ -13,17 +15,106 @@ import (
|
|||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Sendgrid }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing key in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var keyType string
|
||||
if slices.Contains(info.RawScopes, "user.email.read") {
|
||||
keyType = "full access"
|
||||
} else if slices.Contains(info.RawScopes, "billing.read") {
|
||||
keyType = "billing access"
|
||||
} else {
|
||||
keyType = "restricted access"
|
||||
}
|
||||
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzerpb.AnalyzerType_Sendgrid,
|
||||
Metadata: map[string]any{
|
||||
"key_type": keyType,
|
||||
"2fa_required": slices.Contains(info.RawScopes, "2fa_required"),
|
||||
},
|
||||
Bindings: []analyzers.Binding{},
|
||||
UnboundedResources: []analyzers.Resource{},
|
||||
}
|
||||
|
||||
for _, scope := range info.Scopes {
|
||||
resource := getCategoryResource(scope)
|
||||
|
||||
if len(scope.Permissions) == 0 {
|
||||
result.UnboundedResources = append(result.UnboundedResources, *resource)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, permission := range scope.Permissions {
|
||||
result.Bindings = append(result.Bindings, analyzers.Binding{
|
||||
Resource: *resource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: permission,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func getCategoryResource(scope SendgridScope) *analyzers.Resource {
|
||||
categoryResource := &analyzers.Resource{
|
||||
Name: scope.Category,
|
||||
FullyQualifiedName: scope.Category,
|
||||
Type: "category",
|
||||
Metadata: nil,
|
||||
}
|
||||
|
||||
if scope.SubCategory != "" {
|
||||
return &analyzers.Resource{
|
||||
Name: scope.SubCategory,
|
||||
FullyQualifiedName: fmt.Sprintf("%s/%s", scope.Category, scope.SubCategory),
|
||||
Type: "category",
|
||||
Metadata: nil,
|
||||
Parent: categoryResource,
|
||||
}
|
||||
}
|
||||
|
||||
return categoryResource
|
||||
}
|
||||
|
||||
type ScopesJSON struct {
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
RawScopes []string
|
||||
Scopes []SendgridScope
|
||||
}
|
||||
|
||||
func printPermissions(show_all bool) {
|
||||
func printPermissions(info *SecretInfo, show_all bool) {
|
||||
fmt.Print("\n\n")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
|
@ -33,7 +124,7 @@ func printPermissions(show_all bool) {
|
|||
t.AppendHeader(table.Row{"Scope", "Sub-Scope", "Access"})
|
||||
}
|
||||
// Print the scopes
|
||||
for _, s := range SCOPES {
|
||||
for _, s := range info.Scopes {
|
||||
writer := analyzers.GetWriterFromStatus(s.PermissionType)
|
||||
if show_all {
|
||||
t.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType), writer(strings.Join(s.Permissions, "\n"))})
|
||||
|
@ -49,11 +140,11 @@ func printPermissions(show_all bool) {
|
|||
// It will return the most specific category possible.
|
||||
// For example, if the scope is "mail.send.read", it will return "Mail Send", not just "Mail"
|
||||
// since it's searching "mail.send.read" -> "mail.send" -> "mail"
|
||||
func getScopeIndex(scope string) int {
|
||||
func getScopeIndex(categories []SendgridScope, scope string) int {
|
||||
splitScope := strings.Split(scope, ".")
|
||||
for i := len(splitScope); i > 0; i-- {
|
||||
searchScope := strings.Join(splitScope[:i], ".")
|
||||
for i, s := range SCOPES {
|
||||
for i, s := range categories {
|
||||
for _, prefix := range s.Prefixes {
|
||||
if strings.HasPrefix(searchScope, prefix) {
|
||||
return i
|
||||
|
@ -64,24 +155,36 @@ func getScopeIndex(scope string) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
func processPermissions(rawScopes []string) {
|
||||
func processPermissions(rawScopes []string) []SendgridScope {
|
||||
categoryPermissions := make([]SendgridScope, len(SCOPES))
|
||||
|
||||
// copy all scope categories to the categoryPermissions slice
|
||||
copy(categoryPermissions, SCOPES)
|
||||
for _, scope := range rawScopes {
|
||||
// Skip these scopes since they are not useful for this analysis
|
||||
if scope == "2fa_required" || scope == "sender_verification_eligible" {
|
||||
continue
|
||||
}
|
||||
ind := getScopeIndex(scope)
|
||||
|
||||
// must be part of generated permissions
|
||||
if _, ok := StringToPermission[scope]; !ok {
|
||||
continue
|
||||
}
|
||||
ind := getScopeIndex(categoryPermissions, scope)
|
||||
if ind == -1 {
|
||||
//color.Red("[!] Scope not found: %v", scope)
|
||||
continue
|
||||
}
|
||||
s := &SCOPES[ind]
|
||||
s := &categoryPermissions[ind]
|
||||
s.AddPermission(scope)
|
||||
}
|
||||
|
||||
// Run tests to determine the permission type
|
||||
for i := range SCOPES {
|
||||
SCOPES[i].RunTests()
|
||||
for i := range categoryPermissions {
|
||||
categoryPermissions[i].RunTests()
|
||||
}
|
||||
|
||||
return categoryPermissions
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
||||
|
@ -105,7 +208,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
|||
color.Yellow("[i] 2FA Required for this account")
|
||||
}
|
||||
|
||||
printPermissions(cfg.ShowAll)
|
||||
printPermissions(info, cfg.ShowAll)
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
||||
|
@ -133,7 +236,10 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
|||
// Now you can access the scopes
|
||||
rawScopes := jsonScopes.Scopes
|
||||
|
||||
processPermissions(rawScopes)
|
||||
categoryScope := processPermissions(rawScopes)
|
||||
|
||||
return &SecretInfo{RawScopes: rawScopes}, nil
|
||||
return &SecretInfo{
|
||||
RawScopes: rawScopes,
|
||||
Scopes: categoryScope,
|
||||
}, nil
|
||||
}
|
||||
|
|
97
pkg/analyzer/analyzers/sendgrid/sendgrid_test.go
Normal file
97
pkg/analyzer/analyzers/sendgrid/sendgrid_test.go
Normal file
File diff suppressed because one or more lines are too long
|
@ -55,7 +55,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_ElevenLabs,
|
||||
Raw: []byte(match),
|
||||
ExtraData: map[string]string{"version": "1"},
|
||||
ExtraData: map[string]string{
|
||||
"version": "1",
|
||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/elevenlabs/",
|
||||
},
|
||||
}
|
||||
|
||||
if verify {
|
||||
|
|
|
@ -46,6 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Eraser,
|
||||
Raw: []byte(match),
|
||||
ExtraData: map[string]string{
|
||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/eraser/",
|
||||
},
|
||||
}
|
||||
|
||||
if verify {
|
||||
|
|
|
@ -42,6 +42,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Groq,
|
||||
Raw: []byte(match),
|
||||
ExtraData: map[string]string{
|
||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/groq/",
|
||||
},
|
||||
}
|
||||
|
||||
if verify {
|
||||
|
|
|
@ -49,7 +49,7 @@ var (
|
|||
// Keywords are used for efficiently pre-filtering chunks.
|
||||
// Use identifiers in the secret preferably, or the provider name.
|
||||
func (s Scanner) Keywords() []string {
|
||||
return []string{"lark", "larksuite", "t-", "a-", "u-"}
|
||||
return []string{"lark", "larksuite"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify Larksuite secrets in a given set of bytes.
|
||||
|
|
|
@ -3,10 +3,11 @@ package mailchimp
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
|
||||
|
@ -63,6 +64,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
s.Verified = true
|
||||
}
|
||||
}
|
||||
s.AnalysisInfo = map[string]string{
|
||||
"key": match,
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s)
|
||||
|
|
|
@ -93,6 +93,7 @@ func TestMailchimp_FromChunk(t *testing.T) {
|
|||
t.Fatal("no raw secret present")
|
||||
}
|
||||
got[i].Raw = nil
|
||||
got[i].AnalysisInfo = nil
|
||||
}
|
||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||
t.Errorf("Mailchimp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
|
|
|
@ -51,7 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
Raw: []byte(resMatch),
|
||||
}
|
||||
s1.ExtraData = map[string]string{
|
||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/ms/",
|
||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/microsoftteams/",
|
||||
}
|
||||
|
||||
if verify {
|
||||
|
|
|
@ -87,7 +87,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
}
|
||||
} else {
|
||||
s1.Verified = false
|
||||
|
||||
}
|
||||
s1.AnalysisInfo = map[string]string{
|
||||
"key": resMatch,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,6 +92,7 @@ func TestOpsgenie_FromChunk(t *testing.T) {
|
|||
t.Fatalf("no raw secret present: \n %+v", got[i])
|
||||
}
|
||||
got[i].Raw = nil
|
||||
got[i].AnalysisInfo = nil
|
||||
}
|
||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||
t.Errorf("Opsgenie.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
|
|
|
@ -3,10 +3,11 @@ package postman
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
|
@ -56,6 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
isVerified, verificationErr := verifyPostman(ctx, client, resMatch)
|
||||
s1.Verified = isVerified
|
||||
s1.SetVerificationError(verificationErr, resMatch)
|
||||
s1.AnalysisInfo = map[string]string{
|
||||
"key": resMatch,
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
|
|
@ -133,7 +133,7 @@ func TestPostman_FromChunk(t *testing.T) {
|
|||
t.Errorf("Postman.FromData() error = %v, wantErr %v", got[i].VerificationError(), tt.wantVerificationErr)
|
||||
}
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Postman.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
|
|
195
pkg/detectors/robinhoodcrypto/robinhoodcrypto.go
Normal file
195
pkg/detectors/robinhoodcrypto/robinhoodcrypto.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package robinhoodcrypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
defaultClient = common.SaneHttpClient()
|
||||
|
||||
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
|
||||
// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication
|
||||
keyPat = regexp.MustCompile(`\b(rh-api-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\b`)
|
||||
|
||||
// Matches base64 strings. Taken from https://stackoverflow.com/a/475217.
|
||||
privKeyBase64Pat = regexp.MustCompile(`(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)`)
|
||||
)
|
||||
|
||||
// Keywords are used for efficiently pre-filtering chunks.
|
||||
// Use identifiers in the secret preferably, or the provider name.
|
||||
func (s Scanner) Keywords() []string {
|
||||
return []string{"rh-api-"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify RobinhoodCrypto secrets in a given set of bytes.
|
||||
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||
dataStr := string(data)
|
||||
|
||||
apiKeyMatches := make(map[string]struct{})
|
||||
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
|
||||
apiKeyMatches[match[1]] = struct{}{}
|
||||
}
|
||||
|
||||
base64PrivateKeyMatches := make(map[string]struct{})
|
||||
for _, match := range privKeyBase64Pat.FindAllString(dataStr, -1) {
|
||||
base64PrivateKeyMatches[match] = struct{}{}
|
||||
}
|
||||
|
||||
for apiKey := range apiKeyMatches {
|
||||
for base64PrivateKey := range base64PrivateKeyMatches {
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Raw: []byte(apiKey),
|
||||
RawV2: []byte(apiKey + base64PrivateKey),
|
||||
}
|
||||
|
||||
if verify {
|
||||
client := s.client
|
||||
if client == nil {
|
||||
client = defaultClient
|
||||
}
|
||||
|
||||
isVerified, extraData, verificationErr := verifyMatch(ctx, client, apiKey, base64PrivateKey)
|
||||
s1.Verified = isVerified
|
||||
s1.ExtraData = extraData
|
||||
s1.SetVerificationError(verificationErr, apiKey, base64PrivateKey)
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func verifyMatch(ctx context.Context, client *http.Client, apiKey, base64PrivateKey string) (
|
||||
bool, map[string]string, error,
|
||||
) {
|
||||
// Decode the base64 private key.
|
||||
privateBytes, err := base64.StdEncoding.DecodeString(base64PrivateKey)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("failed to decode base64 private key: %w", err)
|
||||
}
|
||||
|
||||
// Sanity check the private key length.
|
||||
if len(privateBytes) < 32 {
|
||||
return false, nil, fmt.Errorf("private key is too short, expected at least 32 bytes, got %d", len(privateBytes))
|
||||
}
|
||||
|
||||
// Create the private key from the seed.
|
||||
privateKey := ed25519.NewKeyFromSeed(privateBytes[:32])
|
||||
|
||||
// Draft the message to be signed.
|
||||
// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication/Headers-and-Signature
|
||||
var (
|
||||
timestamp = fmt.Sprint(time.Now().UTC().Unix())
|
||||
path = "/api/v1/crypto/trading/accounts/"
|
||||
method = http.MethodGet
|
||||
body = ""
|
||||
)
|
||||
|
||||
message := apiKey + timestamp + path + method + body
|
||||
signature := ed25519.Sign(privateKey, []byte(message))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, "https://trading.robinhood.com/"+path, strings.NewReader(body))
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// Set the required headers.
|
||||
headers := map[string]string{
|
||||
"x-api-key": apiKey,
|
||||
"x-signature": base64.StdEncoding.EncodeToString(signature),
|
||||
"x-timestamp": timestamp,
|
||||
}
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
switch res.StatusCode {
|
||||
// StatusOK: The secret is verified.
|
||||
case http.StatusOK:
|
||||
// Include the additional information returned by the endpoint.
|
||||
if len(res.Header) > 0 && res.Header.Get("Content-Type") == "application/json" {
|
||||
response := struct {
|
||||
AccountNumber string `json:"account_number"`
|
||||
Status string `json:"status"`
|
||||
BuyingPower string `json:"buying_power"`
|
||||
BuyingPowerCurrency string `json:"buying_power_currency"`
|
||||
}{}
|
||||
|
||||
if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||
return true, nil, fmt.Errorf("failed to obtain additional information: %w", err)
|
||||
}
|
||||
|
||||
return true, map[string]string{"Robinhood Crypto Account Number": response.AccountNumber}, nil
|
||||
}
|
||||
|
||||
// The secret is verified, but there is no additional information.
|
||||
return true, nil, nil
|
||||
|
||||
// StatusForbidden: The secret is valid, but the credentials do not have access to the endpoint.
|
||||
case http.StatusForbidden:
|
||||
return true, map[string]string{"Explanation": "Valid credentials without access to Get Crypto Trading Account Details API"}, nil
|
||||
|
||||
// StatusUnauthorized:
|
||||
// Two scenarios can happen,
|
||||
// 1. The secret is verified, but is currently inactive.
|
||||
// 2. The secret is determinately not verified.
|
||||
case http.StatusUnauthorized:
|
||||
// Check if the secret is verified but currently inactive.
|
||||
// We want to handle this case because an inactive secret can be activated in the future, at which point it
|
||||
// becomes a security risk.
|
||||
if len(res.Header) > 0 && res.Header.Get("Content-Type") == "text/plain" {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
// The secret is considered verified but inactive only if the body suggests so. Since the body is not
|
||||
// readable, we cannot determine if the secret is verified but inactive.
|
||||
return false, nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(body)) == "API credential is not active." {
|
||||
return true, map[string]string{"Explanation": "Valid credentials in inactive state"}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// The secret is determinately not verified (nothing to do)
|
||||
return false, nil, nil
|
||||
default:
|
||||
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_RobinhoodCrypto
|
||||
}
|
274
pkg/detectors/robinhoodcrypto/robinhoodcrypto_test.go
Normal file
274
pkg/detectors/robinhoodcrypto/robinhoodcrypto_test.go
Normal file
|
@ -0,0 +1,274 @@
|
|||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package robinhoodcrypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
func TestRobinhoodCrypto_Pattern(t *testing.T) {
|
||||
d := Scanner{}
|
||||
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "typical pattern",
|
||||
input: `
|
||||
api_key = "rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc"
|
||||
private_key_base64 = "aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ=="
|
||||
`,
|
||||
want: []string{
|
||||
"rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc" +
|
||||
"aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ==",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(
|
||||
test.name, func(t *testing.T) {
|
||||
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
|
||||
if len(matchedDetectors) == 0 {
|
||||
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := d.FromData(context.Background(), false, []byte(test.input))
|
||||
if err != nil {
|
||||
t.Errorf("error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(results) != len(test.want) {
|
||||
if len(results) == 0 {
|
||||
t.Errorf("did not receive result")
|
||||
} else {
|
||||
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
actual := make(map[string]struct{}, len(results))
|
||||
for _, r := range results {
|
||||
if len(r.RawV2) > 0 {
|
||||
actual[string(r.RawV2)] = struct{}{}
|
||||
} else {
|
||||
actual[string(r.Raw)] = struct{}{}
|
||||
}
|
||||
}
|
||||
expected := make(map[string]struct{}, len(test.want))
|
||||
for _, v := range test.want {
|
||||
expected[v] = struct{}{}
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRobinhoodcrypto_FromChunk(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
// Valid and active credentials.
|
||||
apiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY")
|
||||
privateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY")
|
||||
|
||||
// Valid but inactive credentials.
|
||||
inactiveApiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY_INACTIVE")
|
||||
inactivePrivateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY_INACTIVE")
|
||||
|
||||
// Invalid credentials.
|
||||
deletedApiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY_DELETED")
|
||||
deletedPrivateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY_DELETED")
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
data []byte
|
||||
verify bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
s Scanner
|
||||
args args
|
||||
want []detectors.Result
|
||||
wantErr bool
|
||||
wantVerificationErr bool
|
||||
}{
|
||||
{
|
||||
name: "found, verified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf(
|
||||
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
|
||||
)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Verified: true,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, verified, but inactive",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf(
|
||||
"You can find a robinhoodcrypto api key %s and a private key %s within", inactiveApiKey,
|
||||
inactivePrivateKey,
|
||||
)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Verified: true,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, unverified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf(
|
||||
"You can find a robinhoodcrypto api key %s and a private key %s within", deletedApiKey,
|
||||
deletedPrivateKey,
|
||||
)), // the secret would satisfy the regex but not pass validation
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte("You cannot find the secret within"),
|
||||
verify: true,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, would be verified if not for timeout",
|
||||
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf(
|
||||
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
|
||||
)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: true,
|
||||
},
|
||||
{
|
||||
name: "found, verified but unexpected api surface",
|
||||
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf(
|
||||
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
|
||||
)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Robinhoodcrypto.FromData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
if len(got[i].Raw) == 0 {
|
||||
t.Fatalf("no raw secret present: \n %+v", got[i])
|
||||
}
|
||||
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
|
||||
t.Fatalf(
|
||||
"wantVerificationError = %v, verification error = %v", tt.wantVerificationErr,
|
||||
got[i].VerificationError(),
|
||||
)
|
||||
}
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "ExtraData", "Raw", "RawV2", "verificationError")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Robinhoodcrypto.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromData(benchmark *testing.B) {
|
||||
ctx := context.Background()
|
||||
s := Scanner{}
|
||||
for name, data := range detectors.MustGetBenchmarkData() {
|
||||
benchmark.Run(
|
||||
name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := s.FromData(ctx, false, data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||
s1.Verified = verified
|
||||
s1.ExtraData = extraData
|
||||
s1.SetVerificationError(verificationErr)
|
||||
s1.AnalysisInfo = map[string]string{"key": token}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
|
|
@ -126,7 +126,7 @@ func TestSendgrid_FromChunk(t *testing.T) {
|
|||
t.Errorf("Sendgrid.FromData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo", "ExtraData")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Sendgrid.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
|
|
|
@ -577,6 +577,7 @@ import (
|
|||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ringcentral"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ritekit"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roaring"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/robinhoodcrypto"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rocketreach"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rockset"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roninapp"
|
||||
|
@ -1626,6 +1627,7 @@ func DefaultDetectors() []detectors.Detector {
|
|||
atlassianv1.Scanner{},
|
||||
atlassianv2.Scanner{},
|
||||
netsuite.Scanner{},
|
||||
robinhoodcrypto.Scanner{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,15 @@ type Config struct {
|
|||
// that have been detected by multiple detectors.
|
||||
// By default, it is set to true.
|
||||
VerificationOverlap bool
|
||||
|
||||
// DetectorWorkerMultiplier is used to determine the number of detector workers to spawn.
|
||||
DetectorWorkerMultiplier int
|
||||
|
||||
// NotificationWorkerMultiplier is used to determine the number of notification workers to spawn.
|
||||
NotificationWorkerMultiplier int
|
||||
|
||||
// VerificationOverlapWorkerMultiplier is used to determine the number of verification overlap workers to spawn.
|
||||
VerificationOverlapWorkerMultiplier int
|
||||
}
|
||||
|
||||
// Engine represents the core scanning engine responsible for detecting secrets in input data.
|
||||
|
@ -195,24 +204,34 @@ type Engine struct {
|
|||
|
||||
// Note: bad hack only used for testing.
|
||||
verificationOverlapTracker *verificationOverlapTracker
|
||||
|
||||
// detectorWorkerMultiplier is used to calculate the number of detector workers.
|
||||
detectorWorkerMultiplier int
|
||||
// notificationWorkerMultiplier is used to calculate the number of notification workers.
|
||||
notificationWorkerMultiplier int
|
||||
// verificationOverlapWorkerMultiplier is used to calculate the number of verification overlap workers.
|
||||
verificationOverlapWorkerMultiplier int
|
||||
}
|
||||
|
||||
// NewEngine creates a new Engine instance with the provided configuration.
|
||||
func NewEngine(ctx context.Context, cfg *Config) (*Engine, error) {
|
||||
engine := &Engine{
|
||||
concurrency: cfg.Concurrency,
|
||||
decoders: cfg.Decoders,
|
||||
detectors: cfg.Detectors,
|
||||
dispatcher: cfg.Dispatcher,
|
||||
verify: cfg.Verify,
|
||||
filterUnverified: cfg.FilterUnverified,
|
||||
filterEntropy: cfg.FilterEntropy,
|
||||
printAvgDetectorTime: cfg.PrintAvgDetectorTime,
|
||||
retainFalsePositives: cfg.LogFilteredUnverified,
|
||||
verificationOverlap: cfg.VerificationOverlap,
|
||||
sourceManager: cfg.SourceManager,
|
||||
scanEntireChunk: cfg.ShouldScanEntireChunk,
|
||||
detectorVerificationOverrides: cfg.DetectorVerificationOverrides,
|
||||
concurrency: cfg.Concurrency,
|
||||
decoders: cfg.Decoders,
|
||||
detectors: cfg.Detectors,
|
||||
dispatcher: cfg.Dispatcher,
|
||||
verify: cfg.Verify,
|
||||
filterUnverified: cfg.FilterUnverified,
|
||||
filterEntropy: cfg.FilterEntropy,
|
||||
printAvgDetectorTime: cfg.PrintAvgDetectorTime,
|
||||
retainFalsePositives: cfg.LogFilteredUnverified,
|
||||
verificationOverlap: cfg.VerificationOverlap,
|
||||
sourceManager: cfg.SourceManager,
|
||||
scanEntireChunk: cfg.ShouldScanEntireChunk,
|
||||
detectorVerificationOverrides: cfg.DetectorVerificationOverrides,
|
||||
detectorWorkerMultiplier: cfg.DetectorWorkerMultiplier,
|
||||
notificationWorkerMultiplier: cfg.NotificationWorkerMultiplier,
|
||||
verificationOverlapWorkerMultiplier: cfg.VerificationOverlapWorkerMultiplier,
|
||||
}
|
||||
if engine.sourceManager == nil {
|
||||
return nil, fmt.Errorf("source manager is required")
|
||||
|
@ -303,7 +322,19 @@ func (e *Engine) setDefaults(ctx context.Context) {
|
|||
ctx.Logger().Info("No concurrency specified, defaulting to max", "cpu", numCPU)
|
||||
e.concurrency = numCPU
|
||||
}
|
||||
ctx.Logger().V(3).Info("engine started", "workers", e.concurrency)
|
||||
|
||||
if e.detectorWorkerMultiplier < 1 {
|
||||
// bound by net i/o so it's higher than other workers
|
||||
e.detectorWorkerMultiplier = 8
|
||||
}
|
||||
|
||||
if e.notificationWorkerMultiplier < 1 {
|
||||
e.notificationWorkerMultiplier = 1
|
||||
}
|
||||
|
||||
if e.verificationOverlapWorkerMultiplier < 1 {
|
||||
e.verificationOverlapWorkerMultiplier = 1
|
||||
}
|
||||
|
||||
// Default decoders handle common encoding formats.
|
||||
if len(e.decoders) == 0 {
|
||||
|
@ -457,6 +488,7 @@ func (e *Engine) initialize(ctx context.Context) error {
|
|||
// This reflects the anticipated lower volume of data that needs re-verification.
|
||||
// The buffer size is a trade-off between memory usage and the need to prevent blocking.
|
||||
verificationOverlapChunksChanMultiplier = 25
|
||||
resultsChanMultiplier = detectableChunksChanMultiplier
|
||||
)
|
||||
|
||||
// Channels are used for communication between different parts of the engine,
|
||||
|
@ -467,7 +499,7 @@ func (e *Engine) initialize(ctx context.Context) error {
|
|||
e.verificationOverlapChunksChan = make(
|
||||
chan verificationOverlapChunk, defaultChannelBuffer*verificationOverlapChunksChanMultiplier,
|
||||
)
|
||||
e.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer)
|
||||
e.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer*resultsChanMultiplier)
|
||||
e.dedupeCache = cache
|
||||
ctx.Logger().V(4).Info("engine initialized")
|
||||
|
||||
|
@ -624,9 +656,10 @@ func (e *Engine) startScannerWorkers(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (e *Engine) startDetectorWorkers(ctx context.Context) {
|
||||
const detectorWorkerMultiplier = 4
|
||||
ctx.Logger().V(2).Info("starting detector workers", "count", e.concurrency*detectorWorkerMultiplier)
|
||||
for worker := uint64(0); worker < uint64(e.concurrency*detectorWorkerMultiplier); worker++ {
|
||||
numWorkers := e.concurrency * e.detectorWorkerMultiplier
|
||||
|
||||
ctx.Logger().V(2).Info("starting detector workers", "count", numWorkers)
|
||||
for worker := 0; worker < numWorkers; worker++ {
|
||||
e.wgDetectorWorkers.Add(1)
|
||||
go func() {
|
||||
ctx := context.WithValue(ctx, "detector_worker_id", common.RandomID(5))
|
||||
|
@ -638,8 +671,10 @@ func (e *Engine) startDetectorWorkers(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {
|
||||
ctx.Logger().V(2).Info("starting verificationOverlap workers", "count", e.concurrency)
|
||||
for worker := uint64(0); worker < uint64(e.concurrency); worker++ {
|
||||
numWorkers := e.concurrency * e.verificationOverlapWorkerMultiplier
|
||||
|
||||
ctx.Logger().V(2).Info("starting verificationOverlap workers", "count", numWorkers)
|
||||
for worker := 0; worker < numWorkers; worker++ {
|
||||
e.verificationOverlapWg.Add(1)
|
||||
go func() {
|
||||
ctx := context.WithValue(ctx, "verification_overlap_worker_id", common.RandomID(5))
|
||||
|
@ -651,13 +686,10 @@ func (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (e *Engine) startNotifierWorkers(ctx context.Context) {
|
||||
const notifierWorkerRatio = 4
|
||||
maxNotifierWorkers := 1
|
||||
if numWorkers := e.concurrency / notifierWorkerRatio; numWorkers > 0 {
|
||||
maxNotifierWorkers = numWorkers
|
||||
}
|
||||
ctx.Logger().V(2).Info("starting notifier workers", "count", maxNotifierWorkers)
|
||||
for worker := 0; worker < maxNotifierWorkers; worker++ {
|
||||
numWorkers := e.notificationWorkerMultiplier * e.concurrency
|
||||
|
||||
ctx.Logger().V(2).Info("starting notifier workers", "count", numWorkers)
|
||||
for worker := 0; worker < numWorkers; worker++ {
|
||||
e.WgNotifier.Add(1)
|
||||
go func() {
|
||||
ctx := context.WithValue(ctx, "notifier_worker_id", common.RandomID(5))
|
||||
|
|
|
@ -26,17 +26,17 @@ func (e *Engine) ScanPostman(ctx context.Context, c sources.PostmanConfig) error
|
|||
CollectionPaths: c.CollectionPaths,
|
||||
EnvironmentPaths: c.EnvironmentPaths,
|
||||
}
|
||||
|
||||
// Check if postman data is going to be accessed via an api call using a token, or
|
||||
// if it has been already exported and exists locally
|
||||
if len(c.Token) > 0 {
|
||||
connection.Credential = &sourcespb.Postman_Token{
|
||||
Token: c.Token,
|
||||
}
|
||||
} else {
|
||||
} else if len(c.WorkspacePaths) > 0 || len(c.CollectionPaths) > 0 || len(c.EnvironmentPaths) > 0 {
|
||||
connection.Credential = &sourcespb.Postman_Unauthenticated{}
|
||||
}
|
||||
|
||||
if len(c.Workspaces) == 0 && len(c.Collections) == 0 && len(c.Environments) == 0 && len(c.Token) == 0 && len(c.WorkspacePaths) == 0 && len(c.CollectionPaths) == 0 && len(c.EnvironmentPaths) == 0 {
|
||||
ctx.Logger().Error(errors.New("no postman workspaces, collections, environments or API token provided"), "failed to scan postman")
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("no path to locally exported data or API token provided")
|
||||
}
|
||||
|
||||
// Turn AhoCorasick keywordsToDetectors into a map of keywords
|
||||
|
|
77
pkg/engine/postman_test.go
Normal file
77
pkg/engine/postman_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
)
|
||||
|
||||
func TestPostmanEngine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
postmanConfig sources.PostmanConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "scanned Postman with a token",
|
||||
postmanConfig: sources.PostmanConfig{
|
||||
Token: "dummy_key",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scanned Postman with workspacePath",
|
||||
postmanConfig: sources.PostmanConfig{
|
||||
WorkspacePaths: []string{"Downloads/Test API.postman_collection.json"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scanned Postman with environmentPath",
|
||||
postmanConfig: sources.PostmanConfig{
|
||||
EnvironmentPaths: []string{"Downloads/Mobile - Points Unlock Redeemables.postman_environment.json"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no token or file path provided",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
const defaultOutputBufferSize = 64
|
||||
opts := []func(*sources.SourceManager){
|
||||
sources.WithSourceUnits(),
|
||||
sources.WithBufferedOutput(defaultOutputBufferSize),
|
||||
}
|
||||
|
||||
sourceManager := sources.NewManager(opts...)
|
||||
|
||||
conf := Config{
|
||||
Concurrency: 1,
|
||||
Decoders: decoders.DefaultDecoders(),
|
||||
Detectors: DefaultDetectors(),
|
||||
Verify: false,
|
||||
SourceManager: sourceManager,
|
||||
Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
|
||||
}
|
||||
|
||||
e, err := NewEngine(ctx, &conf)
|
||||
assert.NoError(t, err)
|
||||
e.Start(ctx)
|
||||
err = e.ScanPostman(ctx, test.postmanConfig)
|
||||
if err != nil && !test.wantErr {
|
||||
t.Errorf("ScanPostman() got: %v, want: %v", err, nil)
|
||||
return
|
||||
}
|
||||
if err == nil && test.wantErr {
|
||||
t.Errorf("ScanPostman() got: %v, want: %v", err, "error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
8
pkg/feature/feature.go
Normal file
8
pkg/feature/feature.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package feature
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
var (
|
||||
ForceSkipBinaries = atomic.Bool{}
|
||||
ForceSkipArchives = atomic.Bool{}
|
||||
)
|
|
@ -9,6 +9,7 @@ import (
|
|||
"pault.ag/go/debian/deb"
|
||||
|
||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
)
|
||||
|
||||
// arHandler handles AR archive formats.
|
||||
|
@ -24,6 +25,11 @@ func newARHandler() *arHandler {
|
|||
func (h *arHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
|
||||
archiveChan := make(chan []byte, defaultBufferSize)
|
||||
|
||||
if feature.ForceSkipArchives.Load() {
|
||||
close(archiveChan)
|
||||
return archiveChan, nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
|
||||
defer cancel()
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
@ -50,6 +51,11 @@ func newArchiveHandler() *archiveHandler {
|
|||
func (h *archiveHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
|
||||
dataChan := make(chan []byte, defaultBufferSize)
|
||||
|
||||
if feature.ForceSkipArchives.Load() {
|
||||
close(dataChan)
|
||||
return dataChan, nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
|
||||
defer cancel()
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/sassoftware/go-rpmutils"
|
||||
|
||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
)
|
||||
|
||||
// rpmHandler specializes archiveHandler to manage RPM package files.
|
||||
|
@ -24,6 +25,11 @@ func newRPMHandler() *rpmHandler {
|
|||
func (h *rpmHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
|
||||
archiveChan := make(chan []byte, defaultBufferSize)
|
||||
|
||||
if feature.ForceSkipArchives.Load() {
|
||||
close(archiveChan)
|
||||
return archiveChan, nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
|
||||
defer cancel()
|
||||
|
|
|
@ -1097,6 +1097,7 @@ const (
|
|||
DetectorType_EndorLabs DetectorType = 993
|
||||
DetectorType_ElevenLabs DetectorType = 994
|
||||
DetectorType_Netsuite DetectorType = 995
|
||||
DetectorType_RobinhoodCrypto DetectorType = 996
|
||||
)
|
||||
|
||||
// Enum value maps for DetectorType.
|
||||
|
@ -2094,6 +2095,7 @@ var (
|
|||
993: "EndorLabs",
|
||||
994: "ElevenLabs",
|
||||
995: "Netsuite",
|
||||
996: "RobinhoodCrypto",
|
||||
}
|
||||
DetectorType_value = map[string]int32{
|
||||
"Alibaba": 0,
|
||||
|
@ -3088,6 +3090,7 @@ var (
|
|||
"EndorLabs": 993,
|
||||
"ElevenLabs": 994,
|
||||
"Netsuite": 995,
|
||||
"RobinhoodCrypto": 996,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -3541,7 +3544,7 @@ var file_detectors_proto_rawDesc = []byte{
|
|||
0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34,
|
||||
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45,
|
||||
0x10, 0x04, 0x2a, 0x98, 0x7f, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54,
|
||||
0x10, 0x04, 0x2a, 0xae, 0x7f, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57,
|
||||
0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12, 0x0a,
|
||||
|
@ -4558,12 +4561,13 @@ var file_detectors_proto_rawDesc = []byte{
|
|||
0x75, 0x69, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0xe0, 0x07, 0x12, 0x0e, 0x0a,
|
||||
0x09, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe1, 0x07, 0x12, 0x0f, 0x0a,
|
||||
0x0a, 0x45, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe2, 0x07, 0x12, 0x0d,
|
||||
0x0a, 0x08, 0x4e, 0x65, 0x74, 0x73, 0x75, 0x69, 0x74, 0x65, 0x10, 0xe3, 0x07, 0x42, 0x3d, 0x5a,
|
||||
0x3b, 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, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62,
|
||||
0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x0a, 0x08, 0x4e, 0x65, 0x74, 0x73, 0x75, 0x69, 0x74, 0x65, 0x10, 0xe3, 0x07, 0x12, 0x14, 0x0a,
|
||||
0x0f, 0x52, 0x6f, 0x62, 0x69, 0x6e, 0x68, 0x6f, 0x6f, 0x64, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f,
|
||||
0x10, 0xe4, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x76, 0x33, 0x2f,
|
||||
0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,
|
||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitparse"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
|
@ -1232,7 +1233,7 @@ func (s *Git) handleBinary(ctx context.Context, gitDir string, reporter sources.
|
|||
return nil
|
||||
}
|
||||
|
||||
if s.skipBinaries {
|
||||
if s.skipBinaries || feature.ForceSkipBinaries.Load() {
|
||||
fileCtx.Logger().V(5).Info("skipping binary file", "path", path)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/go-logr/logr"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||
"golang.org/x/exp/rand"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
@ -1392,35 +1393,34 @@ func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget,
|
|||
return fmt.Errorf("invalid GitHub URL")
|
||||
}
|
||||
|
||||
qry := commitQuery{
|
||||
repo: segments[2],
|
||||
owner: segments[1],
|
||||
sha: meta.GetCommit(),
|
||||
filename: meta.GetFile(),
|
||||
readCloser, resp, err := s.connector.APIClient().Repositories.DownloadContents(
|
||||
ctx,
|
||||
segments[1],
|
||||
segments[2],
|
||||
meta.GetFile(),
|
||||
&github.RepositoryContentGetOptions{Ref: meta.GetCommit()})
|
||||
// As of this writing, if the returned readCloser is not nil, it's just the Body of the returned github.Response, so
|
||||
// there's no need to independently close it.
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
res, err := s.getDiffForFileInCommit(ctx, qry)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not download file for scan: %w", err)
|
||||
}
|
||||
chunk := &sources.Chunk{
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected HTTP response status when trying to download file for scan: %v", resp.Status)
|
||||
}
|
||||
|
||||
reporter := sources.ChanReporter{Ch: chunksChan}
|
||||
chunkSkel := sources.Chunk{
|
||||
SourceType: s.Type(),
|
||||
SourceName: s.name,
|
||||
SourceID: s.SourceID(),
|
||||
JobID: s.JobID(),
|
||||
SecretID: target.SecretID,
|
||||
Data: []byte(stripLeadingPlusMinus(res)),
|
||||
SourceMetadata: &source_metadatapb.MetaData{
|
||||
Data: &source_metadatapb.MetaData_Github{Github: meta},
|
||||
},
|
||||
Verify: s.verify,
|
||||
}
|
||||
|
||||
return common.CancellableWrite(ctx, chunksChan, chunk)
|
||||
}
|
||||
|
||||
// stripLeadingPlusMinus removes leading + and - characters from lines in a diff string. These characters exist in the
|
||||
// diffs returned when performing a targeted scan and need to be removed so that detectors are operating on the correct
|
||||
// text.
|
||||
func stripLeadingPlusMinus(diff string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(diff, "\n+", "\n"), "\n-", "\n")
|
||||
Verify: s.verify}
|
||||
return handlers.HandleFile(ctx, readCloser, &chunkSkel, reporter)
|
||||
}
|
||||
|
|
|
@ -758,6 +758,24 @@ func TestSource_Chunks_TargetedScan(t *testing.T) {
|
|||
},
|
||||
wantChunks: 1,
|
||||
},
|
||||
{
|
||||
name: "targeted scan, binary file",
|
||||
init: init{
|
||||
name: "test source",
|
||||
connection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}},
|
||||
queryCriteria: &source_metadatapb.MetaData{
|
||||
Data: &source_metadatapb.MetaData_Github{
|
||||
Github: &source_metadatapb.Github{
|
||||
Repository: "https://github.com/truffle-sandbox/test-secrets.git",
|
||||
Link: "https://github.com/truffle-sandbox/test-secrets/blob/70bef8590f87257c0992eecc7db529827a12b801/null_text_w_ptp.ipynb",
|
||||
Commit: "70bef8590f87257c0992eecc7db529827a12b801",
|
||||
File: "null_text_w_ptp.ipynb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantChunks: 607,
|
||||
},
|
||||
{
|
||||
name: "no file in commit",
|
||||
init: init{
|
||||
|
|
|
@ -281,60 +281,6 @@ func (s *Source) wikiIsReachable(ctx context.Context, repoURL string) bool {
|
|||
return wikiURL == res.Request.URL.String()
|
||||
}
|
||||
|
||||
// commitQuery represents the details required to fetch a commit.
|
||||
type commitQuery struct {
|
||||
repo string
|
||||
owner string
|
||||
sha string
|
||||
filename string
|
||||
}
|
||||
|
||||
// getDiffForFileInCommit retrieves the diff for a specified file in a commit.
|
||||
// If the file or its diff is not found, it returns an error.
|
||||
func (s *Source) getDiffForFileInCommit(ctx context.Context, query commitQuery) (string, error) {
|
||||
var (
|
||||
commit *github.RepositoryCommit
|
||||
err error
|
||||
)
|
||||
for {
|
||||
commit, _, err = s.connector.APIClient().Repositories.GetCommit(ctx, query.owner, query.repo, query.sha, nil)
|
||||
if s.handleRateLimit(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error fetching commit %s: %w", query.sha, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if len(commit.Files) == 0 {
|
||||
return "", fmt.Errorf("commit %s does not contain any files", query.sha)
|
||||
}
|
||||
|
||||
res := new(strings.Builder)
|
||||
// Only return the diff if the file is in the commit.
|
||||
for _, file := range commit.Files {
|
||||
if *file.Filename != query.filename {
|
||||
continue
|
||||
}
|
||||
|
||||
if file.Patch == nil {
|
||||
return "", fmt.Errorf("commit %s file %s does not have a diff", query.sha, query.filename)
|
||||
}
|
||||
|
||||
if _, err := res.WriteString(*file.Patch); err != nil {
|
||||
return "", fmt.Errorf("buffer write error for commit %s file %s: %w", query.sha, query.filename, err)
|
||||
}
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
if res.Len() == 0 {
|
||||
return "", fmt.Errorf("commit %s does not contain patch for file %s", query.sha, query.filename)
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func (s *Source) normalizeRepo(repo string) (string, error) {
|
||||
// If there's a '/', assume it's a URL and try to normalize it.
|
||||
if strings.ContainsRune(repo, '/') {
|
||||
|
|
|
@ -65,6 +65,7 @@ func New(c common.Common) *SourceSelect {
|
|||
OssItem("Git", "Scan git repositories."),
|
||||
OssItem("GitHub", "Scan GitHub repositories and/or organizations."),
|
||||
OssItem("Filesystem", "Scan your filesystem by selecting what directories to scan."),
|
||||
OssItem("Hugging Face", "Scan Hugging Face, an AI/ML community."),
|
||||
OssItem("Jenkins", "Scan Jenkins, a CI/CD platform. (Recently open-sourced from enterprise!)"),
|
||||
OssItem("Elasticsearch", "Scan your Elasticsearch cluster or Elastic Cloud instance."),
|
||||
OssItem("Postman", "Scan a collection, workspace, or environment from Postman, the API platform."),
|
||||
|
|
77
pkg/tui/sources/huggingface/huggingface.go
Normal file
77
pkg/tui/sources/huggingface/huggingface.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package huggingface
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type huggingFaceCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetNote() string {
|
||||
return "Please enter the organization, user, model, space, or dataset you would like to scan."
|
||||
}
|
||||
|
||||
func GetFields() huggingFaceCmdModel {
|
||||
org := textinputs.InputConfig{
|
||||
Label: "Organization",
|
||||
Key: "org",
|
||||
Required: false,
|
||||
Help: "Hugging Face organization name. This will scan all models, datasets, and spaces belonging to the organization.",
|
||||
}
|
||||
user := textinputs.InputConfig{
|
||||
Label: "Username",
|
||||
Key: "user",
|
||||
Required: false,
|
||||
Help: "Hugging Face user. This will scan all models, datasets, and spaces belonging to the user.",
|
||||
}
|
||||
model := textinputs.InputConfig{
|
||||
Label: "Model",
|
||||
Key: "model",
|
||||
Required: false,
|
||||
Help: "Hugging Face model. Example: org/model_name or user/model_name",
|
||||
}
|
||||
space := textinputs.InputConfig{
|
||||
Label: "Space",
|
||||
Key: "space",
|
||||
Required: false,
|
||||
Help: "Hugging Face space. Example: org/space_name or user/space_name.",
|
||||
}
|
||||
dataset := textinputs.InputConfig{
|
||||
Label: "Dataset",
|
||||
Key: "dataset",
|
||||
Required: false,
|
||||
Help: "Hugging Face dataset. Example: org/dataset_name or user/dataset_name.",
|
||||
}
|
||||
|
||||
return huggingFaceCmdModel{textinputs.New([]textinputs.InputConfig{org, user, model, space, dataset})}
|
||||
}
|
||||
|
||||
func (m huggingFaceCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "huggingface")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
keys := []string{"org", "user", "model", "space", "dataset"}
|
||||
|
||||
for _, key := range keys {
|
||||
val, ok := inputs[key]
|
||||
if !ok || val.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
command = append(command, "--"+key+"="+val.Value)
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m huggingFaceCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
keys := []string{"org", "user", "model", "space", "dataset"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/git"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/github"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/gitlab"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/huggingface"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/jenkins"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/postman"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/s3"
|
||||
|
@ -27,6 +28,8 @@ func GetSourceNotes(sourceName string) string {
|
|||
return postman.GetNote()
|
||||
case "elasticsearch":
|
||||
return elasticsearch.GetNote()
|
||||
case "huggingface":
|
||||
return huggingface.GetNote()
|
||||
case "jenkins":
|
||||
return jenkins.GetNote()
|
||||
|
||||
|
@ -63,6 +66,8 @@ func GetSourceFields(sourceName string) CmdModel {
|
|||
return github.GetFields()
|
||||
case "gitlab":
|
||||
return gitlab.GetFields()
|
||||
case "hugging face":
|
||||
return huggingface.GetFields()
|
||||
case "jenkins":
|
||||
return jenkins.GetFields()
|
||||
case "postman":
|
||||
|
|
|
@ -1005,6 +1005,7 @@ enum DetectorType {
|
|||
EndorLabs = 993;
|
||||
ElevenLabs = 994;
|
||||
Netsuite = 995;
|
||||
RobinhoodCrypto = 996;
|
||||
}
|
||||
|
||||
message Result {
|
||||
|
|
|
@ -331,6 +331,18 @@ message Elasticsearch {
|
|||
string timestamp = 3;
|
||||
}
|
||||
|
||||
message Sentry {
|
||||
string event_id = 1;
|
||||
string organization_id = 2;
|
||||
string organization_slug = 3;
|
||||
string organization_date_created = 4;
|
||||
string project_id = 5;
|
||||
string project_slug = 6;
|
||||
string issue_id = 7;
|
||||
string date_created = 8;
|
||||
string link = 9;
|
||||
}
|
||||
|
||||
message MetaData {
|
||||
oneof data {
|
||||
Azure azure = 1;
|
||||
|
@ -365,5 +377,6 @@ message MetaData {
|
|||
Webhook webhook = 30;
|
||||
Elasticsearch elasticsearch = 31;
|
||||
Huggingface huggingface = 32;
|
||||
Sentry sentry = 33;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ enum SourceType {
|
|||
SOURCE_TYPE_ELASTICSEARCH = 35;
|
||||
SOURCE_TYPE_HUGGINGFACE = 36;
|
||||
SOURCE_TYPE_GITHUB_EXPERIMENTAL = 37;
|
||||
SOURCE_TYPE_SENTRY = 38;
|
||||
}
|
||||
|
||||
message LocalSource {
|
||||
|
@ -458,3 +459,14 @@ message Elasticsearch {
|
|||
string since_timestamp = 9;
|
||||
bool best_effort_scan = 10;
|
||||
}
|
||||
|
||||
message Sentry {
|
||||
string endpoint = 1;
|
||||
oneof credential {
|
||||
string auth_token = 2;
|
||||
string dsn_key = 3;
|
||||
string api_key = 4;
|
||||
}
|
||||
bool insecure_skip_verify_tls = 5;
|
||||
string projects = 6;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue