Compare commits

..
106 Commits
Author SHA1 Message Date
sethsec-bf 00e63b0e18 found a way to fix the apigateway types conflict with gob 2024-03-15 16:05:43 -04:00
sethsec-bf eaac503bff bumped version to 1.13.4 for release with apigateway fix 2024-03-15 11:35:29 -04:00
sethsec-bf cec0d49602 Fixing a bug in the new cached versions of the apigateway sdk calls 2024-03-15 11:28:35 -04:00
sethsec-bf 6a21b9665d Auth0 not ready yet - also this new version lists unknown federated types not instead of ignoring them 2024-03-14 14:49:04 -04:00
sethsec-bf a1903f67f7 Major update for role trusts parsing. Cleaner version has case statement on federated principal value and not soley on condition data 2024-03-14 14:47:17 -04:00
sethsec-bf 8d039323bf Fix for #77 2024-03-08 09:01:59 -05:00
sethsec-bf fd01647a8a Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2024-02-28 09:32:25 -05:00
Seth Art f415f991c8 Merge pull request #74 from enzowritescode/feature/aws-neptune
Amazon Neptune Support
2024-02-27 12:24:16 -05:00
Seth Art 15e0507ad6 Merge branch 'main' into feature/aws-neptune 2024-02-27 10:13:10 -05:00
sethsec-bf 80c00f3d97 Added test for databases command 2024-02-27 10:10:56 -05:00
sethsec-bf 59afbb381e Switched RDS database command from instances to clusters, and since it grabs Neptune and DocsDB clusters, we don't need to run those api calls. (they all return the same data). Also added back port info and added role info to the RDS clusters in -o wide mode 2024-02-23 13:14:09 -05:00
enzowritescode ca437d3626 Filter Neptune results 2024-02-19 13:05:05 -07:00
sethsec-bf 1ca5dbfb10 Fix bug in role-trusts command around new vendor lookup feature, enabled caching on apigateway commands 2024-02-08 16:49:50 -05:00
Seth Art f00044670b Merge pull request #75 from BishopFox/seth-dev
Version 1.13.2 - 2 bug fixes on new functionality in 1.13.1
2024-02-02 13:48:47 -05:00
sethsec-bf ae1dac6c21 Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2024-02-02 13:46:29 -05:00
sethsec-bf 237b0735a8 Bumped version to 1.13.2 2024-02-02 13:45:41 -05:00
sethsec-bf 18e38bf1ea Fixed bug in env-vars command introduced in 1.13.1 with the new interesting version of the table written to disk. was still recording them all. now the second table only has interesting env-vars 2024-02-02 13:42:37 -05:00
sethsec-bf b5908fc720 Fixed bug in the role trusts command introduced in 1.13.1 where cloudfox only shows princiapls with :root trust and not ALL role trusts 2024-02-02 13:29:33 -05:00
enzowritescode 29514a96b5 Merge in latest and fix merge conflicts 2024-01-25 19:33:19 -07:00
Seth Art b2c450ee02 Merge pull request #73 from BishopFox/seth-dev
Release PR for 1.13.1
2024-01-25 20:31:07 -05:00
sethsec-bf a65610390b Bumped to version 1.31.1 before PR 2024-01-25 20:26:49 -05:00
sethsec-bf 997c13d4de Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2024-01-25 20:25:45 -05:00
sethsec-bf 1d1d198a01 More fixes from #72. Took the suggestion from @johnkeates and fixed the prepopulated nmap commands to NOT include a profile for the cases where the user did not specify a profile for cloudfox 2024-01-25 20:23:03 -05:00
sethsec-bf 6069267758 Fix for sub issue found by @cyberbutler in #72 2024-01-25 16:42:37 -05:00
sethsec-bf e08967bb31 This potential fix for #72 makes a lot more sense. Rather than overrwite the profile attribute of the struct, i have an AWSProfileProvided and a AWSProfileFake so that I can just pass the orig to other modules (since each module cleans it up itself). 2024-01-25 15:47:38 -05:00
Seth Art 8e99347789 Update README.md 2024-01-23 21:50:00 -05:00
Seth Art ff810ba17d Update README.md 2024-01-23 09:50:26 -05:00
Seth Art 23fc346e4f Update README.md 2024-01-22 21:20:51 -05:00
sethsec-bf 89789a05c1 updated pmapper command info 2024-01-22 21:16:25 -05:00
sethsec-bf 5827abb2a2 Hopefully this is a fix for #72 2024-01-22 20:47:11 -05:00
sethsec-bf 13a03c5de2 Fixed panic bug that occured when user specified a profile that did not exist 2024-01-22 20:43:36 -05:00
sethsec-bf dd6bfefa9d Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2024-01-22 15:44:59 -05:00
enzowritescode 8c4bcc035b Add .idea to gitignore for GoLand 2024-01-20 19:55:15 -07:00
enzowritescode a38451c1cd Typo fixes, reduced copy pasta, Neptune support 2024-01-20 19:35:13 -07:00
Seth Art 5df1ca6d75 Update README.md 2024-01-06 10:23:22 -05:00
sethsec-bf ea2ee3dd2c Fixed bug where instances without instance profile were not showing up 2024-01-02 21:22:55 -05:00
sethsec-bf 97e2fef388 Add data from fwd:cloudsec's known_aws_accounts repo into role-trust module 2024-01-02 21:22:27 -05:00
sethsec-bf 1d53b984ac Used the output2 loot mechanism for api-gws, updated tests 2023-12-28 12:11:07 -05:00
sethsec-bf 525f262460 Added mocks for apigw and apigwv2, and a test for the new api-gw command 2023-12-27 14:54:03 -05:00
sethsec-bf f13df0708c Added mocks for apigw and apigwv2, and a test for the new api-gw command 2023-12-27 14:53:38 -05:00
Seth Art 7520999386 Merge pull request #67 from BishopFox/seth-dev
v1.13.0
2023-12-26 13:04:20 -05:00
sethsec-bf f1b6eae9f1 codespell was tripping up on a has from go.sum 2023-12-26 13:00:34 -05:00
sethsec-bf 06f8e36be2 bumped version to 1.13.0 2023-12-26 12:03:03 -05:00
sethsec-bf cba252d151 Normalize/clean up console messages. Also re-order the way the messages print in multi-profile mode 2023-12-26 12:02:26 -05:00
sethsec-bf 5c4d0fda89 go get -u and fixes 2023-12-26 10:54:39 -05:00
sethsec-bf d648e5b347 Added ability to supply just a princpal name, and the command will try it as a role and a user 2023-12-26 10:34:55 -05:00
sethsec-bf d1af1bbf8f Fixed a bug where permissions custom runs were overwriting the files for the default run. 2023-12-26 10:34:15 -05:00
sethsec-bf 2b19d0ac62 For 1.13, switching default AWS verbosity to 2 (with the exception of all-checks which will be hardocded to a versbosity of 1) 2023-12-26 10:20:04 -05:00
sethsec-bf cef6c9e656 Fixed a bug where iam-simulator custom commands were overwriting the files for the default run. 2023-12-26 10:09:29 -05:00
sethsec-bf 6e146a567a add mfatoken param from recent update to api-gw code 2023-12-26 09:40:25 -05:00
sethsec-bf ae7ed9e19a Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2023-12-24 10:16:36 -05:00
sethsec-bf a690ad3185 bumped version to 1.13.0-prerelease 2023-12-24 10:15:34 -05:00
Seth Art 26cb99778c Merge pull request #64 from wdahlenburg/api-gw
API Gateway Module
2023-12-24 10:13:43 -05:00
sethsec-bf 98af83933e Added intersting functionality to env-vars and workloads 2023-12-23 12:31:51 -05:00
sethsec-bf 82738dbf63 Fixed a bug where when pmapper data was missing iam simulator was not getting IAM roles properly. Also got workloads command working 2023-12-22 10:09:37 -05:00
sethsec-bf 96ab676f49 Feature: First draft of new workloads command - summarizes all workloads 2023-12-21 21:25:05 -05:00
sethsec-bf 83866f46cc Feature: Added a new output file for role trusts that just highlights the overly permissive root trusts without external ID 2023-12-21 21:24:01 -05:00
sethsec-bf 7343852a66 Updated endpoints to use the newly created sdk functions 2023-12-21 11:22:10 -05:00
sethsec-bf b1c248bbe6 Used SDK, added more counters
Moved all api calls to the sdk package.
Added counter to getEndpointsPerAPIGatewayv2 and
getEndpointsPerAPIGateway because in regions with lots of apigateways
this took a long time and it just hung on 66/68 tasks and i worried. The
additional counters will help give visabilty that things are still
moving along.
Added an additional alias api-gws
2023-12-21 10:18:00 -05:00
Seth Art 75a446764d Merge pull request #66 from BishopFox/seth-dev
Seth dev
2023-12-19 16:24:13 -05:00
sethsec-bf 9be3792c97 bumped version to 1.12.3 2023-12-13 10:07:23 -05:00
sethsec-bf 27a511073e for #65, decided to add a prompt to ask for MFA token if the user does not specify it at runtime 2023-12-13 10:05:11 -05:00
sethsec-bf a9a0c7ae30 more updates for #65. Added a cli flag --mfa-token so you can pass the token at the cloudfox command line. Ended up reworking AWSConfigFileLoader to use a map to store fetched configs which should have other performance improvemnts 2023-12-13 09:50:30 -05:00
sethsec-bf 2ed079686d fixed bug where glue resource policy data was not caching properly. 2023-12-13 09:48:15 -05:00
sethsec-bf 5e1ed54a28 fixed bug in cache loading that would error out if even a single cache file was not accounted for 2023-12-11 18:26:48 -05:00
sethsec-bf ff38b2cf65 fix for #65 -- adds config.WithAssumeRoleCredentialOptions section to the LoadDefaultConfig loader 2023-12-06 15:58:04 -05:00
sethsec-bf 1819b11c5f Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2023-12-06 15:53:27 -05:00
Wyatt Dahlenburg 2ce1f0f82f Codespell 2023-11-16 15:35:13 -07:00
Wyatt Dahlenburg 5eff1c71ac Merging with main + minor updates 2023-11-16 15:05:54 -07:00
Seth Art f51c1e9f49 Update README.md 2023-11-11 15:41:00 -05:00
sethsec-bf 8f5a571ef0 fixed issue where role trusts was not sorting services correctly anymore 2023-11-10 12:53:35 -05:00
Seth Art 7b95a05215 Merge pull request #61 from BishopFox/seth-dev
ECR Fix and more
2023-11-02 13:50:48 -04:00
sethsec-bf e0f0627de8 new idea for resource trusts field 2023-11-02 10:39:30 -04:00
sethsec-bf d505bc4e3e Updating the tests/mocks for ECR 2023-11-02 09:46:59 -04:00
sethsec-bf 6a6f7586b3 Fixed a bad bug in ECR that was only looking at the fist repo 2023-11-01 20:28:36 -04:00
sethsec-bf 06344b9421 added secrets manager to resourcetrusts and cloud9 to inventory 2023-11-01 08:53:22 -04:00
sethsec-bf 54f784903a added accountID col to every table and removed the service col in all commands that are single service. 2023-10-26 12:39:25 -04:00
Seth Art 09f33f0908 Merge pull request #59 from BishopFox/seth-dev
Customizable Tables
2023-10-25 08:53:30 -04:00
sethsec-bf 2dc20eaa52 fixed codespell items 2023-10-25 08:50:43 -04:00
sethsec-bf 7656267367 fixes #36. -o wide will give you the arn, and the default (-o brief) will only give you the name for brevity 2023-10-25 08:41:04 -04:00
sethsec-bf b43d6ad074 go get -u and fixes 2023-10-25 08:19:44 -04:00
sethsec-bf 1215a27e19 completed --cols & -o wide support for ALL aws commands 2023-10-24 20:15:06 -04:00
sethsec-bf 7d3a61afe2 fixed caching issues in sdk/ecs 2023-10-24 13:06:08 -04:00
sethsec-bf 909590860d added user controled columns to cloudformation, and access keys 2023-10-23 16:28:22 -04:00
sethsec-bf 4ee0da839d added user controled columns to buckets, role-trusts, permissions 2023-10-23 16:00:48 -04:00
sethsec-bf 57d9e911f7 removed newline from buckets policy analyis 2023-10-20 12:30:39 -04:00
sethsec-bf dad2277942 moved policy analysis behind a feature flag in the buckets command 2023-10-19 10:04:53 -04:00
sethsec-bf 50493e6624 fixed bug in #53 - buckets with no policy were not creating policy cache stubs so each bucket without a policy would always look for a new policy 2023-10-11 12:12:22 -04:00
sethsec-bf 818c3a88f4 fixed cache load issue, updated aws instances command to use cached data 2023-09-22 15:16:38 -04:00
sethsec-bf bcfa318b31 cache load was loading json files along with the gob files and erroring. skipping the json files. also, updated ioutil library to os library 2023-09-20 13:22:22 -04:00
Seth Art f9567c7fc2 Merge pull request #56 from BishopFox/sethsec-bf-patch-1
Update README.md
2023-09-19 11:49:38 -04:00
Seth Art 187299ae82 Update README.md 2023-09-15 13:34:45 -04:00
Seth Art 8d145b135e Update README.md with more install methods 2023-09-08 08:48:25 -04:00
Seth Art 729c59e198 Merge pull request #52 from cclauss/codespell-github-action
GitHub Action to run codespell
2023-09-07 09:44:54 -04:00
Christian Clauss 6d1b47128e Update codespell.yml 2023-09-07 15:39:47 +02:00
Christian Clauss 217ac7a018 GitHub Action to run codespell 2023-09-07 15:38:03 +02:00
Seth Art 4b0238c362 Merge pull request #51 from cclauss/codespell
Fix typos discovered by codespell
2023-09-07 09:26:01 -04:00
Christian Clauss f78f1490d2 Fix typos discovered by codespell 2023-09-07 15:17:08 +02:00
Seth Art 0159638ab7 Merge pull request #50 from cclauss/patch-1
Upgrade GitHub Actions
2023-09-07 09:02:35 -04:00
Christian Clauss 1ff35a72ad Upgrade GitHub Actions
* https://github.com/actions/checkout/releases
* https://github.com/actions/setup-go/releases
2023-09-07 11:24:40 +02:00
Seth Art 29bfd2c863 Merge pull request #49 from BishopFox/seth-dev
bumped version to fix another local filesystem write
2023-09-06 14:05:38 -04:00
sethsec-bf 40e0c92ef1 another fix to pass brew tests 2023-09-06 14:04:50 -04:00
sethsec-bf 9f8ae08262 another fix to pass brew tests 2023-09-06 14:03:52 -04:00
Seth Art c85aa5de89 Merge pull request #48 from BishopFox/seth-dev
v1.12.1
2023-09-06 11:31:45 -04:00
sethsec-bf 42d7cfef1e hacky fix to have error log written to local dir if user dir does not exist (so it can pass brew test) 2023-09-05 18:04:25 -04:00
Wyatt Dahlenburg 17011f345c Additional enumeration on API gateways compared to the endpoints module 2023-08-14 21:33:27 -06:00
97 changed files with 6843 additions and 2826 deletions

No files matched your search

+3 -3
View File
@@ -15,8 +15,8 @@ jobs:
steps:
- name: Go 1.20
uses: actions/setup-go@v2
- name: Go 1.21
uses: actions/setup-go@v4
with:
go-version: ^1.20
id: go
@@ -28,7 +28,7 @@ jobs:
gpg --list-secret-keys --keyid-format LONG
- name: Check Out Code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
+16
View File
@@ -0,0 +1,16 @@
# This Action uses minimal steps to run in ~5 seconds to rapidly:
# Looks for typos in the codebase using codespell
# https://github.com/codespell-project/codespell#readme
name: codespell
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install --user codespell
- run: codespell --ignore-words-list="aks" --skip="*.sum"
+3 -1
View File
@@ -15,6 +15,7 @@
# Dependency directories (remove the comment below to include it)
# vendor/
.vscode
.idea
launch.json
######################################################
@@ -69,4 +70,5 @@ cloudfox
*.txt
*.json
*.csv
*.log
*.log
dist/
+38 -9
View File
@@ -14,8 +14,17 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
## Demos, Examples, Walkthroughs
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
* [Video - CloudFox + CloudFoxable A Powerful Duo for Mastering the Art of Identifying and Exploiting AWS Attack Paths](https://www.youtube.com/watch?v=RdQiIvCrSzk)
* [Video - Penetration Testing with CloudFox](https://www.youtube.com/watch?v=Ljt_JUp5HbM)
* [Video - CloudFox Intro Demos](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
## Intentionally Vulnerable Playground
* [CloudFoxable - A Gamified Cloud Hacking Sandbox](https://cloudfoxable.bishopfox.com/)
## Want to chat about CloudFox?
Join us on the [RedSec discord server](https://discord.gg/redsec)
## Quick Start
CloudFox is modular (you can run one command at a time), but there is an `aws all-checks` command that will run the other aws commands for you with sane defaults:
@@ -39,7 +48,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
| Provider| CloudFox Commands |
| - | - |
| AWS | 30 |
| AWS | 33 |
| Azure | 4 |
| GCP | Support Planned |
| Kubernetes | Support Planned |
@@ -49,22 +58,36 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
**Option 1:** Download the [latest binary release](https://github.com/BishopFox/cloudfox/releases) for your platform.
**Option 2:** [Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
**Option 2:** If you use homebrew: `brew install cloudfox`
**Option 3:** [Install Go](https://golang.org/doc/install), use `go install github.com/BishopFox/cloudfox@latest` to install from the remote source
**Option 4:** Developer mode:
[Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
```
# git clone https://github.com/BishopFox/cloudfox.git
...omitted for brevity...
# cd ./cloudfox
# Make any changes necessary
# go build .
# ./cloudfox
```
**Option 5:** Testing a bug fix
```
git clone [email protected]:BishopFox/cloudfox.git
git checkout seth-dev
go build .
./cloudfox [rest of the command options]
```
# Prerequisites
### AWS
* AWS CLI installed
* Supports AWS profiles, AWS environment variables, or metadata retrieval (on an ec2 instance)
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names seperated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names separated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
* A principal with one recommended policies attached (described below)
* Recommended attached policies: **`SecurityAudit` + [CloudFox custom policy](./misc/aws/cloudfox-policy.json)**
@@ -86,8 +109,11 @@ Additional policy notes (as of 09/2022):
| - | - | - |
| AWS | [all-checks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#all-checks) | Run all of the other commands using reasonable defaults. You'll still want to check out the non-default options of each command, but this is a great place to start. |
| AWS | [access-keys](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#access-keys) | Lists active access keys for all users. Useful for cross referencing a key you found with which in-scope account it belongs to. |
| AWS | [api-gw](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#api-gw) | Lists API gateway endpoints and gives you custom curl commands including API tokens if they are stored in metadata. |
| AWS | [buckets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#filesystems) | Lists the buckets in the account and gives you handy commands for inspecting them further. |
| AWS | [cloudformation](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#cloudformation) | Lists the cloudformation stacks in the account. Generates loot file with stack details, stack parameters, and stack output - look for secrets. |
| AWS | [codebuild](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#codebuild) | Enumerate CodeBuild projects |
| AWS | [databases](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#databases) | Enumerate RDS databases. Get a loot file with connection strings. |
| AWS | [ecr](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ecr) | List the most recently pushed image URI from all repositories. Use the loot file to pull selected images down with docker/nerdctl for inspection. |
| AWS | [ecs-tasks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ecs-tasks) | List all ecs tasks. This returns a list of ecs tasks and associated cluster, task definition, container instance, launch type, and associated IAM principal. |
| AWS | [eks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#eks) | List all EKS clusters, see if they expose their endpoint publicly, and check the associated IAM roles attached to reach cluster or node group. Generates a loot file with the `aws eks udpate-kubeconfig` command needed to connect to each cluster. |
@@ -100,27 +126,30 @@ Additional policy notes (as of 09/2022):
| AWS | [inventory](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#inventory) | Gain a rough understanding of size of the account and preferred regions. |
| AWS | [lambda](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#lambda) | Lists the lambda functions in the account, including which one's have admin roles attached. Also gives you handy commands for downloading each function. |
| AWS | [network-ports](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#network-ports) | Enumerates AWS services that are potentially exposing a network service. The security groups and the network ACLs are parsed for each resource to determine what ports are potentially exposed. |
| AWS | [outbound-assumed-roles](#outbound-assumed-roles) | List the roles that have been assumed by principals in this account. This is an excellent way to find outbound attack paths that lead into other accounts. |
| AWS | [orgs](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#orgs) | Enumerate accounts in an organization |
| AWS | [outbound-assumed-roles](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#outbound-assumed-roles) | List the roles that have been assumed by principals in this account. This is an excellent way to find outbound attack paths that lead into other accounts. |
| AWS | [permissions](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#permissions) | Enumerates IAM permissions associated with all users and roles. Grep this output to figure out what permissions a particular principal has rather than logging into the AWS console and painstakingly expanding each policy attached to the principal you are investigating. |
| AWS | [principals](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#principals) | Enumerates IAM users and Roles so you have the data at your fingertips. |
| AWS | [pmapper](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#pmapper) | Looks for pmapper data stored on the local filesystem, [in the locations defined here](https://github.com/nccgroup/PMapper/wiki/Frequently-Asked-Questions#where-does-pmapper-store-its-data). If pmapper data has been found (you already ran `pmapper graph create`), then this command will use this data to build a graph in cloudfox memory let you know who can privesc to admin.
| AWS | [principals](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#principals) | Enumerates IAM users and Roles so you have the data at your fingertips. |
| AWS | [ram](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ram) | List all resources in this account that are shared with other accounts, or resources from other accounts that are shared with this account. Useful for cross-account attack paths. |
| AWS | [resource-trusts](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#resource-trusts) | Looks through multiple services that support resource policies and helps you find any overly permissive resource trusts.|
| AWS | [role-trusts](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#role-trusts) | Enumerates IAM role trust policies so you can look for overly permissive role trusts or find roles that trust a specific service. |
| AWS | [route53](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#route53) | Enumerate all records from all route53 managed zones. Use this for application and service enumeration. |
| AWS | [secrets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#secrets) | List secrets from SecretsManager and SSM. Look for interesting secrets in the list and then see who has access to them using use `cloudfox iam-simulator` and/or `pmapper`. |
| AWS | [sns](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#sns) | This command enumerates all of the sns topics and gives you the commands to subscribe to a topic or send messages to a topic (if you have the permissions needed). This command only deals with topics, and not the SMS functionality. This command also attempts to summarize topic resource policies if they exist.|
| AWS | [sqs](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#sqs) | This command enumerates all of the sqs queues and gives you the commands to receive messages from a queue and send messages to a queue (if you have the permissions needed). This command also attempts to summarize queue resource policies if they exist.|
| AWS | [tags](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#tags) | List all resources with tags, and all of the tags. This can be used similar to inventory as another method to identify what types of resources exist in an account. |
| AWS | [workloads](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#workloads) | List all of the compute workloads and what role they have. Tells you if any of the roles are admin (bad) and if you have pmapper data locally, it will tell you if any of the roles can privesc to admin (also bad) |
# Azure Commands
| Provider | Command Name | Description
| - | - | - |
| Azure | [whoami](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#whoami) | Displays information on the tenant, subscriptions and resource groups available to your current Azure CLI session. This is useful to provide situation awareness on what tenant and subscription IDs to use with the other sub commands. |
| Azure | [instances](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#instances) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
| Azure | [inventory](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#inventory) | Display an inventory table of all resources per location. |
| Azure | [rbac](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac) | Lists Azure RBAC role assignments at subscription or tenant level |
| Azure | [storage](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#storage) | The storage command is still under development. Currently it only displays limited data about the storage accounts |
| Azure | [vms](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#vms) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
# Authors
* [Carlos Vendramini](https://github.com/carlosvendramini-bf)
@@ -138,7 +167,7 @@ CloudFox doesn't create any alerts or findings, and doesn't check your environme
**Why do I see errors in some CloudFox commands?**
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't suppor the service will return errors.
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't support the service will return errors.
* You don't have permission - Another reason you might see errors if you don't have permissions to make calls that CloudFox is making. Either because the policy doesn't allow it (e.g., SecurityAudit doesn't allow all of the permissions CloudFox needs. Or, it might be an SCP that is blocking you.
You can always look in the ~/.cloudfox/cloudfox-error.log file to get more information on errors.
+37 -9
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
@@ -18,9 +19,10 @@ type AccessKeysModule struct {
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSProfile string
OutputFormat string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
CommandCounter internal.CommandCounter
// Main module data
@@ -36,8 +38,8 @@ type UserKeys struct {
Key string
}
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "access-keys"
@@ -55,6 +57,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
// Variables used to draw table output
m.output.Headers = []string{
"Account",
"User Name",
"Access Key ID",
}
@@ -65,6 +68,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
key.Username,
key.Key,
},
@@ -79,9 +83,6 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
//fmt.Printf("[%s][%s] Preparing output.\n\n")
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -89,10 +90,37 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"User Name",
"Access Key ID",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"User Name",
"Access Key ID",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+681
View File
@@ -0,0 +1,681 @@
package aws
import (
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
apigatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
apigatewayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
var CURL_COMMAND string = "curl -X %s %s"
type ApiGwModule struct {
// General configuration data
APIGatewayClient sdk.APIGatewayClientInterface
APIGatewayv2Client sdk.APIGatewayv2ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Gateways []ApiGateway
CommandCounter internal.CommandCounter
Errors []string
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type ApiGateway struct {
AWSService string
Region string
Name string
Endpoint string
ApiKey string
Public string
Method string
}
func (m *ApiGwModule) PrintApiGws(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "api-gw"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating api-gateways for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan ApiGateway)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
//execute regional checks
for _, region := range m.AWSRegions {
wg.Add(1)
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
sort.Slice(m.Gateways, func(i, j int) bool {
return m.Gateways[i].AWSService < m.Gateways[j].AWSService
})
m.output.Headers = []string{
"Service",
"Region",
"Name",
"Method",
"Endpoint",
"ApiKey",
"Public",
}
// Table rows
for i := range m.Gateways {
m.output.Body = append(
m.output.Body,
[]string{
m.Gateways[i].AWSService,
m.Gateways[i].Region,
m.Gateways[i].Name,
m.Gateways[i].Method,
m.Gateways[i].Endpoint,
m.Gateways[i].ApiKey,
m.Gateways[i].Public,
},
)
}
if len(m.output.Body) > 0 {
filepath := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
DirectoryName: filepath,
},
Loot: internal.LootClient{
DirectoryName: filepath,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
loot := m.writeLoot(filepath, verbosity)
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
Name: m.output.CallingModule,
Contents: loot,
})
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
fmt.Printf("[%s][%s] %s API gateways found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No API gateways found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *ApiGwModule) Receiver(receiver chan ApiGateway, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Gateways = append(m.Gateways, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *ApiGwModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
defer wg.Done()
// check the concurrency semaphore
// semaphore <- struct{}{}
// defer func() {
// <-semaphore
// }()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("apigateway", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getAPIGatewayAPIsPerRegion(r, wg, semaphore, dataReceiver)
m.CommandCounter.Total++
wg.Add(1)
go m.getAPIGatewayVIPsPerRegion(r, wg, semaphore, dataReceiver)
m.CommandCounter.Total++
wg.Add(1)
go m.getAPIGatewayv2APIsPerRegion(r, wg, semaphore, dataReceiver)
m.CommandCounter.Total++
wg.Add(1)
go m.getAPIGatewayv2VIPsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
path := filepath.Join(outputDirectory, "loot")
f := filepath.Join(path, "api-gws.txt")
var out string
for _, endpoint := range m.Gateways {
method := endpoint.Method
// Write a GET and POST for ANY
if endpoint.Method == "ANY" {
line := fmt.Sprintf(CURL_COMMAND, "GET", endpoint.Endpoint)
if endpoint.ApiKey != "" {
line += fmt.Sprintf(" -H 'X-Api-Key: %s'", endpoint.ApiKey)
}
out += line + "\n"
method = "POST"
}
line := fmt.Sprintf(CURL_COMMAND, method, endpoint.Endpoint)
if endpoint.ApiKey != "" {
line += fmt.Sprintf(" -H 'X-Api-Key: %s'", endpoint.ApiKey)
}
if method == "DELETE" || method == "PATCH" || method == "POST" || method == "PUT" {
line += " -H 'Content-Type: application/json' -d '{}'"
}
out += line + "\n"
}
// err = os.WriteFile(f, []byte(out), 0644)
// if err != nil {
// m.modLog.Error(err.Error())
// m.CommandCounter.Error++
// panic(err.Error())
// }
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Send these requests through your favorite interception proxy"))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
return out
}
func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, api := range Items {
m.CommandCounter.Total++
for _, endpoint := range m.getEndpointsPerAPIGateway(r, api) {
dataReceiver <- endpoint
}
}
}
func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
GetDomainNames, err := sdk.CachedApiGatewayGetDomainNames(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, item := range GetDomainNames {
domain := aws.ToString(item.DomainName)
GetBasePathMappings, err := sdk.CachedApiGatewayGetBasePathMappings(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, item.DomainName)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mapping := range GetBasePathMappings {
stage := aws.ToString(mapping.Stage)
basePath := aws.ToString(mapping.BasePath)
if basePath == "(none)" {
basePath = "" // Empty string since '/' is already prepended
}
for _, api := range Items {
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
m.CommandCounter.Total++
endpoints := m.getEndpointsPerAPIGateway(r, api)
for _, endpoint := range endpoints {
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if basePath == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
}
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
}
break
}
}
}
}
}
func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.RestApi) []ApiGateway {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
}()
var gateways []ApiGateway
//var PaginationControl2 *string
awsService := "APIGateway"
var public string
name := aws.ToString(api.Name)
id := aws.ToString(api.Id)
raw_endpoint := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com", id, r)
endpointType := *api.EndpointConfiguration
//fmt.Println(endpointType)
if endpointType.Types[0] == "PRIVATE" {
public = "False"
} else {
public = "True"
}
GetStages, err := sdk.CachedApiGatewayGetStages(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return gateways
}
resources, err := sdk.CachedApiGatewayGetResources(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, stage := range GetStages.Item {
stageName := aws.ToString(stage.StageName)
for _, resource := range resources {
if len(resource.ResourceMethods) != 0 {
for method := range resource.ResourceMethods {
// Check if API Key is required for endpoint
apiKey := ""
if m.ApiGatewayApiKeyRequired(r, api.Id, resource.Id, method) {
apiKey, err = m.GetApiGatewayApiKey(r, id, stageName)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
path := aws.ToString(resource.Path)
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
gateways = append(gateways, ApiGateway{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Method: method,
Public: public,
ApiKey: apiKey,
})
}
}
}
}
return gateways
}
func (m *ApiGwModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, api := range Items {
m.CommandCounter.Total++
for _, endpoint := range m.getEndpointsPerAPIGatewayv2(r, api) {
dataReceiver <- endpoint
}
}
}
func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
GetDomainNames, err := sdk.CachedAPIGatewayv2GetDomainNames(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, item := range GetDomainNames {
domain := aws.ToString(item.DomainName)
GetApiMappings, err := sdk.CachedAPIGatewayv2GetApiMappings(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, domain)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mapping := range GetApiMappings {
stage := aws.ToString(mapping.Stage)
if stage == "$default" {
stage = ""
}
path := aws.ToString(mapping.ApiMappingKey)
for _, api := range Items {
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
m.CommandCounter.Total++
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
for _, endpoint := range endpoints {
var old string
if stage == "" {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
} else {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
}
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if path == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, path)
}
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
}
break
}
}
}
}
}
func (m *ApiGwModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Types.Api) []ApiGateway {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
}()
var gateways []ApiGateway
awsService := "APIGatewayv2"
var public string
name := aws.ToString(api.Name)
raw_endpoint := aws.ToString(api.ApiEndpoint)
id := aws.ToString(api.ApiId)
var stages []string
GetStages, err := sdk.CachedAPIGatewayv2GetStages(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, stage := range GetStages {
s := aws.ToString(stage.StageName)
if s == "$default" {
s = ""
}
stages = append(stages, s)
}
GetRoutes, err := sdk.CachedAPIGatewayv2GetRoutes(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, stage := range stages {
for _, route := range GetRoutes {
routeKey := route.RouteKey
var method string
var path string
if len(strings.Fields(*routeKey)) == 2 {
method = strings.Fields(*routeKey)[0]
path = strings.Fields(*routeKey)[1]
}
var endpoint string
if stage == "" {
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
} else {
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
}
public = "True"
gateways = append(gateways, ApiGateway{
AWSService: awsService,
Region: r,
Name: name,
Method: method,
Endpoint: endpoint,
Public: public,
})
}
}
return gateways
}
func (m *ApiGwModule) ApiGatewayApiKeyRequired(r string, ApiId *string, ResourceId *string, method string) bool {
GetMethod, err := sdk.CachedApiGatewayGetMethod(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, aws.ToString(ApiId), aws.ToString(ResourceId), method)
if err != nil {
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
m.modLog.Error(err.Error())
m.CommandCounter.Error++
} else {
return aws.ToBool(GetMethod.ApiKeyRequired)
}
return false
}
func (m *ApiGwModule) GetApiGatewayApiKey(r string, ApiId string, Stage string) (string, error) {
var items []apigatewayTypes.UsagePlan
GetUsagePlans, err := sdk.CachedApiGatewayGetUsagePlans(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
return "", err
}
for _, item := range GetUsagePlans {
items = append(items, item)
}
for _, item := range items {
for _, apiStage := range item.ApiStages {
if aws.ToString(apiStage.ApiId) == ApiId && aws.ToString(apiStage.Stage) == Stage {
GetUsagePlanKeys, err := sdk.CachedApiGatewayGetUsagePlanKeys(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, aws.ToString(item.Id))
if err != nil {
return "", err
}
for _, i := range GetUsagePlanKeys {
if aws.ToString(i.Type) == "API_KEY" {
return aws.ToString(i.Value), nil
}
}
}
}
}
return "", nil
}
+60
View File
@@ -0,0 +1,60 @@
package aws
import (
"path/filepath"
"strings"
"testing"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go/ptr"
"github.com/spf13/afero"
)
func TestApiGw(t *testing.T) {
m := ApiGwModule{
AWSProfile: "unittesting",
AWSRegions: []string{"us-east-1"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
WrapTable: false,
APIGatewayClient: &sdk.MockedAWSAPIGatewayClient{},
APIGatewayv2Client: &sdk.MockedAWSAPIGatewayv2Client{},
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
//tmpDir := "~/.cloudfox/"
tmpDir := ptr.ToString(internal.GetLogDirPath())
m.PrintApiGws(tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/api-gw.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
//print the results file to the screen
//fmt.Println(string(resultsFile))
// I want a test that runs the main function and checks the output to see if the following items are in the output: "https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1", "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2"
expectedResults := []string{
"https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1",
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2",
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage2/route1",
"23oieuwefo3rfs",
}
for _, expected := range expectedResults {
if !strings.Contains(string(resultsFile), expected) {
t.Errorf("Expected %s to be in the results file", expected)
}
}
}
+92 -46
View File
@@ -20,24 +20,26 @@ import (
type BucketsModule struct {
// General configuration data
//BucketsS3Client CloudFoxS3Client
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
CheckBucketPolicies bool
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
AWSTableCols string
AWSOutputType string
OutputFormat string
Goroutines int
WrapTable bool
Goroutines int
WrapTable bool
// Main module data
Buckets []Bucket
Buckets []BucketRow
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Bucket struct {
type BucketRow struct {
Arn string
AWSService string
Region string
@@ -53,8 +55,8 @@ type Bucket struct {
ResourcePolicySummary string
}
func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *BucketsModule) PrintBuckets(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "buckets"
@@ -78,7 +80,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Bucket)
dataReceiver := make(chan BucketRow)
// Create a channel to signal to stop
receiverDone := make(chan bool)
@@ -100,6 +102,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Name",
"Region",
"Public?",
@@ -111,17 +114,16 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Buckets[i].Name,
m.Buckets[i].Region,
m.Buckets[i].IsPublic,
m.Buckets[i].ResourcePolicySummary,
},
)
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -129,10 +131,47 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"Region",
"Public?",
"Resource Policy Summary",
}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{
"Name",
"Region",
"Public?",
"Resource Policy Summary",
}
}
// Remove the Public? and Resource Policy Summary columns if the user did not specify CheckBucketPolicies
if !m.CheckBucketPolicies {
tableCols = removeStringFromSlice(tableCols, "Public?")
tableCols = removeStringFromSlice(tableCols, "Resource Policy Summary")
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
TableCols: tableCols,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -148,7 +187,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
func (m *BucketsModule) Receiver(receiver chan BucketRow, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
@@ -161,7 +200,7 @@ func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
}
}
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
defer wg.Done()
m.CommandCounter.Total++
@@ -215,7 +254,7 @@ func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile
}
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
@@ -237,42 +276,49 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}
for _, b := range ListBuckets {
bucket := &Bucket{
bucket := &BucketRow{
Name: aws.ToString(b.Name),
AWSService: "S3",
}
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
region = "Unknown"
}
bucket.Region = region
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
} else {
bucket.PolicyJSON = policyJSON
}
if m.CheckBucketPolicies {
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
} else {
bucket.PolicyJSON = policyJSON
}
bucket.IsPublic = "No"
if !bucket.Policy.IsEmpty() {
m.analyseBucketPolicy(bucket, dataReceiver)
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
bucket.IsPublic = "No"
if !bucket.Policy.IsEmpty() {
m.analyseBucketPolicy(bucket, dataReceiver)
} else {
bucket.Access = "No resource policy"
dataReceiver <- *bucket
}
} else {
bucket.Access = "No resource policy"
// Send Bucket object through the channel to the receiver
bucket.Access = "Skipped"
bucket.IsPublic = "Skipped"
dataReceiver <- *bucket
}
// Send Bucket object through the channel to the receiver
}
}
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
@@ -280,11 +326,11 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool
if err != nil {
return false
}
return publicAccessBlock.IgnorePublicAcls
return aws.ToBool(publicAccessBlock.IgnorePublicAcls) && aws.ToBool(publicAccessBlock.BlockPublicPolicy) && aws.ToBool(publicAccessBlock.RestrictPublicBuckets)
}
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
func (m *BucketsModule) analyseBucketPolicy(bucket *BucketRow, dataReceiver chan BucketRow) {
m.storeAccessPolicy(bucket)
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
@@ -299,14 +345,14 @@ func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bu
} else {
bucket.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
//bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
}
dataReceiver <- *bucket
}
func (m *BucketsModule) storeAccessPolicy(bucket *Bucket) {
func (m *BucketsModule) storeAccessPolicy(bucket *BucketRow) {
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", bucket.Name))
if err := m.storeFile(f, bucket.PolicyJSON); err != nil {
+12 -10
View File
@@ -63,10 +63,10 @@ func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBu
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
output := &s3.GetPublicAccessBlockOutput{
PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: false,
RestrictPublicBuckets: true,
BlockPublicAcls: aws.Bool(true),
BlockPublicPolicy: aws.Bool(true),
IgnorePublicAcls: aws.Bool(false),
RestrictPublicBuckets: aws.Bool(true),
},
}
return output, nil
@@ -81,9 +81,11 @@ func TestListBuckets(t *testing.T) {
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
Goroutines: 3,
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
AWSOutputType: "",
CheckBucketPolicies: true,
Goroutines: 3,
}
subtests := []struct {
@@ -91,14 +93,14 @@ func TestListBuckets(t *testing.T) {
outputDirectory string
verbosity int
testModule BucketsModule
expectedResult []Bucket
expectedResult []BucketRow
}{
{
name: "test1",
outputDirectory: ".",
verbosity: 2,
testModule: m,
expectedResult: []Bucket{{
expectedResult: []BucketRow{{
Name: "mockBucket123",
}},
},
@@ -110,7 +112,7 @@ func TestListBuckets(t *testing.T) {
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintBuckets(subtest.outputDirectory, subtest.verbosity)
for index, expectedBucket := range subtest.expectedResult {
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/buckets.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
+40 -28
View File
@@ -7,9 +7,11 @@ import (
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/fsx"
"github.com/aws/aws-sdk-go-v2/service/glue"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/organizations"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -18,23 +20,23 @@ import (
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
iamSimMod := IamSimulatorModule{
IAMClient: iamSimPPClient,
Caller: caller,
AWSProfile: AWSProfile,
Goroutines: Goroutines,
IAMClient: iamSimPPClient,
Caller: caller,
AWSProfileProvided: AWSProfile,
Goroutines: Goroutines,
}
return iamSimMod
}
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool, AWSMFAToken string) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
cloudFoxSNSClient := SNSModule{
SNSClient: sns.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
}
@@ -42,25 +44,25 @@ func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string
}
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, AWSMFAToken string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
cloudFoxS3Client := BucketsModule{
S3Client: s3.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return cloudFoxS3Client
}
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
sqsClient := SQSModule{
SQSClient: sqs.NewFromConfig(AWSConfig),
Caller: caller,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
@@ -69,58 +71,68 @@ func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVers
}
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
lambdaClient := LambdasModule{
LambdaClient: lambda.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return lambdaClient
}
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
codeBuildClient := CodeBuildModule{
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return codeBuildClient
}
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
ecrClient := ECRModule{
ECRClient: ecr.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return ecrClient
}
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
fileSystemsClient := FilesystemsModule{
EFSClient: efs.NewFromConfig(AWSConfig),
FSxClient: fsx.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return fileSystemsClient
}
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
orgClient := OrgModule{
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return orgClient
}
func InitSecretsManagerClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *secretsmanager.Client {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
return secretsmanager.NewFromConfig(AWSConfig)
}
func InitGlueClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *glue.Client {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
return glue.NewFromConfig(AWSConfig)
}
+64 -32
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -20,12 +21,13 @@ type CloudformationModule struct {
// General configuration data
CloudFormationClient sdk.CloudFormationClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
AWSProfile string
WrapTable bool
AWSOutputType string
AWSTableCols string
// Main module data
CFStacks []CFStack
@@ -45,8 +47,8 @@ type CFStack struct {
Template string
}
func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *CloudformationModule) PrintCloudformationStacks(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "cloudformation"
@@ -93,48 +95,45 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
"Role",
// "Parameters",
// "Outputs",
"Parameters",
"Outputs",
}
// Table rows
for i := range m.CFStacks {
// var isParameters string
// var isOutputs string
// if m.CFStacks[i].Parameters != nil {
// isParameters = "Y"
// } else {
// isParameters = "N"
// }
// if m.CFStacks[i].Outputs != nil {
// isOutputs = "Y"
// } else {
// isOutputs = "N"
// }
var hasParameters string
var hasOutputs string
if m.CFStacks[i].Parameters != nil {
hasParameters = "Y"
} else {
hasParameters = "N"
}
if m.CFStacks[i].Outputs != nil {
hasOutputs = "Y"
} else {
hasOutputs = "N"
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.CFStacks[i].AWSService,
m.CFStacks[i].Region,
m.CFStacks[i].Name,
m.CFStacks[i].Role,
// isParameters,
// isOutputs,
hasParameters,
hasOutputs,
},
)
}
if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -142,10 +141,43 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Role",
"Parameters",
"Outputs",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Role",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+1 -2
View File
@@ -25,7 +25,6 @@ func TestCloudFormation(t *testing.T) {
testModule: CloudformationModule{
CloudFormationClient: &sdk.MockedCloudformationClient{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
@@ -39,7 +38,7 @@ func TestCloudFormation(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintCloudformationStacks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintCloudformationStacks(subtest.outputDirectory, subtest.verbosity)
for index, expectedStack := range subtest.expectedResult {
if expectedStack.Name != subtest.testModule.CFStacks[index].Name {
log.Fatal("Stack name does not match expected name")
+55 -40
View File
@@ -3,6 +3,7 @@ package aws
import (
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -18,9 +19,11 @@ type CodeBuildModule struct {
CodeBuildClient sdk.CodeBuildClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -46,8 +49,8 @@ type Project struct {
CanPrivEsc string
}
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *CodeBuildModule) PrintCodeBuildProjects(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "codebuild"
@@ -107,50 +110,66 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
m.output.Headers = []string{
"Account",
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
// Table rows
for i := range m.Projects {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
}
var seen []string
@@ -162,11 +181,6 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
//fmt.Printf("[%s][%s] %d projects with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -175,9 +189,10 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+1 -1
View File
@@ -124,7 +124,7 @@ func TestCodeBuildProjects(t *testing.T) {
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintCodeBuildProjects("table", tmpDir, 2)
m.PrintCodeBuildProjects(tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/codebuild.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
+274 -109
View File
@@ -3,10 +3,10 @@ package aws
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -22,13 +22,16 @@ type DatabasesModule struct {
RedshiftClient sdk.AWSRedShiftClientInterface
DynamoDBClient sdk.DynamoDBClientInterface
DocDBClient sdk.DocDBClientInterface
NeptuneClient sdk.NeptuneClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
Databases []Database
CommandCounter internal.CommandCounter
@@ -50,10 +53,11 @@ type Database struct {
Protocol string
Public string
Size string
Roles string
}
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "databases"
@@ -65,13 +69,13 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
}
fmt.Printf("[%s][%s] Enumerating databases for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: RDS, DynamoDB\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
fmt.Printf("[%s][%s] Supported Services: RDS, Redshift, DynamoDB, DocumentDB, Neptune\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
//fire up the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
@@ -101,6 +105,7 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
})
m.output.Headers = []string{
"Account",
"Service",
"Engine",
"Region",
@@ -108,17 +113,57 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
"Size",
"UserName",
"Endpoint",
//"Port",
"Port",
"Roles",
//"Protocol",
//"Public",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
"Port",
"Roles",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
"Port",
}
}
// Table rows
for i := range m.Databases {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Databases[i].AWSService,
m.Databases[i].Engine,
m.Databases[i].Region,
@@ -126,7 +171,8 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
m.Databases[i].Size,
m.Databases[i].UserName,
m.Databases[i].Endpoint,
// strconv.Itoa(int(m.Databases[i].Port)),
strconv.Itoa(int(m.Databases[i].Port)),
m.Databases[i].Roles,
// m.Databases[i].Protocol,
// m.Databases[i].Public,
},
@@ -134,27 +180,35 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
filepath := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
Wrap: m.WrapTable,
DirectoryName: filepath,
},
Loot: internal.LootClient{
DirectoryName: filepath,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
loot := m.writeLoot(filepath, verbosity)
//o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.writeLoot(o.Table.DirectoryName, verbosity)
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
Name: "databases-UrlsOnly.txt",
Contents: loot,
})
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
fmt.Printf("[%s][%s] %s databases found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No databases found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -179,56 +233,100 @@ func (m *DatabasesModule) Receiver(receiver chan Database, receiverDone chan boo
func (m *DatabasesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
serviceMap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("rds", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getRdsClustersPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("redshift", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getRedshiftDatabasesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("dynamodb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDynamoDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("docdb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDocDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
m.executeRdsCheck(r, wg, semaphore, dataReceiver, serviceMap) // Also returns Neptune and DocDB
m.executeRedshiftCheck(r, wg, semaphore, dataReceiver, serviceMap)
m.executeDynamoDbCheck(r, wg, semaphore, dataReceiver, serviceMap)
//m.executeDocDbCheck(r, wg, semaphore, dataReceiver, serviceMap)
//m.executeNeptuneCheck(r, wg, semaphore, dataReceiver, serviceMap)
}
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
type check struct {
region string
wg *sync.WaitGroup
semaphore chan struct{}
dataReceiver chan Database
serviceMap *awsservicemap.AwsServiceMap
service string
executor func(string, *sync.WaitGroup, chan struct{}, chan Database)
}
func (m *DatabasesModule) executeCheck(check check) {
res, err := check.serviceMap.IsServiceInRegion(check.service, check.region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
check.wg.Add(1)
go check.executor(check.region, check.wg, check.semaphore, check.dataReceiver)
}
}
func (m *DatabasesModule) executeRdsCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
m.executeCheck(check{
region: r,
wg: wg,
semaphore: semaphore,
dataReceiver: dataReceiver,
serviceMap: servicemap,
service: "rds",
executor: m.getRdsClustersPerRegion,
})
}
func (m *DatabasesModule) executeRedshiftCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
m.executeCheck(check{
region: r,
wg: wg,
semaphore: semaphore,
dataReceiver: dataReceiver,
serviceMap: servicemap,
service: "redshift",
executor: m.getRedshiftDatabasesPerRegion,
})
}
func (m *DatabasesModule) executeDynamoDbCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
m.executeCheck(check{
region: r,
wg: wg,
semaphore: semaphore,
dataReceiver: dataReceiver,
serviceMap: servicemap,
service: "dynamodb",
executor: m.getDynamoDBTablesPerRegion,
})
}
func (m *DatabasesModule) executeDocDbCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
m.executeCheck(check{
region: r,
wg: wg,
semaphore: semaphore,
dataReceiver: dataReceiver,
serviceMap: servicemap,
service: "docdb",
executor: m.getDocDBTablesPerRegion,
})
}
func (m *DatabasesModule) executeNeptuneCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
m.executeCheck(check{
region: r,
wg: wg,
semaphore: semaphore,
dataReceiver: dataReceiver,
serviceMap: servicemap,
service: "neptune",
executor: m.getNeptuneDatabasesPerRegion,
})
}
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) string {
path := filepath.Join(outputDirectory, "loot")
f := filepath.Join(path, "databases-UrlsOnly.txt")
var out string
@@ -237,12 +335,12 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
out = out + fmt.Sprintln(database.Endpoint)
}
err = os.WriteFile(f, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
// err = os.WriteFile(f, []byte(out), 0644)
// if err != nil {
// m.modLog.Error(err.Error())
// m.CommandCounter.Error++
// panic(err.Error())
// }
if verbosity > 2 {
fmt.Println()
@@ -253,6 +351,8 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
return out
}
func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
@@ -269,45 +369,58 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
m.CommandCounter.Pending--
m.CommandCounter.Executing++
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
DBClusters, err := sdk.CachedRDSDescribeDBClusters(m.RDSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var public string
for _, instance := range DBInstances {
if instance.Endpoint != nil {
name := aws.ToString(instance.DBInstanceIdentifier)
port := instance.Endpoint.Port
endpoint := aws.ToString(instance.Endpoint.Address)
engine := aws.ToString(instance.Engine)
if instance.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
dataReceiver <- Database{
AWSService: "RDS",
Region: r,
Name: name,
Engine: engine,
Endpoint: endpoint,
UserName: aws.ToString(instance.MasterUsername),
Port: port,
Protocol: aws.ToString(instance.Engine),
Public: public,
}
for _, cluster := range DBClusters {
var public string
var service string
var roles string
if cluster.Endpoint == nil {
continue
}
}
name := aws.ToString(cluster.DBClusterIdentifier)
port := cluster.Port
endpoint := aws.ToString(cluster.Endpoint)
engine := aws.ToString(cluster.Engine)
if aws.ToBool(cluster.PubliclyAccessible) {
public = "True"
} else {
public = "False"
}
if isNeptune(cluster.Engine) {
service = "Neptune"
} else if isDocDB(cluster.Engine) {
service = "DocsDB"
} else {
service = "RDS"
}
associatedRoles := cluster.AssociatedRoles
for _, role := range associatedRoles {
roles = roles + aws.ToString(role.RoleArn) + " "
}
dataReceiver <- Database{
AWSService: service,
Region: r,
Name: name,
Engine: engine,
Endpoint: endpoint,
UserName: aws.ToString(cluster.MasterUsername),
Port: aws.ToInt32(port),
Protocol: aws.ToString(cluster.Engine),
Public: public,
Roles: roles,
}
}
}
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
@@ -331,7 +444,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -343,13 +456,13 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
name := aws.ToString(cluster.DBName)
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
port := aws.ToInt32(cluster.Endpoint.Port)
if cluster.PubliclyAccessible {
if aws.ToBool(cluster.PubliclyAccessible) {
public = "True"
} else {
public = "False"
}
port := cluster.Endpoint.Port
dataReceiver <- Database{
AWSService: awsService,
Region: r,
@@ -404,7 +517,6 @@ func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGrou
Endpoint: "N/A",
}
}
}
func (m *DatabasesModule) getDocDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
@@ -449,3 +561,56 @@ func (m *DatabasesModule) getDocDBTablesPerRegion(r string, wg *sync.WaitGroup,
}
}
}
func (m *DatabasesModule) getNeptuneDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
m.CommandCounter.Pending--
m.CommandCounter.Executing++
clusters, err := sdk.CachedNeptuneDescribeDBClusters(m.NeptuneClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, cluster := range clusters {
if !isNeptune(cluster.Engine) {
continue
}
name := aws.ToString(cluster.DBClusterIdentifier)
endpoint := aws.ToString(cluster.Endpoint)
port := aws.ToInt32(cluster.Port)
userName := aws.ToString(cluster.MasterUsername)
engine := aws.ToString(cluster.Engine)
dataReceiver <- Database{
AWSService: "Neptune",
Region: r,
Name: name,
Engine: engine,
Endpoint: endpoint,
Port: port,
UserName: userName,
}
}
}
func isNeptune(engine *string) bool {
return *engine == "neptune"
}
func isDocDB(engine *string) bool {
return *engine == "docdb"
}
+58
View File
@@ -0,0 +1,58 @@
package aws
import (
"path/filepath"
"strings"
"testing"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go/ptr"
"github.com/spf13/afero"
)
func TestDatabasesCommand(t *testing.T) {
m := DatabasesModule{
AWSProfile: "unittesting",
AWSRegions: []string{"us-east-1"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
WrapTable: false,
RDSClient: &sdk.MockedRDSClient{},
NeptuneClient: &sdk.MockedNeptuneClient{},
DynamoDBClient: &sdk.MockedAWSDynamoDBClient{},
RedshiftClient: &sdk.MockedRedshiftClient{},
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := ptr.ToString(internal.GetLogDirPath())
m.PrintDatabases(tmpDir, 2)
//resultsFile, err := afero.ReadFile(fs, "table/databases.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/databases.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFile, err)
}
expectedResults := []string{
"db1.cluster-123456789012.us-west-2.rds.amazonaws.com",
"db2.cluster-123456789012.us-west-2.rds.amazonaws.com",
"db3.cluster-123456789012.us-west-2.neptune.amazonaws.com",
"db4.cluster-123456789012.us-west-2.docdb.amazonaws.com",
}
for _, expected := range expectedResults {
if !strings.Contains(string(resultsFile), expected) {
t.Errorf("Expected %s to be in the results file", expected)
}
}
}
+117 -77
View File
@@ -21,13 +21,15 @@ import (
type ECRModule struct {
// General configuration data
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Repositories []Repository
@@ -49,8 +51,8 @@ type Repository struct {
PolicyJSON string
}
func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *ECRModule) PrintECR(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ecr"
@@ -95,8 +97,9 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -106,11 +109,53 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
"ImageSize",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"URI",
"PushedAt",
"ImageTags",
"ImageSize",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"URI",
"PushedAt",
"ImageTags",
"ImageSize",
}
}
// sort the table by Name
sort.Slice(m.Repositories, func(i, j int) bool {
return m.Repositories[i].Name < m.Repositories[j].Name
})
// Table rows
for i := range m.Repositories {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Repositories[i].AWSService,
m.Repositories[i].Region,
m.Repositories[i].Name,
@@ -124,10 +169,7 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -136,9 +178,10 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -239,92 +282,89 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
<-semaphore
}()
var allImages []types.ImageDetail
var repoURI string
var repoName string
DescribeRepositories, err := m.describeRepositories(r)
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, repo := range DescribeRepositories {
repoName = aws.ToString(repo.RepositoryName)
repoURI = aws.ToString(repo.RepositoryUri)
for _, repo := range Repositories {
repoName := aws.ToString(repo.RepositoryName)
repoURI := aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
images, err := m.describeImages(r, repoName)
images, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
continue
}
allImages = append(allImages, images...)
}
sort.Slice(allImages, func(i, j int) bool {
return allImages[i].ImagePushedAt.Format("2006-01-02 15:04:05") < allImages[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
sort.Slice(images, func(i, j int) bool {
return images[i].ImagePushedAt.Format("2006-01-02 15:04:05") < images[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
var image types.ImageDetail
var imageTags string
if len(allImages) > 1 {
image = allImages[len(allImages)-1]
} else if len(allImages) == 1 {
image = allImages[0]
} else {
return
}
if len(images) > 1 {
image = images[len(images)-1]
} else if len(images) == 1 {
image = images[0]
} else {
continue
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
} else {
imageTags = "No tags"
}
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
}
}
func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
// func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
var repositories []types.Repository
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
// var repositories []types.Repository
// Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
// if err != nil {
// m.CommandCounter.Error++
// return nil, err
// }
repositories = append(repositories, Repositories...)
// repositories = append(repositories, Repositories...)
return repositories, nil
}
// return repositories, nil
// }
func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
var images []types.ImageDetail
// func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
// var images []types.ImageDetail
ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
images = append(images, ImageDetails...)
return images, nil
}
// ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
// if err != nil {
// m.CommandCounter.Error++
// return nil, err
// }
// images = append(images, ImageDetails...)
// return images, nil
// }
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
var repoPolicy policy.Policy
+22 -51
View File
@@ -1,52 +1,15 @@
package aws
import (
"context"
"log"
"testing"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
type MockedECRClient struct {
}
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []types.Repository{
{
RepositoryName: aws.String("test1"),
RepositoryUri: aws.String("testURI"),
},
},
}, nil
}
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []types.ImageDetail{
{
ImagePushedAt: aws.Time(time.Now()),
ImageSizeInBytes: aws.Int64(123456),
ImageTags: []string{"latest"},
},
},
}, nil
}
func (m *MockedECRClient) GetRepositoryPolicy(context.Context, *ecr.GetRepositoryPolicyInput, ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
return &ecr.GetRepositoryPolicyOutput{
PolicyText: aws.String("policyText"),
}, nil
}
func TestDescribeRepos(t *testing.T) {
subtests := []struct {
@@ -61,29 +24,37 @@ func TestDescribeRepos(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ECRModule{
ECRClient: &MockedECRClient{},
ECRClient: &sdk.MockedECRClient{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
AWSProfile: "test",
Goroutines: 30,
AWSRegions: []string{"us-east-1"},
},
expectedResult: []Repository{{
Name: "test1",
URI: "testURI:latest",
PushedAt: "2022-10-25 15:14:06",
ImageTags: "latest",
ImageSize: 123456,
}},
expectedResult: []Repository{
{
Name: "repo1",
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1",
PushedAt: "2022-10-25 15:14:00",
ImageTags: "customtag, tag2",
ImageSize: 123456,
},
{
Name: "repo2",
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2",
PushedAt: "2021-10-15 11:14:00",
ImageTags: "latest",
ImageSize: 2222222,
}},
},
}
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintECR(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintECR(subtest.outputDirectory, subtest.verbosity)
for index, expectedRepo := range subtest.expectedResult {
if expectedRepo.Name != subtest.testModule.Repositories[index].Name {
log.Fatal("Repo name does not match expected name")
+66 -51
View File
@@ -25,9 +25,11 @@ type ECSTasksModule struct {
EC2Client sdk.AWSEC2ClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
AWSProfile string
Goroutines int
SkipAdminCheck bool
@@ -56,7 +58,7 @@ type MappedECSTask struct {
CanPrivEsc string
}
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
func (m *ECSTasksModule) ECSTasks(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ecs-tasks"
@@ -119,7 +121,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
receiverDone <- true
<-receiverDone
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printECSTaskData(outputDirectory, dataReceiver, verbosity)
}
@@ -136,9 +138,36 @@ func (m *ECSTasksModule) Receiver(receiver chan MappedECSTask, receiverDone chan
}
}
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
if m.pmapperError == nil {
m.output.Headers = []string{
func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Cluster",
"TaskDefinition",
"LaunchType",
@@ -149,61 +178,46 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
if m.pmapperError == nil {
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
ecsTask.CanPrivEsc,
},
)
}
} else {
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
//ecsTask.CanPrivEsc,
},
)
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
ecsTask.CanPrivEsc,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -212,9 +226,10 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+1 -1
View File
@@ -43,7 +43,7 @@ func TestECSTasks(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.ECSTasks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.ECSTasks(subtest.outputDirectory, subtest.verbosity)
for index, expectedTask := range subtest.expectedResult {
if expectedTask.Cluster != subtest.testModule.MappedECSTasks[index].Cluster {
log.Fatal("Cluster name does not match expected value")
+75 -63
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -21,9 +22,11 @@ type EKSModule struct {
EKSClient sdk.EKSClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -52,8 +55,8 @@ type Cluster struct {
CanPrivEsc string
}
func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EKSModule) EKS(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "eks"
@@ -122,10 +125,48 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"Endpoint",
"Public",
"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Region",
"Name",
//"Endpoint",
@@ -136,58 +177,32 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Service",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
// Table rows
for i := range m.Clusters {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Clusters[i].AWSService,
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
m.Clusters[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Clusters[i].AWSService,
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
//m.Clusters[i].CanPrivEsc,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
m.Clusters[i].CanPrivEsc,
},
)
}
var seen []string
@@ -199,10 +214,6 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -211,9 +222,10 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+1 -2
View File
@@ -86,7 +86,6 @@ func TestEks(t *testing.T) {
EKSClient: &MockedEKSClientInterface{},
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
@@ -104,7 +103,7 @@ func TestEks(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.EKS(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.EKS(subtest.outputDirectory, subtest.verbosity)
for index, expectedCluster := range subtest.expectedResult {
if expectedCluster.Name != subtest.testModule.Clusters[index].Name {
log.Fatal("Cluster name does not match expected value")
+54 -14
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -19,11 +20,13 @@ import (
type ElasticNetworkInterfacesModule struct {
EC2Client sdk.AWSEC2ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
AWSProfile string
WrapTable bool
MappedENIs []MappedENI
CommandCounter internal.CommandCounter
@@ -42,7 +45,7 @@ type MappedENI struct {
Description string
}
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat string, outputDirectory string, verbosity int) {
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "elastic-network-interfaces"
@@ -82,7 +85,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
receiverDone <- true
<-receiverDone
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printENIsData(outputDirectory, dataReceiver, verbosity)
}
@@ -99,8 +102,9 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
}
}
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
func (m *ElasticNetworkInterfacesModule) printENIsData(outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
m.output.Headers = []string{
"Account",
"ID",
"Type",
"External IP",
@@ -109,10 +113,48 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
"Attached Instance",
"Description",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"ID",
"Type",
"External IP",
"Internal IP",
"VPC ID",
"Attached Instance",
"Description",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"ID",
"Type",
"External IP",
"Internal IP",
"VPC ID",
"Attached Instance",
"Description",
}
}
for _, eni := range m.MappedENIs {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
eni.ID,
eni.Type,
eni.ExternalIP,
@@ -125,10 +167,7 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -137,9 +176,10 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+1 -1
View File
@@ -44,7 +44,7 @@ func TestElasticNetworkInterfaces(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.ElasticNetworkInterfaces("table", ".", 3)
subtest.testModule.ElasticNetworkInterfaces(".", 3)
for index, expectedTask := range subtest.expectedResult {
if expectedTask.ExternalIP != subtest.testModule.MappedENIs[index].ExternalIP {
t.Errorf("expected %s, got %s", expectedTask.ExternalIP, subtest.testModule.MappedENIs[index].ExternalIP)
+207 -311
View File
@@ -55,12 +55,14 @@ type EndpointsModule struct {
AppRunnerClient *apprunner.Client
LightsailClient *lightsail.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Endpoints []Endpoint
@@ -83,8 +85,8 @@ type Endpoint struct {
var oe *smithy.OperationError
func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EndpointsModule) PrintEndpoints(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "endpoints"
@@ -147,6 +149,7 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
})
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -156,11 +159,48 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
"Public",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Endpoint",
"Port",
"Protocol",
"Public",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Endpoint",
"Port",
"Protocol",
"Public",
}
}
// Table rows
for i := range m.Endpoints {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Endpoints[i].AWSService,
m.Endpoints[i].Region,
m.Endpoints[i].Name,
@@ -174,10 +214,6 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -186,9 +222,10 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -545,7 +582,7 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
m.CommandCounter.Error++
continue
}
if BrokerDetails.PubliclyAccessible {
if aws.ToBool(BrokerDetails.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -615,7 +652,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
var endpoint string
var kibana_endpoint string
// This exits thie function if an opensearch domain exists but there is no endpoint
// This exits the function if an opensearch domain exists but there is no endpoint
if raw_endpoint == nil {
return
} else {
@@ -893,10 +930,6 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl2 *string
var PaginationControl3 *string
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
@@ -906,16 +939,20 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
return
}
for {
GetDomainNames, err := m.APIGatewayClient.GetDomainNames(
context.TODO(),
&apigateway.GetDomainNamesInput{
Position: PaginationControl2,
},
func(o *apigateway.Options) {
o.Region = r
},
)
GetDomainNames, err := sdk.CachedApiGatewayGetDomainNames(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, item := range GetDomainNames {
domain := aws.ToString(item.DomainName)
GetBasePathMappings, err := sdk.CachedApiGatewayGetBasePathMappings(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, item.DomainName)
if err != nil {
if errors.As(err, &oe) {
@@ -926,82 +963,43 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
break
}
for _, item := range GetDomainNames.Items {
for _, mapping := range GetBasePathMappings {
stage := aws.ToString(mapping.Stage)
basePath := aws.ToString(mapping.BasePath)
if basePath == "(none)" {
basePath = "" // Empty string since '/' is already prepended
}
domain := aws.ToString(item.DomainName)
for _, api := range Items {
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
endpoints := m.getEndpointsPerAPIGateway(r, api)
for _, endpoint := range endpoints {
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
for {
GetBasePathMappings, err := m.APIGatewayClient.GetBasePathMappings(
context.TODO(),
&apigateway.GetBasePathMappingsInput{
DomainName: item.DomainName,
Position: PaginationControl3,
},
func(o *apigateway.Options) {
o.Region = r
},
)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mapping := range GetBasePathMappings.Items {
stage := aws.ToString(mapping.Stage)
basePath := aws.ToString(mapping.BasePath)
if basePath == "(none)" {
basePath = "" // Empty string since '/' is already prepended
}
for _, api := range Items {
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
endpoints := m.getEndpointsPerAPIGateway(r, api)
for _, endpoint := range endpoints {
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if basePath == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
}
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if basePath == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
}
break
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
}
}
if GetBasePathMappings.Position != nil {
PaginationControl3 = GetBasePathMappings.Position
} else {
PaginationControl3 = nil
break
}
}
}
if GetDomainNames.Position != nil {
PaginationControl2 = GetDomainNames.Position
} else {
PaginationControl2 = nil
break
}
}
}
func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.RestApi) []Endpoint {
var endpoints []Endpoint
var PaginationControl2 *string
awsService := "APIGateway"
var public string
@@ -1019,15 +1017,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
public = "True"
}
GetStages, err := m.APIGatewayClient.GetStages(
context.TODO(),
&apigateway.GetStagesInput{
RestApiId: &id,
},
func(o *apigateway.Options) {
o.Region = r
},
)
GetStages, err := sdk.CachedApiGatewayGetStages(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
if errors.As(err, &oe) {
@@ -1037,54 +1027,38 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
m.CommandCounter.Error++
}
for {
GetResources, err := m.APIGatewayClient.GetResources(
context.TODO(),
&apigateway.GetResourcesInput{
RestApiId: &id,
Position: PaginationControl2,
},
func(o *apigateway.Options) {
o.Region = r
},
)
GetResources, err := sdk.CachedApiGatewayGetResources(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
for _, stage := range GetStages.Item {
stageName := aws.ToString(stage.StageName)
for _, resource := range GetResources.Items {
if len(resource.ResourceMethods) != 0 {
path := aws.ToString(resource.Path)
}
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
for _, stage := range GetStages.Item {
stageName := aws.ToString(stage.StageName)
for _, resource := range GetResources {
if len(resource.ResourceMethods) != 0 {
path := aws.ToString(resource.Path)
endpoints = append(endpoints, Endpoint{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
})
}
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
endpoints = append(endpoints, Endpoint{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
})
}
}
if GetResources.Position != nil {
PaginationControl2 = GetResources.Position
} else {
PaginationControl2 = nil
break
}
}
return endpoints
}
@@ -1133,10 +1107,6 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl2 *string
var PaginationControl3 *string
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
@@ -1146,16 +1116,20 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
return
}
for {
GetDomainNames, err := m.APIGatewayv2Client.GetDomainNames(
context.TODO(),
&apigatewayv2.GetDomainNamesInput{
NextToken: PaginationControl2,
},
func(o *apigatewayv2.Options) {
o.Region = r
},
)
GetDomainNames, err := sdk.CachedAPIGatewayv2GetDomainNames(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, item := range GetDomainNames {
domain := aws.ToString(item.DomainName)
GetApiMappings, err := sdk.CachedAPIGatewayv2GetApiMappings(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, domain)
if err != nil {
if errors.As(err, &oe) {
@@ -1166,87 +1140,46 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
break
}
for _, item := range GetDomainNames.Items {
for _, mapping := range GetApiMappings {
stage := aws.ToString(mapping.Stage)
if stage == "$default" {
stage = ""
}
path := aws.ToString(mapping.ApiMappingKey)
domain := aws.ToString(item.DomainName)
for {
GetApiMappings, err := m.APIGatewayv2Client.GetApiMappings(
context.TODO(),
&apigatewayv2.GetApiMappingsInput{
DomainName: item.DomainName,
NextToken: PaginationControl3,
},
func(o *apigatewayv2.Options) {
o.Region = r
},
)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mapping := range GetApiMappings.Items {
stage := aws.ToString(mapping.Stage)
if stage == "$default" {
stage = ""
}
path := aws.ToString(mapping.ApiMappingKey)
for _, api := range Items {
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
for _, endpoint := range endpoints {
var old string
if stage == "" {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
} else {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
}
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if path == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, path)
}
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
for _, api := range Items {
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
for _, endpoint := range endpoints {
var old string
if stage == "" {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
} else {
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
}
if strings.HasPrefix(endpoint.Endpoint, old) {
var new string
if path == "" {
new = fmt.Sprintf("https://%s/", domain)
} else {
new = fmt.Sprintf("https://%s/%s/", domain, path)
}
break
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
endpoint.Name = domain
dataReceiver <- endpoint
}
}
}
if GetApiMappings.NextToken != nil {
PaginationControl3 = GetApiMappings.NextToken
} else {
PaginationControl3 = nil
break
}
}
}
if GetDomainNames.NextToken != nil {
PaginationControl2 = GetDomainNames.NextToken
} else {
PaginationControl2 = nil
break
}
}
}
func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Types.Api) []Endpoint {
var endpoints []Endpoint
var PaginationControl2 *string
var PaginationControl3 *string
awsService := "APIGatewayv2"
var public string
@@ -1258,98 +1191,61 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2
protocol := "https"
var stages []string
for {
GetStages, err := m.APIGatewayv2Client.GetStages(
context.TODO(),
&apigatewayv2.GetStagesInput{
ApiId: &id,
NextToken: PaginationControl2,
},
func(o *apigatewayv2.Options) {
o.Region = r
},
)
GetStages, err := sdk.CachedAPIGatewayv2GetStages(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, stage := range GetStages.Items {
s := aws.ToString(stage.StageName)
if s == "$default" {
s = ""
}
stages = append(stages, s)
for _, stage := range GetStages {
s := aws.ToString(stage.StageName)
if s == "$default" {
s = ""
}
stages = append(stages, s)
}
if GetStages.NextToken != nil {
PaginationControl2 = GetStages.NextToken
} else {
PaginationControl2 = nil
break
GetRoutes, err := sdk.CachedAPIGatewayv2GetRoutes(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, stage := range stages {
for _, route := range GetRoutes {
routeKey := route.RouteKey
var path string
if len(strings.Fields(*routeKey)) == 2 {
path = strings.Fields(*routeKey)[1]
}
var endpoint string
if stage == "" {
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
} else {
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
}
public = "True"
endpoints = append(endpoints, Endpoint{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
})
}
}
for {
GetRoutes, err := m.APIGatewayv2Client.GetRoutes(
context.TODO(),
&apigatewayv2.GetRoutesInput{
ApiId: &id,
NextToken: PaginationControl3,
},
func(o *apigatewayv2.Options) {
o.Region = r
},
)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
for _, stage := range stages {
for _, route := range GetRoutes.Items {
routeKey := route.RouteKey
var path string
if len(strings.Fields(*routeKey)) == 2 {
path = strings.Fields(*routeKey)[1]
}
var endpoint string
if stage == "" {
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
} else {
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
}
public = "True"
endpoints = append(endpoints, Endpoint{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
})
}
}
if GetRoutes.NextToken != nil {
PaginationControl3 = GetRoutes.NextToken
} else {
PaginationControl3 = nil
break
}
}
return endpoints
}
@@ -1386,7 +1282,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
endpoint := aws.ToString(instance.Endpoint.Address)
awsService := "RDS"
if instance.PubliclyAccessible {
if aws.ToBool(instance.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -1397,7 +1293,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Port: aws.ToInt32(port),
Protocol: aws.ToString(instance.Engine),
Public: public,
}
@@ -1442,7 +1338,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
if aws.ToBool(cluster.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -1455,7 +1351,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Port: aws.ToInt32(port),
Protocol: protocol,
Public: public,
}
+123 -25
View File
@@ -6,6 +6,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -34,12 +35,14 @@ import (
type EnvsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
OutputFormat string
Goroutines int
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
AWSOutputType string
AWSTableCols string
Goroutines int
WrapTable bool
// Service Clients
ECSClient *ecs.Client
@@ -63,10 +66,11 @@ type EnvironmentVariable struct {
region string
environmentVarName string
environmentVarValue string
interesting bool
}
func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "env-vars"
@@ -119,6 +123,7 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
// Table headers
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -126,24 +131,70 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
"Value",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Key",
"Value",
}
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Key",
"Value",
}
}
m.determineIfInteresting()
//Table rows
for _, envVar := range m.EnvironmentVariables {
m.output.Body = append(
m.output.Body, []string{
envVar.service,
envVar.region,
envVar.name,
envVar.environmentVarName,
envVar.environmentVarValue,
},
)
if envVar.interesting {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
magenta(envVar.environmentVarName),
magenta(envVar.environmentVarValue),
},
)
} else {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
envVar.environmentVarName,
envVar.environmentVarValue,
},
)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -151,13 +202,27 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
// Create another table file that only contains the environment variables that seem to be secrets.
body := m.interestingEnvVarsOnly()
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: body,
TableCols: tableCols,
Name: fmt.Sprintf("%s-interesting", m.output.CallingModule),
SkipPrintToScreen: true,
})
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] %s environment variables found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
@@ -167,6 +232,20 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *EnvsModule) determineIfInteresting() {
for i, envVar := range m.EnvironmentVariables {
// If the environment variable name contains the word secret, key, token, or password, we'll make it magenta to make it stand out.
lowerEnvVar := strings.ToLower(envVar.environmentVarName)
if (strings.Contains(lowerEnvVar, "secret") && !strings.Contains(lowerEnvVar, "arn")) ||
strings.Contains(lowerEnvVar, "key") ||
strings.Contains(lowerEnvVar, "token") ||
strings.Contains(lowerEnvVar, "user") ||
strings.Contains(lowerEnvVar, "pass") {
m.EnvironmentVariables[i].interesting = true
}
}
}
func EnvVarsContains(element EnvironmentVariable, array []EnvironmentVariable) bool {
for _, v := range array {
if v == element {
@@ -751,3 +830,22 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
}
}
func (m *EnvsModule) interestingEnvVarsOnly() [][]string {
var interestingBody [][]string
for _, envVar := range m.EnvironmentVariables {
if envVar.interesting {
interestingBody = append(
interestingBody, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
magenta(envVar.environmentVarName),
magenta(envVar.environmentVarValue),
},
)
}
}
return interestingBody
}
+54 -17
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -27,12 +28,14 @@ type FilesystemsModule struct {
EFSClient *efs.Client
FSxClient *fsx.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Filesystems []FilesystemObject
@@ -55,8 +58,8 @@ type FilesystemObject struct {
Permissions string
}
func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *FilesystemsModule) PrintFilesystems(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "filesystems"
@@ -107,7 +110,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
})
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Name",
"DNS Name",
@@ -117,12 +120,48 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
"Permissions",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"DNS Name",
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
} else {
tableCols = []string{
"Region",
"Name",
"DNS Name",
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
}
// Table rows
for i := range m.Filesystems {
m.output.Body = append(
m.output.Body,
[]string{
m.Filesystems[i].AWSService,
aws.ToString(m.Caller.Account),
m.Filesystems[i].Region,
m.Filesystems[i].Name,
m.Filesystems[i].DnsName,
@@ -137,10 +176,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -149,9 +185,10 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+113 -43
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -20,13 +21,16 @@ import (
type IamSimulatorModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfileProvided string
AWSProfileStub string
WrapTable bool
// Main module data
SimulatorResults []SimulatorResult
@@ -64,23 +68,28 @@ var (
TxtLogger = internal.TxtLogger()
)
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputFormat string, outputDirectory string, verbosity int) {
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "iam-simulator"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
var filename string
var actionList []string
var pmapperCommands []string
var pmapperOutFileName string
var inputArn string
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
if m.AWSProfileProvided == "" {
m.AWSProfileStub = internal.BuildAWSPath(m.Caller)
} else {
m.AWSProfileStub = m.AWSProfileProvided
}
wg := new(sync.WaitGroup)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
@@ -99,37 +108,69 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
if principal != "" {
if action != "" {
// The user specified a specific --principal and a specific --action
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal, action)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal, action)
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
actionList = append(actionList, action)
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
if !strings.Contains(principal, "arn:") {
// try as a role
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
// try as a user
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
} else {
// the arn was supplied so just run it
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
}
} else {
// The user specified a specific --principal, but --action was empty
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal)
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
if !strings.Contains(principal, "arn:") {
// try as a role
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
// try as a user
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
} else {
// the arn was supplied so just run it
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
}
}
} else {
if action != "" {
// The did not specify a specific --principal, but they did specify an --action
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), action)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), action)
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
actionList = append(actionList, action)
wg.Add(1)
m.getIAMUsers(wg, actionList, resource, dataReceiver)
wg.Add(1)
m.getIAMRoles(wg, actionList, resource, dataReceiver)
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
if m.AWSProfileProvided != "" {
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
} else {
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
}
} else {
// Both --principal and --action are empty. Run in default mode!
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.output.FullFilename = m.output.CallingModule
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
filename = m.output.CallingModule
m.executeChecks(wg, resource, dataReceiver)
for _, action := range defaultActionNames {
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
if m.AWSProfileProvided != "" {
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
} else {
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
}
}
}
@@ -149,11 +190,36 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
// Regardless of what options were selected, for now at least, we will always print the data using the output module (table/csv mode)
m.output.Headers = []string{
"Service",
"Account",
"Principal",
"Query",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Principal",
"Query",
}
} else {
tableCols = []string{
"Principal",
"Query",
}
}
sort.Slice(m.SimulatorResults, func(i, j int) bool {
return m.SimulatorResults[i].Query < m.SimulatorResults[j].Query
})
@@ -163,7 +229,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
m.output.Body = append(
m.output.Body,
[]string{
m.SimulatorResults[i].AWSService,
aws.ToString(m.Caller.Account),
m.SimulatorResults[i].Principal,
m.SimulatorResults[i].Query,
},
@@ -171,9 +237,8 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -182,14 +247,15 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: filename,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.PrefixIdentifier = m.AWSProfileStub
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
// for _, line := range pmapperCommands {
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
@@ -197,9 +263,9 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
m.writeLoot(o.Table.DirectoryName, verbosity, pmapperCommands)
} else if principal != "" || action != "" {
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), m.output.CallingModule)
}
func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pmapperCommands []string) {
@@ -212,7 +278,11 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
outFile := filepath.Join(path, "iam-simulator-pmapper-commands.txt")
var out string
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfile)
if m.AWSProfileProvided != "" {
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfileProvided)
} else {
out = out + fmt.Sprintf("pmapper graph create\n")
}
for _, line := range pmapperCommands {
out = out + line
}
@@ -224,12 +294,12 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("End of loot file."))
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), outFile)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), outFile)
}
+131 -139
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
@@ -13,7 +14,6 @@ import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
@@ -24,11 +24,13 @@ import (
type InstancesModule struct {
// General configuration data
EC2Client *ec2.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
EC2Client sdk.AWSEC2ClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
@@ -63,8 +65,8 @@ type MappedInstance struct {
CanPrivEsc string
}
func (m *InstancesModule) Instances(filter string, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *InstancesModule) Instances(filter string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "instances"
@@ -147,9 +149,9 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
// This conditional block will either dump the userData attribute content or the general instances data, depending on what you select via command line.
//fmt.Printf("\n[*] Preparing output...\n\n")
if m.UserDataAttributesOnly {
m.printInstancesUserDataAttributesOnly(outputFormat, outputDirectory, dataReceiver)
m.printInstancesUserDataAttributesOnly(outputDirectory, dataReceiver)
} else {
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printGeneralInstanceData(outputDirectory, dataReceiver, verbosity)
}
}
@@ -167,7 +169,7 @@ func (m *InstancesModule) Receiver(receiver chan MappedInstance, receiverDone ch
}
}
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputDirectory string, dataReceiver chan MappedInstance) {
defer func() {
m.output.CallingModule = "instances"
}()
@@ -217,12 +219,39 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
}
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
func (m *InstancesModule) printGeneralInstanceData(outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
// Prepare Table headers
//m.output.Headers = table.Row{
if m.pmapperError == nil {
m.output.Headers = []string{
"Account",
//"ID",
"Name",
//"Arn",
"ID",
"Zone",
"State",
"External IP",
"Internal IP",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
m.output.Headers = []string{
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
//"ID",
"Name",
//"Arn",
@@ -235,8 +264,9 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
//"ID",
"Name",
//"Arn",
@@ -247,59 +277,40 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
"Internal IP",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
//Table rows
if m.pmapperError == nil {
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, instance := range m.MappedInstances {
m.output.Body = append(
m.output.Body,
//table.Row{
[]string{
//instance.ID,
instance.Name,
//instance.Arn,
instance.ID,
instance.AvailabilityZone,
instance.State,
instance.ExternalIP,
instance.PrivateIP,
instance.Role,
instance.Admin,
instance.CanPrivEsc,
},
)
}
} else {
for _, instance := range m.MappedInstances {
m.output.Body = append(
m.output.Body,
//table.Row{
[]string{
//instance.ID,
instance.Name,
//instance.Arn,
instance.ID,
instance.AvailabilityZone,
instance.State,
instance.ExternalIP,
instance.PrivateIP,
instance.Role,
instance.Admin,
//instance.CanPrivEsc,
},
)
}
//Table rows
for _, instance := range m.MappedInstances {
m.output.Body = append(
m.output.Body,
//table.Row{
[]string{
aws.ToString(m.Caller.Account),
//instance.ID,
instance.Name,
//instance.Arn,
instance.ID,
instance.AvailabilityZone,
instance.State,
instance.ExternalIP,
instance.PrivateIP,
instance.Role,
instance.Admin,
instance.CanPrivEsc,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
////m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
@@ -310,9 +321,10 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -440,27 +452,17 @@ func (m *InstancesModule) executeChecks(instancesToSearch []string, r string, wg
}
func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, region string) (userData *string, err error) {
Attributes, err := m.EC2Client.DescribeInstanceAttribute(
context.TODO(),
&ec2.DescribeInstanceAttributeInput{
InstanceId: instanceID,
Attribute: types.InstanceAttributeName("userData"),
},
func(o *ec2.Options) {
o.Region = region
},
)
UserData, err := sdk.CachedEC2DescribeInstanceAttributeUserData(m.EC2Client, aws.ToString(m.Caller.Account), region, aws.ToString(instanceID))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return nil, err
} else {
if Attributes.UserData.Value == nil {
if UserData == "" {
return aws.String("NoUserData"), nil
} else {
data, _ := base64.StdEncoding.DecodeString(*Attributes.UserData.Value)
data, _ := base64.StdEncoding.DecodeString(UserData)
return aws.String(string(data)), nil
}
}
@@ -470,53 +472,27 @@ func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, regio
func (m *InstancesModule) getDescribeInstances(instancesToSearch []string, region string, dataReceiver chan MappedInstance) {
// The "PaginationControl" value is nil when there's no more data to return.
var PaginationControl *string
for {
DescribeInstances, err := m.EC2Client.DescribeInstances(
context.TODO(),
&(ec2.DescribeInstancesInput{
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, instance := range Instances {
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
m.loadInstanceData(instance, region, dataReceiver)
}
for _, reservation := range DescribeInstances.Reservations {
accountId := reservation.OwnerId
for _, instance := range reservation.Instances {
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
m.loadInstanceData(instance, region, accountId, dataReceiver)
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if DescribeInstances.NextToken != nil {
PaginationControl = DescribeInstances.NextToken
} else {
PaginationControl = nil
break
}
}
}
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, accountId *string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, dataReceiver chan MappedInstance) {
var profile string
var externalIP string
var name string = ""
var adminRole string = ""
var roleArn string = ""
var profileArn, profileName, name string
// The name is in a tag so we have to do this to grab the value from the right tag
for _, tag := range instance.Tags {
@@ -536,34 +512,50 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
if instance.IamInstanceProfile == nil {
profile = "NoInstanceProfile"
} else {
// This returns only the role name without the preceding forward slash.
profileARN := aws.ToString(instance.IamInstanceProfile.Arn)
profileID := aws.ToString(instance.IamInstanceProfile.Id)
profile = strings.Split(profileARN, "/")[len(strings.Split(profileARN, "/"))-1]
if roles, ok := m.InstanceProfileToRolesMap[profileID]; ok {
for _, role := range roles {
roleArn = aws.ToString(role.Arn)
}
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
PrivateIP: aws.ToString(instance.PrivateIpAddress),
Profile: profile,
Role: "",
Region: region,
Admin: adminRole,
CanPrivEsc: "",
}
}
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(accountId), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
PrivateIP: aws.ToString(instance.PrivateIpAddress),
Profile: profile,
Role: roleArn,
Region: region,
Admin: adminRole,
CanPrivEsc: "",
}
} else {
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)
// Extracting instance profile name from ARN
profileName = strings.Split(profileArn, "/")[len(strings.Split(profileArn, "/"))-1]
// Describe the IAM instance profile
profileOutput, err := sdk.CachedIamGetInstanceProfile(m.IAMClient, aws.ToString(m.Caller.Account), profileName)
if err != nil {
log.Printf("failed to get instance profile for %s, %v", profileArn, err)
}
for _, role := range profileOutput.Roles {
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
PrivateIP: aws.ToString(instance.PrivateIpAddress),
Profile: profile,
Role: aws.ToString(role.Arn),
Region: region,
Admin: adminRole,
CanPrivEsc: "",
}
}
}
}
func (m *InstancesModule) getRolesFromInstanceProfiles() {
+71 -25
View File
@@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
"github.com/aws/aws-sdk-go-v2/service/athena"
"github.com/aws/aws-sdk-go-v2/service/cloud9"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
@@ -45,6 +46,7 @@ type Inventory2Module struct {
APIGatewayv2Client *apigatewayv2.Client
AppRunnerClient *apprunner.Client
AthenaClient *athena.Client
Cloud9Client *cloud9.Client
CloudFormationClient *cloudformation.Client
CloudfrontClient *cloudfront.Client
CodeArtifactClient sdk.AWSCodeArtifactClientInterface
@@ -80,12 +82,14 @@ type Inventory2Module struct {
SecretsManagerClient *secretsmanager.Client
StepFunctionClient sdk.StepFunctionsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
RegionResourceCount int
@@ -109,9 +113,9 @@ type GlobalResourceCount2 struct {
count int
}
func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDirectory string, verbosity int) {
func (m *Inventory2Module) PrintInventoryPerRegion(outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "inventory"
@@ -127,6 +131,7 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
"Athena Databases",
//"Athena Data Catalogs",
"AppRunner Services",
"Cloud9 Environments",
"CloudFormation Stacks",
"Cloudfront Distributions",
"CodeArtifact Repositories",
@@ -243,7 +248,7 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
spinnerDone <- true
<-spinnerDone
// This creates the header row (columns) dynamically - a region oly gets printed if it has at least one resource.
// This creates the header row (columns) dynamically - a region only gets printed if it has at least one resource.
m.output.Headers = append(m.output.Headers, "Resource Type")
type kv struct {
@@ -311,7 +316,7 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
}
// check to see if all regions have no resources for the service. Skip the first column, which is hte resource type.
// check to see if all regions have no resources for the service. Skip the first column, which is the resource type.
// If any value is other than "-" set rowEmpty to false.
var rowEmtpy bool = true
for _, val := range temprow[1:] {
@@ -322,7 +327,7 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
}
// If rowEmpty is still true at the end of the row, we dont add the row to the output, otherwise we do.
if !rowEmtpy {
// Convert the slice of strings to a slice of interfaces??? not sure, but this was needed. I couldnt just pass temp row to the output.Body
// Convert the slice of strings to a slice of interfaces??? not sure, but this was needed. I couldn't just pass temp row to the output.Body
for _, val := range temprow {
outputRow = append(outputRow, val)
@@ -341,10 +346,6 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
// }
if len(m.output.Body) > 0 {
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -360,18 +361,11 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
// var header []string
// var body [][]string
// header, body = m.PrintGlobalResources(outputFormat, outputDirectory, verbosity, dataReceiver)
// o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
// Header: header,
// Body: body,
// Name: "inventory-global",
// })
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
m.PrintTotalResources(outputFormat)
m.PrintTotalResources(m.AWSOutputType)
//m.writeLoot(m.output.FilePath, verbosity)
} else {
fmt.Printf("[%s][%s] No resources identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -464,6 +458,16 @@ func (m *Inventory2Module) executeChecks(r string, wg *sync.WaitGroup, semaphore
// go m.getAthenaDataCatalogsPerRegion(r, wg, semaphore)
}
res, err = servicemap.IsServiceInRegion("cloud9", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getCloud9EnvironmentsPerRegion(r, wg, semaphore)
}
res, err = servicemap.IsServiceInRegion("cloudformation", r)
if err != nil {
m.modLog.Error(err)
@@ -786,7 +790,7 @@ func (m *Inventory2Module) executeChecks(r string, wg *sync.WaitGroup, semaphore
}
func (m *Inventory2Module) PrintTotalResources(outputFormat string) {
func (m *Inventory2Module) PrintTotalResources(AWSOutputType string) {
var totalResources int
for _, r := range m.AWSRegions {
if m.totalRegionCounts[r] != 0 {
@@ -2856,6 +2860,48 @@ func (m *Inventory2Module) getStepFunctionsPerRegion(r string, wg *sync.WaitGrou
m.mu.Unlock()
}
func (m *Inventory2Module) getCloud9EnvironmentsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
defer func() {
wg.Done()
m.CommandCounter.Executing--
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
var totalCountThisServiceThisRegion = 0
var service = "Cloud9 Environments"
var resourceNames []string
Environments, err := sdk.CachedCloud9ListEnvironments(m.Cloud9Client, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// Add this page of resources to the total count
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(Environments)
// Add this page of resources to the module's resource list
for _, env := range Environments {
arn := "arn:aws:cloud9:" + r + ":" + aws.ToString(m.Caller.Account) + ":environment:" + env
resourceNames = append(resourceNames, arn)
}
// No more pages, update the module's service map
m.mu.Lock()
m.resources = append(m.resources, resourceNames...)
m.serviceMap[service][r] = totalCountThisServiceThisRegion
m.mu.Unlock()
}
// Global Resources
func (m *Inventory2Module) getBuckets(verbosity int, dataReceiver chan GlobalResourceCount2) {
+63 -46
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -25,9 +26,11 @@ type LambdasModule struct {
LambdaClient *lambda.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -49,15 +52,16 @@ type Lambda struct {
Region string
Type string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
Public string
}
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
func (m *LambdasModule) PrintLambdas(outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "lambda"
@@ -123,24 +127,52 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
m.output.Headers = []string{
"Account",
"Region",
"Name",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Resource",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Service",
"Region",
"Resource",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
sort.Slice(m.Lambdas, func(i, j int) bool {
return m.Lambdas[i].AWSService < m.Lambdas[j].AWSService
@@ -149,41 +181,24 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
// Table rows
for i := range m.Lambdas {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Lambdas[i].AWSService,
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Lambdas[i].AWSService,
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
//m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Arn,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -192,9 +207,10 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -300,13 +316,14 @@ func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaph
}
for _, function := range functions {
//arn := aws.ToString(function.FunctionArn)
arn := aws.ToString(function.FunctionArn)
name := aws.ToString(function.FunctionName)
role := aws.ToString(function.Role)
dataReceiver <- Lambda{
AWSService: "Lambda",
Name: name,
Arn: arn,
Region: r,
Type: "",
Role: role,
+52 -20
View File
@@ -65,13 +65,15 @@ type NetworkPortsModule struct {
LightsailClient *lightsail.Client
RDSClient *rds.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Verbosity int
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
Verbosity int
// Main module data
IPv4_Private []NetworkService
@@ -136,8 +138,8 @@ var naclToSG = map[string]string{
var validSecurityGroupProtocols = []string{"-1", "tcp", "udp"}
func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirectory string) {
// These stuct values are used by the output module
func (m *NetworkPortsModule) PrintNetworkPorts(outputDirectory string) {
// These struct values are used by the output module
m.output.Verbosity = m.Verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "network-ports"
@@ -184,6 +186,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Protocol",
@@ -191,6 +194,36 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
"Ports",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Protocol",
"Host",
"Ports",
}
} else {
tableCols = []string{
"Service",
"Region",
"Protocol",
"Host",
"Ports",
}
}
// Table rows
for _, arr := range [][]NetworkService{m.IPv4_Private, m.IPv4_Public, m.IPv6} {
for _, i := range arr {
@@ -198,6 +231,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
i.AWSService,
i.Region,
i.Protocol,
@@ -211,9 +245,6 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
if len(m.IPv4_Private) > 0 || len(m.IPv4_Public) > 0 || len(m.IPv6) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: m.Verbosity,
CallingModule: m.output.CallingModule,
@@ -222,9 +253,10 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -742,7 +774,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Memcached
if cluster.ConfigurationEndpoint != nil {
ipv4_private = addHost(ipv4_private, aws.ToString(cluster.ConfigurationEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, cluster.ConfigurationEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(cluster.ConfigurationEndpoint.Port))
} else {
replicationGroupId := aws.ToString(cluster.ReplicationGroupId)
for _, group := range nodes {
@@ -751,7 +783,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Primary
if g.PrimaryEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address)) {
ipv4_private = addHost(ipv4_private, aws.ToString(g.PrimaryEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, g.PrimaryEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.PrimaryEndpoint.Port))
reportedClusters = addHost(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address))
}
@@ -759,7 +791,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Reader
if g.ReaderEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.ReaderEndpoint.Address)) {
ipv4_private = addHost(ipv4_private, aws.ToString(g.ReaderEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, g.ReaderEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.ReaderEndpoint.Port))
reportedClusters = addHost(reportedClusters, aws.ToString(g.ReaderEndpoint.Address))
}
@@ -769,7 +801,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
if aws.ToString(m.CacheClusterId) == aws.ToString(cluster.CacheClusterId) {
if m.ReadEndpoint != nil {
ipv4_private = addHost(ipv4_private, aws.ToString(m.ReadEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, m.ReadEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(m.ReadEndpoint.Port))
}
}
}
@@ -1047,7 +1079,7 @@ func (m *NetworkPortsModule) getRdsNetworkPortsPerRegion(r string, dataReceiver
for _, instance := range DBInstances {
if aws.ToString(instance.DBInstanceStatus) == "available" {
host := []string{aws.ToString(instance.Endpoint.Address)}
var port int32 = instance.Endpoint.Port
var port int32 = aws.ToInt32(instance.Endpoint.Port)
var groups []SecurityGroup
for _, group := range instance.VpcSecurityGroups {
+49 -14
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -19,12 +20,14 @@ type OrgModule struct {
OrganizationsClient sdk.OrganizationsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
// Main module data
Accounts []Account
@@ -52,8 +55,8 @@ type Account struct {
OrgId string
}
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *OrgModule) PrintOrgAccounts(outputDirectory string, verbosity int) {
// These struct values are used by the output module
var err error
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
@@ -85,6 +88,7 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
}
m.output.Headers = []string{
"Account",
"Name",
"ID",
"isManagementAccount?",
@@ -92,12 +96,44 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
"Email",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
}
// Table rows
for i := range m.Accounts {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Accounts[i].Name,
m.Accounts[i].Id,
strconv.FormatBool(m.Accounts[i].isManagementAccount),
@@ -109,8 +145,6 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -119,9 +153,10 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -272,7 +307,7 @@ func (m *OrgModule) addOrgAccount() {
isManagementAccount: false,
Name: "This account",
Id: aws.ToString(m.Caller.Account),
Email: "Unkonwn",
Email: "Unknown",
Arn: aws.ToString(m.Caller.Arn),
Status: "ACTIVE",
})
+53 -16
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
@@ -22,12 +23,14 @@ type OutboundAssumedRolesModule struct {
// General configuration data
CloudTrailClient *cloudtrail.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
OutboundAssumeRoleEntries []OutboundAssumeRoleEntry
@@ -114,8 +117,8 @@ type CloudTrailEvent struct {
} `json:"tlsDetails"`
}
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "outbound-assumed-roles"
@@ -161,7 +164,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
<-receiverDone
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Type",
//"Source Account",
@@ -171,12 +174,48 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
"Log Entry Timestamp",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Type",
//"Source Account",
"Source Principal",
//"Destination Account",
"Destination Principal",
"Log Entry Timestamp",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Region",
"Type",
//"Source Account",
"Source Principal",
//"Destination Account",
"Destination Principal",
"Log Entry Timestamp",
}
}
// Table rows
for i := range m.OutboundAssumeRoleEntries {
m.output.Body = append(
m.output.Body,
[]string{
m.OutboundAssumeRoleEntries[i].AWSService,
aws.ToString(m.Caller.Account),
m.OutboundAssumeRoleEntries[i].Region,
m.OutboundAssumeRoleEntries[i].Type,
//m.OutboundAssumeRoleEntries[i].SourceAccount,
@@ -191,9 +230,6 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -202,9 +238,10 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+64 -20
View File
@@ -21,12 +21,14 @@ type IamPermissionsModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
@@ -76,18 +78,19 @@ type PermissionsRow struct {
Arn string
PolicyType string
PolicyName string
PolicyArn string
Effect string
Action string
Resource string
Condition string
}
func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDirectory string, verbosity int, principal string) {
// These stuct values are used by the output module
func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbosity int, principal string) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "permissions"
m.output.FullFilename = m.output.CallingModule
filename := m.output.CallingModule
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
@@ -98,19 +101,20 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
if principal != "" {
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
}
m.getGAAD()
m.parsePermissions(principal)
m.output.Headers = []string{
//"Service",
"Account",
"Type",
"Name",
//"Arn",
"Arn",
"Policy",
"Policy Name",
"Policy Arn",
"Effect",
"Action",
"Resource",
@@ -122,12 +126,13 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.output.Body = append(
m.output.Body,
[]string{
//m.Rows[i].AWSService,
aws.ToString(m.Caller.Account),
m.Rows[i].Type,
m.Rows[i].Name,
//m.Rows[i].Arn,
m.Rows[i].Arn,
m.Rows[i].PolicyType,
m.Rows[i].PolicyName,
m.Rows[i].PolicyArn,
m.Rows[i].Effect,
m.Rows[i].Action,
m.Rows[i].Resource,
@@ -139,9 +144,6 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector3(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -149,10 +151,48 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Type",
//"Name",
"Arn",
"Policy",
"Policy Name",
"Policy Arn",
"Effect",
"Action",
"Resource",
"Condition"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{
"Type",
"Name",
//"Arn",
"Policy",
"Policy Name",
"Effect",
"Action",
"Resource",
"Condition",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
TableCols: tableCols,
Body: m.output.Body,
Name: filename,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -402,6 +442,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
PolicyArn: p.Arn,
Effect: effect,
Action: action,
Resource: resource,
@@ -428,6 +469,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
PolicyArn: p.Arn,
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
@@ -473,6 +515,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: action,
Resource: resource,
@@ -498,6 +541,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
+44 -13
View File
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
@@ -17,12 +18,14 @@ import (
type PmapperModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
pmapperGraph graph.Graph[string, string]
@@ -136,8 +139,8 @@ func (m *PmapperModule) DoesPrincipalHaveAdmin(principal string) bool {
return false
}
func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *PmapperModule) PrintPmapperData(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "pmapper"
@@ -163,11 +166,39 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
}
m.output.Headers = []string{
"Account",
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
}
//Table rows
var isAdmin, pathToAdmin string
for i := range m.Nodes {
@@ -186,6 +217,7 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Nodes[i].Arn,
isAdmin,
pathToAdmin,
@@ -196,8 +228,6 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -206,9 +236,10 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+56 -25
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
@@ -16,12 +17,14 @@ type IamPrincipalsModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Users []User
@@ -61,8 +64,8 @@ type Role struct {
InlinePolicies []string
}
func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "principals"
@@ -92,13 +95,44 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
m.output.Headers = []string{
"Service",
"Account",
"Type",
"Name",
"Arn",
"AttachedPolicies",
"InlinePolicies",
}
// "AttachedPolicies",
// "InlinePolicies",
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Type",
"Name",
"Arn",
//"AttachedPolicies",
//"InlinePolicies",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Type",
"Name",
"Arn",
// "AttachedPolicies",
// "InlinePolicies",
}
}
//Table rows
@@ -106,13 +140,12 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
m.output.Body = append(
m.output.Body,
[]string{
m.Users[i].AWSService,
aws.ToString(m.Caller.Account),
m.Users[i].Type,
m.Users[i].Name,
m.Users[i].Arn,
// m.Users[i].AttachedPolicies,
// m.Users[i].InlinePolicies,
strings.Join(m.Users[i].AttachedPolicies, " , "),
strings.Join(m.Users[i].InlinePolicies, " , "),
},
)
@@ -122,22 +155,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
m.output.Body = append(
m.output.Body,
[]string{
m.Roles[i].AWSService,
aws.ToString(m.Caller.Account),
m.Roles[i].Type,
m.Roles[i].Name,
m.Roles[i].Arn,
// m.Roles[i].AttachedPolicies,
// m.Roles[i].InlinePolicies,
strings.Join(m.Roles[i].AttachedPolicies, " , "),
strings.Join(m.Roles[i].InlinePolicies, " , "),
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -146,9 +176,10 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+50 -16
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/internal"
@@ -20,12 +21,14 @@ type RAMModule struct {
// General configuration data
RAMClient *ram.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Resources []Resource
@@ -44,8 +47,8 @@ type Resource struct {
ShareType string
}
func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *RAMModule) PrintRAM(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ram"
@@ -91,7 +94,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Share Name",
"Type",
@@ -99,12 +102,44 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
"Share Type",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Share Name",
"Type",
"Owner",
"Share Type",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Share Name",
"Type",
"Owner",
"Share Type",
}
}
// Table rows
for i := range m.Resources {
m.output.Body = append(
m.output.Body,
[]string{
m.Resources[i].AWSService,
aws.ToString(m.Caller.Account),
m.Resources[i].Region,
m.Resources[i].Name,
m.Resources[i].Type,
@@ -116,9 +151,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -127,9 +160,10 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+223 -37
View File
@@ -20,12 +20,17 @@ import (
type ResourceTrustsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSProfile string
CloudFoxVersion string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
AWSMFAToken string
AWSConfig aws.Config
AWSProfileProvided string
AWSProfileStub string
CloudFoxVersion string
Resources2 []Resource2
CommandCounter internal.CommandCounter
@@ -44,23 +49,30 @@ type Resource2 struct {
ResourcePolicySummary string
Public string
Interesting string
TrustedPrincipals string
TrustsCrossAccount string
TrustsAllAccounts string
HasConditions string
}
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "resource-trusts"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Lambda, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
if m.AWSProfileProvided == "" {
m.AWSProfileStub = internal.BuildAWSPath(m.Caller)
} else {
m.AWSProfileStub = m.AWSProfileProvided
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileProvided, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
@@ -95,13 +107,42 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
//"Account ID",
"Account",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
} else {
tableCols = []string{
//"Account ID",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
}
// sort the table roles by Interesting
sort.Slice(m.Resources2, func(i, j int) bool {
return m.Resources2[j].Interesting > m.Resources2[i].Interesting
@@ -112,7 +153,7 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
m.output.Body = append(
m.output.Body,
[]string{
//m.Resources2[i].AccountID,
aws.ToString(m.Caller.Account),
m.Resources2[i].ARN,
m.Resources2[i].Public,
m.Resources2[i].Interesting,
@@ -122,8 +163,6 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -132,20 +171,21 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.PrefixIdentifier = m.AWSProfileStub
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), strconv.Itoa(len(m.output.Body)))
//fmt.Printf("[%s][%s] Resource policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), m.output.CallingModule)
}
@@ -212,6 +252,24 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("secretsmanager", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("glue", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
@@ -238,7 +296,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.WrapTable, m.AWSMFAToken)
ListTopics, err := cloudFoxSNSClient.listTopics(r)
if err != nil {
@@ -270,7 +328,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
topic.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
// If there is a resource policy, convert the resource policy into plain english
if !topic.Policy.IsEmpty() {
for i, statement := range topic.Policy.Statement {
@@ -314,7 +372,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.AWSMFAToken)
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
@@ -325,7 +383,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
for _, b := range ListBuckets {
var statementSummaryInEnglish string
var isInteresting string = "No"
bucket := &Bucket{
bucket := &BucketRow{
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
}
name := aws.ToString(b.Name)
@@ -357,7 +415,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
bucket.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
// If there is a resource policy, convert the resource policy into plain english
if !bucket.Policy.IsEmpty() {
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
@@ -397,7 +455,7 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListQueues, err := cloudFoxSQSClient.listQueues(r)
if err != nil {
@@ -459,9 +517,9 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
DescribeRepositories, err := sdk.CachedECRDescribeRepositories(cloudFoxECRClient.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
return
@@ -523,7 +581,7 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
if err != nil {
@@ -597,7 +655,7 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
if err != nil {
@@ -662,7 +720,7 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
@@ -718,6 +776,134 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
}
}
// getSecretsManagerSecretsPoliciesPerRegion retrieves the resource policies for all Secrets Manager secrets in the specified region.
// It sends the resulting Resource2 objects to the dataReceiver channel.
// It uses a semaphore to limit the number of concurrent requests and a WaitGroup to wait for all requests to complete.
// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to.
func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSecretsManagerClient := InitSecretsManagerClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListSecrets, err := sdk.CachedSecretsManagerListSecrets(cloudFoxSecretsManagerClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, s := range ListSecrets {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
secretPolicy, err := sdk.CachedSecretsManagerGetResourcePolicy(cloudFoxSecretsManagerClient, aws.ToString(s.ARN), r, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if secretPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !secretPolicy.IsEmpty() {
for i, statement := range secretPolicy.Statement {
prefix := ""
if len(secretPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(s.ARN),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(s.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxGlueClient := InitGlueClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ResourcePolicies, err := sdk.CachedGlueGetResourcePolicies(cloudFoxGlueClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, resourcePolicy := range ResourcePolicies {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
if resourcePolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !resourcePolicy.IsEmpty() {
for _, statement := range resourcePolicy.Statement {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
resources := statement.Resource
for _, resource := range resources {
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: resource,
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: resource,
Region: r,
Interesting: isInteresting,
}
}
}
}
}
}
func isResourcePolicyInteresting(statementSummaryInEnglish string) bool {
// check if the statement has any of the following items, but make sure the check is case insensitive
// if it does, then return true
+43
View File
@@ -0,0 +1,43 @@
package aws
import (
"testing"
)
func TestIsResourcePolicyInteresting(t *testing.T) {
testCases := []struct {
name string
input string
expected bool
}{
{
name: "test1",
input: "Everyone can sqs:SendMessage & can sqs:ReceiveMessage",
expected: true,
},
{
name: "PrincipalOrgPaths",
input: "aws:PrincipalOrgPaths",
expected: true,
},
{
name: "Empty",
input: "",
expected: false,
},
{
name: "NotInteresting",
input: "sns.amazonaws.com can lambda:InvokeFunction",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := isResourcePolicyInteresting(tc.input)
if actual != tc.expected {
t.Errorf("Expected %v but got %v for input %s", tc.expected, actual, tc.input)
}
})
}
}
+383 -261
View File
@@ -1,22 +1,19 @@
package aws
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/knownawsaccountslookup"
"github.com/sirupsen/logrus"
)
@@ -31,12 +28,18 @@ type RoleTrustsModule struct {
CommandCounter internal.CommandCounter
SkipAdminCheck bool
WrapTable bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
AWSOutputType string
AWSTableCols string
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Main module data
AnalyzedRoles []AnalyzedRole
AnalyzedRoles []AnalyzedRole
RoleTrustTable []RoleTrustRow
vendors *knownawsaccountslookup.Vendors
// Used to store output data for pretty printing
output internal.OutputData2
@@ -44,49 +47,27 @@ type RoleTrustsModule struct {
modLog *logrus.Entry
}
type RoleTrustRow struct {
RoleARN string
RoleName string
TrustedPrincipal string
TrustedService string
TrustedFederatedProvider string
TrustedFederatedSubject string
ExternalID string
IsAdmin string
CanPrivEsc string
}
type AnalyzedRole struct {
roleARN *string
trustsDoc trustPolicyDocument
trustsDoc policy.TrustPolicyDocument
// trustType string // UNUSED FIELD, PLEASE REVIEW
Admin string
CanPrivEsc string
}
type trustPolicyDocument struct {
Version string `json:"Version"`
Statement []RoleTrustStatementEntry `json:"Statement"`
}
type RoleTrustStatementEntry struct {
Sid string `json:"Sid"`
Effect string `json:"Effect"`
Principal struct {
AWS ListOfPrincipals `json:"AWS"`
Service ListOfPrincipals `json:"Service"`
Federated ListOfPrincipals `json:"Federated"`
} `json:"Principal"`
Action string `json:"Action"`
Condition struct {
StringEquals struct {
StsExternalID string `json:"sts:ExternalId"`
SAMLAud string `json:"SAML:aud"`
OidcEksSub string `json:"OidcEksSub"`
OidcEksAud string `json:"OidcEksAud"`
CognitoAud string `json:"cognito-identity.amazonaws.com:aud"`
} `json:"StringEquals"`
StringLike struct {
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
OidcEksSub string `json:"OidcEksSub"`
OidcEksAud string `json:"OidcEksAud"`
} `json:"StringLike"`
ForAnyValueStringLike struct {
CognitoAMR string `json:"cognito-identity.amazonaws.com:amr"`
} `json:"ForAnyValue:StringLike"`
} `json:"Condition"`
}
func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory string, verbosity int) {
func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "role-trusts"
@@ -97,6 +78,9 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
localAdminMap := make(map[string]bool)
m.vendors = knownawsaccountslookup.NewVendorMap()
m.vendors.PopulateKnownAWSAccounts()
fmt.Printf("[%s][%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
//fmt.Printf("[%s][%s] Looking for pmapper data for this account and building a PrivEsc graph in golang if it exists.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
@@ -118,6 +102,7 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
}
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -129,25 +114,35 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
principalsHeader, principalsBody, principalTableCols := m.printPrincipalTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: principalsHeader,
Body: principalsBody,
Name: "role-trusts-principals",
Header: principalsHeader,
Body: principalsBody,
TableCols: principalTableCols,
Name: "role-trusts-principals",
})
rootPrincipalsHeader, rootPrincipalsBody, rootPrincipalTableCols := m.printPrincipalTrustsRootOnly(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: rootPrincipalsHeader,
Body: rootPrincipalsBody,
TableCols: rootPrincipalTableCols,
Name: "role-trusts-principals-root-trusts-without-external-id",
SkipPrintToScreen: true,
})
servicesHeader, servicesBody, serviceTableCols := m.printServiceTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: servicesHeader,
Body: servicesBody,
TableCols: serviceTableCols,
Name: "role-trusts-services",
})
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, outputDirectory)
federatedHeader, federatedBody, federatedTableCols := m.printFederatedTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: servicesHeader,
Body: servicesBody,
Name: "role-trusts-services",
})
federatedHeader, federatedBody := m.printFederatedTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: federatedHeader,
Body: federatedBody,
Name: "role-trusts-federated",
Header: federatedHeader,
Body: federatedBody,
TableCols: federatedTableCols,
Name: "role-trusts-federated",
})
o.WriteFullOutput(o.Table.TableFiles, nil)
@@ -157,251 +152,414 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(servicesBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
fmt.Printf("[%s][%s] %s service role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(federatedBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
fmt.Printf("[%s][%s] %s federated role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
}
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-principals"
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Account", "Role Arn", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{"Role Name", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := principal
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
//check to see if the accountID is known
if strings.Contains(principal, "arn:aws:iam::") || strings.Contains(principal, "root") {
accountID := strings.Split(principal, ":")[4]
vendorName := m.vendors.GetVendorNameFromAccountID(accountID)
if vendorName != "" {
principal = fmt.Sprintf("%s (%s)", principal, vendorName)
}
}
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedPrincipal,
RoleTrustRow.ExternalID,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
return header, body, tableCols
}
// printPrincipalTrusts but only those that have a trusted principal that contains :root and also does not have an external ID
func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-principals-root-trusts-without-external-id"
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Account", "Role Arn", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{"Role Name", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == "" {
accountID := strings.Split(principal, ":")[4]
vendorName := m.vendors.GetVendorNameFromAccountID(accountID)
if vendorName != "" {
principal = fmt.Sprintf("%s (%s)", principal, vendorName)
}
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedPrincipal,
RoleTrustRow.ExternalID,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// ////m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// //internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
// fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
m.sortTrustsTablePerTrustedPrincipal()
return header, body, tableCols
}
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printServiceTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-services"
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Service",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Account", "Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
tableCols = []string{"Role Name", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, service := range statement.Principal.Service {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := service
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedService: service,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedService,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
// sort the rows based on column 2 (service)
sort.SliceStable(body, func(i, j int) bool {
return body[i][1] < body[j][1]
return body[i][3] < body[j][3]
})
//m.sortTrustsTablePerTrustedPrincipal()
return header, body
return header, body, tableCols
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-federated"
var column1, column3, column2 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Account", "Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{"Role Name", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
for _, statement := range role.trustsDoc.Statement {
if len(statement.Principal.Federated) > 0 {
column2, column3 = parseFederatedTrustPolicy(statement)
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
provider, subjects := parseFederatedTrustPolicy(statement)
for _, subject := range subjects {
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedFederatedProvider: provider,
TrustedFederatedSubject: subject,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedFederatedProvider,
RoleTrustRow.TrustedFederatedSubject,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
return header, body
return header, body, tableCols
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, string) {
var column2, column3 string
if statement.Condition.StringLike.TokenActionsGithubusercontentComAud != "" || len(statement.Condition.StringLike.TokenActionsGithubusercontentComSub) > 0 {
column2 = "GitHub Actions" // (" + statement.Principal.Federated[0] + ")"
trustedRepos := strings.Join(statement.Condition.StringLike.TokenActionsGithubusercontentComSub, "\n")
if trustedRepos == "" {
column3 = "ALL REPOS!!!"
func parseFederatedTrustPolicy(statement policy.RoleTrustStatementEntry) (string, []string) {
var provider string
var subjects []string
if len(statement.Principal.Federated) > 1 {
sharedLogger.Warnf("Multiple federated providers found in the trust policy. This is not currently supported. Please review the trust policy for specifics.")
provider = "Multiple Federated Providers"
subjects = append(subjects, "Review policy for specifics\nand submit issue to cloudfox repo.")
}
switch {
// lets use the Federated field to determine the provider, then based on the provider we can grab the list of subjects
case strings.Contains(statement.Principal.Federated[0], "token.actions.githubusercontent.com"):
provider = "GitHub"
if len(statement.Condition.StringLike.TokenActionsGithubusercontentComSub) > 0 {
subjects = append(subjects, statement.Condition.StringLike.TokenActionsGithubusercontentComSub...)
} else if len(statement.Condition.StringEquals.TokenActionsGithubusercontentComSub) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.TokenActionsGithubusercontentComSub...)
} else {
column3 = trustedRepos
subjects = append(subjects, "ALL REPOS!!!")
}
} else if statement.Condition.StringEquals.SAMLAud == "https://signin.aws.amazon.com/saml" {
if strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
} else if strings.Contains(statement.Principal.Federated[0], "Okta") {
column2 = "Okta" // (" + statement.Principal.Federated[0] + ")"
}
column3 = "Not applicable"
} else if statement.Condition.StringEquals.OidcEksAud != "" || statement.Condition.StringEquals.OidcEksSub != "" || statement.Condition.StringLike.OidcEksAud != "" || statement.Condition.StringLike.OidcEksSub != "" {
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.StringEquals.OidcEksSub != "" {
column3 = statement.Condition.StringEquals.OidcEksSub
} else if statement.Condition.StringLike.OidcEksSub != "" {
column3 = statement.Condition.StringLike.OidcEksSub
case strings.Contains(statement.Principal.Federated[0], "oidc.eks"):
provider = "EKS"
if len(statement.Condition.StringLike.OidcEksSub) > 0 {
subjects = append(subjects, statement.Condition.StringLike.OidcEksSub...)
} else if len(statement.Condition.StringEquals.OidcEksSub) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.OidcEksSub...)
} else {
column3 = "ALL SERVICE ACCOUNTS!"
subjects = append(subjects, "ALL SERVICE ACCOUNTS!!!")
}
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
// terraform case
case strings.Contains(statement.Principal.Federated[0], "app.terraform.io"):
provider = "Terraform Cloud"
if len(statement.Condition.StringLike.TerraformSub) > 0 {
subjects = append(subjects, statement.Condition.StringLike.TerraformSub...)
} else if len(statement.Condition.StringEquals.TerraformSub) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.TerraformSub...)
} else {
subjects = append(subjects, "ALL WORKSPACES")
}
} else {
if column2 == "" && strings.Contains(statement.Principal.Federated[0], "oidc.eks") {
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
column3 = "ALL SERVICE ACCOUNTS!"
} else if column2 == "" && strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
// Azure AD case
case strings.Contains(statement.Principal.Federated[0], "http://sts.windows.net"):
provider = "Azure AD"
if len(statement.Condition.StringLike.AzureADIss) > 0 {
subjects = append(subjects, statement.Condition.StringLike.AzureADIss...)
} else if len(statement.Condition.StringEquals.AzureADIss) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.AzureADIss...)
} else {
subjects = append(subjects, "ALL ISSUERS")
}
// AWS SSO case
case strings.Contains(statement.Principal.Federated[0], "AWSSSO"):
provider = "AWS SSO"
subjects = append(subjects, "Not applicable")
// okta case
case strings.Contains(statement.Principal.Federated[0], "Okta"):
provider = "Okta"
subjects = append(subjects, "Not applicable")
// cognito case
case statement.Principal.Federated[0] == "cognito-identity.amazonaws.com":
provider = "Cognito"
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
subjects = append(subjects, statement.Condition.ForAnyValueStringLike.CognitoAMR)
} else {
subjects = append(subjects, "ALL IDENTITIES")
}
// google workspace case
case strings.Contains(statement.Principal.Federated[0], "workspace.google.com"):
provider = "Google Workspace"
if len(statement.Condition.StringLike.GoogleWorkspaceSub) > 0 {
subjects = append(subjects, statement.Condition.StringLike.GoogleWorkspaceSub...)
} else if len(statement.Condition.StringEquals.GoogleWorkspaceSub) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.GoogleWorkspaceSub...)
} else {
subjects = append(subjects, "ALL USERS")
}
// GCP case
case strings.Contains(statement.Principal.Federated[0], "accounts.google.com"):
provider = "GCP"
if len(statement.Condition.StringLike.GCPSub) > 0 {
subjects = append(subjects, statement.Condition.StringLike.GCPSub...)
} else if len(statement.Condition.StringEquals.GCPSub) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.GCPSub...)
} else {
subjects = append(subjects, "ALL USERS")
}
// auth0 case
//not ready yet
// case strings.Contains(statement.Principal.Federated[0], "auth0.com"):
// provider = "Auth0"
// if len(statement.Condition.ForAnyValueStringLike.Auth0Amr) > 0 {
// subjects = append(subjects, statement.Condition.ForAnyValueStringLike.Auth0Amr...)
// } else {
// subjects = append(subjects, "ALL GROUPS")
// }
// circleci case
case strings.Contains(statement.Principal.Federated[0], "oidc.circleci.com"):
provider = "CircleCI"
if len(statement.Condition.StringLike.CircleCIAud) > 0 {
subjects = append(subjects, statement.Condition.StringLike.CircleCIAud...)
} else if len(statement.Condition.StringEquals.CircleCIAud) > 0 {
subjects = append(subjects, statement.Condition.StringEquals.CircleCIAud...)
} else {
subjects = append(subjects, "ALL PROJECTS")
}
default:
provider = "Unknown Federated Provider"
subjects = append(subjects, "Review policy for specifics\nand submit issue to cloudfox repo.")
}
return column2, column3
return provider, subjects
}
func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
@@ -421,7 +579,7 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
}
for _, role := range ListRoles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
trustsdoc, err := policy.ParseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -435,45 +593,9 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
Admin: "",
CanPrivEsc: "",
})
}
}
}
func parseRoleTrustPolicyDocument(role types.Role) (trustPolicyDocument, error) {
document, _ := url.QueryUnescape(aws.ToString(role.AssumeRolePolicyDocument))
// These next six lines are a hack, needed because the EKS OIDC json field name is dynamic
// and therefore can't be used to unmarshall in a predictable way. The hack involves replacing
// the random pattern with a predictable one so that we can add the predictable one in the struct
// used to unmarshall.
pattern := `(\w+)\:`
pattern2 := `".[a-zA-Z0-9\-\.]+/id/`
var reEKSSub = regexp.MustCompile(pattern2 + pattern + "sub")
var reEKSAud = regexp.MustCompile(pattern2 + pattern + "aud")
document = reEKSSub.ReplaceAllString(document, "\"OidcEksSub")
document = reEKSAud.ReplaceAllString(document, "\"OidcEksAud")
var parsedDocumentToJSON trustPolicyDocument
_ = json.Unmarshal([]byte(document), &parsedDocumentToJSON)
return parsedDocumentToJSON, nil
}
// A custom unmarshaller is necessary because the list of principals can be an array of strings or a string.
// https://stackoverflow.com/questions/65854778/parsing-arn-from-iam-policy-using-regex
type ListOfPrincipals []string
func (r *ListOfPrincipals) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err == nil {
*r = append(*r, s)
return nil
}
var ss []string
if err := json.Unmarshal(b, &ss); err == nil {
*r = ss
return nil
}
return errors.New("cannot unmarshal neither to a string nor a slice of strings")
}
+44 -14
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
@@ -17,9 +18,11 @@ type Route53Module struct {
// General configuration data
Route53Client sdk.AWSRoute53ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
@@ -41,9 +44,9 @@ type Record struct {
PrivateZone string
}
func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string, verbosity int) {
func (m *Route53Module) PrintRoute53(outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "route53"
@@ -59,19 +62,48 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
m.getRoute53Records()
m.output.Headers = []string{
"Service",
"Account",
"Name",
"Type",
"Value",
"PrivateZone",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"Type",
"Value",
"PrivateZone",
}
} else {
tableCols = []string{
"Name",
"Type",
"Value",
"PrivateZone",
}
}
// Table rows
for i := range m.Records {
m.output.Body = append(
m.output.Body,
[]string{
m.Records[i].AWSService,
aws.ToString(m.Caller.Account),
m.Records[i].Name,
m.Records[i].Type,
m.Records[i].Value,
@@ -83,10 +115,7 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -95,9 +124,10 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+340 -1
View File
@@ -6,16 +6,51 @@ import (
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
"github.com/patrickmn/go-cache"
)
type APIGatewayClientInterface interface {
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
GetStages(context.Context, *apigateway.GetStagesInput, ...func(*apigateway.Options)) (*apigateway.GetStagesOutput, error)
GetResources(context.Context, *apigateway.GetResourcesInput, ...func(*apigateway.Options)) (*apigateway.GetResourcesOutput, error)
GetDomainNames(context.Context, *apigateway.GetDomainNamesInput, ...func(*apigateway.Options)) (*apigateway.GetDomainNamesOutput, error)
GetBasePathMappings(context.Context, *apigateway.GetBasePathMappingsInput, ...func(*apigateway.Options)) (*apigateway.GetBasePathMappingsOutput, error)
GetMethod(context.Context, *apigateway.GetMethodInput, ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error)
GetUsagePlans(context.Context, *apigateway.GetUsagePlansInput, ...func(*apigateway.Options)) (*apigateway.GetUsagePlansOutput, error)
GetUsagePlanKeys(context.Context, *apigateway.GetUsagePlanKeysInput, ...func(*apigateway.Options)) (*apigateway.GetUsagePlanKeysOutput, error)
}
type CachedGetMethodOutput struct {
ApiKeyRequired *bool
AuthorizationScopes []string
AuthorizationType *string
AuthorizerId *string
HttpMethod *string
MethodIntegration *apiGatewayTypes.Integration
MethodResponses map[string]apiGatewayTypes.MethodResponse
OperationName *string
RequestModels map[string]string
RequestParameters map[string]bool
RequestValidatorId *string
ResourceId *string
}
type CachedGetStagesOutput struct {
Item []apiGatewayTypes.Stage
}
func init() {
gob.Register([]apiGatewayTypes.RestApi{})
gob.Register(CachedGetStagesOutput{})
gob.Register([]apiGatewayTypes.Resource{})
gob.Register([]apiGatewayTypes.DomainName{})
gob.Register([]apiGatewayTypes.BasePathMapping{})
gob.Register(CachedGetMethodOutput{})
gob.Register([]apiGatewayTypes.UsagePlan{})
gob.Register([]apiGatewayTypes.UsagePlanKey{})
}
// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
@@ -51,6 +86,310 @@ func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID str
}
PaginationControl = GetRestApis.Position
}
internal.Cache.Set(cacheKey, restAPIs, cache.DefaultExpiration)
return restAPIs, nil
}
// create a CachedApiGatewayGetStages function that accepts a client, account id, region, and rest api id. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetStages(client APIGatewayClientInterface, accountID string, region string, restAPIID string) (*apigateway.GetStagesOutput, error) {
cacheKey := fmt.Sprintf("%s-apigateway-GetStages-%s-%s", accountID, region, restAPIID)
cached, found := internal.Cache.Get(cacheKey)
if found {
// Convert cached data back to GetStagesOutput before returning
cachedOutput := cached.(*CachedGetStagesOutput) // Ensure this type assertion matches your caching logic
return fromCachedGetStagesOutput(cachedOutput), nil
}
GetStages, err := client.GetStages(
context.TODO(),
&apigateway.GetStagesInput{
RestApiId: &restAPIID,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return &apigateway.GetStagesOutput{}, err
}
// Convert GetStagesOutput to CachedGetStagesOutput before caching
cachedVersion := toCachedGetStagesOutput(GetStages)
internal.Cache.Set(cacheKey, cachedVersion, cache.DefaultExpiration)
return GetStages, err
}
// Convert from AWS SDK type to custom type for caching
func toCachedGetStagesOutput(gso *apigateway.GetStagesOutput) *CachedGetStagesOutput {
return &CachedGetStagesOutput{
Item: gso.Item,
}
}
// Convert back to AWS SDK type after fetching from cache
func fromCachedGetStagesOutput(cgso *CachedGetStagesOutput) *apigateway.GetStagesOutput {
return &apigateway.GetStagesOutput{
Item: cgso.Item,
// Initialize ResultMetadata or leave it as zero value if it's not required for your use case
}
}
// create a CachedApiGatewayGetResources function that accepts a client, account id, region, and rest api id. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetResources(client APIGatewayClientInterface, accountID string, region string, restAPIID string) ([]apiGatewayTypes.Resource, error) {
var PaginationControl *string
var resources []apiGatewayTypes.Resource
cacheKey := fmt.Sprintf("%s-apigateway-GetResources-%s-%s", accountID, region, restAPIID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.Resource), nil
}
for {
GetResources, err := client.GetResources(
context.TODO(),
&apigateway.GetResourcesInput{
RestApiId: &restAPIID,
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return resources, err
}
resources = append(resources, GetResources.Items...)
//pagination
if GetResources.Position == nil {
break
}
PaginationControl = GetResources.Position
}
internal.Cache.Set(cacheKey, resources, cache.DefaultExpiration)
return resources, nil
}
// create a CachedApiGatewayGetDomainNames function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
func CachedApiGatewayGetDomainNames(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.DomainName, error) {
var PaginationControl *string
var domainNames []apiGatewayTypes.DomainName
cacheKey := fmt.Sprintf("%s-apigateway-GetDomainNames-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.DomainName), nil
}
for {
GetDomainNames, err := client.GetDomainNames(
context.TODO(),
&apigateway.GetDomainNamesInput{
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return domainNames, err
}
domainNames = append(domainNames, GetDomainNames.Items...)
//pagination
if GetDomainNames.Position == nil {
break
}
PaginationControl = GetDomainNames.Position
}
internal.Cache.Set(cacheKey, domainNames, cache.DefaultExpiration)
return domainNames, nil
}
// create a CachedApiGatewayGetBasePathMappings function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
func CachedApiGatewayGetBasePathMappings(client APIGatewayClientInterface, accountID string, region string, domain *string) ([]apiGatewayTypes.BasePathMapping, error) {
var PaginationControl *string
var basePathMappings []apiGatewayTypes.BasePathMapping
cacheKey := fmt.Sprintf("%s-apigateway-GetBasePathMappings-%s-%s", accountID, region, aws.ToString(domain))
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.BasePathMapping), nil
}
for {
GetBasePathMappings, err := client.GetBasePathMappings(
context.TODO(),
&apigateway.GetBasePathMappingsInput{
DomainName: domain,
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return basePathMappings, err
}
basePathMappings = append(basePathMappings, GetBasePathMappings.Items...)
//pagination
if GetBasePathMappings.Position == nil {
break
}
PaginationControl = GetBasePathMappings.Position
}
internal.Cache.Set(cacheKey, basePathMappings, cache.DefaultExpiration)
return basePathMappings, nil
}
// create a CachedApiGatewayGetMethod function that accepts a client, account id, region, rest api id, and resource id. Make sure it handles caching, the region option and pagination if needed
func CachedApiGatewayGetMethod(client APIGatewayClientInterface, accountID string, region string, restAPIID string, resourceID string, method string) (*apigateway.GetMethodOutput, error) {
cacheKey := fmt.Sprintf("%s-apigateway-GetMethod-%s-%s-%s-%s", accountID, region, restAPIID, resourceID, method)
cached, found := internal.Cache.Get(cacheKey)
if found {
// Convert cached data back to GetMethodOutput before returning
cachedOutput := cached.(*CachedGetMethodOutput) // Ensure this type assertion matches your caching logic
return fromCachedGetMethodOutput(cachedOutput), nil
}
GetMethod, err := client.GetMethod(
context.TODO(),
&apigateway.GetMethodInput{
RestApiId: &restAPIID,
ResourceId: &resourceID,
HttpMethod: &method,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return &apigateway.GetMethodOutput{}, err
}
// Convert GetMethodOutput to CachedGetMethodOutput before caching
cachedVersion := toCachedGetMethodOutput(GetMethod)
internal.Cache.Set(cacheKey, cachedVersion, cache.DefaultExpiration)
return GetMethod, nil
}
// Convert from AWS SDK type to custom type for caching
func toCachedGetMethodOutput(gmo *apigateway.GetMethodOutput) *CachedGetMethodOutput {
return &CachedGetMethodOutput{
ApiKeyRequired: gmo.ApiKeyRequired,
AuthorizationScopes: gmo.AuthorizationScopes,
AuthorizationType: gmo.AuthorizationType,
AuthorizerId: gmo.AuthorizerId,
HttpMethod: gmo.HttpMethod,
MethodIntegration: gmo.MethodIntegration,
MethodResponses: gmo.MethodResponses,
OperationName: gmo.OperationName,
RequestModels: gmo.RequestModels,
RequestParameters: gmo.RequestParameters,
RequestValidatorId: gmo.RequestValidatorId,
}
}
// Convert back to AWS SDK type after fetching from cache
func fromCachedGetMethodOutput(cgmo *CachedGetMethodOutput) *apigateway.GetMethodOutput {
return &apigateway.GetMethodOutput{
ApiKeyRequired: cgmo.ApiKeyRequired,
AuthorizationScopes: cgmo.AuthorizationScopes,
AuthorizationType: cgmo.AuthorizationType,
AuthorizerId: cgmo.AuthorizerId,
HttpMethod: cgmo.HttpMethod,
MethodIntegration: cgmo.MethodIntegration,
MethodResponses: cgmo.MethodResponses,
OperationName: cgmo.OperationName,
RequestModels: cgmo.RequestModels,
RequestParameters: cgmo.RequestParameters,
RequestValidatorId: cgmo.RequestValidatorId,
// ResultMetadata: This will be missing or zero value; handle accordingly
}
}
// create a CachedApiGatewayGetUsagePlans function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
func CachedApiGatewayGetUsagePlans(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.UsagePlan, error) {
var PaginationControl *string
var usagePlans []apiGatewayTypes.UsagePlan
cacheKey := fmt.Sprintf("%s-apigateway-GetUsagePlans-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.UsagePlan), nil
}
for {
GetUsagePlans, err := client.GetUsagePlans(
context.TODO(),
&apigateway.GetUsagePlansInput{
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return usagePlans, err
}
usagePlans = append(usagePlans, GetUsagePlans.Items...)
//pagination
if GetUsagePlans.Position == nil {
break
}
PaginationControl = GetUsagePlans.Position
}
internal.Cache.Set(cacheKey, usagePlans, cache.DefaultExpiration)
return usagePlans, nil
}
// create a CachedApiGatewayGetUsagePlanKeys function that accepts a client, account id, region, and usage plan id. Make sure it handles caching, the region option and pagination if needed
func CachedApiGatewayGetUsagePlanKeys(client APIGatewayClientInterface, accountID string, region string, usagePlanID string) ([]apiGatewayTypes.UsagePlanKey, error) {
var PaginationControl *string
var usagePlanKeys []apiGatewayTypes.UsagePlanKey
cacheKey := fmt.Sprintf("%s-apigateway-GetUsagePlanKeys-%s-%s", accountID, region, usagePlanID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.UsagePlanKey), nil
}
for {
GetUsagePlanKeys, err := client.GetUsagePlanKeys(
context.TODO(),
&apigateway.GetUsagePlanKeysInput{
UsagePlanId: &usagePlanID,
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return usagePlanKeys, err
}
usagePlanKeys = append(usagePlanKeys, GetUsagePlanKeys.Items...)
//pagination
if GetUsagePlanKeys.Position == nil {
break
}
PaginationControl = GetUsagePlanKeys.Position
}
internal.Cache.Set(cacheKey, usagePlanKeys, cache.DefaultExpiration)
return usagePlanKeys, nil
}
+117 -2
View File
@@ -15,12 +15,127 @@ func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apig
return &apigateway.GetRestApisOutput{
Items: []apiGatewayTypes.RestApi{
{
Id: aws.String("api1"),
Id: aws.String("abcdefg"),
Name: aws.String("api1"),
EndpointConfiguration: &apiGatewayTypes.EndpointConfiguration{
Types: []apiGatewayTypes.EndpointType{
apiGatewayTypes.EndpointTypePrivate,
},
},
},
{
Id: aws.String("api2"),
Id: aws.String("qwerty"),
Name: aws.String("api2"),
EndpointConfiguration: &apiGatewayTypes.EndpointConfiguration{
Types: []apiGatewayTypes.EndpointType{
apiGatewayTypes.EndpointTypeRegional,
},
},
},
},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetStages(ctx context.Context, input *apigateway.GetStagesInput, options ...func(*apigateway.Options)) (*apigateway.GetStagesOutput, error) {
return &apigateway.GetStagesOutput{
Item: []apiGatewayTypes.Stage{
{
StageName: aws.String("stage1"),
},
{
StageName: aws.String("stage2"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetResources(ctx context.Context, input *apigateway.GetResourcesInput, options ...func(*apigateway.Options)) (*apigateway.GetResourcesOutput, error) {
return &apigateway.GetResourcesOutput{
Items: []apiGatewayTypes.Resource{
{
Id: aws.String("resource1"),
Path: aws.String("/path1"),
ResourceMethods: map[string]apiGatewayTypes.Method{
"GET": {
ApiKeyRequired: aws.Bool(true),
AuthorizerId: aws.String("authorizer1"),
OperationName: aws.String("operation1"),
},
},
},
{
Id: aws.String("resource2"),
Path: aws.String("/path2"),
ResourceMethods: map[string]apiGatewayTypes.Method{
"ANY": {
ApiKeyRequired: aws.Bool(true),
AuthorizerId: aws.String("authorizer2"),
OperationName: aws.String("operation2"),
},
},
},
},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetDomainNames(ctx context.Context, input *apigateway.GetDomainNamesInput, options ...func(*apigateway.Options)) (*apigateway.GetDomainNamesOutput, error) {
return &apigateway.GetDomainNamesOutput{
Items: []apiGatewayTypes.DomainName{},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetBasePathMappings(ctx context.Context, input *apigateway.GetBasePathMappingsInput, options ...func(*apigateway.Options)) (*apigateway.GetBasePathMappingsOutput, error) {
return &apigateway.GetBasePathMappingsOutput{
Items: []apiGatewayTypes.BasePathMapping{
{
BasePath: aws.String("basepath1"),
RestApiId: aws.String("abcdefg"),
Stage: aws.String("stage1"),
},
{
BasePath: aws.String("basepath2"),
RestApiId: aws.String("qwerty"),
Stage: aws.String("stage2"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetMethod(ctx context.Context, input *apigateway.GetMethodInput, options ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error) {
return &apigateway.GetMethodOutput{
ApiKeyRequired: aws.Bool(true),
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetUsagePlans(ctx context.Context, input *apigateway.GetUsagePlansInput, options ...func(*apigateway.Options)) (*apigateway.GetUsagePlansOutput, error) {
return &apigateway.GetUsagePlansOutput{
Items: []apiGatewayTypes.UsagePlan{
{
Id: aws.String("usageplan1"),
Name: aws.String("usageplan1"),
ApiStages: []apiGatewayTypes.ApiStage{
{
ApiId: aws.String("abcdefg"),
Stage: aws.String("stage2"),
},
},
},
},
}, nil
}
func (m *MockedAWSAPIGatewayClient) GetUsagePlanKeys(ctx context.Context, input *apigateway.GetUsagePlanKeysInput, options ...func(*apigateway.Options)) (*apigateway.GetUsagePlanKeysOutput, error) {
return &apigateway.GetUsagePlanKeysOutput{
Items: []apiGatewayTypes.UsagePlanKey{
{
Id: aws.String("usageplankey1"),
Type: aws.String("API_KEY"),
Value: aws.String("23oieuwefo3rfs"),
},
{
Id: aws.String("usageplankey2"),
Type: aws.String("API_KEY"),
Value: aws.String("982yf98fdv8dlds"),
},
},
}, nil
+163
View File
@@ -14,10 +14,22 @@ import (
type APIGatewayv2ClientInterface interface {
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
GetDomainNames(context.Context, *apigatewayv2.GetDomainNamesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetDomainNamesOutput, error)
GetApiMappings(context.Context, *apigatewayv2.GetApiMappingsInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApiMappingsOutput, error)
GetStages(context.Context, *apigatewayv2.GetStagesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetStagesOutput, error)
GetRoutes(context.Context, *apigatewayv2.GetRoutesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetRoutesOutput, error)
}
type domainName apiGatwayV2Types.DomainName
type stage apiGatwayV2Types.Stage
func init() {
gob.Register([]apiGatwayV2Types.Api{})
//need to do this to avoid conflicts with the Instance type in the ec2 package
gob.Register([]domainName{})
gob.Register([]apiGatwayV2Types.ApiMapping{})
gob.Register([]stage{})
gob.Register([]apiGatwayV2Types.Route{})
}
func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID string, region string) ([]apiGatwayV2Types.Api, error) {
@@ -55,3 +67,154 @@ func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID str
internal.Cache.Set(cacheKey, apis, cache.DefaultExpiration)
return apis, nil
}
func CachedAPIGatewayv2GetDomainNames(client APIGatewayv2ClientInterface, accountID string, region string) ([]domainName, error) {
var PaginationControl *string
var domainNames []domainName
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetDomainNames-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]domainName), nil
}
for {
GetDomainNames, err := client.GetDomainNames(
context.TODO(),
&apigatewayv2.GetDomainNamesInput{
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return domainNames, err
}
// convert []apiGatwayV2Types.DomainName to []domainName
// Convert each element to domainName before appending to avoid conflicting with apigateway's domainname type
for _, item := range GetDomainNames.Items {
domainNames = append(domainNames, domainName(item))
}
//pagination
if GetDomainNames.NextToken == nil {
break
}
PaginationControl = GetDomainNames.NextToken
}
internal.Cache.Set(cacheKey, domainNames, cache.DefaultExpiration)
return domainNames, nil
}
func CachedAPIGatewayv2GetApiMappings(client APIGatewayv2ClientInterface, accountID string, region string, domain string) ([]apiGatwayV2Types.ApiMapping, error) {
var PaginationControl *string
var apiMappings []apiGatwayV2Types.ApiMapping
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetApiMappings-%s-%s", accountID, region, domain)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.ApiMapping), nil
}
for {
GetApiMappings, err := client.GetApiMappings(
context.TODO(),
&apigatewayv2.GetApiMappingsInput{
DomainName: &domain,
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return apiMappings, err
}
apiMappings = append(apiMappings, GetApiMappings.Items...)
//pagination
if GetApiMappings.NextToken == nil {
break
}
PaginationControl = GetApiMappings.NextToken
}
internal.Cache.Set(cacheKey, apiMappings, cache.DefaultExpiration)
return apiMappings, nil
}
func CachedAPIGatewayv2GetStages(client APIGatewayv2ClientInterface, accountID string, region string, apiID string) ([]apiGatwayV2Types.Stage, error) {
var PaginationControl *string
var stages []apiGatwayV2Types.Stage
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetStages-%s-%s", accountID, region, apiID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.Stage), nil
}
for {
GetStages, err := client.GetStages(
context.TODO(),
&apigatewayv2.GetStagesInput{
ApiId: &apiID,
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return stages, err
}
stages = append(stages, GetStages.Items...)
//pagination
if GetStages.NextToken == nil {
break
}
PaginationControl = GetStages.NextToken
}
internal.Cache.Set(cacheKey, stages, cache.DefaultExpiration)
return stages, nil
}
func CachedAPIGatewayv2GetRoutes(client APIGatewayv2ClientInterface, accountID string, region string, apiID string) ([]apiGatwayV2Types.Route, error) {
var PaginationControl *string
var routes []apiGatwayV2Types.Route
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetRoutes-%s-%s", accountID, region, apiID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.Route), nil
}
for {
GetRoutes, err := client.GetRoutes(
context.TODO(),
&apigatewayv2.GetRoutesInput{
ApiId: &apiID,
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return routes, err
}
routes = append(routes, GetRoutes.Items...)
//pagination
if GetRoutes.NextToken == nil {
break
}
PaginationControl = GetRoutes.NextToken
}
internal.Cache.Set(cacheKey, routes, cache.DefaultExpiration)
return routes, nil
}
+60 -4
View File
@@ -15,12 +15,68 @@ func (m *MockedAWSAPIGatewayv2Client) GetApis(ctx context.Context, input *apigat
return &apigatewayv2.GetApisOutput{
Items: []apiGatwayV2Types.Api{
{
ApiId: aws.String("api1"),
Name: aws.String("api1"),
ApiId: aws.String("asdfsdfasdf"),
Name: aws.String("api1"),
ApiEndpoint: aws.String("https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com"),
},
{
ApiId: aws.String("api2"),
Name: aws.String("api2"),
ApiId: aws.String("qwertyqwerty"),
Name: aws.String("api2"),
ApiEndpoint: aws.String("https://qwertyqwerty.execute-api.us-east-1.amazonaws.com"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayv2Client) GetDomainNames(ctx context.Context, input *apigatewayv2.GetDomainNamesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetDomainNamesOutput, error) {
return &apigatewayv2.GetDomainNamesOutput{
Items: []apiGatwayV2Types.DomainName{
{
DomainName: aws.String("domain1"),
},
{
DomainName: aws.String("domain2"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayv2Client) GetApiMappings(ctx context.Context, input *apigatewayv2.GetApiMappingsInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApiMappingsOutput, error) {
return &apigatewayv2.GetApiMappingsOutput{
Items: []apiGatwayV2Types.ApiMapping{
{
ApiMappingId: aws.String("apiMapping1"),
},
{
ApiMappingId: aws.String("apiMapping2"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayv2Client) GetStages(ctx context.Context, input *apigatewayv2.GetStagesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetStagesOutput, error) {
return &apigatewayv2.GetStagesOutput{
Items: []apiGatwayV2Types.Stage{
{
StageName: aws.String("stage1"),
},
{
StageName: aws.String("stage2"),
},
},
}, nil
}
func (m *MockedAWSAPIGatewayv2Client) GetRoutes(ctx context.Context, input *apigatewayv2.GetRoutesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetRoutesOutput, error) {
return &apigatewayv2.GetRoutesOutput{
Items: []apiGatwayV2Types.Route{
{
RouteId: aws.String("route1"),
RouteKey: aws.String("POST /route1"),
},
{
RouteId: aws.String("route2"),
RouteKey: aws.String("GET /route2"),
},
},
}, nil
+86
View File
@@ -0,0 +1,86 @@
package sdk
import (
"context"
"encoding/gob"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/cloud9"
cloud9Types "github.com/aws/aws-sdk-go-v2/service/cloud9/types"
"github.com/patrickmn/go-cache"
)
type AWSCloud9ClientInterface interface {
ListEnvironments(context.Context, *cloud9.ListEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error)
DescribeEnvironments(context.Context, *cloud9.DescribeEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error)
}
func init() {
gob.Register([]string{})
}
func CachedCloud9ListEnvironments(client AWSCloud9ClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var environments []string
cacheKey := "cloud9-ListEnvironments-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListEnvironments, err := client.ListEnvironments(
context.TODO(),
&cloud9.ListEnvironmentsInput{
NextToken: PaginationControl,
},
func(o *cloud9.Options) {
o.Region = region
},
)
if err != nil {
return environments, err
}
environments = append(environments, ListEnvironments.EnvironmentIds...)
//pagination
if ListEnvironments.NextToken == nil {
break
}
PaginationControl = ListEnvironments.NextToken
}
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
return environments, nil
}
func CachedCloud9DescribeEnvironments(client AWSCloud9ClientInterface, accountID string, region string, environmentIDs []string) ([]cloud9Types.Environment, error) {
var environments []cloud9Types.Environment
cacheKey := "cloud9-DescribeEnvironments-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloud9Types.Environment), nil
}
for _, environmentID := range environmentIDs {
DescribeEnvironments, err := client.DescribeEnvironments(
context.TODO(),
&cloud9.DescribeEnvironmentsInput{
EnvironmentIds: []string{environmentID},
},
func(o *cloud9.Options) {
o.Region = region
},
)
if err != nil {
return environments, err
}
environments = append(environments, DescribeEnvironments.Environments...)
}
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
return environments, nil
}
+36
View File
@@ -0,0 +1,36 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloud9"
"github.com/aws/aws-sdk-go-v2/service/cloud9/types"
)
type MockedAWSCloud9Client struct {
}
func (m *MockedAWSCloud9Client) ListEnvironments(ctx context.Context, input *cloud9.ListEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error) {
return &cloud9.ListEnvironmentsOutput{
EnvironmentIds: []string{
"env1",
"env2",
},
}, nil
}
func (m *MockedAWSCloud9Client) DescribeEnvironments(ctx context.Context, input *cloud9.DescribeEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error) {
return &cloud9.DescribeEnvironmentsOutput{
Environments: []types.Environment{
{
Name: aws.String("env1"),
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env1"),
},
{
Name: aws.String("env2"),
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env2"),
},
},
}, nil
}
+1 -1
View File
@@ -87,7 +87,7 @@ func CachedCodeBuildBatchGetProjects(CodeBuildClient CodeBuildClientInterface, a
return project, err
}
internal.Cache.Set(cacheKey, BatchGetProjects.Projects[0], cache.DefaultExpiration)
internal.Cache.Set(cacheKey, BatchGetProjects.Projects, cache.DefaultExpiration)
return BatchGetProjects.Projects[0], nil
}
+20
View File
@@ -3,6 +3,7 @@ package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
)
@@ -17,3 +18,22 @@ func (m *MockedCodeBuildClient) ListProjects(ctx context.Context, input *codebui
},
}, nil
}
func (m *MockedCodeBuildClient) GetResourcePolicy(ctx context.Context, input *codebuild.GetResourcePolicyInput, options ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
return &codebuild.GetResourcePolicyOutput{
Policy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "codebuild:BatchGetProjects",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
}
]
}
`),
}, nil
}
+1 -1
View File
@@ -21,7 +21,7 @@ type DocDBClientInterface interface {
func init() {
gob.Register([]docdbTypes.GlobalCluster{})
gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBInstance{})
}
+6 -4
View File
@@ -10,10 +10,6 @@ import (
type MockedAWSDynamoDBClient struct {
}
func (m *MockedAWSDynamoDBClient) ListTables() ([]string, error) {
return []string{"table1", "table2"}, nil
}
func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, options ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) {
return &dynamodb.DescribeTableOutput{
Table: &dynamodbTypes.TableDescription{
@@ -21,3 +17,9 @@ func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dyna
},
}, nil
}
func (m *MockedAWSDynamoDBClient) ListTables(ctx context.Context, input *dynamodb.ListTablesInput, options ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) {
return &dynamodb.ListTablesOutput{
TableNames: []string{"table1", "table2"},
}, nil
}
+34 -12
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/patrickmn/go-cache"
@@ -17,6 +18,7 @@ type AWSEC2ClientInterface interface {
DescribeSnapshots(context.Context, *ec2.DescribeSnapshotsInput, ...func(*ec2.Options)) (*ec2.DescribeSnapshotsOutput, error)
DescribeVolumes(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error)
DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error)
DescribeInstanceAttribute(context.Context, *ec2.DescribeInstanceAttributeInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error)
}
func init() {
@@ -66,6 +68,30 @@ func CachedEC2DescribeInstances(client AWSEC2ClientInterface, accountID string,
return instances, nil
}
func CachedEC2DescribeInstanceAttributeUserData(client AWSEC2ClientInterface, accountID string, region string, instanceID string) (string, error) {
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstanceAttributeUserData-%s-%s", accountID, region, instanceID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
DescribeInstanceAttribute, err := client.DescribeInstanceAttribute(
context.TODO(),
&ec2.DescribeInstanceAttributeInput{
Attribute: ec2Types.InstanceAttributeNameUserData,
InstanceId: &instanceID,
},
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return "", err
}
internal.Cache.Set(cacheKey, aws.ToString(DescribeInstanceAttribute.UserData.Value), cache.DefaultExpiration)
return aws.ToString(DescribeInstanceAttribute.UserData.Value), nil
}
func CachedEC2DescribeNetworkInterfaces(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.NetworkInterface, error) {
var PaginationControl *string
var NetworkInterfaces []ec2Types.NetworkInterface
@@ -87,9 +113,8 @@ func CachedEC2DescribeNetworkInterfaces(client AWSEC2ClientInterface, accountID
if err != nil {
return NetworkInterfaces, err
}
for _, networkInterface := range DescribeNetworkInterfaces.NetworkInterfaces {
NetworkInterfaces = append(NetworkInterfaces, networkInterface)
}
NetworkInterfaces = append(NetworkInterfaces, DescribeNetworkInterfaces.NetworkInterfaces...)
if DescribeNetworkInterfaces.NextToken == nil {
break
}
@@ -122,9 +147,8 @@ func CachedEC2DescribeSnapshots(client AWSEC2ClientInterface, accountID string,
if err != nil {
return Snapshots, err
}
for _, snapshot := range DescribeSnapshots.Snapshots {
Snapshots = append(Snapshots, snapshot)
}
Snapshots = append(Snapshots, DescribeSnapshots.Snapshots...)
if DescribeSnapshots.NextToken == nil {
break
}
@@ -156,9 +180,8 @@ func CachedEC2DescribeVolumes(client AWSEC2ClientInterface, accountID string, re
if err != nil {
return Volumes, err
}
for _, volume := range DescribeVolumes.Volumes {
Volumes = append(Volumes, volume)
}
Volumes = append(Volumes, DescribeVolumes.Volumes...)
if DescribeVolumes.NextToken == nil {
break
}
@@ -191,9 +214,8 @@ func CachedEC2DescribeImages(client AWSEC2ClientInterface, accountID string, reg
if err != nil {
return Images, err
}
for _, image := range DescribeImages.Images {
Images = append(Images, image)
}
Images = append(Images, DescribeImages.Images...)
if DescribeImages.NextToken == nil {
break
}
+14
View File
@@ -265,6 +265,12 @@ type DescribeVolumes struct {
} `json:"Volumes"`
}
// DescribeInstanceAttribute is a struct for the DescribeInstanceAttribute function - include userdata
type DescribeInstanceAttribute struct {
UserData string `json:"UserData"`
InstanceID string `json:"InstanceId"`
}
func (c *MockedEC2Client2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
var nics []ec2types.NetworkInterface
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
@@ -372,3 +378,11 @@ func (c *MockedEC2Client2) DescribeImages(ctx context.Context, input *ec2.Descri
}
return &ec2.DescribeImagesOutput{Images: images}, nil
}
func (c *MockedEC2Client2) DescribeInstanceAttribute(ctx context.Context, input *ec2.DescribeInstanceAttributeInput, f ...func(o *ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error) {
return &ec2.DescribeInstanceAttributeOutput{
UserData: &ec2types.AttributeValue{
Value: aws.String("userdata"),
},
}, nil
}
+1 -1
View File
@@ -70,7 +70,7 @@ func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID st
func CachedECRDescribeImages(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) ([]ecrTypes.ImageDetail, error) {
var PaginationControl *string
var images []ecrTypes.ImageDetail
cacheKey := fmt.Sprintf("%s-efs-DescribImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cacheKey := fmt.Sprintf("%s-ecr-DescribeImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Images data")
+47 -11
View File
@@ -2,6 +2,7 @@ package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
@@ -16,29 +17,64 @@ func (m *MockedECRClient) DescribeRepositories(ctx context.Context, input *ecr.D
Repositories: []ecrTypes.Repository{
{
RepositoryName: aws.String("repo1"),
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1"),
},
{
RepositoryName: aws.String("repo2"),
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2"),
},
},
}, nil
}
func (m *MockedECRClient) DescribeImages(ctx context.Context, input *ecr.DescribeImagesInput, options ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"tag1",
"tag2",
if aws.ToString(input.RepositoryName) == "repo1" {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"customtag",
"tag2",
},
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(123456),
},
},
{
ImageTags: []string{
"tag3", "tag4", "tag5", "tag6"},
}, nil
} else if aws.ToString(input.RepositoryName) == "repo2" {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"latest",
},
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(2222222),
},
},
},
}, nil
}, nil
} else {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"customtag",
"tag2",
},
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(111),
},
{
ImageTags: []string{
"latest",
},
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(333),
},
},
}, nil
}
}
func (m *MockedECRClient) GetRepositoryPolicy(ctx context.Context, input *ecr.GetRepositoryPolicyInput, options ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
+9 -2
View File
@@ -23,6 +23,7 @@ type AWSECSClientInterface interface {
func init() {
gob.Register([]string{})
gob.Register(ecsTypes.Task{})
gob.Register([]ecsTypes.Task{})
gob.Register(ecsTypes.TaskDefinition{})
}
@@ -113,7 +114,10 @@ func CachedECSListTasks(ECSClient AWSECSClientInterface, accountID string, regio
func CachedECSDescribeTasks(ECSClient AWSECSClientInterface, accountID string, region string, cluster string, tasks []string) ([]ecsTypes.Task, error) {
var taskDetails []ecsTypes.Task
cacheKey := fmt.Sprintf("%s-ecs-DescribeTasks-%s-%s", accountID, region, cluster)
//replace semi-colons with underscores in task definition name
clusterFileSystemSafe := strings.ReplaceAll(cluster, ":", "_")
clusterFileSystemSafe = strings.ReplaceAll(clusterFileSystemSafe, "/", "_")
cacheKey := fmt.Sprintf("%s-ecs-DescribeTasks-%s-%s", accountID, region, clusterFileSystemSafe)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS task details data")
@@ -143,7 +147,10 @@ func CachedECSDescribeTasks(ECSClient AWSECSClientInterface, accountID string, r
func CachedECSDescribeTaskDefinition(ECSClient AWSECSClientInterface, accountID string, region string, taskDefinition string) (ecsTypes.TaskDefinition, error) {
var taskDefinitionDetails ecsTypes.TaskDefinition
cacheKey := fmt.Sprintf("%s-ecs-DescribeTaskDefinition-%s-%s", accountID, region, taskDefinition)
//replace semi-colons with underscores in task definition name
taskDefinitionFileSystemSafe := strings.ReplaceAll(taskDefinition, ":", "_")
taskDefinitionFileSystemSafe = strings.ReplaceAll(taskDefinitionFileSystemSafe, "/", "_")
cacheKey := fmt.Sprintf("%s-ecs-DescribeTaskDefinition-%s-%s", accountID, region, taskDefinitionFileSystemSafe)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS task definition details data")
+1 -1
View File
@@ -126,7 +126,7 @@ func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region
func CachedEKSDescribeNodeGroup(client EKSClientInterface, accountID string, region string, clusterName string, nodeGroupName string) (eksTypes.Nodegroup, error) {
var nodeGroup eksTypes.Nodegroup
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, nodeGroupName)
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, clusterName, nodeGroupName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Nodegroup), nil
+1 -1
View File
@@ -32,7 +32,7 @@ func (m *MockedElasticacheClient) DescribeCacheClusters(ctx context.Context, inp
},
},
AutoMinorVersionUpgrade: true,
AutoMinorVersionUpgrade: aws.Bool(true),
PreferredMaintenanceWindow: aws.String("sun:05:00-sun:06:00"),
},
},
+56
View File
@@ -3,8 +3,10 @@ package sdk
import (
"context"
"encoding/gob"
"errors"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/glue"
glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types"
@@ -16,6 +18,7 @@ type AWSGlueClientInterface interface {
ListJobs(ctx context.Context, params *glue.ListJobsInput, optFns ...func(*glue.Options)) (*glue.ListJobsOutput, error)
GetTables(ctx context.Context, params *glue.GetTablesInput, optFns ...func(*glue.Options)) (*glue.GetTablesOutput, error)
GetDatabases(ctx context.Context, params *glue.GetDatabasesInput, optFns ...func(*glue.Options)) (*glue.GetDatabasesOutput, error)
GetResourcePolicies(ctx context.Context, params *glue.GetResourcePoliciesInput, optFns ...func(*glue.Options)) (*glue.GetResourcePoliciesOutput, error)
}
func init() {
@@ -24,6 +27,7 @@ func init() {
gob.Register(glueTypes.Job{})
gob.Register([]glueTypes.Table{})
gob.Register([]glueTypes.Database{})
gob.Register([]policy.Policy{})
}
func CachedGlueListDevEndpoints(GlueClient AWSGlueClientInterface, accountID string, region string) ([]string, error) {
@@ -190,3 +194,55 @@ func CachedGlueGetDatabases(GlueClient AWSGlueClientInterface, accountID string,
return databases, nil
}
func CachedGlueGetResourcePolicies(GlueClient AWSGlueClientInterface, accountID string, region string) ([]policy.Policy, error) {
var PaginationControl *string
var GluePolicy policy.Policy
var policies []policy.Policy
var policyJSON string
cacheKey := "glue-GetResourcePolicies-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue resource policies data")
return cached.([]policy.Policy), nil
}
for {
GetResourcePolicies, err := GlueClient.GetResourcePolicies(
context.TODO(),
&glue.GetResourcePoliciesInput{
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
for _, policyPointer := range GetResourcePolicies.GetResourcePoliciesResponseList {
policyJSON = aws.ToString(policyPointer.PolicyInJson)
GluePolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return policies, errors.New("error parsing Glue policy")
}
policies = append(policies, GluePolicy)
}
// Pagination control.
if GetResourcePolicies.NextToken != nil {
PaginationControl = GetResourcePolicies.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, policies, cache.DefaultExpiration)
return policies, nil
}
// in the resource trust command, need to parse the actual resource policies to determine the resources for the cloudfox table. crazy pants
+28 -1
View File
@@ -23,6 +23,7 @@ type AWSIAMClientInterface interface {
SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error)
ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
ListGroups(ctx context.Context, params *iam.ListGroupsInput, optFns ...func(*iam.Options)) (*iam.ListGroupsOutput, error)
GetInstanceProfile(ctx context.Context, params *iam.GetInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error)
}
func init() {
@@ -34,6 +35,7 @@ func init() {
gob.Register([]iamTypes.InstanceProfile{})
gob.Register([]iamTypes.EvaluationResult{})
gob.Register(customGAADOutput{})
gob.Register(iamTypes.InstanceProfile{})
}
func CachedIamListUsers(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.User, error) {
@@ -207,7 +209,7 @@ func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID
var EvaluationResults []iamTypes.EvaluationResult
md5hashedActionNames := md5.Sum([]byte(strings.Join(actionNames, "")))
md5hashedResourceArns := md5.Sum([]byte(strings.Join(resourceArns, "")))
// proccess arn and get the name of the resource
// process arn and get the name of the resource
arn, err := arn.Parse(*principal)
if err != nil {
return EvaluationResults, err
@@ -288,3 +290,28 @@ func CachedIamListGroups(IAMClient AWSIAMClientInterface, accountID string) ([]i
return Groups, nil
}
func CachedIamGetInstanceProfile(IAMClient AWSIAMClientInterface, accountID string, instanceProfileName string) (iamTypes.InstanceProfile, error) {
var InstanceProfile iamTypes.InstanceProfile
cacheKey := fmt.Sprintf("%s-iam-GetInstanceProfile-%s", accountID, instanceProfileName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(iamTypes.InstanceProfile), nil
}
GetInstanceProfile, err := IAMClient.GetInstanceProfile(
context.TODO(),
&iam.GetInstanceProfileInput{
InstanceProfileName: &instanceProfileName,
},
)
if err != nil {
return InstanceProfile, err
}
InstanceProfile = *GetInstanceProfile.InstanceProfile
internal.Cache.Set(cacheKey, InstanceProfile, cache.DefaultExpiration)
return InstanceProfile, nil
}
-1
View File
@@ -21,7 +21,6 @@ func init() {
//need to do this to avoid conflicts with the Instance type in the ec2 package
type lightsailInstance lightsailTypes.Instance
gob.Register([]lightsailInstance{})
gob.Register([]lightsailTypes.ContainerService{})
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/neptune"
neptuneTypes "github.com/aws/aws-sdk-go-v2/service/neptune/types"
"github.com/patrickmn/go-cache"
)
type NeptuneClientInterface interface {
DescribeDBClusters(ctx context.Context, params *neptune.DescribeDBClustersInput, optFns ...func(*neptune.Options)) (*neptune.DescribeDBClustersOutput, error)
}
func init() {
//gob.RegisterName("neptune.DBCluster", []neptuneTypes.DBCluster{})
}
func CachedNeptuneDescribeDBClusters(client NeptuneClientInterface, accountID string, region string) ([]neptuneTypes.DBCluster, error) {
var paginationControl *string
var clusters []neptuneTypes.DBCluster
cacheKey := fmt.Sprintf("%s-neptune-DescribeDBClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]neptuneTypes.DBCluster), nil
}
for {
describeDBClusters, err := client.DescribeDBClusters(
context.TODO(),
&neptune.DescribeDBClustersInput{
Marker: paginationControl,
},
func(o *neptune.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, describeDBClusters.DBClusters...)
//pagination
if describeDBClusters.Marker == nil {
break
}
paginationControl = describeDBClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
+34
View File
@@ -0,0 +1,34 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/neptune"
neptuneTypes "github.com/aws/aws-sdk-go-v2/service/neptune/types"
)
type MockedNeptuneClient struct {
}
func (m *MockedNeptuneClient) DescribeDBClusters(ctx context.Context, params *neptune.DescribeDBClustersInput, optFns ...func(*neptune.Options)) (*neptune.DescribeDBClustersOutput, error) {
return &neptune.DescribeDBClustersOutput {
DBClusters: []neptuneTypes.DBCluster{
{
DBClusterIdentifier: aws.String("db1"),
Engine: aws.String("neptune"),
EngineVersion: aws.String("1.3.0.0"),
ClusterCreateTime: aws.Time(time.Now()),
MasterUsername: aws.String("neptune"),
},
{
DBClusterIdentifier: aws.String("db2"),
Engine: aws.String("neptune"),
EngineVersion: aws.String("1.3.0.0"),
ClusterCreateTime: aws.Time(time.Now()),
MasterUsername: aws.String("neptune"),
},
},
}, nil
}
+39
View File
@@ -14,10 +14,13 @@ import (
type RDSClientInterface interface {
DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error)
DescribeDBClusters(context.Context, *rds.DescribeDBClustersInput, ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error)
}
func init() {
gob.Register([]rdsTypes.DBInstance{})
gob.Register([]rdsTypes.DBCluster{})
}
func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBInstance, error) {
@@ -55,3 +58,39 @@ func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, r
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
func CachedRDSDescribeDBClusters(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBCluster, error) {
var PaginationControl *string
var clusters []rdsTypes.DBCluster
cacheKey := fmt.Sprintf("%s-rds-DescribeDBClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]rdsTypes.DBCluster), nil
}
for {
DescribeDBClusters, err := client.DescribeDBClusters(
context.TODO(),
&rds.DescribeDBClustersInput{
Marker: PaginationControl,
},
func(o *rds.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, DescribeDBClusters.DBClusters...)
//pagination
if DescribeDBClusters.Marker == nil {
break
}
PaginationControl = DescribeDBClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
+52 -2
View File
@@ -9,10 +9,10 @@ import (
rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
)
type MOckedRDSClient struct {
type MockedRDSClient struct {
}
func (m *MOckedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.DescribeDBInstancesInput, options ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) {
func (m *MockedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.DescribeDBInstancesInput, options ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) {
return &rds.DescribeDBInstancesOutput{
DBInstances: []rdsTypes.DBInstance{
{
@@ -32,3 +32,53 @@ func (m *MOckedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.De
},
}, nil
}
func (m *MockedRDSClient) DescribeDBClusters(ctx context.Context, input *rds.DescribeDBClustersInput, options ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error) {
return &rds.DescribeDBClustersOutput{
DBClusters: []rdsTypes.DBCluster{
{
DBClusterIdentifier: aws.String("db1"),
Engine: aws.String("aurora-postgresql"),
EngineVersion: aws.String("13.3"),
Endpoint: aws.String("db1.cluster-123456789012.us-west-2.rds.amazonaws.com"),
ClusterCreateTime: aws.Time(time.Now()),
MasterUsername: aws.String("postgres"),
Port: aws.Int32(5432),
AssociatedRoles: nil,
},
{
DBClusterIdentifier: aws.String("db2"),
Engine: aws.String("aurora-postgresql"),
EngineVersion: aws.String("13.3"),
Endpoint: aws.String("db2.cluster-123456789012.us-west-2.rds.amazonaws.com"),
ClusterCreateTime: aws.Time(time.Now()),
MasterUsername: aws.String("postgres"),
Port: aws.Int32(5432),
AssociatedRoles: nil,
},
{
DBClusterIdentifier: aws.String("db3"),
Engine: aws.String("neptune"),
EngineVersion: aws.String("1.2.1.0"),
Endpoint: aws.String("db3.cluster-123456789012.us-west-2.neptune.amazonaws.com"),
MasterUsername: aws.String("neptune"),
Port: aws.Int32(8182),
AssociatedRoles: []rdsTypes.DBClusterRole{
{
RoleArn: aws.String("arn:aws:iam::123456789012:role/NeptuneRole"),
Status: aws.String("active"),
},
},
},
{
DBClusterIdentifier: aws.String("db4"),
Engine: aws.String("docsdb"),
EngineVersion: aws.String("4.0.0"),
Endpoint: aws.String("db4.cluster-123456789012.us-west-2.docdb.amazonaws.com"),
MasterUsername: aws.String("docsdb"),
Port: aws.Int32(27017),
AssociatedRoles: nil,
},
},
}, nil
}
+2 -2
View File
@@ -20,7 +20,7 @@ func (m *MockedRedshiftClient) DescribeClusters(ctx context.Context, input *reds
DBName: aws.String("db1"),
Endpoint: &redshiftTypes.Endpoint{
Address: aws.String("cluster1.us-east-1.redshift.amazonaws.com"),
Port: 5439,
Port: aws.Int32(5439),
},
},
{
@@ -29,7 +29,7 @@ func (m *MockedRedshiftClient) DescribeClusters(ctx context.Context, input *reds
DBName: aws.String("db2"),
Endpoint: &redshiftTypes.Endpoint{
Address: aws.String("cluster2.us-east-1.redshift.amazonaws.com"),
Port: 5439,
Port: aws.Int32(5439),
},
},
},
+1
View File
@@ -90,6 +90,7 @@ func CachedGetBucketPolicy(S3Client AWSS3ClientInterface, accountID string, r st
},
)
if err != nil {
internal.Cache.Set(cacheKey, "", cache.DefaultExpiration)
return "", err
}
+4 -4
View File
@@ -66,10 +66,10 @@ func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, input *s3.GetBucke
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, input *s3.GetPublicAccessBlockInput, options ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
return &s3.GetPublicAccessBlockOutput{
PublicAccessBlockConfiguration: &s3Types.PublicAccessBlockConfiguration{
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
BlockPublicAcls: aws.Bool(true),
BlockPublicPolicy: aws.Bool(true),
IgnorePublicAcls: aws.Bool(true),
RestrictPublicBuckets: aws.Bool(true),
},
}, nil
}
+35
View File
@@ -6,6 +6,8 @@ import (
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
secretsmanagerTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
"github.com/patrickmn/go-cache"
@@ -13,10 +15,13 @@ import (
type SecretsManagerClientInterface interface {
ListSecrets(context.Context, *secretsmanager.ListSecretsInput, ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error)
GetResourcePolicy(context.Context, *secretsmanager.GetResourcePolicyInput, ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error)
}
func init() {
gob.Register([]secretsmanagerTypes.SecretListEntry{})
gob.Register(policy.Policy{})
}
func CachedSecretsManagerListSecrets(client SecretsManagerClientInterface, accountID string, region string) ([]secretsmanagerTypes.SecretListEntry, error) {
@@ -54,3 +59,33 @@ func CachedSecretsManagerListSecrets(client SecretsManagerClientInterface, accou
internal.Cache.Set(cacheKey, secrets, cache.DefaultExpiration)
return secrets, nil
}
func CachedSecretsManagerGetResourcePolicy(client SecretsManagerClientInterface, secretId string, region string, accountID string) (policy.Policy, error) {
var secretPolicy policy.Policy
var policyJSON string
cacheKey := fmt.Sprintf("%s-secretsmanager-GetResourcePolicy-%s-%s", accountID, region, secretId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(policy.Policy), nil
}
GetResourcePolicy, err := client.GetResourcePolicy(
context.TODO(),
&secretsmanager.GetResourcePolicyInput{
SecretId: &secretId,
},
func(o *secretsmanager.Options) {
o.Region = region
},
)
if err != nil {
return secretPolicy, err
}
policyJSON = aws.ToString(GetResourcePolicy.ResourcePolicy)
secretPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return secretPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", secretId, err)
}
internal.Cache.Set(cacheKey, secretPolicy, cache.DefaultExpiration)
return secretPolicy, nil
}
+35
View File
@@ -23,3 +23,38 @@ func (m *MockedSecretsManagerClient) ListSecrets(ctx context.Context, input *sec
},
}, nil
}
func (m *MockedSecretsManagerClient) GetResourcePolicy(ctx context.Context, input *secretsmanager.GetResourcePolicyInput, options ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error) {
return &secretsmanager.GetResourcePolicyOutput{
ResourcePolicy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RetrieveSecret",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "*"
},
{
"Sid": "RetrieveSecret",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
],
"Resource": "*"
}
]
}`),
}, nil
}
+45 -14
View File
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/internal"
@@ -22,11 +23,13 @@ type SecretsModule struct {
SecretsManagerClient *secretsmanager.Client
SSMClient *ssm.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
Goroutines int
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
// Main module data
Secrets []Secret
@@ -45,8 +48,8 @@ type Secret struct {
Description string
}
func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *SecretsModule) PrintSecrets(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "secrets"
@@ -94,17 +97,48 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
// fmt.Printf("\nAnalyzed Resources by Region\n\n")
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
"Description",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Description",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Description",
}
}
// Table rows
for i := range m.Secrets {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Secrets[i].AWSService,
m.Secrets[i].Region,
m.Secrets[i].Name,
@@ -116,11 +150,7 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -129,9 +159,10 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
+10 -2
View File
@@ -18,9 +18,8 @@ var red = color.New(color.FgRed).SprintFunc()
var yellow = color.New(color.FgRed).SprintFunc()
var blue = color.New(color.FgBlue).SprintFunc()
var magenta = color.New(color.FgMagenta).SprintFunc()
var green = color.New(color.FgGreen).SprintFunc()
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
var sharedLogger = internal.TxtLogger()
func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod IamSimulatorModule, localAdminMap map[string]bool) (string, string) {
@@ -178,3 +177,12 @@ func GetResourceNameFromArn(arn string) string {
return resourceName
}
func removeStringFromSlice(slice []string, element string) []string {
for i, v := range slice {
if v == element {
return append(slice[:i], slice[i+1:]...)
}
}
return slice
}
+43 -13
View File
@@ -29,9 +29,11 @@ type SNSModule struct {
AWSProfile string
Caller sts.GetCallerIdentityOutput
StorePolicies bool
OutputFormat string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
Goroutines int
WrapTable bool
// Main module data
Topics []SNSTopic
@@ -61,8 +63,8 @@ type SNSTopic struct {
ResourcePolicySummary string
}
func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *SNSModule) PrintSNS(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "sns"
@@ -108,12 +110,40 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"ARN",
"Public?",
"Resource Policy Summary",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"ARN",
"Public?",
"Resource Policy Summary",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"ARN",
"Public?",
"Resource Policy Summary",
}
}
sort.SliceStable(m.Topics, func(i, j int) bool {
return m.Topics[i].ARN < m.Topics[j].ARN
})
@@ -123,6 +153,7 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Topics[i].ARN,
m.Topics[i].IsPublic,
m.Topics[i].ResourcePolicySummary,
@@ -131,9 +162,7 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
//m.output.OutputSelector(outputFormat)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -142,9 +171,10 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -288,7 +318,7 @@ func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphor
if !topic.Policy.IsEmpty() {
m.analyseTopicPolicy(topic, dataReceiver)
} else {
// If the topic policy "resource policy" is empty, the only principals that have permisisons
// If the topic policy "resource policy" is empty, the only principals that have permissions
// are those that are granted access by IAM policies
//topic.Access = "Private. Access allowed by IAM policies"
topic.Access = "Only intra-account access (via IAM) allowed"
+1 -1
View File
@@ -42,7 +42,7 @@ func TestSNSQueues(t *testing.T) {
tmpDir := "."
// execute the module with verbosity = 2
m.PrintSNS("table", tmpDir, 2)
m.PrintSNS(tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/sns.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
+47 -16
View File
@@ -28,12 +28,14 @@ type SQSModule struct {
StorePolicies bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Queues []Queue
@@ -64,8 +66,8 @@ type Queue struct {
ResourcePolicySummary string
}
func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *SQSModule) PrintSQS(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "sqs"
@@ -111,11 +113,40 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Arn",
"Public?",
"Resource Policy Summary",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Arn",
"Public?",
"Resource Policy Summary",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Arn",
"Public?",
"Resource Policy Summary",
}
}
sort.SliceStable(m.Queues, func(i, j int) bool {
return m.Queues[i].URL < m.Queues[j].URL
})
@@ -125,6 +156,7 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Queues[i].Arn,
m.Queues[i].IsPublic,
m.Queues[i].ResourcePolicySummary,
@@ -133,9 +165,7 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
//m.output.OutputSelector(outputFormat)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -144,9 +174,10 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -283,7 +314,7 @@ func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
if !queue.Policy.IsEmpty() {
m.analyseQueuePolicy(queue, dataReceiver)
} else {
// If the queue policy "resource policy" is empty, the only principals that have permisisons
// If the queue policy "resource policy" is empty, the only principals that have permissions
// are those that are granted access by IAM policies
//queue.Access = "Private. Access allowed by IAM policies"
queue.Access = "Only intra-account access (via IAM) allowed"
@@ -390,7 +421,7 @@ func (m *SQSModule) analyseQueuePolicy(queue *Queue, dataReceiver chan Queue) {
} else {
queue.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
queue.ResourcePolicySummary = strings.TrimSuffix(queue.ResourcePolicySummary, "\n")
//queue.ResourcePolicySummary = strings.TrimSuffix(queue.ResourcePolicySummary, "\n")
}
dataReceiver <- *queue
+1 -1
View File
@@ -48,7 +48,7 @@ func TestSQSQueues(t *testing.T) {
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintSQS("table", tmpDir, 2)
m.PrintSQS(tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/sqs.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
+50 -12
View File
@@ -26,9 +26,11 @@ type TagsModule struct {
// General configuration data
ResourceGroupsTaggingApiInterface TagsGetResourcesAPI
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
@@ -54,8 +56,8 @@ type Tag struct {
Value string
}
func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *TagsModule) PrintTags(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "tags"
@@ -102,6 +104,7 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Type",
@@ -111,6 +114,41 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
"Value",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Type",
//"Name",
//"Resource Arn",
"Key",
"Value",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Type",
//"Name",
//"Resource Arn",
"Key",
"Value",
}
}
sort.Slice(m.Tags, func(i, j int) bool {
return m.Tags[i].AWSService < m.Tags[j].AWSService
})
@@ -121,6 +159,7 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Tags[i].AWSService,
m.Tags[i].Region,
m.Tags[i].Type,
@@ -134,9 +173,7 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -145,9 +182,10 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
@@ -284,7 +322,7 @@ func (m *TagsModule) getResources(r string) ([]types.ResourceTagMapping, error)
var PaginationControl *string
var resources []types.ResourceTagMapping
// a for loop that accepts user input. If no user input, it will continue to paginate until there are no mor pages. If there is user input, it will paginate until the user input is reached.
// a for loop that accepts user input. If no user input, it will continue to paginate until there are no more pages. If there is user input, it will paginate until the user input is reached.
for {
if len(resources) < m.MaxResourcesPerRegion || m.MaxResourcesPerRegion == 0 {
+1 -2
View File
@@ -53,7 +53,6 @@ func TestTags(t *testing.T) {
UserId: aws.String("AIDAJDPLRKLG7UEXAMPLE"),
},
AWSRegions: []string{"us-east-1"},
OutputFormat: "table",
Goroutines: 10,
AWSProfile: "test",
WrapTable: false,
@@ -73,7 +72,7 @@ func TestTags(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintTags(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintTags(subtest.outputDirectory, subtest.verbosity)
if len(subtest.testModule.Tags) != len(subtest.expectedResult) {
t.Errorf("Expected %d results, got %d", len(subtest.expectedResult), len(subtest.testModule.Tags))
}
+554
View File
@@ -0,0 +1,554 @@
package aws
import (
"context"
"fmt"
"log"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type WorkloadsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
AWSOutputType string
AWSTableCols string
Goroutines int
WrapTable bool
SkipAdminCheck bool
// Service Clients
EC2Client sdk.AWSEC2ClientInterface
ECSClient sdk.AWSECSClientInterface
LambdaClient sdk.LambdaClientInterface
AppRunnerClient sdk.AppRunnerClientInterface
IAMClient sdk.AWSIAMClientInterface
//LightsailClient sdk.MockedLightsailClient
//SagemakerClient *sagemaker.Client
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
InstanceProfileToRolesMap map[string][]iamTypes.Role
// Main module data
Workloads []Workload
CommandCounter internal.CommandCounter
modLog *logrus.Entry
// Used to store output data for pretty printing
output internal.OutputData2
}
type Workload struct {
AWSService string
Region string
Type string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
Public string
}
func (m *WorkloadsModule) PrintWorkloads(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "workloads"
localAdminMap := make(map[string]bool)
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating compute workloads in all regions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: App Runner, EC2, ECS, Lambda \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Workload)
// Create a channel to signal to stop
receiverDone := make(chan bool)
// Create a channel to signal to stop
go m.Receiver(dataReceiver, receiverDone)
// Get roles from instance profiles to use for EC2 instance to role lookups
m.getRolesFromInstanceProfiles()
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Perform role analysis
if m.pmapperError == nil {
for i := range m.Workloads {
m.Workloads[i].Admin, m.Workloads[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Workloads[i].Role)
}
} else {
for i := range m.Workloads {
m.Workloads[i].Admin, m.Workloads[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Workloads[i].Role, m.iamSimClient, localAdminMap)
}
}
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
// Table headers
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
sort.Slice(m.Workloads, func(i, j int) bool {
return m.Workloads[i].AWSService < m.Workloads[j].AWSService
})
// Table rows
for i := range m.Workloads {
// If the role is an admin or can privesc to admin, make it magenta
if m.Workloads[i].Admin == "YES" || m.Workloads[i].CanPrivEsc == "YES" {
m.Workloads[i].AWSService = magenta(m.Workloads[i].AWSService)
m.Workloads[i].Region = magenta(m.Workloads[i].Region)
m.Workloads[i].Name = magenta(m.Workloads[i].Name)
m.Workloads[i].Role = magenta(m.Workloads[i].Role)
m.Workloads[i].Admin = magenta(m.Workloads[i].Admin)
m.Workloads[i].CanPrivEsc = magenta(m.Workloads[i].CanPrivEsc)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Workloads[i].AWSService,
m.Workloads[i].Region,
m.Workloads[i].Name,
m.Workloads[i].Arn,
m.Workloads[i].Role,
m.Workloads[i].Admin,
m.Workloads[i].CanPrivEsc,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
// Create another table file that contains only the admin roles or the ones that can privesc to admin. Call another function to do this.
body := m.adminOnlyResults(m.output.Headers, m.output.Body)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: body,
TableCols: tableCols,
Name: fmt.Sprintf("%s-admin", m.output.CallingModule),
SkipPrintToScreen: true,
})
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] %s compute workloads found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No compute workloads found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *WorkloadsModule) Receiver(receiver chan Workload, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Workloads = append(m.Workloads, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *WorkloadsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, _ := servicemap.IsServiceInRegion("ec2", r)
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getEC2WorkloadsPerRegion(r, wg, semaphore, dataReceiver)
}
res, _ = servicemap.IsServiceInRegion("ecs", r)
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getECSWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err := servicemap.IsServiceInRegion("lambda", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getLambdaWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
}
// AppRunner is not supported in the aws service region catalog so we have to run it in all regions
m.CommandCounter.Total++
wg.Add(1)
go m.getAppRunnerWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
}
func (m *WorkloadsModule) getEC2WorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// Get EC2 instances
ec2Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err)
}
for _, instance := range ec2Instances {
var profileArn, profileName, name string
// The name is in a tag so we have to do this to grab the value from the right tag
for _, tag := range instance.Tags {
if *tag.Key == "Name" {
name = *tag.Value
}
}
if name == "" {
name = aws.ToString(instance.InstanceId)
}
if instance.IamInstanceProfile != nil {
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)
// Extracting instance profile name from ARN
profileName = strings.Split(profileArn, "/")[len(strings.Split(profileArn, "/"))-1]
// Describe the IAM instance profile
profileOutput, err := sdk.CachedIamGetInstanceProfile(m.IAMClient, aws.ToString(m.Caller.Account), profileName)
if err != nil {
log.Printf("failed to get instance profile for %s, %v", profileArn, err)
continue
}
for _, role := range profileOutput.Roles {
dataReceiver <- Workload{
AWSService: "EC2",
Region: r,
Type: "instance",
Name: name,
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", r, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
Role: aws.ToString(role.Arn),
}
}
}
}
}
func (m *WorkloadsModule) getECSWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// Get ECS clusters
ecsClusters, err := sdk.CachedECSListClusters(m.ECSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err)
}
for _, cluster := range ecsClusters {
// Get ECS tasks
taskARNs, err := sdk.CachedECSListTasks(m.ECSClient, aws.ToString(m.Caller.Account), r, cluster)
if err != nil {
m.modLog.Error(err)
}
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), r, cluster, taskARNs)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, task := range Tasks {
var role string
// Get ECS task definition
ecsTaskDefinition, err := sdk.CachedECSDescribeTaskDefinition(m.ECSClient, aws.ToString(m.Caller.Account), r, aws.ToString(task.TaskDefinitionArn))
if err != nil {
m.modLog.Error(err)
}
if ecsTaskDefinition.TaskRoleArn != nil {
role = aws.ToString(ecsTaskDefinition.TaskRoleArn)
}
dataReceiver <- Workload{
AWSService: "ECS",
Region: r,
Type: "task",
Name: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
Arn: aws.ToString(task.TaskArn),
Role: role,
}
}
}
}
func (m *WorkloadsModule) getLambdaWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// Get Lambda functions
lambdaFunctions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err)
}
for _, function := range lambdaFunctions {
//var role string
// if function.Role != nil {
// role = aws.ToString(function.Role)
// }
dataReceiver <- Workload{
AWSService: "Lambda",
Region: r,
Type: "function",
Name: aws.ToString(function.FunctionName),
Arn: aws.ToString(function.FunctionArn),
Role: aws.ToString(function.Role),
}
}
}
func (m *WorkloadsModule) getAppRunnerWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// Get App Runner services
appRunnerServices, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err)
}
for _, service := range appRunnerServices {
var role string
if service.ServiceArn != nil {
role = aws.ToString(service.ServiceArn)
}
dataReceiver <- Workload{
AWSService: "App Runner",
Region: r,
Type: "service",
Name: aws.ToString(service.ServiceName),
Arn: aws.ToString(service.ServiceArn),
Role: role,
}
}
}
func (m *WorkloadsModule) adminOnlyResults(headers []string, body [][]string) [][]string {
var adminOnlyBody [][]string
var adminOnlyHeaders []string
var adminOnlyTableCols []string
var adminColIndex int
var canPrivEscColIndex int
// First find which col is the admin col and which is the can privesc col and simply note which index they are in. Then we can use those indexes to see if the value in the body is "YES".
// For any row where isAdmin or CanPrivEsc is "YES", we will append that row to the adminOnlyBody.
// Find the index of the admin col
for i, header := range headers {
if header == "IsAdminRole?" {
adminOnlyHeaders = append(adminOnlyHeaders, header)
adminOnlyTableCols = append(adminOnlyTableCols, header)
adminColIndex = i
}
}
// Find the index of the can privesc col
for i, header := range headers {
if header == "CanPrivEscToAdmin?" {
adminOnlyHeaders = append(adminOnlyHeaders, header)
adminOnlyTableCols = append(adminOnlyTableCols, header)
canPrivEscColIndex = i
}
}
// Now that we have the indexes of the admin and can privesc cols, we can iterate through the body and append the rows where isAdmin or CanPrivEsc is "YES" to the adminOnlyBody
for _, row := range body {
if row[adminColIndex] == "YES" || row[canPrivEscColIndex] == "YES" {
adminOnlyBody = append(adminOnlyBody, row)
}
}
return adminOnlyBody
}
func (m *WorkloadsModule) getRolesFromInstanceProfiles() {
// The "PaginationControl" value is nil when there's no more data to return.
var PaginationMarker *string
PaginationControl := true
m.InstanceProfileToRolesMap = map[string][]iamTypes.Role{}
for PaginationControl {
ListInstanceProfiles, err := m.IAMClient.ListInstanceProfiles(
context.TODO(),
&(iam.ListInstanceProfilesInput{
Marker: PaginationMarker,
}),
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, instanceProfile := range ListInstanceProfiles.InstanceProfiles {
m.InstanceProfileToRolesMap[aws.ToString(instanceProfile.InstanceProfileId)] = instanceProfile.Roles
}
if aws.ToString(ListInstanceProfiles.Marker) != "" {
PaginationMarker = ListInstanceProfiles.Marker
} else {
PaginationMarker = nil
break
}
}
}
+1 -1
View File
@@ -80,7 +80,7 @@ func GetTenantIDPerSubscription(subscriptionID string) *string {
return nil
}
// function that determins if AzSubsriptionType is a subscritpion ID or a subscription display name and returns the AzSubsriptionType struct with both populated
// function that determines if AzSubsriptionType is a subscription ID or a subscription display name and returns the AzSubsriptionType struct with both populated
func PopulateSubsriptionType(subscription string) SubsriptionInfo {
subs := GetSubscriptions()
for _, s := range subs {
+1 -1
View File
@@ -36,7 +36,7 @@ func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerb
// cloudfox azure whoami
header, body = getWhoamiRelevantDataSubsOnly()
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
// append timetamp to filename (time from epoch)
// append timestamp to filename (time from epoch)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
+617 -379
View File
File diff suppressed because it is too large. Load diff
+94 -86
View File
@@ -1,70 +1,78 @@
module github.com/BishopFox/cloudfox
go 1.20
go 1.21.2
require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
github.com/aquasecurity/table v1.8.0
github.com/aws/aws-sdk-go-v2 v1.19.0
github.com/aws/aws-sdk-go-v2/config v1.18.28
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.14
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.14
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.12
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.9
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.2
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.16
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.20.1
github.com/aws/aws-sdk-go-v2/service/ec2 v1.105.0
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.14
github.com/aws/aws-sdk-go-v2/service/ecs v1.28.1
github.com/aws/aws-sdk-go-v2/service/efs v1.20.4
github.com/aws/aws-sdk-go-v2/service/eks v1.27.15
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.3
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.13
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.14
github.com/aws/aws-sdk-go-v2/service/fsx v1.30.0
github.com/aws/aws-sdk-go-v2/service/glue v1.55.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.4
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1
github.com/aws/aws-sdk-go-v2/service/lambda v1.37.1
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2
github.com/aws/aws-sdk-go-v2/service/mq v1.15.1
github.com/aws/aws-sdk-go-v2/service/opensearch v1.18.3
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.9
github.com/aws/aws-sdk-go-v2/service/ram v1.18.5
github.com/aws/aws-sdk-go-v2/service/rds v1.46.2
github.com/aws/aws-sdk-go-v2/service/redshift v1.28.1
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.15
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.92.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.11
github.com/aws/aws-sdk-go-v2/service/sns v1.20.14
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.3
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.8
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3
github.com/aws/smithy-go v1.13.5
github.com/bishopfox/awsservicemap v1.0.2
github.com/aws/aws-sdk-go-v2 v1.24.1
github.com/aws/aws-sdk-go-v2/config v1.26.2
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6
github.com/aws/smithy-go v1.19.0
github.com/bishopfox/awsservicemap v1.0.3
github.com/dominikbraun/graph v0.23.0
github.com/fatih/color v1.15.0
github.com/fatih/color v1.16.0
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.11.0
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
golang.org/x/crypto v0.17.0
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/aws/aws-sdk-go-v2/service/neptune v1.28.2 // indirect
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
@@ -73,52 +81,52 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect
github.com/aws/aws-sdk-go-v2/service/athena v1.30.4
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.18.8
github.com/aws/aws-sdk-go-v2/service/codecommit v1.14.15
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.16.14
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.14.13
github.com/aws/aws-sdk-go-v2/service/docdb v1.22.0
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.15.13
github.com/aws/aws-sdk-go-v2/service/emr v1.27.1
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 // indirect
github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.15
github.com/aws/aws-sdk-go-v2/service/sfn v1.18.1
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.13
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/errors v0.21.0 // indirect
github.com/go-openapi/strfmt v0.21.10 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
+191 -588
View File
@@ -1,56 +1,18 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1 h1:AMf7YbZOZIW5b66cXNHMWWT/zkjhz5+a+k/3x40EO7E=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1/go.mod h1:uwfk06ZBcvL/g4VHNjurPfVln9NMbsk2XIZxJ+hu81k=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
@@ -78,154 +40,157 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0=
github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 h1:cZG7psLfqpkB6H+fIrgUDWmlzM474St1LP0jcz272yI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27/go.mod h1:ZdjYvJpDlefgh8/hWelJhqgqJeodxu4SmbVsSdBlL7E=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.14 h1:mXf/MQX2zcKpWTfI4YgHrD4UYBh6AzyBCRfVdsxExaU=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.14/go.mod h1:KJyzRVA5DkFaU4hVgKDoHiSrCobfmYP8UpRXlybTuTU=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.14 h1:2jRKwxT+e40l1vepcIIenlrZBXrCWnx1Z2JkoUUqWrU=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.14/go.mod h1:lg/1D90DDo2//C84mvygysHF4JRo+Vf/W5YbkHoeUk8=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.12 h1:N+D3pKbf4KsQZqyjkp6wxHmOoDspMg1NwZ5yamU2m1s=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.12/go.mod h1:M7bk3j8yRkWkLSWoDEAi0g/4sMESF833RyaNvXv1dY4=
github.com/aws/aws-sdk-go-v2/service/athena v1.30.4 h1:x6pNnhCWXrkGX43gkJkcdCtlYSFx3tzqJKnm2QBqz6k=
github.com/aws/aws-sdk-go-v2/service/athena v1.30.4/go.mod h1:XyrQmcmWx6BNhu1K5la/Zub8gX29MqiIMQ9silULHjk=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.1 h1:MHKSdt+ECxOWD98MYj/Ocy4GS8GgAjgEDSPaiTaXP6U=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.1/go.mod h1:laKFhtn8EH6gcPl7KEQ4kcuSYcQF1tqUm82ENxMwlMk=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.9 h1:4O/O22aiQnseomkhDKmLmxbu/cvuh3h04TIkIt3y52g=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.9/go.mod h1:NmocATyq+MjavfhK3WQwXpsVosmwkaV5fq4dSco8xtA=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.2 h1:GFzRU2A1lQQRtdwVlnUaP7ZUMbqDY8wJ8PmIy8CRK4A=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.2/go.mod h1:KVIJ9sAGIzNEL5mdHa/AImzRKeu8hl9aeXefbZ1J6a8=
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.18.8 h1:cWssbdIoAQpXqaVvdlXkGMvoBtMQnyCj2YAnLCsRClo=
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.18.8/go.mod h1:Zdiq2FAX/UUo8sxidJFSHnvfnj3ZWNkVXW9tshdzQhE=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.16 h1:fNS2i46UC2lcenFPn0BUR6BQsNjT3gdH7XB5772Rku4=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.16/go.mod h1:eM+6ViLu4JRJLd6ZkQ/wDCh1KlOkhZ812kPLENq0t4k=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.14.15 h1:TEwSIgYLucLnjeKHR6zGp157gW4sLzlwdVAQ2x7u6ZE=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.14.15/go.mod h1:JBOP8ous+Xpzr2FcXLauTA4xa7Ybrji0dsW2tUuVLvs=
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.16.14 h1:EVKDqeD08oM5ixpnV06StrPmRVB7uuM/o9Sl4rHVeR0=
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.16.14/go.mod h1:J/+zzr7xwnR7NBecD5KSWcc/TCa1F6MHV76A1iZnO/o=
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.14.13 h1:ecYKjPdg4cypyt6WbHUp/Fjd0UeQNO0sXgDdWUuS+W0=
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.14.13/go.mod h1:mesCNwh+iqOeg228D7bYNgW/AHQveoC/0fkFDlSJ5RY=
github.com/aws/aws-sdk-go-v2/service/docdb v1.22.0 h1:pj/zAKhHzoPQkR2GW1pA3qg9L7bIf+A6zSoV0FXCqfw=
github.com/aws/aws-sdk-go-v2/service/docdb v1.22.0/go.mod h1:XwDAgHepy8Zz8Tt+CDvbyJ8ahGkzhFF2QNALcMjst+o=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.20.1 h1:gknY3OHEGXaLamootb1VaJSohtHwcIMGvm23VnZVIzE=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.20.1/go.mod h1:iA/evsHrPWhDyMj6cuMa6qlFTqSqYXoKs8LSvIFauTA=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.105.0 h1:8lYKOgr+FTjPFk6fHRDuZ4e96p3ULA+ZCKzB2nIwif8=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.105.0/go.mod h1:/0btVmMZJ0sn9JQ2N96XszlQNeRCJhhXOS/sPZgDeew=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.14 h1:dcbDHH0NqKqalrVVwuEcvMvKBBY4AHcI8+JTiytJKDw=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.14/go.mod h1:ALDmr/JM6zozlNH9a/SBdXIDFdwKfSl//1Eg5YE+jww=
github.com/aws/aws-sdk-go-v2/service/ecs v1.28.1 h1:PxWgrtfQvct60NjxSrFsSWG/Yg1HATRKP4IeUPiLlrE=
github.com/aws/aws-sdk-go-v2/service/ecs v1.28.1/go.mod h1:eZBCsRjzc+ZX8x3h0beHOu+uxRWRwnEHzzvDgKy9v0E=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.4 h1:Ji42n3FRxDb+ATocYrNsFeXVlxIX7SCvM7P2p9HUwNE=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.4/go.mod h1:gcYTldik9KddkXidRaSjVXc+56+lpVT9C0aABQBQ7vE=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.15 h1:Q48ivwZJ136hfkk8Dua1fMM7m1e1s/0rBRyRX/J9XAY=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.15/go.mod h1:9mqDBj08MtFxKFQWUEMm4iFnIdM9gFpnSJvHUEIfsiU=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.3 h1:6bmDGXMc8y/M8khlDxtDWvUPCUEpwei8pUQ30f6mVBU=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.3/go.mod h1:ZPycHtrD86labux1w15bbSWw4acVxgh+UTVZZ5iCoE8=
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.15.13 h1:cUFujjKLQ17m6aEvNRnmZP1XmLJlAsDa8LZ6x2eguOk=
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.15.13/go.mod h1:GtN44WU1jWBvpbqnWS1TIX466t1DzJ2GiFPc1sDRVHE=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.13 h1:iGBC7Z41yj6NvDreXhFtxtyjPuxS+l4qAtP4sWb5j2M=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.13/go.mod h1:P8vJCgR0ZIIliJ/13O0nIyQf32ZFUe5IrcDeNEvzHGE=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.14 h1:ekfFZUYzAqzBYhh1bwIen4SNLIn4KiMNDWyRmfbp62I=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.14/go.mod h1:0eT2aeVd4MnWmyT935I2MTwP5xT7cFVteV02BgJ/F+E=
github.com/aws/aws-sdk-go-v2/service/emr v1.27.1 h1:DKtcRNy5Ax+la1EB3zom38xp6CO/SQyhBhcLY68UVPA=
github.com/aws/aws-sdk-go-v2/service/emr v1.27.1/go.mod h1:zctGR4lbmEwybnWFh3jZNJe5mSWDMh0wWYEc9u3AvN0=
github.com/aws/aws-sdk-go-v2/service/fsx v1.30.0 h1:mOJHvtiMYG+pyo4wuA63FCO/ZaawIiySEe+Gy5DrwtI=
github.com/aws/aws-sdk-go-v2/service/fsx v1.30.0/go.mod h1:Xyl3hExYEINtN3PSM2j4x/fyUGQCICH+uQS33WTgjWE=
github.com/aws/aws-sdk-go-v2/service/glue v1.55.0 h1:tdqCJ2VXVKBgab3Ugzdet+/PCftkyNnYSxjaZFqeQYs=
github.com/aws/aws-sdk-go-v2/service/glue v1.55.0/go.mod h1:Q9zH4k9LjOE7V1NqIsRHXc2M7WF3hi02v5TgFtqVErc=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.4 h1:InFGzy71oXUzTMBzhclha3j8mgWnTvDN7FCVDXrXyHE=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.4/go.mod h1:5K62PcnljmwuUv6qE2RhGBjAWxFyo57VTLjeYMW76Ak=
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1 h1:VTCWgsrromZqnlRgfziqqWWcW7LFkQLwJVYgf/5zgWA=
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1/go.mod h1:LBsjrFczXiQLASO6FtDGTeHuZh6oHuIH6VKaOozFghg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 h1:Bje8Xkh2OWpjBdNfXLrnn8eZg569dUQmhgtydxAYyP0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30/go.mod h1:qQtIBl5OVMfmeQkz8HaVyh5DzFmmFXyvK27UgIgOr4c=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.29 h1:gajv/wALzb2KgK9YKq1jW+y2ZgL5o4A+UZmFfZi8lSY=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.29/go.mod h1:SYEgYIjFeLoPSOCIqdFr44QiBwGlnsUIHqMD5OZnsgg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 h1:hx4WksB0NRQ9utR+2c3gEGzl6uKj3eM6PMQ6tN3lgXs=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4/go.mod h1:JniVpqvw90sVjNqanGLufrVapWySL28fhBlYgl96Q/w=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.15 h1:ntCXWPydulxVCe1/Jzw/CpffNI5Rz04+4ALDbxLvzjM=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.15/go.mod h1:Vtt7mN35Ev7fC7NHpSXmprMUP7DkEBTRMNHK0gQojeM=
github.com/aws/aws-sdk-go-v2/service/lambda v1.37.1 h1:BRdW2JcxZSsen77Y0WoWIWY4+H9EXT55uEPWZKIcDHY=
github.com/aws/aws-sdk-go-v2/service/lambda v1.37.1/go.mod h1:zmdE2b9ZX8milexhZc3SeC3LwJRJpJ0k0fsuMBOSCEI=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco=
github.com/aws/aws-sdk-go-v2/service/mq v1.15.1 h1:TW18FUzhvAFZpGhvSiJYw/1z3sq+2pZl61YwLP7Ukzg=
github.com/aws/aws-sdk-go-v2/service/mq v1.15.1/go.mod h1:98Ia1qM4KLiezDJ001VFhzTgFU2XpBtiFveZ5CcoZ1c=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.18.3 h1:NCFBj/m5ga7XZsMmrwkb/BKlLr7NqxgWTtRoFTdfIhc=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.18.3/go.mod h1:zZ5INI6l4mLcOO/rfXTgm/M4r7u5wMNu1MfLdhDdYac=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.9 h1:zGbv0aBHnx6LAZeClqK9fpO1C8IIGyToP0u62pEQiBA=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.9/go.mod h1:OvoCgJ4TLrd6ADYbUcWMc58RfeY6SM/2V1l5WqTkQUs=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.5 h1:NaXBILMGYxTny+NliB9xePKQW2zytcCpsNCW9xWtC2A=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.5/go.mod h1:0Kw6acC8zS+XHVfyatwG2sJ0bPtT3kRipUH8i48KcxM=
github.com/aws/aws-sdk-go-v2/service/rds v1.46.2 h1:y3bLlmitL3ASnYNFZJ+ySl2ONEPWJt/GQ9N9OE/auLA=
github.com/aws/aws-sdk-go-v2/service/rds v1.46.2/go.mod h1:+T+8IxRb5KRF52F8xfWanPDfUCGFL3j1yRl/VVF0q+M=
github.com/aws/aws-sdk-go-v2/service/redshift v1.28.1 h1:ZuQTY/P127S/Gyv5HZ6gGC0kqFLyTZMlyPduRvyPy/w=
github.com/aws/aws-sdk-go-v2/service/redshift v1.28.1/go.mod h1:ihY8HhdhtqDaAvlUEd2iU3IfNboMiDVE6qicbacH0w4=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.15 h1:5I9Yi2Ls1q8/VTpRmlLOGilFCtJNsEms+64BhYybm7Y=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.15/go.mod h1:86l8OObGPcaNgQ2pVaRRdaHTepispGs2UYLp8niWkSM=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k=
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 h1:PalLOEGZ/4XfQxpGZFTLaoJSmPoybnqJYotaIZEf/Rg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0/go.mod h1:PwyKKVL0cNkC37QwLcrhyeCrAk+5bY8O2ou7USyAS2A=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.92.0 h1:MJ/Ras3Ea9eUEnQs/T59lil3EMt6OVUj4zjkOlK/8/Q=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.92.0/go.mod h1:r5UufmVymEbTga9pVC6VkIF6pnNyCMowP0sD5Rf1Z0E=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.11 h1:g2uwGxEXNsjd2OJHIW1KrP4q/BAUcyn6440LQ9gR+nQ=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.11/go.mod h1:ywwMMBG8ioLfw1LccY05t/egwONbtwDkoUVuXNgDZjs=
github.com/aws/aws-sdk-go-v2/service/sfn v1.18.1 h1:2lBYdhSTuqep5O54NNnyncuaI/xwABFRmRhNkfTIu1I=
github.com/aws/aws-sdk-go-v2/service/sfn v1.18.1/go.mod h1:Ar9VDauywKQCtfEVZ2AiXBMwHtsDaqngdt4/aUbqtUc=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.14 h1:zl5JH5C7gE9bnvelqK7AKrmwp3lZRMh1281HoJ4qRj0=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.14/go.mod h1:bhIXgiDmP5qREwaPNFMyflXgjtd8USRrISXYQygSDi0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.3 h1:yA25gnP6qmggck6ypwNFxurfXnyx4XJexwJVDko/UH0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.3/go.mod h1:FcXJKz137Ousb8wFnHyYI/qjUB7nUUFqKZvWaBe7Fy0=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.8 h1:Z9bclrIuHR0/yd8yGikJAbYS4iIDySF+Fo7lwBuDWfo=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.8/go.mod h1:Uwh2QwiXNf2+WCU3z5K13HE6f2bLCu9WpioFRkWjUVk=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bishopfox/awsservicemap v1.0.2 h1:+NnuInVcQkja6y6h6eGseneU3m5Nzp9O++gR1s+FiyM=
github.com/bishopfox/awsservicemap v1.0.2/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.26.2 h1:+RWLEIWQIGgrz2pBPAUoGgNGs1TOyF4Hml7hCnYj2jc=
github.com/aws/aws-sdk-go-v2/config v1.26.2/go.mod h1:l6xqvUxt0Oj7PI/SUXYLNyZ9T/yBPn3YTQcJLLOdtR8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.13 h1:WLABQ4Cp4vXtXfOWOS3MEZKr6AAYUpMczLhgKtAjQ/8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.13/go.mod h1:Qg6x82FXwW0sJHzYruxGiuApNo31UEtJvXVSZAXeWiw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 h1:ePPaOVn92r5n8Neecdpy93hDmR0PBH6H6b7VQCE5vKE=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6/go.mod h1:P/zwE9uiC6eK/kL3CS60lxTTVC2zAvaS4iW31io41V4=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 h1:bCdxKjM8DpkNJXnOLVx+Hnav0eM4yJK8kof56VvIjMc=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6/go.mod h1:zQ6tOYz7oGI7MbLRDBXfo63puDoTroVcVNXWfmRDA1E=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5 h1:YpUU6SUj7EYB+l4kGQk2ch5Vz+HK/cn2bfYsJk81+GI=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5/go.mod h1:W1Z87aVGwtb3egdiWLrnMyj5oHzShACix2iebsixuVA=
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 h1:qNLkDi/rOaauOuh33a4MNZjyfxvwIgC5qsDiHPvjDk0=
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3/go.mod h1:MlpC6swcjh1Il80u6XoeY2BTHIZRZWvoXOfaq3rfh8I=
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5 h1:eSu3COqcATUvidX7AfqOhp6fJadqGD2gTX2Z0oWGUXQ=
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5/go.mod h1:R80rQE5F5/Qf+l4pTpxq8GiEd0OqLo3/S5bNBNEs04A=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5 h1:5+m0XrCIwjjeP4f3AdC1wyQBc2ClIJi2mP4e3Wkdgvw=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5/go.mod h1:oPk8ZMctRUtGC13pOE83Zp0baZgJsmzuKm4IRR+zQOI=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5 h1:synDXYpTr5FA80g8twNr49Dd7iAKnxerp93l/kNm/cQ=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5/go.mod h1:Dil6nVeCPyPc1gF5EeCrVUTtXexn80MpfqhgSp/Zb64=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 h1:Yc+avPLGARzp4A9Oi9VRxvlcGqI+0MYIg4tPSupKv2U=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6/go.mod h1:zrqdG1b+4AGoTwTMVFzvzY7ARB3GPo4gKRuK8WPEo8w=
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5 h1:KKuWOQhF/0JPurE6c4Uaem2/Vd221FI0io5gv+rXty0=
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5/go.mod h1:P7DrCurmyD78GbU8IRD+JIK3f9FkeuTe9A8/zXDJv7Q=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 h1:EPnlDd4V2EXywlOPAw/pMUW4PHUgSulKm4zXFU6bixE=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5/go.mod h1:G2JUWf01sbb5/A8qGcM4dqy4nbl4y4IGWmaCDWAvA2Y=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0 h1:bKW3m6CmWzV/HNqgHiv+HNH0epvyUhnfGAkMzIegUIo=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0/go.mod h1:pI87yFGLN6qjkKhI4Hdh1cpcak8bu/QAJFesJNCSj7k=
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2 h1:3b8fwDhM0bJoOVglvM1w4665Ry6mrb3Rp+AH8iGI6qg=
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2/go.mod h1:RiusqJl55/p7S8LNMh2J3ZsDHDqxRiPdsfIaZRKeEUo=
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5 h1:zkIKbco/gM4fmbU90ajz4ah3ErgAgot7aTE0PpIxNuE=
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5/go.mod h1:+G+TF84SIgMdGDjZKKYPpx6LwXSN4QcywQGa8e2vJ1U=
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5 h1:txsajy47TIyoL7/BQt0VwqmzLPIsfAT/RWI9iD4q5vU=
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5/go.mod h1:8d1RpdlgxFU6VO2aWru1ckR0Vsm4EgqCZgOamw5OHpw=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7 h1:X60rMbnylU1xmmhv4+/N78t+lKOCC4ELst5eR25dyqg=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7/go.mod h1:o7TD9sjdgrl8l/g2a2IkYjuhxjPy9DMP2sWo7piaRBQ=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0 h1:VrFC1uEZjX4ghkm/et8ATVGb1mT75Iv8aPKPjUE+F8A=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0/go.mod h1:qjhtI9zjpUHRc6khtrIM9fb48+ii6+UikL3/b+MKYn0=
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6 h1:cT7h+GWP2k0hJSsPmppKgxl4C9R6gCC5/oF4oHnmpK4=
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6/go.mod h1:AOHmGMoPtSY9Zm2zBuwUJQBisIvYAZeA1n7b6f4e880=
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs=
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368=
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5 h1:N1ezZV2yy7NV2w/bA4s4I/+0n2xpL4DzlmroEg5qFsg=
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5/go.mod h1:PJHqaboMcF/eLy1F/Y9hyls4CQGP5+T5f0iRq6CPXu4=
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0 h1:tCIkZ/ZdJMGZ1MOwdcioYhOUkkD4F58KFvQTgR3ZIlc=
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0/go.mod h1:L1uv3UgQlAkdM9v0gpec7nnfUiQkCnGMjBE7MJArfWQ=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6 h1:Y/5eE9Sc+OBID9pZ4EVFzyQviv1d1RbqB17HRur9ySg=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6/go.mod h1:iPx2i26hgUULkNh1Jk4QzYzzQKd2nXl/rD9Fm5hQ2uk=
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5 h1:wUyVpgx2Rs8wYFbzeZTN2uzAuORh8Ea5iV1eIDjatM0=
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5/go.mod h1:EhlhcjyCljBHkCR17DvnvSnmVYrdgsIzPTH3+vnhaV0=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6 h1:pTyTNb1QVqMT0livj/Goj+68cnJg7fe4o+wFZvasB4M=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6/go.mod h1:N37+67ROdmH7BgLyp1cwCjRpKism3cwkeDlOktRLXMQ=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6 h1:twI2uRmpbm0KBog3Ay61IqOtNp6+QxKfSA78zftME/o=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6/go.mod h1:Tpt4kC8x1HfYuh2rG/6yXZrxjABETERrUl9IdA/IS98=
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6 h1:eJ1K3IaNErKlR8MXg0c4aMjPpvp9qI39WCzx50u+XtQ=
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6/go.mod h1:Drh6y2qLaw/wnDKTIcdqM2m358MIRXsZ2Bj2tjhVLq0=
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0 h1:bH7oEgVZ4pR27sciaYeNuACsZy6wQXjqRjli54tunOg=
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0/go.mod h1:GnnK4J/WQajWEAwXd/82wpF275XVhNgsYsXtK1baOjE=
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0 h1:KMA3nvkeQf5I40cFjjl3GaoasKRPtph+J8RfcWlLlWU=
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0/go.mod h1:ALQEuXs/XUdwrkAucRl3juNFbiomoaPICShOoGzHNiE=
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5 h1:1HGLgX6zvanwVsruEyamDDxXZKQlIPFZYzQVR5Zhv3Q=
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5/go.mod h1:g9Hl5MYDQtTfNldyymQL5/D93cxP8PGu2B/776IMml0=
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5 h1:Ts2eDDuMLrrmd0ARlg5zSoBQUvhdthgiNnPdiykTJs0=
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5/go.mod h1:kKI0gdVsf+Ev9knh/3lBJbchtX5LLNH25lAzx3KDj3Q=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10 h1:h8uweImUHGgyNKrxIUwpPs6XiH0a6DJ17hSJvFLgPAo=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10/go.mod h1:LZKVtMBiZfdvUWgwg61Qo6kyAmE5rn9Dw36AqnycvG8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6/go.mod h1:Sj7qc+P/GOGOPMDn8+B7Cs+WPq1Gk+R6CXRXVhZtWcA=
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 h1:w8lI9zlVwRTL9f4KB9fRThddhRivv+EQQzv2nU8JDQo=
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5 h1:0KVnA62WGcVdeJKH+DTUkxNms2OsIky+AmB2iX93eAs=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5/go.mod h1:wI7palPB84YaqCYglfNiyAlDcXTFbcJ9rDHMu15cFto=
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 h1:n86T5yw0kS6a5nbpkEpDzLPCBXXb35lx3iDkmQWlizA=
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6/go.mod h1:phfKOOpMQhlBv2KE8gF17P82zLcSedA9b7fMSGTLBdQ=
github.com/aws/aws-sdk-go-v2/service/neptune v1.28.2 h1:DOv1kEXarl5GGLxUKr4JSGB10gIEFi/XxDIJHOFAEss=
github.com/aws/aws-sdk-go-v2/service/neptune v1.28.2/go.mod h1:blUZTHNHhlAi31tve04hxwROjCFoLt3HXM9d5ftliR0=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1 h1:qmxbwQY/gnN7eBElvy9saejXWaKD6ENU8JF5SuoAWLQ=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1/go.mod h1:l89g2sqHGQRg0UZ1ldFfOxh96mZAGn8ZBlaXpoJ2psQ=
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5 h1:4sW8XPTtuH6PX8CUcpUxBKg0Pf67k1MOOgq9Y+v4ls8=
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5/go.mod h1:AMzAwJifk4gEft+ElIMFjOb2qUNqHODfjSszVL5Nfeo=
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7 h1:xZdtfxmMRR65HqUFJ3Lfhzkmd9KhVI30TM8Sjj25Pio=
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7/go.mod h1:VgmJolFAkLhZ5Qnnmyfqqyu6+Dt+5E761HzU2ou0Tjg=
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0 h1:WUQ6kmnta31GhQvRJtHPVoO4hSNF8Yh2CQIFCZbhZ8g=
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow=
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU=
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6 h1:wiM6xGxWTPI8Yck4efgQGS0lanuMILbng8oukqa4bNM=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6/go.mod h1:Nngchp1Q7LNBS8J10r4P0npfroNRaCVz6wWNfBz7j4E=
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0 h1:7wh6KdJnej4T7sE/xfnZf5T+GQzp6GfoZi+5r6ZPlW8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0/go.mod h1:F9El48+5Tf+TkYJB/6M9H7oqXw9Mr9eVetwJ6SUql7g=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7 h1:o0ASbVwUAIrfp/WcCac+6jioZt4Hd8k/1X8u7GJ/QeM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0 h1:YSTmNx+/qyprl8gkBdkxuKW7lI1zPRG7QBSDJVdiZic=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0/go.mod h1:aGWPLBmB3O7wnYYJLHZdahmPt/ai3QAnOTjxxNmMB2A=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs=
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6 h1:agEKwGJ+CyvQ2oARsHsA8fn/CCz7I402CgfWcnhIPGE=
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6/go.mod h1:goJW4NkHiLfCWTNykK9w7PkACje1y9OIT1IOn8kmRvw=
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI=
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc=
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc=
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6 h1:EZw+TRx/4qlfp6VJ0P1sx04Txd9yGNK+NiO1upaXmh4=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6/go.mod h1:uXndCJoDO9gpuK24rNWVCnrGNUydKFEAYAZ7UU9S0rQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6 h1:HJeiuZ2fldpd0WqngyMR6KW7ofkXNLyOaHwEIGm39Cs=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/bishopfox/awsservicemap v1.0.3 h1:0T+mJLwG+vQV9+o3dzwzxhWJWE40VpoCLWtaPBwixYc=
github.com/bishopfox/awsservicemap v1.0.3/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb h1:ot96tC/kdm0GKV1kl+aXJorqJbyx92R9bjRQvbBmLKU=
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb/go.mod h1:2OnSqu4B86+2xGSIE5D4z3Rze9yJ/LNNjNXHhwMR+vY=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -234,88 +199,23 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho=
github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
@@ -324,15 +224,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
@@ -340,206 +232,72 @@ github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2px
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -548,185 +306,30 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+69 -19
View File
@@ -12,6 +12,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go/ptr"
@@ -25,32 +26,76 @@ var (
TxtLoggerName = "root"
TxtLog = TxtLogger()
UtilsFs = afero.NewOsFs()
credsMap = map[string]aws.Credentials{}
ConfigMap = map[string]aws.Config{}
)
func AWSConfigFileLoader(AWSProfile string, version string) aws.Config {
// Ensures the profile in the aws config file meets all requirements (valid keys and a region defined). I noticed some calls fail without a default region.
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
func() aws.Retryer {
return retry.AddWithMaxAttempts(retry.NewStandard(), 3)
}))
if err != nil {
fmt.Println(err)
TxtLog.Println(err)
}
func AWSConfigFileLoader(AWSProfile string, version string, AwsMfaToken string) aws.Config {
// Loads the AWS config file and returns a config object
_, err = cfg.Credentials.Retrieve(context.TODO())
if err != nil {
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
TxtLog.Printf("Could not retrieve the specified profile name %s", err)
var cfg aws.Config
var err error
// Check if the profile is already in the config map. If not, load it and retrieve the credentials. If it is, return the cached config object
// The AssumeRoleOptions below are used to pass the MFA token to the AssumeRole call (when applicable)
if _, ok := ConfigMap[AWSProfile]; !ok {
// Ensures the profile in the aws config file meets all requirements (valid keys and a region defined). I noticed some calls fail without a default region.
if AwsMfaToken != "" {
cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
func() aws.Retryer {
return retry.AddWithMaxAttempts(retry.NewStandard(), 3)
}), config.WithAssumeRoleCredentialOptions(func(options *stscreds.AssumeRoleOptions) {
options.TokenProvider = func() (string, error) {
return AwsMfaToken, nil
}
}),
)
} else {
cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
func() aws.Retryer {
return retry.AddWithMaxAttempts(retry.NewStandard(), 3)
}), config.WithAssumeRoleCredentialOptions(func(options *stscreds.AssumeRoleOptions) {
options.TokenProvider = stscreds.StdinTokenProvider
}),
)
}
if err != nil {
//fmt.Println(err)
if AWSProfile != "" {
TxtLog.Println(err)
fmt.Printf("[%s][%s] The specified profile [%s] does not exist or there was an error loading the credentials.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile), AWSProfile)
TxtLog.Fatalf("Could not retrieve the specified profile name %s", err)
} else {
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
TxtLog.Fatalf("Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
}
//os.Exit(1)
}
_, err := cfg.Credentials.Retrieve(context.TODO())
if err != nil {
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
} else {
// update the config map with the new config for future lookups
ConfigMap[AWSProfile] = cfg
//return the config object for this first iteration
return cfg
}
} else {
//fmt.Println("Using cached config")
cfg = ConfigMap[AWSProfile]
return cfg
}
return cfg
}
func AWSWhoami(awsProfile string, version string) (*sts.GetCallerIdentityOutput, error) {
func AWSWhoami(awsProfile string, version string, AwsMfaToken string) (*sts.GetCallerIdentityOutput, error) {
// Connects to STS and checks caller identity. Same as running "aws sts get-caller-identity"
//fmt.Printf("[%s] Retrieving caller's identity\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)))
STSService := sts.NewFromConfig(AWSConfigFileLoader(awsProfile, version))
STSService := sts.NewFromConfig(AWSConfigFileLoader(awsProfile, version, AwsMfaToken))
CallerIdentity, err := STSService.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{})
if err != nil {
fmt.Printf("[%s][%s] Could not get caller's identity\n\nError: %s\n\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(awsProfile), err)
@@ -61,9 +106,9 @@ func AWSWhoami(awsProfile string, version string) (*sts.GetCallerIdentityOutput,
return CallerIdentity, err
}
func GetEnabledRegions(awsProfile string, version string) []string {
func GetEnabledRegions(awsProfile string, version string, AwsMfaToken string) []string {
var enabledRegions []string
ec2Client := ec2.NewFromConfig(AWSConfigFileLoader(awsProfile, version))
ec2Client := ec2.NewFromConfig(ConfigMap[awsProfile])
regions, err := ec2Client.DescribeRegions(
context.TODO(),
&ec2.DescribeRegionsInput{
@@ -126,8 +171,13 @@ func GetEnabledRegions(awsProfile string, version string) []string {
// txtLogger - Returns the txt logger
func TxtLogger() *logrus.Logger {
var txtFile *os.File
var err error
txtLogger := logrus.New()
txtFile, err := os.OpenFile(fmt.Sprintf("%s/cloudfox-error.log", ptr.ToString(GetLogDirPath())), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
txtFile, err = os.OpenFile(fmt.Sprintf("%s/cloudfox-error.log", ptr.ToString(GetLogDirPath())), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
txtFile, err = os.OpenFile(fmt.Sprintf("./cloudfox-error.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
if err != nil {
panic(fmt.Sprintf("Failed to open log file %v", err))
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
)
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
// Conditions have the folling general structure:
// Conditions have the following general structure:
// "Condition" : { "{condition-operator}" : { "{condition-key}" : "{condition-value}" }}
type PolicyStatementCondition map[string]map[string]ListOrString
@@ -20,7 +20,7 @@ func (psc *PolicyStatementCondition) IsEmpty() bool {
// IsScopedOnAccountOrOrganization returns true if the policy condition ensures access only for specific
// AWS accounts or organizations. If may return false even if access is restricted in such a way.
// Such policies should be reported to the user and analyzed case by case to judge if conditions are sufficently restrictive.
// Such policies should be reported to the user and analyzed case by case to judge if conditions are sufficiently restrictive.
func (psc *PolicyStatementCondition) IsScopedOnAccountOrOrganization() bool {
for condition, kv := range *psc {
for k, v := range kv {
+14
View File
@@ -3,6 +3,7 @@ package policy
import (
"encoding/json"
"errors"
"strings"
)
type PolicyStatementPrincipal struct {
@@ -92,3 +93,16 @@ func (ls *ListOrString) UnmarshalJSON(b []byte) error {
return errors.New("not a string or list of strings")
}
// create a method on *PolicyStatementPrincipalObject that will determine if trusted principal is from the same account as the resource or a different account
func (pspo *PolicyStatementPrincipalObject) IsTrustedPrincipalSameAccount(accountID string) bool {
for _, principal := range pspo.CanonicalUser {
if strings.Contains(principal, ":") {
principalAccount := strings.Split(principal, ":")[4]
if principalAccount == accountID {
return true
}
}
}
return false
}
+119
View File
@@ -0,0 +1,119 @@
package policy
import (
"encoding/json"
"errors"
"net/url"
"regexp"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
)
type TrustPolicyDocument struct {
Version string `json:"Version"`
Statement []RoleTrustStatementEntry `json:"Statement"`
}
type RoleTrustStatementEntry struct {
Sid string `json:"Sid"`
Effect string `json:"Effect"`
Principal struct {
AWS ListOfPrincipals `json:"AWS"`
Service ListOfPrincipals `json:"Service"`
Federated ListOfPrincipals `json:"Federated"`
} `json:"Principal"`
Action string `json:"Action"`
Condition struct {
StringEquals struct {
StsExternalID string `json:"sts:ExternalId"`
SAMLAud string `json:"SAML:aud"`
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
OidcEksSub ListOfPrincipals `json:"OidcEksSub"`
OidcEksAud string `json:"OidcEksAud"`
CognitoAud string `json:"cognito-identity.amazonaws.com:aud"`
TerraformAud string `json:"app.terraform.io:aud"` // Terraform Cloud specific
TerraformSub ListOfPrincipals `json:"app.terraform.io:sub"` // Terraform Cloud specific
GCPAud string `json:"accounts.google.com:aud"`
GCPSub ListOfPrincipals `json:"accounts.google.com:sub"`
AzureADIss ListOfPrincipals `json:"http://sts.windows.net/tenant-id/iss"` // Azure AD specific
AzureADSub ListOfPrincipals `json:"sub"` // Common among OIDC providers
PingClientId string `json:"pingidentity.com:client_id"`
GoogleWorkspaceAud string `json:"workspace.google.com:aud"`
GoogleWorkspaceSub ListOfPrincipals `json:"workspace.google.com:sub"`
CircleCIAud ListOfPrincipals `json:"CircleCIAud"`
CircleCISub ListOfPrincipals `json:"CircleCISub"`
// Add other provider-specific claims here
} `json:"StringEquals"`
StringLike struct {
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
OidcEksSub ListOfPrincipals `json:"OidcEksSub"`
OidcEksAud string `json:"OidcEksAud"`
TerraformAud string `json:"app.terraform.io:aud"` // Terraform Cloud specific
TerraformSub ListOfPrincipals `json:"app.terraform.io:sub"` // Terraform Cloud specific
GCPAud string `json:"accounts.google.com:aud"`
GCPSub ListOfPrincipals `json:"accounts.google.com:sub"`
AzureADIss ListOfPrincipals `json:"http://sts.windows.net/tenant-id/iss"` // Azure AD specific
AzureADSub ListOfPrincipals `json:"sub"` // Common among OIDC providers
PingClientId string `json:"pingidentity.com:client_id"`
GoogleWorkspaceAud string `json:"workspace.google.com:aud"`
GoogleWorkspaceSub ListOfPrincipals `json:"workspace.google.com:sub"`
CircleCIAud ListOfPrincipals `json:"CircleCIAud"`
CircleCISub ListOfPrincipals `json:"CircleCISub"`
// Add patterns for provider-specific claims that support wildcards or partial matches
} `json:"StringLike"`
ForAnyValueStringLike struct {
CognitoAMR string `json:"cognito-identity.amazonaws.com:amr"`
//Auth0Amr ListOfPrincipals `json:"Auth0Amr"`
// This section can be extended with any-value-like conditions for other providers if they support such conditions
} `json:"ForAnyValue:StringLike"`
} `json:"Condition"`
}
// A custom unmarshaller is necessary because the list of principals can be an array of strings or a string.
// https://stackoverflow.com/questions/65854778/parsing-arn-from-iam-policy-using-regex
type ListOfPrincipals []string
func (r *ListOfPrincipals) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err == nil {
*r = append(*r, s)
return nil
}
var ss []string
if err := json.Unmarshal(b, &ss); err == nil {
*r = ss
return nil
}
return errors.New("cannot unmarshal neither to a string nor a slice of strings")
}
func ParseRoleTrustPolicyDocument(role types.Role) (TrustPolicyDocument, error) {
document, _ := url.QueryUnescape(aws.ToString(role.AssumeRolePolicyDocument))
// These next six lines are a hack, needed because the EKS OIDC json field name is dynamic
// and therefore can't be used to unmarshall in a predictable way. The hack involves replacing
// the random pattern with a predictable one so that we can add the predictable one in the struct
// used to unmarshall.
pattern := `(\w+)\:`
pattern2 := `".[a-zA-Z0-9\-\.]+/id/`
//auth0Pattern := `"auth0.com\:`
circleCIPattern := `"oidc.circleci.com/org/[a-zA-Z0-9\-]+:`
var reEKSSub = regexp.MustCompile(pattern2 + pattern + "sub")
var reEKSAud = regexp.MustCompile(pattern2 + pattern + "aud")
//var reAuth0Sub = regexp.MustCompile(auth0Pattern + "amr")
var reCircleCIAud = regexp.MustCompile(circleCIPattern + "aud")
var reCircleCISSub = regexp.MustCompile(circleCIPattern + "sub")
document = reEKSSub.ReplaceAllString(document, "\"OidcEksSub")
document = reEKSAud.ReplaceAllString(document, "\"OidcEksAud")
//document = reAuth0Sub.ReplaceAllString(document, "\"Auth0Amr")
document = reCircleCIAud.ReplaceAllString(document, "\"CircleCIAud")
document = reCircleCISSub.ReplaceAllString(document, "\"CircleCISub")
var parsedDocumentToJSON TrustPolicyDocument
_ = json.Unmarshal([]byte(document), &parsedDocumentToJSON)
return parsedDocumentToJSON, nil
}
+5 -12
View File
@@ -54,18 +54,6 @@ func (ps *PolicyStatement) GetAllActionsAsString() string {
func (ps *PolicyStatement) GetAllPrincipalsAsString() string {
principals := ""
// if len(ps.Principal.O.GetListOfPrincipals()) < 3 {
// for _, principal := range ps.Principal.O.GetListOfPrincipals() {
// principals = fmt.Sprintf("%s%s & ", principals, principal)
// }
// } else {
// principals = fmt.Sprintf("%s%d principals", principals, len(ps.Principal.O.GetListOfPrincipals()))
// }
// principals = strings.TrimSuffix(principals, " & ")
// principals = principals + "\n"
// return principals
for _, principal := range ps.Principal.O.GetListOfPrincipals() {
if len(ps.Principal.O.GetListOfPrincipals()) > 1 {
principals = fmt.Sprintf("%s%s \n& ", principals, principal)
@@ -133,3 +121,8 @@ func (ps *PolicyStatement) GetStatementSummaryInEnglish(caller string) string {
return statementSummary
}
// GetResources as list of strings
func (ps *PolicyStatement) GetResources() []string {
return ps.Resource
}
+1 -1
View File
@@ -71,7 +71,7 @@ func TestGetSelectedAWSProfiles(t *testing.T) {
afero.WriteFile(UtilsFs, "/tmp/myfile.txt", test.fileData, 0755)
output := GetSelectedAWSProfiles("/tmp/myfile.txt")
if !compareSlice(output, test.expectedOutput) {
t.Errorf("Test Failed: %v inputted, %v expected, recieved: %v", test.fileData, test.expectedOutput, output)
t.Errorf("Test Failed: %v inputted, %v expected, received: %v", test.fileData, test.expectedOutput, output)
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/BishopFox/cloudfox/globals"
)
// Requires Az CLI Authentication to passs
// Requires Az CLI Authentication to pass
func TestGetAuthorizer(t *testing.T) {
t.Skip()
subtests := []struct {
+10 -6
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -37,7 +36,7 @@ func SaveCacheToFiles(directory string, accountID string) error {
}
filename := filepath.Join(directory, key+".json")
err = ioutil.WriteFile(filename, jsonData, 0644)
err = os.WriteFile(filename, jsonData, 0644)
if err != nil {
return err
}
@@ -55,7 +54,7 @@ func LoadCacheFromFiles(directory string) error {
return err
}
files, err := ioutil.ReadDir(directory)
files, err := os.ReadDir(directory)
if err != nil {
return err
}
@@ -66,7 +65,7 @@ func LoadCacheFromFiles(directory string) error {
}
filename := filepath.Join(directory, file.Name())
jsonData, err := ioutil.ReadFile(filename)
jsonData, err := os.ReadFile(filename)
if err != nil {
return err
}
@@ -133,7 +132,8 @@ func LoadCacheFromGobFiles(directory string) error {
return err
}
files, err := ioutil.ReadDir(directory)
files, err := os.ReadDir(directory)
if err != nil {
return err
}
@@ -142,6 +142,10 @@ func LoadCacheFromGobFiles(directory string) error {
if file.IsDir() {
continue
}
// if the filetype is json, skip it
if filepath.Ext(file.Name()) == ".json" {
continue
}
filename := filepath.Join(directory, file.Name())
file, err := os.Open(filename)
@@ -155,7 +159,7 @@ func LoadCacheFromGobFiles(directory string) error {
err = decoder.Decode(&entry)
if err != nil {
sharedLogger.Errorf("Could not decode the following file: %s", filename)
return err
continue
}
// the key should remove the directory and the .json suffix from the filename and also trim the first slash
+3 -1
View File
@@ -23,7 +23,9 @@ func GetLogDirPath() *string {
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatalf("[-] Failed to read or create cloudfox directory")
log.Printf("[-] Failed to read or create cloudfox directory")
dir, err = os.Getwd()
return ptr.String(dir)
}
}
return ptr.String(dir)
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"golang.org/x/crypto/ssh/terminal"
)
// This struct is here to mantain compatibility with legacy cloudfox code
// This struct is here to maintain compatibility with legacy cloudfox code
type OutputData2 struct {
Headers []string
Body [][]string
+87 -23
View File
@@ -8,6 +8,7 @@ import (
"os"
"path"
"regexp"
"strings"
"github.com/aquasecurity/table"
"github.com/fatih/color"
@@ -37,12 +38,14 @@ type TableClient struct {
}
type TableFile struct {
Name string
TableFilePointer afero.File
CSVFilePointer afero.File
JSONFilePointer afero.File
Header []string
Body [][]string
Name string
TableFilePointer afero.File
CSVFilePointer afero.File
JSONFilePointer afero.File
TableCols []string
Header []string
Body [][]string
SkipPrintToScreen bool
}
type LootClient struct {
@@ -141,8 +144,11 @@ func (l *LootClient) createLootFiles(lootFiles []LootFile) {
if l.DirectoryName == "" {
l.DirectoryName = "."
}
if _, err := fileSystem.Stat(l.DirectoryName); os.IsNotExist(err) {
err = fileSystem.MkdirAll(l.DirectoryName, 0700)
lootDirectory := path.Join(l.DirectoryName, "loot")
if _, err := fileSystem.Stat(lootDirectory); os.IsNotExist(err) {
err = fileSystem.MkdirAll(lootDirectory, 0700)
if err != nil {
log.Fatal(err)
}
@@ -153,7 +159,7 @@ func (l *LootClient) createLootFiles(lootFiles []LootFile) {
l.LootFiles[i].Name = fmt.Sprintf("%s.txt", file.Name)
filePointer, err := fileSystem.OpenFile(path.Join(l.DirectoryName, l.LootFiles[i].Name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
filePointer, err := fileSystem.OpenFile(path.Join(lootDirectory, l.LootFiles[i].Name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("error creating output file: %s", err)
}
@@ -166,9 +172,8 @@ func (l *LootClient) writeLootFiles() []string {
var fullFilePaths []string
for _, file := range l.LootFiles {
contents := []byte(file.Contents)
fullPath := path.Join(l.DirectoryName, file.Name)
err := os.WriteFile(fullPath, contents, 0644)
fullPath := path.Join(l.DirectoryName, "loot", file.Name)
err := afero.WriteFile(fileSystem, fullPath, contents, 0644) // Use Afero's WriteFile
if err != nil {
log.Fatalf("error writing loot file %s: %s", file.Name, err)
}
@@ -179,20 +184,13 @@ func (l *LootClient) writeLootFiles() []string {
func (b *TableClient) printTablesToScreen(tableFiles []TableFile) {
for _, tf := range tableFiles {
if tf.SkipPrintToScreen {
continue
}
tf.Body, tf.Header = adjustBodyForTable(tf.TableCols, tf.Header, tf.Body)
standardColumnWidth := 1000
t := table.New(os.Stdout)
// if b.Wrap {
// terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
// if err != nil {
// fmt.Printf("error getting terminal size: %s, please set the --wrap flag to false\n", err)
// return
// }
// columnCount := len(tf.Header)
// // The offset value was defined by trial and error to get the best wrapping
// trialAndErrorOffset := 1
// standardColumnWidth = terminalWidth / (columnCount + trialAndErrorOffset)
// }
if !b.Wrap {
t.SetColumnMaxWidth(standardColumnWidth)
}
@@ -245,6 +243,7 @@ func (b *TableClient) writeTableFiles(files []TableFile) []string {
var fullFilePaths []string
for _, file := range b.TableFiles {
file.Body, file.Header = adjustBodyForTable(file.TableCols, file.Header, file.Body)
standardColumnWidth := 1000
t := table.New(file.TableFilePointer)
@@ -305,6 +304,8 @@ func (b *TableClient) writeCSVFiles() []string {
csvWriter.Write(file.Header)
for _, row := range file.Body {
row = removeColorCodesFromSlice(row)
//row = removeNewLinesFromSlice(row)
csvWriter.Write(row)
}
csvWriter.Flush()
@@ -316,6 +317,39 @@ func (b *TableClient) writeCSVFiles() []string {
return fullFilePaths
}
// replace newlines in row to make them csv and json safe
func removeNewLinesFromNestedSlice(input [][]string) [][]string {
// Regular expression to match new lines
newLineRegExp := regexp.MustCompile(`\n`)
// Create a new slice to store the slices with new lines removed
noNewLineNestedSlice := make([][]string, len(input))
for i, strSlice := range input {
noNewLineNestedSlice[i] = make([]string, len(strSlice))
for j, str := range strSlice {
noNewLineNestedSlice[i][j] = newLineRegExp.ReplaceAllString(str, "")
}
}
return noNewLineNestedSlice
}
// replace newlines in slice of strings to make them render as newlines in csv and json when opened in excel
func removeNewLinesFromSlice(input []string) []string {
// Regular expression to match new lines
newLineRegExp := regexp.MustCompile(`\n`)
// Create a new slice to store the strings with new lines removed
noNewLineSlice := make([]string, len(input))
for i, str := range input {
noNewLineSlice[i] = newLineRegExp.ReplaceAllString(str, " \\n")
}
return noNewLineSlice
}
func (b *TableClient) createJSONFiles() {
for i, file := range b.TableFiles {
if b.DirectoryName == "" {
@@ -397,3 +431,33 @@ func (b *TableClient) writeJSONFiles() []string {
return fullFilePaths
}
func adjustBodyForTable(tableHeaders []string, fullHeaders []string, fullBody [][]string) ([][]string, []string) {
if tableHeaders == nil || len(tableHeaders) == 0 {
return fullBody, fullHeaders
}
columnIndices := make([]int, 0)
selectedHeaders := make([]string, 0)
for _, tableHeader := range tableHeaders {
for j, fullHeader := range fullHeaders {
if strings.ToLower(tableHeader) == strings.ToLower(fullHeader) {
columnIndices = append(columnIndices, j)
selectedHeaders = append(selectedHeaders, fullHeader)
break
}
}
}
adjustedBody := make([][]string, len(fullBody))
for i, row := range fullBody {
newRow := make([]string, len(columnIndices))
for k, index := range columnIndices {
newRow[k] = row[index]
}
adjustedBody[i] = newRow
}
return adjustedBody, selectedHeaders
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var (
rootCmd = &cobra.Command{
Use: os.Args[0],
Version: "1.12.0",
Version: "1.13.4",
}
)