Compare commits

...
65 Commits
Author SHA1 Message Date
Seth Art 75a446764d Merge pull request #66 from BishopFox/seth-dev
Seth dev
2023-12-19 16:24:13 -05:00
sethsec-bf 9be3792c97 bumped version to 1.12.3 2023-12-13 10:07:23 -05:00
sethsec-bf 27a511073e for #65, decided to add a prompt to ask for MFA token if the user does not specify it at runtime 2023-12-13 10:05:11 -05:00
sethsec-bf a9a0c7ae30 more updates for #65. Added a cli flag --mfa-token so you can pass the token at the cloudfox command line. Ended up reworking AWSConfigFileLoader to use a map to store fetched configs which should have other performance improvemnts 2023-12-13 09:50:30 -05:00
sethsec-bf 2ed079686d fixed bug where glue resource policy data was not caching properly. 2023-12-13 09:48:15 -05:00
sethsec-bf 5e1ed54a28 fixed bug in cache loading that would error out if even a single cache file was not accounted for 2023-12-11 18:26:48 -05:00
sethsec-bf ff38b2cf65 fix for #65 -- adds config.WithAssumeRoleCredentialOptions section to the LoadDefaultConfig loader 2023-12-06 15:58:04 -05:00
sethsec-bf 1819b11c5f Merge branch 'main' of github.com:BishopFox/cloudfox into seth-dev 2023-12-06 15:53:27 -05:00
Seth Art f51c1e9f49 Update README.md 2023-11-11 15:41:00 -05:00
sethsec-bf 8f5a571ef0 fixed issue where role trusts was not sorting services correctly anymore 2023-11-10 12:53:35 -05:00
Seth Art 7b95a05215 Merge pull request #61 from BishopFox/seth-dev
ECR Fix and more
2023-11-02 13:50:48 -04:00
sethsec-bf e0f0627de8 new idea for resource trusts field 2023-11-02 10:39:30 -04:00
sethsec-bf d505bc4e3e Updating the tests/mocks for ECR 2023-11-02 09:46:59 -04:00
sethsec-bf 6a6f7586b3 Fixed a bad bug in ECR that was only looking at the fist repo 2023-11-01 20:28:36 -04:00
sethsec-bf 06344b9421 added secrets manager to resourcetrusts and cloud9 to inventory 2023-11-01 08:53:22 -04:00
sethsec-bf 54f784903a added accountID col to every table and removed the service col in all commands that are single service. 2023-10-26 12:39:25 -04:00
Seth Art 09f33f0908 Merge pull request #59 from BishopFox/seth-dev
Customizable Tables
2023-10-25 08:53:30 -04:00
sethsec-bf 2dc20eaa52 fixed codespell items 2023-10-25 08:50:43 -04:00
sethsec-bf 7656267367 fixes #36. -o wide will give you the arn, and the default (-o brief) will only give you the name for brevity 2023-10-25 08:41:04 -04:00
sethsec-bf b43d6ad074 go get -u and fixes 2023-10-25 08:19:44 -04:00
sethsec-bf 1215a27e19 completed --cols & -o wide support for ALL aws commands 2023-10-24 20:15:06 -04:00
sethsec-bf 7d3a61afe2 fixed caching issues in sdk/ecs 2023-10-24 13:06:08 -04:00
sethsec-bf 909590860d added user controled columns to cloudformation, and access keys 2023-10-23 16:28:22 -04:00
sethsec-bf 4ee0da839d added user controled columns to buckets, role-trusts, permissions 2023-10-23 16:00:48 -04:00
sethsec-bf 57d9e911f7 removed newline from buckets policy analyis 2023-10-20 12:30:39 -04:00
sethsec-bf dad2277942 moved policy analysis behind a feature flag in the buckets command 2023-10-19 10:04:53 -04:00
sethsec-bf 50493e6624 fixed bug in #53 - buckets with no policy were not creating policy cache stubs so each bucket without a policy would always look for a new policy 2023-10-11 12:12:22 -04:00
sethsec-bf 818c3a88f4 fixed cache load issue, updated aws instances command to use cached data 2023-09-22 15:16:38 -04:00
sethsec-bf bcfa318b31 cache load was loading json files along with the gob files and erroring. skipping the json files. also, updated ioutil library to os library 2023-09-20 13:22:22 -04:00
Seth Art f9567c7fc2 Merge pull request #56 from BishopFox/sethsec-bf-patch-1
Update README.md
2023-09-19 11:49:38 -04:00
Seth Art 187299ae82 Update README.md 2023-09-15 13:34:45 -04:00
Seth Art 8d145b135e Update README.md with more install methods 2023-09-08 08:48:25 -04:00
Seth Art 729c59e198 Merge pull request #52 from cclauss/codespell-github-action
GitHub Action to run codespell
2023-09-07 09:44:54 -04:00
Christian Clauss 6d1b47128e Update codespell.yml 2023-09-07 15:39:47 +02:00
Christian Clauss 217ac7a018 GitHub Action to run codespell 2023-09-07 15:38:03 +02:00
Seth Art 4b0238c362 Merge pull request #51 from cclauss/codespell
Fix typos discovered by codespell
2023-09-07 09:26:01 -04:00
Christian Clauss f78f1490d2 Fix typos discovered by codespell 2023-09-07 15:17:08 +02:00
Seth Art 0159638ab7 Merge pull request #50 from cclauss/patch-1
Upgrade GitHub Actions
2023-09-07 09:02:35 -04:00
Christian Clauss 1ff35a72ad Upgrade GitHub Actions
* https://github.com/actions/checkout/releases
* https://github.com/actions/setup-go/releases
2023-09-07 11:24:40 +02:00
Seth Art 29bfd2c863 Merge pull request #49 from BishopFox/seth-dev
bumped version to fix another local filesystem write
2023-09-06 14:05:38 -04:00
sethsec-bf 40e0c92ef1 another fix to pass brew tests 2023-09-06 14:04:50 -04:00
sethsec-bf 9f8ae08262 another fix to pass brew tests 2023-09-06 14:03:52 -04:00
Seth Art c85aa5de89 Merge pull request #48 from BishopFox/seth-dev
v1.12.1
2023-09-06 11:31:45 -04:00
sethsec-bf 42d7cfef1e hacky fix to have error log written to local dir if user dir does not exist (so it can pass brew test) 2023-09-05 18:04:25 -04:00
Seth Art a5bce5ad8c Merge pull request #46 from BishopFox/seth-dev
v1.12.0
2023-08-15 14:21:50 -04:00
sethsec-bf 8221b5ff6d bumped version to 1.12.0 2023-08-15 14:20:18 -04:00
sethsec-bf aa42c8b808 renamed azure instances -> vms, got userdata loot working on azure vms command 2023-08-14 13:43:13 -04:00
sethsec-bf 5aab3b4bea updated logging path for tenant data to make it more clear 2023-08-10 15:50:54 -04:00
sethsec-bf 01d320d64d inventory now supports tenant mode, both merged and split. fixed all tests 2023-08-10 14:54:25 -04:00
sethsec-bf 5132622a13 updated azure to use same home directory logic as the aws command 2023-08-10 14:53:22 -04:00
sethsec-bf 08b5cd17af renamed rbac and storage, and converted instances and storage over to user names in addition to IDs as input params. 2023-08-09 10:27:44 -04:00
sethsec-bf 102b91e2c7 Got rbac working with the new directory stucture and accepting common names for tentant and subscirption as input params 2023-08-08 13:02:49 -04:00
sethsec-bf 3a19fdb2fe reworking the tenant/subscription file structure 2023-07-24 09:50:59 -04:00
sethsec-bf d94fafe2b4 go get -u 2023-07-18 15:46:43 -04:00
sethsec-bf 00ed8a7336 Added many new resources to inventory. Made inventory rows dynamic. Only non-empty rows will print 2023-07-18 15:12:25 -04:00
sethsec-bf 60daff386c updated inventory to include more redshift, route53, updated route53 to use new cached functions and to include CNAME records in loot 2023-07-12 16:19:53 -04:00
sethsec-bf cef1aa3a82 updated inventory to include more ec2, ecs, and ecr resources 2023-07-11 09:30:09 -04:00
sethsec-bf e88cb77394 updated inventory to include more ec2, ecs, and ecr resources 2023-07-11 09:29:03 -04:00
sethsec-bf fc52e01192 updated tests, added new sdk functions, added started the migration of keeping all mocks in _mocks.go files 2023-07-10 09:37:06 -04:00
sethsec-bf 659b2e7248 Completed inventory migration to use sdk, also finally removed need for second global table 2023-07-06 22:50:44 -04:00
sethsec-bf 06a7e7ec28 Updated sdk package to do the gob initializations in self contained init functions 2023-07-06 14:35:57 -04:00
sethsec-bf 602332a0c5 Updated ecs-tasks and eni to use cached functions 2023-07-06 12:19:45 -04:00
sethsec-bf 471da59da2 Updated ecs-tasks and eni to use cached functions 2023-07-06 11:29:21 -04: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
160 changed files with 10673 additions and 3254 deletions

No files matched your search

+3 -3
View File
@@ -15,8 +15,8 @@ jobs:
steps:
- name: Go 1.20
uses: actions/setup-go@v2
- name: Go 1.21
uses: actions/setup-go@v4
with:
go-version: ^1.20
id: go
@@ -28,7 +28,7 @@ jobs:
gpg --list-secret-keys --keyid-format LONG
- name: Check Out Code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
+16
View File
@@ -0,0 +1,16 @@
# This Action uses minimal steps to run in ~5 seconds to rapidly:
# Looks for typos in the codebase using codespell
# https://github.com/codespell-project/codespell#readme
name: codespell
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install --user codespell
- run: codespell --ignore-words-list="aks" # --skip="*.css,*.js,*.lock,*.po"
+2 -1
View File
@@ -69,4 +69,5 @@ cloudfox
*.txt
*.json
*.csv
*.log
*.log
dist/
+15 -4
View File
@@ -14,8 +14,12 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
## Demos, Examples, Walkthroughs
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
* [Video - Penetration Testing with CloudFox](https://www.youtube.com/watch?v=Ljt_JUp5HbM)
* [Video - CloudFox Intro Demos](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
## Intentionally Vulnerable Playground
* [CloudFoxable - A Gamified Cloud Hacking Sandbox](https://cloudfoxable.bishopfox.com/)
## Quick Start
CloudFox is modular (you can run one command at a time), but there is an `aws all-checks` command that will run the other aws commands for you with sane defaults:
@@ -49,11 +53,18 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
**Option 1:** Download the [latest binary release](https://github.com/BishopFox/cloudfox/releases) for your platform.
**Option 2:** [Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
**Option 2:** If you use homebrew: `brew install cloudfox`
**Option 3:** [Install Go](https://golang.org/doc/install), use `go install github.com/BishopFox/cloudfox@latest` to install from the remote source
**Option 4:** Developer mode:
[Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
```
# git clone https://github.com/BishopFox/cloudfox.git
...omitted for brevity...
# cd ./cloudfox
# Make any changes necessary
# go build .
# ./cloudfox
```
@@ -64,7 +75,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
### AWS
* AWS CLI installed
* Supports AWS profiles, AWS environment variables, or metadata retrieval (on an ec2 instance)
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names seperated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names separated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
* A principal with one recommended policies attached (described below)
* Recommended attached policies: **`SecurityAudit` + [CloudFox custom policy](./misc/aws/cloudfox-policy.json)**
@@ -138,7 +149,7 @@ CloudFox doesn't create any alerts or findings, and doesn't check your environme
**Why do I see errors in some CloudFox commands?**
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't suppor the service will return errors.
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't support the service will return errors.
* You don't have permission - Another reason you might see errors if you don't have permissions to make calls that CloudFox is making. Either because the policy doesn't allow it (e.g., SecurityAudit doesn't allow all of the permissions CloudFox needs. Or, it might be an SCP that is blocking you.
You can always look in the ~/.cloudfox/cloudfox-error.log file to get more information on errors.
+39 -11
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
@@ -18,9 +19,10 @@ type AccessKeysModule struct {
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSProfile string
OutputFormat string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
CommandCounter internal.CommandCounter
// Main module data
@@ -36,8 +38,8 @@ type UserKeys struct {
Key string
}
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "access-keys"
@@ -55,6 +57,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
// Variables used to draw table output
m.output.Headers = []string{
"Account",
"User Name",
"Access Key ID",
}
@@ -65,6 +68,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
key.Username,
key.Key,
},
@@ -78,10 +82,7 @@ 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", 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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -89,13 +90,40 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"User Name",
"Access Key ID",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"User Name",
"Access Key ID",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)
+93 -47
View File
@@ -20,24 +20,26 @@ import (
type BucketsModule struct {
// General configuration data
//BucketsS3Client CloudFoxS3Client
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
CheckBucketPolicies bool
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
AWSTableCols string
AWSOutputType string
OutputFormat string
Goroutines int
WrapTable bool
Goroutines int
WrapTable bool
// Main module data
Buckets []Bucket
Buckets []BucketRow
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Bucket struct {
type BucketRow struct {
Arn string
AWSService string
Region string
@@ -53,8 +55,8 @@ type Bucket struct {
ResourcePolicySummary string
}
func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *BucketsModule) PrintBuckets(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "buckets"
@@ -65,7 +67,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", aws.ToString(m.Caller.Account), 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 buckets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -78,7 +80,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Bucket)
dataReceiver := make(chan BucketRow)
// Create a channel to signal to stop
receiverDone := make(chan bool)
@@ -100,6 +102,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Name",
"Region",
"Public?",
@@ -111,17 +114,16 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Buckets[i].Name,
m.Buckets[i].Region,
m.Buckets[i].IsPublic,
m.Buckets[i].ResourcePolicySummary,
},
)
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -129,13 +131,50 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"Region",
"Public?",
"Resource Policy Summary",
}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{
"Name",
"Region",
"Public?",
"Resource Policy Summary",
}
}
// Remove the Public? and Resource Policy Summary columns if the user did not specify CheckBucketPolicies
if !m.CheckBucketPolicies {
tableCols = removeStringFromSlice(tableCols, "Public?")
tableCols = removeStringFromSlice(tableCols, "Resource Policy Summary")
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
TableCols: tableCols,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)
@@ -148,7 +187,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
func (m *BucketsModule) Receiver(receiver chan BucketRow, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
@@ -161,7 +200,7 @@ func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
}
}
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
defer wg.Done()
m.CommandCounter.Total++
@@ -215,7 +254,7 @@ func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile
}
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
@@ -237,42 +276,49 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}
for _, b := range ListBuckets {
bucket := &Bucket{
bucket := &BucketRow{
Name: aws.ToString(b.Name),
AWSService: "S3",
}
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
region = "Unknown"
}
bucket.Region = region
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
} else {
bucket.PolicyJSON = policyJSON
}
if m.CheckBucketPolicies {
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
} else {
bucket.PolicyJSON = policyJSON
}
bucket.IsPublic = "No"
if !bucket.Policy.IsEmpty() {
m.analyseBucketPolicy(bucket, dataReceiver)
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
bucket.IsPublic = "No"
if !bucket.Policy.IsEmpty() {
m.analyseBucketPolicy(bucket, dataReceiver)
} else {
bucket.Access = "No resource policy"
dataReceiver <- *bucket
}
} else {
bucket.Access = "No resource policy"
// Send Bucket object through the channel to the receiver
bucket.Access = "Skipped"
bucket.IsPublic = "Skipped"
dataReceiver <- *bucket
}
// Send Bucket object through the channel to the receiver
}
}
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
@@ -284,7 +330,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool
}
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
func (m *BucketsModule) analyseBucketPolicy(bucket *BucketRow, dataReceiver chan BucketRow) {
m.storeAccessPolicy(bucket)
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
@@ -299,14 +345,14 @@ func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bu
} else {
bucket.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
//bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
}
dataReceiver <- *bucket
}
func (m *BucketsModule) storeAccessPolicy(bucket *Bucket) {
func (m *BucketsModule) storeAccessPolicy(bucket *BucketRow) {
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", bucket.Name))
if err := m.storeFile(f, bucket.PolicyJSON); err != nil {
+9 -7
View File
@@ -81,9 +81,11 @@ func TestListBuckets(t *testing.T) {
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
Goroutines: 3,
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
AWSOutputType: "",
CheckBucketPolicies: true,
Goroutines: 3,
}
subtests := []struct {
@@ -91,14 +93,14 @@ func TestListBuckets(t *testing.T) {
outputDirectory string
verbosity int
testModule BucketsModule
expectedResult []Bucket
expectedResult []BucketRow
}{
{
name: "test1",
outputDirectory: ".",
verbosity: 2,
testModule: m,
expectedResult: []Bucket{{
expectedResult: []BucketRow{{
Name: "mockBucket123",
}},
},
@@ -110,9 +112,9 @@ func TestListBuckets(t *testing.T) {
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintBuckets(subtest.outputDirectory, subtest.verbosity)
for index, expectedBucket := range subtest.expectedResult {
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/buckets.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/buckets.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
+36 -24
View File
@@ -7,9 +7,11 @@ import (
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/fsx"
"github.com/aws/aws-sdk-go-v2/service/glue"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/organizations"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -28,13 +30,13 @@ func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCa
}
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool, AWSMFAToken string) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
cloudFoxSNSClient := SNSModule{
SNSClient: sns.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
}
@@ -42,25 +44,25 @@ func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string
}
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, AWSMFAToken string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
cloudFoxS3Client := BucketsModule{
S3Client: s3.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return cloudFoxS3Client
}
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
sqsClient := SQSModule{
SQSClient: sqs.NewFromConfig(AWSConfig),
Caller: caller,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
@@ -69,58 +71,68 @@ func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVers
}
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
lambdaClient := LambdasModule{
LambdaClient: lambda.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return lambdaClient
}
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
codeBuildClient := CodeBuildModule{
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return codeBuildClient
}
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
ecrClient := ECRModule{
ECRClient: ecr.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return ecrClient
}
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
fileSystemsClient := FilesystemsModule{
EFSClient: efs.NewFromConfig(AWSConfig),
FSxClient: fsx.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return fileSystemsClient
}
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
orgClient := OrgModule{
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
}
return orgClient
}
func InitSecretsManagerClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *secretsmanager.Client {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
return secretsmanager.NewFromConfig(AWSConfig)
}
func InitGlueClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *glue.Client {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
return glue.NewFromConfig(AWSConfig)
}
+65 -33
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -20,12 +21,13 @@ type CloudformationModule struct {
// General configuration data
CloudFormationClient sdk.CloudFormationClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
AWSProfile string
WrapTable bool
AWSOutputType string
AWSTableCols string
// Main module data
CFStacks []CFStack
@@ -45,8 +47,8 @@ type CFStack struct {
Template string
}
func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *CloudformationModule) PrintCloudformationStacks(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "cloudformation"
@@ -93,48 +95,45 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
"Role",
// "Parameters",
// "Outputs",
"Parameters",
"Outputs",
}
// Table rows
for i := range m.CFStacks {
// var isParameters string
// var isOutputs string
// if m.CFStacks[i].Parameters != nil {
// isParameters = "Y"
// } else {
// isParameters = "N"
// }
// if m.CFStacks[i].Outputs != nil {
// isOutputs = "Y"
// } else {
// isOutputs = "N"
// }
var hasParameters string
var hasOutputs string
if m.CFStacks[i].Parameters != nil {
hasParameters = "Y"
} else {
hasParameters = "N"
}
if m.CFStacks[i].Outputs != nil {
hasOutputs = "Y"
} else {
hasOutputs = "N"
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.CFStacks[i].AWSService,
m.CFStacks[i].Region,
m.CFStacks[i].Name,
m.CFStacks[i].Role,
// isParameters,
// isOutputs,
hasParameters,
hasOutputs,
},
)
}
if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -142,13 +141,46 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Role",
"Parameters",
"Outputs",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Role",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+3 -70
View File
@@ -1,81 +1,15 @@
package aws
import (
"context"
"encoding/json"
"log"
"testing"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
const DESCRIBE_STACKS_TEST_FILE = "./test-data/cloudformation-describestacks.json"
const TEMPLATE_BODY_TEST_FILE = "./test-data/cloudformation-getTemplate.json"
type Stacks struct {
Stacks []struct {
StackID string `json:"StackId"`
Description string `json:"Description"`
Tags []interface{} `json:"Tags"`
Outputs []types.Output `json:"Outputs"`
Parameters []types.Parameter `json:"Parameters"`
StackStatusReason interface{} `json:"StackStatusReason"`
CreationTime time.Time `json:"CreationTime"`
Capabilities []interface{} `json:"Capabilities"`
StackName string `json:"StackName"`
RoleArn string `json:"RoleArn"`
StackStatus string `json:"StackStatus"`
DisableRollback bool `json:"DisableRollback"`
} `json:"Stacks"`
}
type TemplateBody struct {
TemplateBody string `json:"TemplateBody"`
}
type MockedCloudformationClient struct {
describeStacks Stacks
getTemplateBody TemplateBody
}
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 {
log.Fatalf("can't unmarshall file %s", DESCRIBE_STACKS_TEST_FILE)
}
var stacks []types.Stack
for _, stack := range m.describeStacks.Stacks {
stacks = append(stacks, types.Stack{
StackName: &stack.StackName,
RoleARN: &stack.RoleArn,
Outputs: stack.Outputs,
Parameters: stack.Parameters,
})
}
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
}
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)
}
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
@@ -89,9 +23,8 @@ func TestCloudFormation(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: CloudformationModule{
CloudFormationClient: &MockedCloudformationClient{},
CloudFormationClient: &sdk.MockedCloudformationClient{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
@@ -105,7 +38,7 @@ func TestCloudFormation(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintCloudformationStacks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintCloudformationStacks(subtest.outputDirectory, subtest.verbosity)
for index, expectedStack := range subtest.expectedResult {
if expectedStack.Name != subtest.testModule.CFStacks[index].Name {
log.Fatal("Stack name does not match expected name")
+57 -42
View File
@@ -3,6 +3,7 @@ package aws
import (
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -18,9 +19,11 @@ type CodeBuildModule struct {
CodeBuildClient sdk.CodeBuildClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -46,8 +49,8 @@ type Project struct {
CanPrivEsc string
}
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *CodeBuildModule) PrintCodeBuildProjects(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "codebuild"
@@ -107,50 +110,66 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
m.output.Headers = []string{
"Account",
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
// Table rows
for i := range m.Projects {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
}
var seen []string
@@ -161,12 +180,7 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
//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))
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -175,12 +189,13 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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))
+2 -2
View File
@@ -124,9 +124,9 @@ func TestCodeBuildProjects(t *testing.T) {
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintCodeBuildProjects("table", tmpDir, 2)
m.PrintCodeBuildProjects(tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/codebuild.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/codebuild.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
+60 -22
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -19,16 +20,18 @@ import (
type DatabasesModule struct {
RDSClient sdk.RDSClientInterface
RedshiftClient sdk.RedShiftClientInterface
RedshiftClient sdk.AWSRedShiftClientInterface
DynamoDBClient sdk.DynamoDBClientInterface
DocDBClient sdk.DocDBClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
Databases []Database
CommandCounter internal.CommandCounter
@@ -52,8 +55,8 @@ type Database struct {
Size string
}
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "databases"
@@ -101,6 +104,7 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
})
m.output.Headers = []string{
"Account",
"Service",
"Engine",
"Region",
@@ -114,11 +118,47 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
}
}
// Table rows
for i := range m.Databases {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Databases[i].AWSService,
m.Databases[i].Engine,
m.Databases[i].Region,
@@ -134,11 +174,8 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -147,12 +184,13 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -287,7 +325,7 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
endpoint := aws.ToString(instance.Endpoint.Address)
engine := aws.ToString(instance.Engine)
if instance.PubliclyAccessible {
if aws.ToBool(instance.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -300,7 +338,7 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
Engine: engine,
Endpoint: endpoint,
UserName: aws.ToString(instance.MasterUsername),
Port: port,
Port: aws.ToInt32(port),
Protocol: aws.ToString(instance.Engine),
Public: public,
}
@@ -344,7 +382,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
if aws.ToBool(cluster.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -355,7 +393,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Port: aws.ToInt32(port),
Protocol: protocol,
Public: public,
}
+119 -79
View File
@@ -21,13 +21,15 @@ import (
type ECRModule struct {
// General configuration data
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Repositories []Repository
@@ -49,8 +51,8 @@ type Repository struct {
PolicyJSON string
}
func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *ECRModule) PrintECR(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ecr"
@@ -95,8 +97,9 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -106,11 +109,53 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
"ImageSize",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"URI",
"PushedAt",
"ImageTags",
"ImageSize",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"URI",
"PushedAt",
"ImageTags",
"ImageSize",
}
}
// sort the table by Name
sort.Slice(m.Repositories, func(i, j int) bool {
return m.Repositories[i].Name < m.Repositories[j].Name
})
// Table rows
for i := range m.Repositories {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Repositories[i].AWSService,
m.Repositories[i].Region,
m.Repositories[i].Name,
@@ -123,11 +168,8 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -136,12 +178,13 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -239,92 +282,89 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
<-semaphore
}()
var allImages []types.ImageDetail
var repoURI string
var repoName string
DescribeRepositories, err := m.describeRepositories(r)
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, repo := range DescribeRepositories {
repoName = aws.ToString(repo.RepositoryName)
repoURI = aws.ToString(repo.RepositoryUri)
for _, repo := range Repositories {
repoName := aws.ToString(repo.RepositoryName)
repoURI := aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
images, err := m.describeImages(r, repoName)
images, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
continue
}
allImages = append(allImages, images...)
}
sort.Slice(allImages, func(i, j int) bool {
return allImages[i].ImagePushedAt.Format("2006-01-02 15:04:05") < allImages[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
sort.Slice(images, func(i, j int) bool {
return images[i].ImagePushedAt.Format("2006-01-02 15:04:05") < images[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
var image types.ImageDetail
var imageTags string
if len(allImages) > 1 {
image = allImages[len(allImages)-1]
} else if len(allImages) == 1 {
image = allImages[0]
} else {
return
}
if len(images) > 1 {
image = images[len(images)-1]
} else if len(images) == 1 {
image = images[0]
} else {
continue
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
} else {
imageTags = "No tags"
}
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
}
}
func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
// func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
var repositories []types.Repository
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
// var repositories []types.Repository
// Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
// if err != nil {
// m.CommandCounter.Error++
// return nil, err
// }
repositories = append(repositories, Repositories...)
// repositories = append(repositories, Repositories...)
return repositories, nil
}
// return repositories, nil
// }
func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
var images []types.ImageDetail
// func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
// var images []types.ImageDetail
ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
images = append(images, ImageDetails...)
return images, nil
}
// ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
// if err != nil {
// m.CommandCounter.Error++
// return nil, err
// }
// images = append(images, ImageDetails...)
// return images, nil
// }
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
var repoPolicy policy.Policy
+22 -51
View File
@@ -1,52 +1,15 @@
package aws
import (
"context"
"log"
"testing"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
type MockedECRClient struct {
}
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []types.Repository{
{
RepositoryName: aws.String("test1"),
RepositoryUri: aws.String("testURI"),
},
},
}, nil
}
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []types.ImageDetail{
{
ImagePushedAt: aws.Time(time.Now()),
ImageSizeInBytes: aws.Int64(123456),
ImageTags: []string{"latest"},
},
},
}, nil
}
func (m *MockedECRClient) GetRepositoryPolicy(context.Context, *ecr.GetRepositoryPolicyInput, ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
return &ecr.GetRepositoryPolicyOutput{
PolicyText: aws.String("policyText"),
}, nil
}
func TestDescribeRepos(t *testing.T) {
subtests := []struct {
@@ -61,29 +24,37 @@ func TestDescribeRepos(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ECRModule{
ECRClient: &MockedECRClient{},
ECRClient: &sdk.MockedECRClient{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
AWSProfile: "test",
Goroutines: 30,
AWSRegions: []string{"us-east-1"},
},
expectedResult: []Repository{{
Name: "test1",
URI: "testURI:latest",
PushedAt: "2022-10-25 15:14:06",
ImageTags: "latest",
ImageSize: 123456,
}},
expectedResult: []Repository{
{
Name: "repo1",
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1",
PushedAt: "2022-10-25 15:14:00",
ImageTags: "customtag, tag2",
ImageSize: 123456,
},
{
Name: "repo2",
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2",
PushedAt: "2021-10-15 11:14:00",
ImageTags: "latest",
ImageSize: 2222222,
}},
},
}
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintECR(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.PrintECR(subtest.outputDirectory, subtest.verbosity)
for index, expectedRepo := range subtest.expectedResult {
if expectedRepo.Name != subtest.testModule.Repositories[index].Name {
log.Fatal("Repo name does not match expected name")
+106 -151
View File
@@ -13,7 +13,6 @@ import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -21,20 +20,16 @@ import (
"github.com/sirupsen/logrus"
)
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
IAMClient sdk.AWSIAMClientInterface
ECSClient sdk.AWSECSClientInterface
EC2Client sdk.AWSEC2ClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
AWSProfile string
Goroutines int
SkipAdminCheck bool
@@ -63,7 +58,7 @@ type MappedECSTask struct {
CanPrivEsc string
}
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
func (m *ECSTasksModule) ECSTasks(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ecs-tasks"
@@ -126,7 +121,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
receiverDone <- true
<-receiverDone
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printECSTaskData(outputDirectory, dataReceiver, verbosity)
}
@@ -143,9 +138,36 @@ func (m *ECSTasksModule) Receiver(receiver chan MappedECSTask, receiverDone chan
}
}
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
if m.pmapperError == nil {
m.output.Headers = []string{
func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Cluster",
"TaskDefinition",
"LaunchType",
@@ -156,61 +178,46 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Cluster",
"TaskDefinition",
"LaunchType",
"ID",
"External IP",
"Internal IP",
"RoleArn",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
if m.pmapperError == nil {
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
ecsTask.CanPrivEsc,
},
)
}
} else {
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
//ecsTask.CanPrivEsc,
},
)
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, ecsTask := range m.MappedECSTasks {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
ecsTask.Cluster,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
ecsTask.PrivateIP,
ecsTask.Role,
ecsTask.Admin,
ecsTask.CanPrivEsc,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -219,12 +226,13 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -313,75 +321,37 @@ func (m *ECSTasksModule) executeChecks(r string, wg *sync.WaitGroup, dataReceive
func (m *ECSTasksModule) getListClusters(region string, dataReceiver chan MappedECSTask) {
var PaginationControl *string
for {
ListClusters, err := m.ListClustersClient.ListClusters(
context.TODO(),
&(ecs.ListClustersInput{
NextToken: PaginationControl,
}),
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, clusterARN := range ListClusters.ClusterArns {
m.getListTasks(clusterARN, region, dataReceiver)
}
if ListClusters.NextToken != nil {
PaginationControl = ListClusters.NextToken
} else {
PaginationControl = nil
break
}
ClusterArns, err := sdk.CachedECSListClusters(m.ECSClient, aws.ToString(m.Caller.Account), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, clusterARN := range ClusterArns {
m.getListTasks(clusterARN, region, dataReceiver)
}
}
func (m *ECSTasksModule) getListTasks(clusterARN string, region string, dataReceiver chan MappedECSTask) {
var PaginationControl *string
for {
ListTasks, err := m.ListTasksClient.ListTasks(
context.TODO(),
&(ecs.ListTasksInput{
Cluster: aws.String(clusterARN),
NextToken: PaginationControl,
}),
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
batchSize := 100 // maximum value: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTasks.html#API_DescribeTasks_RequestSyntax
for i := 0; i < len(ListTasks.TaskArns); i += batchSize {
j := i + batchSize
if j > len(ListTasks.TaskArns) {
j = len(ListTasks.TaskArns)
}
m.loadTasksData(clusterARN, ListTasks.TaskArns[i:j], region, dataReceiver)
}
if ListTasks.NextToken != nil {
PaginationControl = ListTasks.NextToken
} else {
PaginationControl = nil
break
}
TaskArns, err := sdk.CachedECSListTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
batchSize := 100 // maximum value: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTasks.html#API_DescribeTasks_RequestSyntax
for i := 0; i < len(TaskArns); i += batchSize {
j := i + batchSize
if j > len(TaskArns) {
j = len(TaskArns)
}
m.loadTasksData(clusterARN, TaskArns[i:j], region, dataReceiver)
}
}
func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, region string, dataReceiver chan MappedECSTask) {
@@ -390,16 +360,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
return
}
DescribeTasks, err := m.DescribeTasksClient.DescribeTasks(
context.TODO(),
&(ecs.DescribeTasksInput{
Cluster: aws.String(clusterARN),
Tasks: taskARNs,
}),
func(o *ecs.Options) {
o.Region = region
},
)
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN, taskARNs)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -407,7 +368,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
eniIDs := []string{}
for _, task := range DescribeTasks.Tasks {
for _, task := range Tasks {
eniID := getElasticNetworkInterfaceIDOfECSTask(task)
if eniID != "" {
eniIDs = append(eniIDs, eniID)
@@ -420,8 +381,9 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
return
}
for _, task := range DescribeTasks.Tasks {
taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
for _, task := range Tasks {
//taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
taskDefinition, err := sdk.CachedECSDescribeTaskDefinition(m.ECSClient, aws.ToString(m.Caller.Account), region, aws.ToString(task.TaskDefinitionArn))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -461,7 +423,7 @@ func getTaskDefinitionContent(taskDefinition types.TaskDefinition) string {
}
func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region string) (types.TaskDefinition, error) {
DescribeTaskDefinition, err := m.DescribeTaskDefinitionClient.DescribeTaskDefinition(
DescribeTaskDefinition, err := m.ECSClient.DescribeTaskDefinition(
context.TODO(),
&ecs.DescribeTaskDefinitionInput{
TaskDefinition: &taskDefinitionArn,
@@ -511,20 +473,13 @@ func (m *ECSTasksModule) loadPublicIPs(eniIDs []string, region string) (map[stri
if len(eniIDs) == 0 {
return eniPublicIPs, nil
}
DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
context.TODO(),
&(ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: eniIDs,
}),
func(o *ec2.Options) {
o.Region = region
},
)
NetworkInterfaces, err := sdk.CachedEC2DescribeNetworkInterfaces(m.EC2Client, aws.ToString(m.Caller.Account), region)
if err != nil {
return nil, fmt.Errorf("getting elastic network interfaces: %s", err)
}
for _, eni := range DescribeNetworkInterfaces.NetworkInterfaces {
for _, eni := range NetworkInterfaces {
eniPublicIPs[aws.ToString(eni.NetworkInterfaceId)] = getPublicIPOfElasticNetworkInterface(eni)
}
+9 -247
View File
@@ -1,249 +1,15 @@
package aws
import (
"context"
"encoding/json"
"log"
"os"
"testing"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/ecs"
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
const DESCRIBE_TASKS_TEST_FILE = "./test-data/describe-tasks.json"
const DESCRIBE_NETWORK_INTEFACES_TEST_FILE = "./test-data/describe-network-interfaces.json"
type ListTasks struct {
TaskArns []string `json:"taskArns"`
}
type DescribeTasks struct {
Tasks []struct {
Attachments []struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Details []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"details"`
} `json:"attachments"`
Attributes []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"attributes"`
AvailabilityZone string `json:"availabilityZone"`
ClusterArn string `json:"clusterArn"`
Connectivity string `json:"connectivity"`
ConnectivityAt string `json:"connectivityAt"`
Containers []struct {
ContainerArn string `json:"containerArn"`
TaskArn string `json:"taskArn"`
Name string `json:"name"`
Image string `json:"image"`
RuntimeID string `json:"runtimeId"`
LastStatus string `json:"lastStatus"`
NetworkBindings []interface{} `json:"networkBindings"`
NetworkInterfaces []struct {
AttachmentID string `json:"attachmentId"`
PrivateIpv4Address string `json:"privateIpv4Address"`
} `json:"networkInterfaces"`
HealthStatus string `json:"healthStatus"`
CPU string `json:"cpu"`
Memory string `json:"memory"`
} `json:"containers"`
CPU string `json:"cpu"`
CreatedAt string `json:"createdAt"`
DesiredStatus string `json:"desiredStatus"`
EnableExecuteCommand bool `json:"enableExecuteCommand"`
Group string `json:"group"`
HealthStatus string `json:"healthStatus"`
LastStatus string `json:"lastStatus"`
LaunchType string `json:"launchType"`
Memory string `json:"memory"`
Overrides struct {
ContainerOverrides []struct {
Name string `json:"name"`
} `json:"containerOverrides"`
InferenceAcceleratorOverrides []interface{} `json:"inferenceAcceleratorOverrides"`
} `json:"overrides"`
PlatformVersion string `json:"platformVersion"`
PlatformFamily string `json:"platformFamily"`
PullStartedAt string `json:"pullStartedAt"`
PullStoppedAt string `json:"pullStoppedAt"`
StartedAt string `json:"startedAt"`
StartedBy string `json:"startedBy"`
Tags []interface{} `json:"tags"`
TaskArn string `json:"taskArn"`
TaskDefinitionArn string `json:"taskDefinitionArn"`
Version int `json:"version"`
EphemeralStorage struct {
SizeInGiB int `json:"sizeInGiB"`
} `json:"ephemeralStorage"`
} `json:"tasks"`
Failures []interface{} `json:"failures"`
}
type DescribeNetworkInterfaces struct {
NetworkInterfaces []struct {
Status string `json:"Status"`
MacAddress string `json:"MacAddress"`
SourceDestCheck bool `json:"SourceDestCheck"`
VpcID string `json:"VpcId"`
Description string `json:"Description"`
Association struct {
PublicIP string `json:"PublicIp"`
AssociationID string `json:"AssociationId"`
PublicDNSName string `json:"PublicDnsName"`
IPOwnerID string `json:"IpOwnerId"`
} `json:"Association"`
NetworkInterfaceID string `json:"NetworkInterfaceId"`
PrivateIPAddresses []struct {
PrivateDNSName string `json:"PrivateDnsName"`
Association struct {
PublicIP string `json:"PublicIp"`
AssociationID string `json:"AssociationId"`
PublicDNSName string `json:"PublicDnsName"`
IPOwnerID string `json:"IpOwnerId"`
} `json:"Association"`
Primary bool `json:"Primary"`
PrivateIPAddress string `json:"PrivateIpAddress"`
} `json:"PrivateIpAddresses"`
RequesterManaged bool `json:"RequesterManaged"`
Ipv6Addresses []interface{} `json:"Ipv6Addresses"`
PrivateDNSName string `json:"PrivateDnsName,omitempty"`
AvailabilityZone string `json:"AvailabilityZone"`
Attachment struct {
Status string `json:"Status"`
DeviceIndex int `json:"DeviceIndex"`
AttachTime time.Time `json:"AttachTime"`
InstanceID string `json:"InstanceId"`
DeleteOnTermination bool `json:"DeleteOnTermination"`
AttachmentID string `json:"AttachmentId"`
InstanceOwnerID string `json:"InstanceOwnerId"`
} `json:"Attachment"`
Groups []struct {
GroupName string `json:"GroupName"`
GroupID string `json:"GroupId"`
} `json:"Groups"`
SubnetID string `json:"SubnetId"`
OwnerID string `json:"OwnerId"`
TagSet []interface{} `json:"TagSet"`
PrivateIPAddress string `json:"PrivateIpAddress"`
} `json:"NetworkInterfaces"`
}
func readTestFile(testFile string) []byte {
file, err := os.ReadFile(testFile)
if err != nil {
log.Fatalf("can't read file %s", testFile)
}
return file
}
type mockedListclustersClient struct {
}
func (c *mockedListclustersClient) ListClusters(context.Context, *ecs.ListClustersInput, ...func(*ecs.Options)) (*ecs.ListClustersOutput, error) {
return &ecs.ListClustersOutput{ClusterArns: []string{
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster",
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster2",
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster3",
}}, nil
}
type mockedListTasksClient struct{}
func (c *mockedListTasksClient) ListTasks(ctx context.Context, input *ecs.ListTasksInput, f ...func(*ecs.Options)) (*ecs.ListTasksOutput, error) {
return &ecs.ListTasksOutput{TaskArns: []string{
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/74de0355a10a4f979ac495c14EXAMPLE",
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/d789e94343414c25b9f6bd59eEXAMPLE",
}}, nil
}
type mockedDescribeTasksClient struct {
describeTasks DescribeTasks
}
func (c *mockedDescribeTasksClient) DescribeTasks(ctx context.Context, input *ecs.DescribeTasksInput, f ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_TASKS_TEST_FILE), &c.describeTasks)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_TASKS_TEST_FILE)
}
var tasks []ecsTypes.Task
for _, mockedTask := range c.describeTasks.Tasks {
if mockedTask.ClusterArn == aws.ToString(input.Cluster) {
for _, inputTask := range input.Tasks {
if mockedTask.TaskArn == inputTask {
var attachments []ecsTypes.Attachment
for _, a := range mockedTask.Attachments {
var deets []ecsTypes.KeyValuePair
for _, detail := range a.Details {
deets = append(deets, ecsTypes.KeyValuePair{
Name: aws.String(detail.Name),
Value: aws.String(detail.Value)})
}
attachments = append(attachments, ecsTypes.Attachment{
Type: aws.String(a.Type),
Details: deets,
Id: aws.String(a.ID),
Status: aws.String(a.Status),
})
}
tasks = append(tasks, ecsTypes.Task{
ClusterArn: aws.String(mockedTask.ClusterArn),
TaskDefinitionArn: aws.String(mockedTask.TaskDefinitionArn),
LaunchType: ecsTypes.LaunchType(*aws.String(mockedTask.LaunchType)),
TaskArn: aws.String(mockedTask.TaskArn),
Attachments: attachments,
})
}
}
}
}
return &ecs.DescribeTasksOutput{Tasks: tasks}, nil
}
type mockedDescribeNetworkInterfacesClient struct {
describeNetworkInterfaces DescribeNetworkInterfaces
}
func (c *mockedDescribeNetworkInterfacesClient) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
var nics []ec2types.NetworkInterface
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
}
for _, mockedNic := range c.describeNetworkInterfaces.NetworkInterfaces {
for _, inputNicID := range input.NetworkInterfaceIds {
if mockedNic.NetworkInterfaceID == inputNicID {
nics = append(nics, ec2types.NetworkInterface{
Association: &ec2types.NetworkInterfaceAssociation{
PublicIp: aws.String(mockedNic.Association.PublicIP),
},
NetworkInterfaceId: aws.String(mockedNic.NetworkInterfaceID)})
}
}
}
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
}
type mockedDescribeTaskDefinitionInterface struct {
}
func (c *mockedDescribeTaskDefinitionInterface) DescribeTaskDefinition(ctx context.Context, input *ecs.DescribeTaskDefinitionInput, f ...func(o *ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) {
testTaskDefinition := ecsTypes.TaskDefinition{}
testTaskDefinition.TaskRoleArn = aws.String("test123")
return &ecs.DescribeTaskDefinitionOutput{TaskDefinition: &testTaskDefinition}, nil
}
func TestECSTasks(t *testing.T) {
subtests := []struct {
name string
@@ -258,17 +24,13 @@ func TestECSTasks(t *testing.T) {
verbosity: 2,
testModule: ECSTasksModule{
AWSProfile: "default",
AWSRegions: []string{"us-east-1", "us-west-1"},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
SkipAdminCheck: true,
Goroutines: 30,
DescribeNetworkInterfacesClient: &mockedDescribeNetworkInterfacesClient{},
DescribeTasksClient: &mockedDescribeTasksClient{},
ListTasksClient: &mockedListTasksClient{},
ListClustersClient: &mockedListclustersClient{},
DescribeTaskDefinitionClient: &mockedDescribeTaskDefinitionInterface{},
//IAMSimulatePrincipalPolicyClient: &mockedDescribeTaskDefinitionsClient{},
AWSProfile: "default",
AWSRegions: []string{"us-east-1", "us-west-1"},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
SkipAdminCheck: true,
Goroutines: 30,
EC2Client: &sdk.MockedEC2Client2{},
ECSClient: &sdk.MockedECSClient{},
},
expectedResult: []MappedECSTask{{
Cluster: "MyCluster",
@@ -281,7 +43,7 @@ func TestECSTasks(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.ECSTasks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.ECSTasks(subtest.outputDirectory, subtest.verbosity)
for index, expectedTask := range subtest.expectedResult {
if expectedTask.Cluster != subtest.testModule.MappedECSTasks[index].Cluster {
log.Fatal("Cluster name does not match expected value")
+77 -65
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -21,9 +22,11 @@ type EKSModule struct {
EKSClient sdk.EKSClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -52,8 +55,8 @@ type Cluster struct {
CanPrivEsc string
}
func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EKSModule) EKS(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "eks"
@@ -122,10 +125,48 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
// This is the complete list of potential table columns
m.output.Headers = []string{
"Account",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"Endpoint",
"Public",
"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Region",
"Name",
//"Endpoint",
@@ -136,58 +177,32 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Service",
"Region",
"Name",
//"Endpoint",
"Public",
//"OIDC",
"NodeGroup",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
// Table rows
for i := range m.Clusters {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Clusters[i].AWSService,
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
m.Clusters[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Clusters[i].AWSService,
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
//m.Clusters[i].CanPrivEsc,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Clusters[i].Region,
m.Clusters[i].Name,
//m.Clusters[i].Endpoint,
m.Clusters[i].Public,
//m.Clusters[i].OIDC,
m.Clusters[i].NodeGroup,
m.Clusters[i].Role,
m.Clusters[i].Admin,
m.Clusters[i].CanPrivEsc,
},
)
}
var seen []string
@@ -198,11 +213,7 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -211,12 +222,13 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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))
+1 -2
View File
@@ -86,7 +86,6 @@ func TestEks(t *testing.T) {
EKSClient: &MockedEKSClientInterface{},
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
@@ -104,7 +103,7 @@ func TestEks(t *testing.T) {
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.EKS(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
subtest.testModule.EKS(subtest.outputDirectory, subtest.verbosity)
for index, expectedCluster := range subtest.expectedResult {
if expectedCluster.Name != subtest.testModule.Clusters[index].Name {
log.Fatal("Cluster name does not match expected value")
+94 -61
View File
@@ -1,16 +1,16 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
@@ -18,14 +18,15 @@ import (
)
type ElasticNetworkInterfacesModule struct {
//EC2Client *ec2.Client
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
EC2Client sdk.AWSEC2ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
AWSProfile string
WrapTable bool
MappedENIs []MappedENI
CommandCounter internal.CommandCounter
@@ -44,7 +45,7 @@ type MappedENI struct {
Description string
}
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat string, outputDirectory string, verbosity int) {
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "elastic-network-interfaces"
@@ -84,7 +85,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
receiverDone <- true
<-receiverDone
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printENIsData(outputDirectory, dataReceiver, verbosity)
}
@@ -101,8 +102,9 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
}
}
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
func (m *ElasticNetworkInterfacesModule) printENIsData(outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
m.output.Headers = []string{
"Account",
"ID",
"Type",
"External IP",
@@ -111,10 +113,48 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
"Attached Instance",
"Description",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"ID",
"Type",
"External IP",
"Internal IP",
"VPC ID",
"Attached Instance",
"Description",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"ID",
"Type",
"External IP",
"Internal IP",
"VPC ID",
"Attached Instance",
"Description",
}
}
for _, eni := range m.MappedENIs {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
eni.ID,
eni.Type,
eni.ExternalIP,
@@ -126,11 +166,8 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -139,12 +176,13 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -213,50 +251,45 @@ func (m *ElasticNetworkInterfacesModule) executeChecks(r string, wg *sync.WaitGr
}
func (m *ElasticNetworkInterfacesModule) getDescribeNetworkInterfaces(region string, dataReceiver chan MappedENI) {
var PaginationControl *string
for {
DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
context.TODO(),
&(ec2.DescribeNetworkInterfacesInput{
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, eni := range DescribeNetworkInterfaces.NetworkInterfaces {
status := string(eni.Status)
if status == "available" {
continue // unused ENI
}
mappedENI := MappedENI{
ID: aws.ToString(eni.NetworkInterfaceId),
Type: string(eni.InterfaceType),
ExternalIP: getPublicIPOfElasticNetworkInterface(eni),
PrivateIP: aws.ToString(eni.PrivateIpAddress),
VPCID: aws.ToString(eni.VpcId),
AttachedInstance: getAttachmentInstanceOfElasticNetworkInterface(eni),
Description: aws.ToString(eni.Description),
}
dataReceiver <- mappedENI
}
if DescribeNetworkInterfaces.NextToken != nil {
PaginationControl = DescribeNetworkInterfaces.NextToken
} else {
PaginationControl = nil
break
}
NetworkInterfaces, err := sdk.CachedEC2DescribeNetworkInterfaces(m.EC2Client, aws.ToString(m.Caller.Account), region)
// var PaginationControl *string
// for {
// DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
// context.TODO(),
// &(ec2.DescribeNetworkInterfacesInput{
// NextToken: PaginationControl,
// }),
// func(o *ec2.Options) {
// o.Region = region
// },
// )
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, eni := range NetworkInterfaces {
status := string(eni.Status)
if status == "available" {
continue // unused ENI
}
mappedENI := MappedENI{
ID: aws.ToString(eni.NetworkInterfaceId),
Type: string(eni.InterfaceType),
ExternalIP: getPublicIPOfElasticNetworkInterface(eni),
PrivateIP: aws.ToString(eni.PrivateIpAddress),
VPCID: aws.ToString(eni.VpcId),
AttachedInstance: getAttachmentInstanceOfElasticNetworkInterface(eni),
Description: aws.ToString(eni.Description),
}
dataReceiver <- mappedENI
}
}
func getPublicIPOfElasticNetworkInterface(elasticNetworkInterface types.NetworkInterface) string {
+44 -33
View File
@@ -1,48 +1,59 @@
package aws
import (
"context"
"encoding/json"
"log"
"testing"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
type mockedDescribeNetworkInterfacesClient2 struct {
describeNetworkInterfaces DescribeNetworkInterfaces
}
func TestElasticNetworkInterfaces(t *testing.T) {
func (c *mockedDescribeNetworkInterfacesClient2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
var nics []ec2types.NetworkInterface
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
m := ElasticNetworkInterfacesModule{
AWSProfile: "default",
AWSRegions: []string{"us-east-1", "us-west-1"},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
EC2Client: &sdk.MockedEC2Client2{},
}
for _, mockednic := range c.describeNetworkInterfaces.NetworkInterfaces {
nics = append(nics, ec2types.NetworkInterface{
Association: &ec2types.NetworkInterfaceAssociation{
PublicIp: aws.String(mockednic.Association.PublicIP),
//m.ElasticNetworkInterfaces("table", ".", 3)
subtests := []struct {
name string
testModule ElasticNetworkInterfacesModule
expectedResult []MappedENI
}{
{
name: "Test ElasticNetworkInterfaces",
testModule: m,
expectedResult: []MappedENI{
{
PrivateIP: "10.0.1.17",
ExternalIP: "203.0.113.12",
},
{
PrivateIP: "10.0.1.149",
ExternalIP: "198.51.100.0",
},
},
NetworkInterfaceId: aws.String(mockednic.NetworkInterfaceID),
PrivateIpAddress: aws.String(mockednic.PrivateIPAddress),
VpcId: aws.String(mockednic.VpcID),
Attachment: &ec2types.NetworkInterfaceAttachment{InstanceId: aws.String(mockednic.Attachment.InstanceID)},
Description: aws.String(mockednic.Description),
},
}
internal.MockFileSystem(true)
for _, subtest := range subtests {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.ElasticNetworkInterfaces(".", 3)
for index, expectedTask := range subtest.expectedResult {
if expectedTask.ExternalIP != subtest.testModule.MappedENIs[index].ExternalIP {
t.Errorf("expected %s, got %s", expectedTask.ExternalIP, subtest.testModule.MappedENIs[index].ExternalIP)
}
if expectedTask.PrivateIP != subtest.testModule.MappedENIs[index].PrivateIP {
t.Errorf("expected %s, got %s", expectedTask.PrivateIP, subtest.testModule.MappedENIs[index].PrivateIP)
}
}
})
}
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
}
func TestElasticNetworkInterfaces(t *testing.T) {
m := ElasticNetworkInterfacesModule{
AWSProfile: "default",
AWSRegions: []string{"us-east-1", "us-west-1"},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
DescribeNetworkInterfacesClient: &mockedDescribeNetworkInterfacesClient2{},
}
m.ElasticNetworkInterfaces("table", ".", 3)
}
+60 -23
View File
@@ -55,12 +55,14 @@ type EndpointsModule struct {
AppRunnerClient *apprunner.Client
LightsailClient *lightsail.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Endpoints []Endpoint
@@ -83,8 +85,8 @@ type Endpoint struct {
var oe *smithy.OperationError
func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EndpointsModule) PrintEndpoints(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "endpoints"
@@ -147,6 +149,7 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
})
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -156,11 +159,48 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
"Public",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Endpoint",
"Port",
"Protocol",
"Public",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Endpoint",
"Port",
"Protocol",
"Public",
}
}
// Table rows
for i := range m.Endpoints {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Endpoints[i].AWSService,
m.Endpoints[i].Region,
m.Endpoints[i].Name,
@@ -173,11 +213,7 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -186,12 +222,13 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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 endpoints found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
@@ -545,7 +582,7 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
m.CommandCounter.Error++
continue
}
if BrokerDetails.PubliclyAccessible {
if aws.ToBool(BrokerDetails.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -615,7 +652,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
var endpoint string
var kibana_endpoint string
// This exits thie function if an opensearch domain exists but there is no endpoint
// This exits the function if an opensearch domain exists but there is no endpoint
if raw_endpoint == nil {
return
} else {
@@ -1386,7 +1423,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
endpoint := aws.ToString(instance.Endpoint.Address)
awsService := "RDS"
if instance.PubliclyAccessible {
if aws.ToBool(instance.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -1397,7 +1434,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Port: aws.ToInt32(port),
Protocol: aws.ToString(instance.Engine),
Public: public,
}
@@ -1442,7 +1479,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
if aws.ToBool(cluster.PubliclyAccessible) {
public = "True"
} else {
public = "False"
@@ -1455,7 +1492,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Port: aws.ToInt32(port),
Protocol: protocol,
Public: public,
}
+51 -16
View File
@@ -6,6 +6,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -34,12 +35,14 @@ import (
type EnvsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
OutputFormat string
Goroutines int
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSProfile string
AWSOutputType string
AWSTableCols string
Goroutines int
WrapTable bool
// Service Clients
ECSClient *ecs.Client
@@ -65,8 +68,8 @@ type EnvironmentVariable struct {
environmentVarValue string
}
func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "env-vars"
@@ -119,6 +122,7 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
// Table headers
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Name",
@@ -126,10 +130,42 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
"Value",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Name",
"Key",
"Value",
}
} else {
tableCols = []string{
"Service",
"Region",
"Name",
"Key",
"Value",
}
}
//Table rows
for _, envVar := range m.EnvironmentVariables {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
@@ -140,10 +176,8 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -152,12 +186,13 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+56 -19
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -27,12 +28,14 @@ type FilesystemsModule struct {
EFSClient *efs.Client
FSxClient *fsx.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Filesystems []FilesystemObject
@@ -55,8 +58,8 @@ type FilesystemObject struct {
Permissions string
}
func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *FilesystemsModule) PrintFilesystems(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "filesystems"
@@ -107,7 +110,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
})
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Name",
"DNS Name",
@@ -117,12 +120,48 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
"Permissions",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Name",
"DNS Name",
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
} else {
tableCols = []string{
"Region",
"Name",
"DNS Name",
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
}
// Table rows
for i := range m.Filesystems {
m.output.Body = append(
m.output.Body,
[]string{
m.Filesystems[i].AWSService,
aws.ToString(m.Caller.Account),
m.Filesystems[i].Region,
m.Filesystems[i].Name,
m.Filesystems[i].DnsName,
@@ -136,11 +175,8 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -149,12 +185,13 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+47 -19
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -20,13 +21,15 @@ import (
type IamSimulatorModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
SimulatorResults []SimulatorResult
@@ -64,16 +67,16 @@ var (
TxtLogger = internal.TxtLogger()
)
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputFormat string, outputDirectory string, verbosity int) {
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "iam-simulator"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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
@@ -149,11 +152,36 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
// Regardless of what options were selected, for now at least, we will always print the data using the output module (table/csv mode)
m.output.Headers = []string{
"Service",
"Account",
"Principal",
"Query",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Principal",
"Query",
}
} else {
tableCols = []string{
"Principal",
"Query",
}
}
sort.Slice(m.SimulatorResults, func(i, j int) bool {
return m.SimulatorResults[i].Query < m.SimulatorResults[j].Query
})
@@ -163,7 +191,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
m.output.Body = append(
m.output.Body,
[]string{
m.SimulatorResults[i].AWSService,
aws.ToString(m.Caller.Account),
m.SimulatorResults[i].Principal,
m.SimulatorResults[i].Query,
},
@@ -171,9 +199,8 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -182,12 +209,13 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)
+92 -116
View File
@@ -13,7 +13,6 @@ import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
@@ -24,11 +23,13 @@ import (
type InstancesModule struct {
// General configuration data
EC2Client *ec2.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
EC2Client sdk.AWSEC2ClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
@@ -63,8 +64,8 @@ type MappedInstance struct {
CanPrivEsc string
}
func (m *InstancesModule) Instances(filter string, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *InstancesModule) Instances(filter string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "instances"
@@ -147,9 +148,9 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
// This conditional block will either dump the userData attribute content or the general instances data, depending on what you select via command line.
//fmt.Printf("\n[*] Preparing output...\n\n")
if m.UserDataAttributesOnly {
m.printInstancesUserDataAttributesOnly(outputFormat, outputDirectory, dataReceiver)
m.printInstancesUserDataAttributesOnly(outputDirectory, dataReceiver)
} else {
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver, verbosity)
m.printGeneralInstanceData(outputDirectory, dataReceiver, verbosity)
}
}
@@ -167,13 +168,13 @@ func (m *InstancesModule) Receiver(receiver chan MappedInstance, receiverDone ch
}
}
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputDirectory string, dataReceiver chan MappedInstance) {
defer func() {
m.output.CallingModule = "instances"
}()
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())
@@ -217,12 +218,39 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
}
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
func (m *InstancesModule) printGeneralInstanceData(outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
// Prepare Table headers
//m.output.Headers = table.Row{
if m.pmapperError == nil {
m.output.Headers = []string{
"Account",
//"ID",
"Name",
//"Arn",
"ID",
"Zone",
"State",
"External IP",
"Internal IP",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
m.output.Headers = []string{
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
//"ID",
"Name",
//"Arn",
@@ -235,8 +263,9 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
//"ID",
"Name",
//"Arn",
@@ -247,59 +276,40 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
"Internal IP",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
//Table rows
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,
},
)
}
for _, instance := range m.MappedInstances {
m.output.Body = append(
m.output.Body,
//table.Row{
[]string{
aws.ToString(m.Caller.Account),
//instance.ID,
instance.Name,
//instance.Arn,
instance.ID,
instance.AvailabilityZone,
instance.State,
instance.ExternalIP,
instance.PrivateIP,
instance.Role,
instance.Admin,
instance.CanPrivEsc,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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.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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
@@ -310,12 +320,13 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -440,27 +451,17 @@ func (m *InstancesModule) executeChecks(instancesToSearch []string, r string, wg
}
func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, region string) (userData *string, err error) {
Attributes, err := m.EC2Client.DescribeInstanceAttribute(
context.TODO(),
&ec2.DescribeInstanceAttributeInput{
InstanceId: instanceID,
Attribute: types.InstanceAttributeName("userData"),
},
func(o *ec2.Options) {
o.Region = region
},
)
UserData, err := sdk.CachedEC2DescribeInstanceAttributeUserData(m.EC2Client, aws.ToString(m.Caller.Account), region, aws.ToString(instanceID))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return nil, err
} else {
if Attributes.UserData.Value == nil {
if UserData == "" {
return aws.String("NoUserData"), nil
} else {
data, _ := base64.StdEncoding.DecodeString(*Attributes.UserData.Value)
data, _ := base64.StdEncoding.DecodeString(UserData)
return aws.String(string(data)), nil
}
}
@@ -470,47 +471,22 @@ func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, regio
func (m *InstancesModule) getDescribeInstances(instancesToSearch []string, region string, dataReceiver chan MappedInstance) {
// The "PaginationControl" value is nil when there's no more data to return.
var PaginationControl *string
for {
DescribeInstances, err := m.EC2Client.DescribeInstances(
context.TODO(),
&(ec2.DescribeInstancesInput{
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, instance := range Instances {
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
m.loadInstanceData(instance, region, dataReceiver)
}
for _, reservation := range DescribeInstances.Reservations {
accountId := reservation.OwnerId
for _, instance := range reservation.Instances {
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
m.loadInstanceData(instance, region, accountId, dataReceiver)
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if DescribeInstances.NextToken != nil {
PaginationControl = DescribeInstances.NextToken
} else {
PaginationControl = nil
break
}
}
}
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, accountId *string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, dataReceiver chan MappedInstance) {
var profile string
var externalIP string
@@ -552,7 +528,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)),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
+1866 -616
View File
File diff suppressed because it is too large. Load diff
+65 -48
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -25,9 +26,11 @@ type LambdasModule struct {
LambdaClient *lambda.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
@@ -49,15 +52,16 @@ type Lambda struct {
Region string
Type string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
Public string
}
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
func (m *LambdasModule) PrintLambdas(outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "lambda"
@@ -123,24 +127,52 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Service",
m.output.Headers = []string{
"Account",
"Region",
"Name",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Resource",
"Arn",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
m.output.Headers = []string{
tableCols = []string{
"Service",
"Region",
"Resource",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
sort.Slice(m.Lambdas, func(i, j int) bool {
return m.Lambdas[i].AWSService < m.Lambdas[j].AWSService
@@ -149,41 +181,24 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
// Table rows
for i := range m.Lambdas {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Lambdas[i].AWSService,
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Lambdas[i].AWSService,
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
//m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
}
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Lambdas[i].Region,
//m.Lambdas[i].Type,
m.Lambdas[i].Name,
m.Lambdas[i].Arn,
m.Lambdas[i].Role,
m.Lambdas[i].Admin,
m.Lambdas[i].CanPrivEsc,
//m.Lambdas[i].Public,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -192,12 +207,13 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -300,13 +316,14 @@ func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaph
}
for _, function := range functions {
//arn := aws.ToString(function.FunctionArn)
arn := aws.ToString(function.FunctionArn)
name := aws.ToString(function.FunctionName)
role := aws.ToString(function.Role)
dataReceiver <- Lambda{
AWSService: "Lambda",
Name: name,
Arn: arn,
Region: r,
Type: "",
Role: role,
+54 -22
View File
@@ -65,13 +65,15 @@ type NetworkPortsModule struct {
LightsailClient *lightsail.Client
RDSClient *rds.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Verbosity int
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
Verbosity int
// Main module data
IPv4_Private []NetworkService
@@ -136,8 +138,8 @@ var naclToSG = map[string]string{
var validSecurityGroupProtocols = []string{"-1", "tcp", "udp"}
func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirectory string) {
// These stuct values are used by the output module
func (m *NetworkPortsModule) PrintNetworkPorts(outputDirectory string) {
// These struct values are used by the output module
m.output.Verbosity = m.Verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "network-ports"
@@ -184,6 +186,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Account",
"Service",
"Region",
"Protocol",
@@ -191,6 +194,36 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
"Ports",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Service",
"Region",
"Protocol",
"Host",
"Ports",
}
} else {
tableCols = []string{
"Service",
"Region",
"Protocol",
"Host",
"Ports",
}
}
// Table rows
for _, arr := range [][]NetworkService{m.IPv4_Private, m.IPv4_Public, m.IPv6} {
for _, i := range arr {
@@ -198,6 +231,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
i.AWSService,
i.Region,
i.Protocol,
@@ -210,10 +244,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
}
if len(m.IPv4_Private) > 0 || len(m.IPv4_Public) > 0 || len(m.IPv6) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: m.Verbosity,
CallingModule: m.output.CallingModule,
@@ -222,12 +253,13 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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 network services found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
@@ -742,7 +774,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Memcached
if cluster.ConfigurationEndpoint != nil {
ipv4_private = addHost(ipv4_private, aws.ToString(cluster.ConfigurationEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, cluster.ConfigurationEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(cluster.ConfigurationEndpoint.Port))
} else {
replicationGroupId := aws.ToString(cluster.ReplicationGroupId)
for _, group := range nodes {
@@ -751,7 +783,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Primary
if g.PrimaryEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address)) {
ipv4_private = addHost(ipv4_private, aws.ToString(g.PrimaryEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, g.PrimaryEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.PrimaryEndpoint.Port))
reportedClusters = addHost(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address))
}
@@ -759,7 +791,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
// Reader
if g.ReaderEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.ReaderEndpoint.Address)) {
ipv4_private = addHost(ipv4_private, aws.ToString(g.ReaderEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, g.ReaderEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.ReaderEndpoint.Port))
reportedClusters = addHost(reportedClusters, aws.ToString(g.ReaderEndpoint.Address))
}
@@ -769,7 +801,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
if aws.ToString(m.CacheClusterId) == aws.ToString(cluster.CacheClusterId) {
if m.ReadEndpoint != nil {
ipv4_private = addHost(ipv4_private, aws.ToString(m.ReadEndpoint.Address))
tcpPortsInts = addPort(tcpPortsInts, m.ReadEndpoint.Port)
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(m.ReadEndpoint.Port))
}
}
}
@@ -1047,7 +1079,7 @@ func (m *NetworkPortsModule) getRdsNetworkPortsPerRegion(r string, dataReceiver
for _, instance := range DBInstances {
if aws.ToString(instance.DBInstanceStatus) == "available" {
host := []string{aws.ToString(instance.Endpoint.Address)}
var port int32 = instance.Endpoint.Port
var port int32 = aws.ToInt32(instance.Endpoint.Port)
var groups []SecurityGroup
for _, group := range instance.VpcSecurityGroups {
+51 -16
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
@@ -19,12 +20,14 @@ type OrgModule struct {
OrganizationsClient sdk.OrganizationsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
// Main module data
Accounts []Account
@@ -52,8 +55,8 @@ type Account struct {
OrgId string
}
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *OrgModule) PrintOrgAccounts(outputDirectory string, verbosity int) {
// These struct values are used by the output module
var err error
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
@@ -85,6 +88,7 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
}
m.output.Headers = []string{
"Account",
"Name",
"ID",
"isManagementAccount?",
@@ -92,12 +96,44 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
"Email",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
}
// Table rows
for i := range m.Accounts {
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Accounts[i].Name,
m.Accounts[i].Id,
strconv.FormatBool(m.Accounts[i].isManagementAccount),
@@ -108,9 +144,7 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -119,12 +153,13 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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))
@@ -272,7 +307,7 @@ func (m *OrgModule) addOrgAccount() {
isManagementAccount: false,
Name: "This account",
Id: aws.ToString(m.Caller.Account),
Email: "Unkonwn",
Email: "Unknown",
Arn: aws.ToString(m.Caller.Arn),
Status: "ACTIVE",
})
+55 -18
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
@@ -22,12 +23,14 @@ type OutboundAssumedRolesModule struct {
// General configuration data
CloudTrailClient *cloudtrail.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
OutboundAssumeRoleEntries []OutboundAssumeRoleEntry
@@ -114,8 +117,8 @@ type CloudTrailEvent struct {
} `json:"tlsDetails"`
}
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "outbound-assumed-roles"
@@ -161,7 +164,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
<-receiverDone
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Type",
//"Source Account",
@@ -171,12 +174,48 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
"Log Entry Timestamp",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Type",
//"Source Account",
"Source Principal",
//"Destination Account",
"Destination Principal",
"Log Entry Timestamp",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Region",
"Type",
//"Source Account",
"Source Principal",
//"Destination Account",
"Destination Principal",
"Log Entry Timestamp",
}
}
// Table rows
for i := range m.OutboundAssumeRoleEntries {
m.output.Body = append(
m.output.Body,
[]string{
m.OutboundAssumeRoleEntries[i].AWSService,
aws.ToString(m.Caller.Account),
m.OutboundAssumeRoleEntries[i].Region,
m.OutboundAssumeRoleEntries[i].Type,
//m.OutboundAssumeRoleEntries[i].SourceAccount,
@@ -190,10 +229,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -202,12 +238,13 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+65 -21
View File
@@ -21,12 +21,14 @@ type IamPermissionsModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
@@ -76,14 +78,15 @@ type PermissionsRow struct {
Arn string
PolicyType string
PolicyName string
PolicyArn string
Effect string
Action string
Resource string
Condition string
}
func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDirectory string, verbosity int, principal string) {
// These stuct values are used by the output module
func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbosity int, principal string) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "permissions"
@@ -94,7 +97,7 @@ 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", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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 != "" {
@@ -105,12 +108,13 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.parsePermissions(principal)
m.output.Headers = []string{
//"Service",
"Account",
"Type",
"Name",
//"Arn",
"Arn",
"Policy",
"Policy Name",
"Policy Arn",
"Effect",
"Action",
"Resource",
@@ -122,12 +126,13 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.output.Body = append(
m.output.Body,
[]string{
//m.Rows[i].AWSService,
aws.ToString(m.Caller.Account),
m.Rows[i].Type,
m.Rows[i].Name,
//m.Rows[i].Arn,
m.Rows[i].Arn,
m.Rows[i].PolicyType,
m.Rows[i].PolicyName,
m.Rows[i].PolicyArn,
m.Rows[i].Effect,
m.Rows[i].Action,
m.Rows[i].Resource,
@@ -138,10 +143,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -149,13 +151,51 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
Wrap: m.WrapTable,
},
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Type",
//"Name",
"Arn",
"Policy",
"Policy Name",
"Policy Arn",
"Effect",
"Action",
"Resource",
"Condition"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{
"Type",
"Name",
//"Arn",
"Policy",
"Policy Name",
"Effect",
"Action",
"Resource",
"Condition",
}
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
TableCols: tableCols,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -402,6 +442,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
PolicyArn: p.Arn,
Effect: effect,
Action: action,
Resource: resource,
@@ -428,6 +469,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Type: IAMtype,
PolicyType: "Managed",
PolicyName: p.Name,
PolicyArn: p.Arn,
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
@@ -473,6 +515,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: action,
Resource: resource,
@@ -498,6 +541,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
Type: IAMtype,
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
+47 -16
View File
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
@@ -17,12 +18,14 @@ import (
type PmapperModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
pmapperGraph graph.Graph[string, string]
@@ -136,8 +139,8 @@ func (m *PmapperModule) DoesPrincipalHaveAdmin(principal string) bool {
return false
}
func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *PmapperModule) PrintPmapperData(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "pmapper"
@@ -148,7 +151,7 @@ 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", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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 {
@@ -163,11 +166,39 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
}
m.output.Headers = []string{
"Account",
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Principal Arn",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
}
//Table rows
var isAdmin, pathToAdmin string
for i := range m.Nodes {
@@ -186,6 +217,7 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
m.output.Body = append(
m.output.Body,
[]string{
aws.ToString(m.Caller.Account),
m.Nodes[i].Arn,
isAdmin,
pathToAdmin,
@@ -195,9 +227,7 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -206,12 +236,13 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)
+58 -27
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
@@ -16,12 +17,14 @@ type IamPrincipalsModule struct {
// General configuration data
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Users []User
@@ -61,8 +64,8 @@ type Role struct {
InlinePolicies []string
}
func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "principals"
@@ -92,13 +95,44 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
m.output.Headers = []string{
"Service",
"Account",
"Type",
"Name",
"Arn",
"AttachedPolicies",
"InlinePolicies",
}
// "AttachedPolicies",
// "InlinePolicies",
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Type",
"Name",
"Arn",
//"AttachedPolicies",
//"InlinePolicies",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Type",
"Name",
"Arn",
// "AttachedPolicies",
// "InlinePolicies",
}
}
//Table rows
@@ -106,13 +140,12 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
m.output.Body = append(
m.output.Body,
[]string{
m.Users[i].AWSService,
aws.ToString(m.Caller.Account),
m.Users[i].Type,
m.Users[i].Name,
m.Users[i].Arn,
// m.Users[i].AttachedPolicies,
// m.Users[i].InlinePolicies,
strings.Join(m.Users[i].AttachedPolicies, " , "),
strings.Join(m.Users[i].InlinePolicies, " , "),
},
)
@@ -122,22 +155,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
m.output.Body = append(
m.output.Body,
[]string{
m.Roles[i].AWSService,
aws.ToString(m.Caller.Account),
m.Roles[i].Type,
m.Roles[i].Name,
m.Roles[i].Arn,
// m.Roles[i].AttachedPolicies,
// m.Roles[i].InlinePolicies,
strings.Join(m.Roles[i].AttachedPolicies, " , "),
strings.Join(m.Roles[i].InlinePolicies, " , "),
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -146,12 +176,13 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+52 -18
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/internal"
@@ -20,12 +21,14 @@ type RAMModule struct {
// General configuration data
RAMClient *ram.Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
Resources []Resource
@@ -44,8 +47,8 @@ type Resource struct {
ShareType string
}
func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *RAMModule) PrintRAM(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "ram"
@@ -91,7 +94,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
"Service",
"Account",
"Region",
"Share Name",
"Type",
@@ -99,12 +102,44 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
"Share Type",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Region",
"Share Name",
"Type",
"Owner",
"Share Type",
}
// Otherwise, use the default columns.
} else {
tableCols = []string{
"Service",
"Region",
"Share Name",
"Type",
"Owner",
"Share Type",
}
}
// Table rows
for i := range m.Resources {
m.output.Body = append(
m.output.Body,
[]string{
m.Resources[i].AWSService,
aws.ToString(m.Caller.Account),
m.Resources[i].Region,
m.Resources[i].Name,
m.Resources[i].Type,
@@ -115,10 +150,8 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -127,12 +160,13 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
+209 -27
View File
@@ -20,10 +20,14 @@ import (
type ResourceTrustsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSOutputType string
AWSTableCols string
AWSMFAToken string
AWSProfile string
CloudFoxVersion string
@@ -44,23 +48,27 @@ type Resource2 struct {
ResourcePolicySummary string
Public string
Interesting string
TrustedPrincipals string
TrustsCrossAccount string
TrustsAllAccounts string
HasConditions string
}
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "resource-trusts"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
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))
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
@@ -95,13 +103,42 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
//"Account ID",
"Account",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
} else {
tableCols = []string{
//"Account ID",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
}
// sort the table roles by Interesting
sort.Slice(m.Resources2, func(i, j int) bool {
return m.Resources2[j].Interesting > m.Resources2[i].Interesting
@@ -112,7 +149,7 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
m.output.Body = append(
m.output.Body,
[]string{
//m.Resources2[i].AccountID,
aws.ToString(m.Caller.Account),
m.Resources2[i].ARN,
m.Resources2[i].Public,
m.Resources2[i].Interesting,
@@ -122,8 +159,6 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -132,12 +167,13 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -212,6 +248,24 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("secretsmanager", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("glue", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
@@ -238,7 +292,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable, m.AWSMFAToken)
ListTopics, err := cloudFoxSNSClient.listTopics(r)
if err != nil {
@@ -270,7 +324,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
topic.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
// If there is a resource policy, convert the resource policy into plain english
if !topic.Policy.IsEmpty() {
for i, statement := range topic.Policy.Statement {
@@ -314,7 +368,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.AWSMFAToken)
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
@@ -325,7 +379,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
for _, b := range ListBuckets {
var statementSummaryInEnglish string
var isInteresting string = "No"
bucket := &Bucket{
bucket := &BucketRow{
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
}
name := aws.ToString(b.Name)
@@ -357,7 +411,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
bucket.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
// If there is a resource policy, convert the resource policy into plain english
if !bucket.Policy.IsEmpty() {
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
@@ -397,7 +451,7 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListQueues, err := cloudFoxSQSClient.listQueues(r)
if err != nil {
@@ -459,9 +513,9 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
DescribeRepositories, err := sdk.CachedECRDescribeRepositories(cloudFoxECRClient.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
return
@@ -523,7 +577,7 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
if err != nil {
@@ -597,7 +651,7 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
if err != nil {
@@ -662,7 +716,7 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
@@ -718,6 +772,134 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
}
}
// getSecretsManagerSecretsPoliciesPerRegion retrieves the resource policies for all Secrets Manager secrets in the specified region.
// It sends the resulting Resource2 objects to the dataReceiver channel.
// It uses a semaphore to limit the number of concurrent requests and a WaitGroup to wait for all requests to complete.
// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to.
func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSecretsManagerClient := InitSecretsManagerClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ListSecrets, err := sdk.CachedSecretsManagerListSecrets(cloudFoxSecretsManagerClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, s := range ListSecrets {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
secretPolicy, err := sdk.CachedSecretsManagerGetResourcePolicy(cloudFoxSecretsManagerClient, aws.ToString(s.ARN), r, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if secretPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !secretPolicy.IsEmpty() {
for i, statement := range secretPolicy.Statement {
prefix := ""
if len(secretPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(s.ARN),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(s.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxGlueClient := InitGlueClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
ResourcePolicies, err := sdk.CachedGlueGetResourcePolicies(cloudFoxGlueClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, resourcePolicy := range ResourcePolicies {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
if resourcePolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !resourcePolicy.IsEmpty() {
for _, statement := range resourcePolicy.Statement {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
resources := statement.Resource
for _, resource := range resources {
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: resource,
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: resource,
Region: r,
Interesting: isInteresting,
}
}
}
}
}
}
func isResourcePolicyInteresting(statementSummaryInEnglish string) bool {
// check if the statement has any of the following items, but make sure the check is case insensitive
// if it does, then return true
+43
View File
@@ -0,0 +1,43 @@
package aws
import (
"testing"
)
func TestIsResourcePolicyInteresting(t *testing.T) {
testCases := []struct {
name string
input string
expected bool
}{
{
name: "test1",
input: "Everyone can sqs:SendMessage & can sqs:ReceiveMessage",
expected: true,
},
{
name: "PrincipalOrgPaths",
input: "aws:PrincipalOrgPaths",
expected: true,
},
{
name: "Empty",
input: "",
expected: false,
},
{
name: "NotInteresting",
input: "sns.amazonaws.com can lambda:InvokeFunction",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := isResourcePolicyInteresting(tc.input)
if actual != tc.expected {
t.Errorf("Expected %v but got %v for input %s", tc.expected, actual, tc.input)
}
})
}
}
+174 -147
View File
@@ -31,12 +31,16 @@ type RoleTrustsModule struct {
CommandCounter internal.CommandCounter
SkipAdminCheck bool
WrapTable bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
AWSOutputType string
AWSTableCols string
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Main module data
AnalyzedRoles []AnalyzedRole
AnalyzedRoles []AnalyzedRole
RoleTrustTable []RoleTrustRow
// Used to store output data for pretty printing
output internal.OutputData2
@@ -44,6 +48,18 @@ type RoleTrustsModule struct {
modLog *logrus.Entry
}
type RoleTrustRow struct {
RoleARN string
RoleName string
TrustedPrincipal string
TrustedService string
TrustedFederatedProvider string
TrustedFederatedSubject string
ExternalID string
IsAdmin string
CanPrivEsc string
}
type AnalyzedRole struct {
roleARN *string
trustsDoc trustPolicyDocument
@@ -86,7 +102,7 @@ type RoleTrustStatementEntry struct {
} `json:"Condition"`
}
func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory string, verbosity int) {
func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int) {
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "role-trusts"
@@ -127,27 +143,30 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), m.AWSProfile))
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
principalsHeader, principalsBody, principalTableCols := m.printPrincipalTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: principalsHeader,
Body: principalsBody,
Name: "role-trusts-principals",
Header: principalsHeader,
Body: principalsBody,
TableCols: principalTableCols,
Name: "role-trusts-principals",
})
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, outputDirectory)
servicesHeader, servicesBody, serviceTableCols := m.printServiceTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: servicesHeader,
Body: servicesBody,
Name: "role-trusts-services",
Header: servicesHeader,
Body: servicesBody,
TableCols: serviceTableCols,
Name: "role-trusts-services",
})
federatedHeader, federatedBody := m.printFederatedTrusts(outputFormat, outputDirectory)
federatedHeader, federatedBody, federatedTableCols := m.printFederatedTrusts(outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: federatedHeader,
Body: federatedBody,
Name: "role-trusts-federated",
Header: federatedHeader,
Body: federatedBody,
TableCols: federatedTableCols,
Name: "role-trusts-federated",
})
o.WriteFullOutput(o.Table.TableFiles, nil)
@@ -169,196 +188,203 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-principals"
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Principal",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Account", "Role Arn", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{"Role Name", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := principal
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
}
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedPrincipal,
RoleTrustRow.ExternalID,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// ////m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// //internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
// fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
m.sortTrustsTablePerTrustedPrincipal()
return header, body, tableCols
}
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printServiceTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-services"
var column1 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Service",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
header = []string{
"Role",
"Trusted Service",
"ExternalID",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
tableCols = []string{"Role Name", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, service := range statement.Principal.Service {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := service
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedService: service,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedService,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
// sort the rows based on column 2 (service)
sort.SliceStable(body, func(i, j int) bool {
return body[i][1] < body[j][1]
return body[i][3] < body[j][3]
})
//m.sortTrustsTablePerTrustedPrincipal()
return header, body
return header, body, tableCols
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]string, [][]string, []string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-federated"
var column1, column3, column2 string
if m.pmapperError == nil {
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
} else {
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
//"CanPrivEscToAdmin?",
}
header = []string{
"Account",
"Role Arn",
"Role Name",
"Trusted Provider",
"Trusted Subject",
"IsAdmin?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
tableCols = strings.Split(m.AWSTableCols, ",")
// If the user specified wide as the output format, use these columns.
} else if m.AWSOutputType == "wide" {
tableCols = []string{"Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
// Otherwise, use the default columns for this module (brief)
} else {
tableCols = []string{"Role Name", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
for _, role := range m.AnalyzedRoles {
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
for _, statement := range role.trustsDoc.Statement {
if len(statement.Principal.Federated) > 0 {
column2, column3 = parseFederatedTrustPolicy(statement)
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
body = append(body, []string{column1, column2, column3, column4})
provider, subject := parseFederatedTrustPolicy(statement)
RoleTrustRow := RoleTrustRow{
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedFederatedProvider: provider,
TrustedFederatedSubject: subject,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
RoleTrustRow.RoleARN,
RoleTrustRow.RoleName,
RoleTrustRow.TrustedFederatedProvider,
RoleTrustRow.TrustedFederatedSubject,
RoleTrustRow.IsAdmin,
RoleTrustRow.CanPrivEsc})
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
return header, body
return header, body, tableCols
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, string) {
@@ -435,6 +461,7 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
Admin: "",
CanPrivEsc: "",
})
}
}
+82 -73
View File
@@ -1,26 +1,28 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"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/route53"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type Route53Module struct {
// General configuration data
Route53Client *route53.Client
Route53Client sdk.AWSRoute53ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
AWSOutputType string
AWSTableCols string
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
@@ -42,9 +44,9 @@ type Record struct {
PrivateZone string
}
func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string, verbosity int) {
func (m *Route53Module) PrintRoute53(outputDirectory string, verbosity int) {
// These stuct values are used by the output module
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "route53"
@@ -60,19 +62,48 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
m.getRoute53Records()
m.output.Headers = []string{
"Service",
"Account",
"Name",
"Type",
"Value",
"PrivateZone",
}
// If the user specified table columns, use those.
// If the user specified -o wide, use the wide default cols for this module.
// Otherwise, use the hardcoded default cols for this module.
var tableCols []string
// If the user specified table columns, use those.
if m.AWSTableCols != "" {
// If the user specified wide as the output format, use these columns.
// remove any spaces between any commas and the first letter after the commas
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
tableCols = strings.Split(m.AWSTableCols, ",")
} else if m.AWSOutputType == "wide" {
tableCols = []string{
"Account",
"Name",
"Type",
"Value",
"PrivateZone",
}
} else {
tableCols = []string{
"Name",
"Type",
"Value",
"PrivateZone",
}
}
// Table rows
for i := range m.Records {
m.output.Body = append(
m.output.Body,
[]string{
m.Records[i].AWSService,
aws.ToString(m.Caller.Account),
m.Records[i].Name,
m.Records[i].Type,
m.Records[i].Value,
@@ -83,11 +114,8 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", 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)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
@@ -96,12 +124,13 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), 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)))
@@ -127,7 +156,7 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
var route53APublicRecords string
for _, record := range m.Records {
if record.Type == "A" || record.Type == "AAAA" {
if record.Type == "A" || record.Type == "AAAA" || record.Type == "CNAME" {
if record.PrivateZone == "True" {
route53APrivateRecords = route53APrivateRecords + fmt.Sprintln(record.Name)
} else {
@@ -176,71 +205,51 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
func (m *Route53Module) getRoute53Records() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var recordName string
var recordType string
for {
ListHostedZones, err := m.Route53Client.ListHostedZones(
context.TODO(),
&route53.ListHostedZonesInput{
Marker: PaginationControl,
},
)
HostedZones, err := sdk.CachedRoute53ListHostedZones(m.Route53Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var privateZone string
for _, zone := range HostedZones {
if zone.Config.PrivateZone {
privateZone = "True"
} else {
privateZone = "False"
}
Records, err := sdk.CachedRoute53ListResourceRecordSets(m.Route53Client, aws.ToString(m.Caller.Account), aws.ToString(zone.Id))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, record := range Records {
recordName = aws.ToString(record.Name)
recordType = string(record.Type)
for _, resourceRecord := range record.ResourceRecords {
recordValue := resourceRecord.Value
m.Records = append(
m.Records,
Record{
AWSService: "Route53",
Name: recordName,
Type: recordType,
Value: aws.ToString(recordValue),
PrivateZone: privateZone,
})
var privateZone string
for _, zone := range ListHostedZones.HostedZones {
id := aws.ToString(zone.Id)
if zone.Config.PrivateZone {
privateZone = "True"
} else {
privateZone = "False"
}
ListResourceRecordSets, err := m.Route53Client.ListResourceRecordSets(
context.TODO(),
&route53.ListResourceRecordSetsInput{
HostedZoneId: &id,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, record := range ListResourceRecordSets.ResourceRecordSets {
recordName = aws.ToString(record.Name)
recordType = string(record.Type)
for _, resourceRecord := range record.ResourceRecords {
recordValue := resourceRecord.Value
m.Records = append(
m.Records,
Record{
AWSService: "Route53",
Name: recordName,
Type: recordType,
Value: aws.ToString(recordValue),
PrivateZone: privateZone,
})
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if ListHostedZones.NextMarker != nil {
PaginationControl = ListHostedZones.NextMarker
} else {
PaginationControl = nil
break
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ type APIGatewayClientInterface interface {
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
}
func RegisterApiGatewayTypes() {
func init() {
gob.Register([]apiGatewayTypes.RestApi{})
}
+27
View File
@@ -0,0 +1,27 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
)
type MockedAWSAPIGatewayClient struct {
}
func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apigateway.GetRestApisInput, options ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error) {
return &apigateway.GetRestApisOutput{
Items: []apiGatewayTypes.RestApi{
{
Id: aws.String("api1"),
Name: aws.String("api1"),
},
{
Id: aws.String("api2"),
Name: aws.String("api2"),
},
},
}, nil
}
+1 -1
View File
@@ -16,7 +16,7 @@ type APIGatewayv2ClientInterface interface {
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
}
func RegisterApiGatewayV2Types() {
func init() {
gob.Register([]apiGatwayV2Types.Api{})
}
+27
View File
@@ -0,0 +1,27 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
)
type MockedAWSAPIGatewayv2Client struct {
}
func (m *MockedAWSAPIGatewayv2Client) GetApis(ctx context.Context, input *apigatewayv2.GetApisInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error) {
return &apigatewayv2.GetApisOutput{
Items: []apiGatwayV2Types.Api{
{
ApiId: aws.String("api1"),
Name: aws.String("api1"),
},
{
ApiId: aws.String("api2"),
Name: aws.String("api2"),
},
},
}, nil
}
+1 -1
View File
@@ -15,7 +15,7 @@ type AppRunnerClientInterface interface {
ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error)
}
func RegisterApprunnerTypes() {
func init() {
gob.Register([]apprunnerTypes.Service{})
gob.Register([]apprunnerTypes.ServiceSummary{})
}
+25
View File
@@ -0,0 +1,25 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types"
)
type MockedAppRunnerClient struct {
}
func (m *MockedAppRunnerClient) ListServices(ctx context.Context, input *apprunner.ListServicesInput, options ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error) {
return &apprunner.ListServicesOutput{
ServiceSummaryList: []apprunnerTypes.ServiceSummary{
{
ServiceName: aws.String("service1"),
},
{
ServiceName: aws.String("service2"),
},
},
}, nil
}
+96
View File
@@ -0,0 +1,96 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/athena"
athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types"
"github.com/patrickmn/go-cache"
)
type AWSAthenaClientInterface interface {
ListDataCatalogs(context.Context, *athena.ListDataCatalogsInput, ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error)
ListDatabases(context.Context, *athena.ListDatabasesInput, ...func(*athena.Options)) (*athena.ListDatabasesOutput, error)
}
func init() {
gob.Register([]athenaTypes.DataCatalogSummary{})
}
func CachedAthenaListDataCatalogs(client AWSAthenaClientInterface, accountID string, region string) ([]athenaTypes.DataCatalogSummary, error) {
var PaginationControl *string
var dataCatalogs []athenaTypes.DataCatalogSummary
cacheKey := fmt.Sprintf("%s-athena-ListDataCatalogs-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]athenaTypes.DataCatalogSummary), nil
}
for {
ListDataCatalogs, err := client.ListDataCatalogs(
context.TODO(),
&athena.ListDataCatalogsInput{
NextToken: PaginationControl,
},
func(o *athena.Options) {
o.Region = region
},
)
if err != nil {
return dataCatalogs, err
}
dataCatalogs = append(dataCatalogs, ListDataCatalogs.DataCatalogsSummary...)
//pagination
if ListDataCatalogs.NextToken == nil {
break
}
PaginationControl = ListDataCatalogs.NextToken
}
internal.Cache.Set(cacheKey, dataCatalogs, cache.DefaultExpiration)
return dataCatalogs, nil
}
func CachedAthenaListDatabases(client AWSAthenaClientInterface, accountID string, region string, catalogName string) ([]string, error) {
var PaginationControl *string
var databases []string
cacheKey := fmt.Sprintf("%s-athena-ListDatabases-%s-%s", accountID, region, catalogName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListDatabases, err := client.ListDatabases(
context.TODO(),
&athena.ListDatabasesInput{
CatalogName: &catalogName,
NextToken: PaginationControl,
},
func(o *athena.Options) {
o.Region = region
},
)
if err != nil {
return databases, err
}
for _, database := range ListDatabases.DatabaseList {
databases = append(databases, *database.Name)
}
//pagination
if ListDatabases.NextToken == nil {
break
}
PaginationControl = ListDatabases.NextToken
}
internal.Cache.Set(cacheKey, databases, cache.DefaultExpiration)
return databases, nil
}
+38
View File
@@ -0,0 +1,38 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/athena"
athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types"
)
type MockedAWSAthenaClient struct {
}
func (m *MockedAWSAthenaClient) ListDatabases(ctx context.Context, input *athena.ListDatabasesInput, options ...func(*athena.Options)) (*athena.ListDatabasesOutput, error) {
return &athena.ListDatabasesOutput{
DatabaseList: []athenaTypes.Database{
{
Name: aws.String("db1"),
},
{
Name: aws.String("db2"),
},
},
}, nil
}
func (m *MockedAWSAthenaClient) ListDataCatalogs(ctx context.Context, input *athena.ListDataCatalogsInput, options ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error) {
return &athena.ListDataCatalogsOutput{
DataCatalogsSummary: []athenaTypes.DataCatalogSummary{
{
CatalogName: aws.String("catalog1"),
},
{
CatalogName: aws.String("catalog2"),
},
},
}, nil
}
+86
View File
@@ -0,0 +1,86 @@
package sdk
import (
"context"
"encoding/gob"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/cloud9"
cloud9Types "github.com/aws/aws-sdk-go-v2/service/cloud9/types"
"github.com/patrickmn/go-cache"
)
type AWSCloud9ClientInterface interface {
ListEnvironments(context.Context, *cloud9.ListEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error)
DescribeEnvironments(context.Context, *cloud9.DescribeEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error)
}
func init() {
gob.Register([]string{})
}
func CachedCloud9ListEnvironments(client AWSCloud9ClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var environments []string
cacheKey := "cloud9-ListEnvironments-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListEnvironments, err := client.ListEnvironments(
context.TODO(),
&cloud9.ListEnvironmentsInput{
NextToken: PaginationControl,
},
func(o *cloud9.Options) {
o.Region = region
},
)
if err != nil {
return environments, err
}
environments = append(environments, ListEnvironments.EnvironmentIds...)
//pagination
if ListEnvironments.NextToken == nil {
break
}
PaginationControl = ListEnvironments.NextToken
}
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
return environments, nil
}
func CachedCloud9DescribeEnvironments(client AWSCloud9ClientInterface, accountID string, region string, environmentIDs []string) ([]cloud9Types.Environment, error) {
var environments []cloud9Types.Environment
cacheKey := "cloud9-DescribeEnvironments-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloud9Types.Environment), nil
}
for _, environmentID := range environmentIDs {
DescribeEnvironments, err := client.DescribeEnvironments(
context.TODO(),
&cloud9.DescribeEnvironmentsInput{
EnvironmentIds: []string{environmentID},
},
func(o *cloud9.Options) {
o.Region = region
},
)
if err != nil {
return environments, err
}
environments = append(environments, DescribeEnvironments.Environments...)
}
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
return environments, nil
}
+36
View File
@@ -0,0 +1,36 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloud9"
"github.com/aws/aws-sdk-go-v2/service/cloud9/types"
)
type MockedAWSCloud9Client struct {
}
func (m *MockedAWSCloud9Client) ListEnvironments(ctx context.Context, input *cloud9.ListEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error) {
return &cloud9.ListEnvironmentsOutput{
EnvironmentIds: []string{
"env1",
"env2",
},
}, nil
}
func (m *MockedAWSCloud9Client) DescribeEnvironments(ctx context.Context, input *cloud9.DescribeEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error) {
return &cloud9.DescribeEnvironmentsOutput{
Environments: []types.Environment{
{
Name: aws.String("env1"),
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env1"),
},
{
Name: aws.String("env2"),
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env2"),
},
},
}, nil
}
+1 -1
View File
@@ -18,7 +18,7 @@ type CloudFormationClientInterface interface {
ListStacks(context.Context, *cloudformation.ListStacksInput, ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error)
}
func RegisterCloudFormationTypes() {
func init() {
gob.Register([]cloudFormationTypes.Stack{})
gob.Register([]cloudFormationTypes.StackSummary{})
}
+73
View File
@@ -0,0 +1,73 @@
package sdk
import (
"context"
"encoding/json"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
)
const DESCRIBE_STACKS_TEST_FILE = "./test-data/cloudformation-describestacks.json"
const TEMPLATE_BODY_TEST_FILE = "./test-data/cloudformation-getTemplate.json"
type Stacks struct {
Stacks []struct {
StackID string `json:"StackId"`
Description string `json:"Description"`
Tags []interface{} `json:"Tags"`
Outputs []types.Output `json:"Outputs"`
Parameters []types.Parameter `json:"Parameters"`
StackStatusReason interface{} `json:"StackStatusReason"`
CreationTime time.Time `json:"CreationTime"`
Capabilities []interface{} `json:"Capabilities"`
StackName string `json:"StackName"`
RoleArn string `json:"RoleArn"`
StackStatus string `json:"StackStatus"`
DisableRollback bool `json:"DisableRollback"`
} `json:"Stacks"`
}
type TemplateBody struct {
TemplateBody string `json:"TemplateBody"`
}
type MockedCloudformationClient struct {
describeStacks Stacks
getTemplateBody TemplateBody
}
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 {
log.Fatalf("can't unmarshall file %s", DESCRIBE_STACKS_TEST_FILE)
}
var stacks []types.Stack
for _, stack := range m.describeStacks.Stacks {
stacks = append(stacks, types.Stack{
StackName: &stack.StackName,
RoleARN: &stack.RoleArn,
Outputs: stack.Outputs,
Parameters: stack.Parameters,
})
}
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
}
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)
}
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
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/patrickmn/go-cache"
)
type AWSCloudFrontClientInterface interface {
ListDistributions(ctx context.Context, params *cloudfront.ListDistributionsInput, optFns ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error)
}
func init() {
gob.Register([]cloudfrontTypes.DistributionSummary{})
gob.Register(cloudfrontTypes.DistributionSummary{})
}
func CachedCloudFrontListDistributions(CloudFrontClient AWSCloudFrontClientInterface, accountID string) ([]cloudfrontTypes.DistributionSummary, error) {
var PaginationControl *string
var distributions []cloudfrontTypes.DistributionSummary
cacheKey := "cloudfront-ListDistributions-" + accountID
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached CloudFront distributions data")
return cached.([]cloudfrontTypes.DistributionSummary), nil
}
for {
ListDistributions, err := CloudFrontClient.ListDistributions(
context.TODO(),
&cloudfront.ListDistributionsInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
distributions = append(distributions, ListDistributions.DistributionList.Items...)
// Pagination control.
if ListDistributions.DistributionList.NextMarker != nil {
PaginationControl = ListDistributions.DistributionList.NextMarker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, distributions, cache.DefaultExpiration)
return distributions, nil
}
+27
View File
@@ -0,0 +1,27 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
)
type MockedAWSCloudFrontClient struct {
}
func (m *MockedAWSCloudFrontClient) ListDistributions(ctx context.Context, input *cloudfront.ListDistributionsInput, options ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error) {
return &cloudfront.ListDistributionsOutput{
DistributionList: &cloudfrontTypes.DistributionList{
Items: []cloudfrontTypes.DistributionSummary{
{
Id: aws.String("distribution1"),
},
{
Id: aws.String("distribution2"),
},
},
},
}, nil
}
+94
View File
@@ -0,0 +1,94 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/codeartifact"
codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types"
"github.com/patrickmn/go-cache"
)
type AWSCodeArtifactClientInterface interface {
ListDomains(context.Context, *codeartifact.ListDomainsInput, ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error)
ListRepositories(context.Context, *codeartifact.ListRepositoriesInput, ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error)
}
func init() {
gob.Register([]codeArtifactTypes.DomainSummary{})
gob.Register([]codeArtifactTypes.RepositorySummary{})
}
func CachedCodeArtifactListDomains(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.DomainSummary, error) {
var PaginationControl *string
var domains []codeArtifactTypes.DomainSummary
cacheKey := fmt.Sprintf("%s-codeartifact-ListDomains-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]codeArtifactTypes.DomainSummary), nil
}
for {
ListDomains, err := client.ListDomains(
context.TODO(),
&codeartifact.ListDomainsInput{
NextToken: PaginationControl,
},
func(o *codeartifact.Options) {
o.Region = region
},
)
if err != nil {
return domains, err
}
domains = append(domains, ListDomains.Domains...)
//pagination
if ListDomains.NextToken == nil {
break
}
PaginationControl = ListDomains.NextToken
}
internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration)
return domains, nil
}
func CachedCodeArtifactListRepositories(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.RepositorySummary, error) {
var PaginationControl *string
var repositories []codeArtifactTypes.RepositorySummary
cacheKey := fmt.Sprintf("%s-codeartifact-ListRepositories-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]codeArtifactTypes.RepositorySummary), nil
}
for {
ListRepositories, err := client.ListRepositories(
context.TODO(),
&codeartifact.ListRepositoriesInput{
NextToken: PaginationControl,
},
func(o *codeartifact.Options) {
o.Region = region
},
)
if err != nil {
return repositories, err
}
repositories = append(repositories, ListRepositories.Repositories...)
//pagination
if ListRepositories.NextToken == nil {
break
}
PaginationControl = ListRepositories.NextToken
}
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
return repositories, nil
}
+46
View File
@@ -0,0 +1,46 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codeartifact"
codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types"
)
type MockedAWSCodeArtifactClient struct {
}
func (m *MockedAWSCodeArtifactClient) ListRepositories(ctx context.Context, input *codeartifact.ListRepositoriesInput, options ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error) {
return &codeartifact.ListRepositoriesOutput{
NextToken: nil,
Repositories: []codeArtifactTypes.RepositorySummary{
{
Name: aws.String("repo1"),
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo1"),
DomainName: aws.String("domain1"),
},
{
Name: aws.String("repo2"),
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo2"),
DomainName: aws.String("domain1"),
},
},
}, nil
}
func (m *MockedAWSCodeArtifactClient) ListDomains(ctx context.Context, input *codeartifact.ListDomainsInput, options ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error) {
return &codeartifact.ListDomainsOutput{
NextToken: nil,
Domains: []codeArtifactTypes.DomainSummary{
{
Name: aws.String("domain1"),
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain1"),
},
{
Name: aws.String("domain2"),
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain2"),
},
},
}, nil
}
+1 -1
View File
@@ -18,7 +18,7 @@ type CodeBuildClientInterface interface {
GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error)
}
func RegisterCodeBuildTypes() {
func init() {
gob.Register(codeBuildTypes.Project{})
}
+39
View File
@@ -0,0 +1,39 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
)
type MockedCodeBuildClient struct {
}
func (m *MockedCodeBuildClient) ListProjects(ctx context.Context, input *codebuild.ListProjectsInput, options ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) {
return &codebuild.ListProjectsOutput{
Projects: []string{
"project1",
"project2",
},
}, nil
}
func (m *MockedCodeBuildClient) GetResourcePolicy(ctx context.Context, input *codebuild.GetResourcePolicyInput, options ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
return &codebuild.GetResourcePolicyOutput{
Policy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "codebuild:BatchGetProjects",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
}
]
}
`),
}, nil
}
+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/codecommit"
codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
"github.com/patrickmn/go-cache"
)
type AWSCodeCommitClientInterface interface {
ListRepositories(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error)
}
func init() {
gob.Register([]codeCommitTypes.RepositoryNameIdPair{})
}
func CachedCodeCommitListRepositories(client AWSCodeCommitClientInterface, accountID string, region string) ([]codeCommitTypes.RepositoryNameIdPair, error) {
var PaginationControl *string
var repositories []codeCommitTypes.RepositoryNameIdPair
cacheKey := fmt.Sprintf("%s-codecommit-ListRepositories-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]codeCommitTypes.RepositoryNameIdPair), nil
}
for {
ListRepositories, err := client.ListRepositories(
context.TODO(),
&codecommit.ListRepositoriesInput{
NextToken: PaginationControl,
},
func(o *codecommit.Options) {
o.Region = region
},
)
if err != nil {
return repositories, err
}
repositories = append(repositories, ListRepositories.Repositories...)
//pagination
if ListRepositories.NextToken == nil {
break
}
PaginationControl = ListRepositories.NextToken
}
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
return repositories, nil
}
+30
View File
@@ -0,0 +1,30 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codecommit"
codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
)
// create mocks for codecommit ListRepositories
type MockedAWSCodeCommitClient struct {
}
func (m *MockedAWSCodeCommitClient) ListRepositories(ctx context.Context, input *codecommit.ListRepositoriesInput, options ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error) {
return &codecommit.ListRepositoriesOutput{
NextToken: nil,
Repositories: []codeCommitTypes.RepositoryNameIdPair{
{
RepositoryId: aws.String("repo1"),
RepositoryName: aws.String("repo1"),
},
{
RepositoryId: aws.String("repo2"),
RepositoryName: aws.String("repo2"),
},
},
}, nil
}
+212
View File
@@ -0,0 +1,212 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/codedeploy"
codeDeployTypes "github.com/aws/aws-sdk-go-v2/service/codedeploy/types"
"github.com/patrickmn/go-cache"
)
type AWSCodeDeployClientInterface interface {
ListApplications(context.Context, *codedeploy.ListApplicationsInput, ...func(*codedeploy.Options)) (*codedeploy.ListApplicationsOutput, error)
GetApplication(context.Context, *codedeploy.GetApplicationInput, ...func(*codedeploy.Options)) (*codedeploy.GetApplicationOutput, error)
ListDeployments(context.Context, *codedeploy.ListDeploymentsInput, ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentsOutput, error)
GetDeployment(context.Context, *codedeploy.GetDeploymentInput, ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentOutput, error)
ListDeploymentConfigs(context.Context, *codedeploy.ListDeploymentConfigsInput, ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentConfigsOutput, error)
GetDeploymentConfig(context.Context, *codedeploy.GetDeploymentConfigInput, ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentConfigOutput, error)
}
func init() {
gob.Register([]string{})
gob.Register([]codeDeployTypes.ApplicationInfo{})
gob.Register([]codeDeployTypes.DeploymentConfigInfo{})
gob.Register([]codeDeployTypes.DeploymentInfo{})
}
func CachedCodeDeployListApplications(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var applications []string
cacheKey := fmt.Sprintf("%s-codedeploy-ListApplications-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListApplications, err := client.ListApplications(
context.TODO(),
&codedeploy.ListApplicationsInput{
NextToken: PaginationControl,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return applications, err
}
applications = append(applications, ListApplications.Applications...)
//pagination
if ListApplications.NextToken == nil {
break
}
PaginationControl = ListApplications.NextToken
}
internal.Cache.Set(cacheKey, applications, cache.DefaultExpiration)
return applications, nil
}
func CachedCodeDeployListDeploymentConfigs(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var deploymentConfigs []string
cacheKey := fmt.Sprintf("%s-codedeploy-ListDeploymentConfigs-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListDeploymentConfigs, err := client.ListDeploymentConfigs(
context.TODO(),
&codedeploy.ListDeploymentConfigsInput{
NextToken: PaginationControl,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return deploymentConfigs, err
}
deploymentConfigs = append(deploymentConfigs, ListDeploymentConfigs.DeploymentConfigsList...)
//pagination
if ListDeploymentConfigs.NextToken == nil {
break
}
PaginationControl = ListDeploymentConfigs.NextToken
}
internal.Cache.Set(cacheKey, deploymentConfigs, cache.DefaultExpiration)
return deploymentConfigs, nil
}
func CachedCodeDeployListDeployments(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var deployments []string
cacheKey := fmt.Sprintf("%s-codedeploy-ListDeployments-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListDeployments, err := client.ListDeployments(
context.TODO(),
&codedeploy.ListDeploymentsInput{
NextToken: PaginationControl,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return deployments, err
}
deployments = append(deployments, ListDeployments.Deployments...)
//pagination
if ListDeployments.NextToken == nil {
break
}
PaginationControl = ListDeployments.NextToken
}
internal.Cache.Set(cacheKey, deployments, cache.DefaultExpiration)
return deployments, nil
}
func CachedCodeDeployGetApplication(client AWSCodeDeployClientInterface, accountID string, region string, applicationName string) (codeDeployTypes.ApplicationInfo, error) {
cacheKey := fmt.Sprintf("%s-codedeploy-GetApplication-%s-%s", accountID, region, applicationName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeDeployTypes.ApplicationInfo), nil
}
GetApplication, err := client.GetApplication(
context.TODO(),
&codedeploy.GetApplicationInput{
ApplicationName: &applicationName,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return *GetApplication.Application, err
}
internal.Cache.Set(cacheKey, *GetApplication.Application, cache.DefaultExpiration)
return *GetApplication.Application, nil
}
func CachedCodeDeployGetDeploymentConfig(client AWSCodeDeployClientInterface, accountID string, region string, deploymentConfigName string) (codeDeployTypes.DeploymentConfigInfo, error) {
cacheKey := fmt.Sprintf("%s-codedeploy-GetDeploymentConfig-%s-%s", accountID, region, deploymentConfigName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeDeployTypes.DeploymentConfigInfo), nil
}
GetDeploymentConfig, err := client.GetDeploymentConfig(
context.TODO(),
&codedeploy.GetDeploymentConfigInput{
DeploymentConfigName: &deploymentConfigName,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return *GetDeploymentConfig.DeploymentConfigInfo, err
}
internal.Cache.Set(cacheKey, *GetDeploymentConfig.DeploymentConfigInfo, cache.DefaultExpiration)
return *GetDeploymentConfig.DeploymentConfigInfo, nil
}
func CachedCodeDeployGetDeployment(client AWSCodeDeployClientInterface, accountID string, region string, deploymentID string) (codeDeployTypes.DeploymentInfo, error) {
cacheKey := fmt.Sprintf("%s-codedeploy-GetDeployment-%s-%s", accountID, region, deploymentID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeDeployTypes.DeploymentInfo), nil
}
GetDeployment, err := client.GetDeployment(
context.TODO(),
&codedeploy.GetDeploymentInput{
DeploymentId: &deploymentID,
},
func(o *codedeploy.Options) {
o.Region = region
},
)
if err != nil {
return *GetDeployment.DeploymentInfo, err
}
internal.Cache.Set(cacheKey, *GetDeployment.DeploymentInfo, cache.DefaultExpiration)
return *GetDeployment.DeploymentInfo, nil
}
+85
View File
@@ -0,0 +1,85 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codedeploy"
codedeployTypes "github.com/aws/aws-sdk-go-v2/service/codedeploy/types"
)
type MockedCodedeployClient struct {
}
func (m *MockedCodedeployClient) ListApplications(ctx context.Context, input *codedeploy.ListApplicationsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListApplicationsOutput, error) {
return &codedeploy.ListApplicationsOutput{
Applications: []string{
"app1",
"app2",
},
}, nil
}
func (m *MockedCodedeployClient) ListDeployments(ctx context.Context, input *codedeploy.ListDeploymentsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentsOutput, error) {
return &codedeploy.ListDeploymentsOutput{
Deployments: []string{
"deployment1",
"deployment2",
},
}, nil
}
func (m *MockedCodedeployClient) ListDeploymentConfigs(ctx context.Context, input *codedeploy.ListDeploymentConfigsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentConfigsOutput, error) {
return &codedeploy.ListDeploymentConfigsOutput{
DeploymentConfigsList: []string{
"deploymentConfig1",
"deploymentConfig2",
},
}, nil
}
func (m *MockedCodedeployClient) GetApplication(ctx context.Context, input *codedeploy.GetApplicationInput, options ...func(*codedeploy.Options)) (*codedeploy.GetApplicationOutput, error) {
return &codedeploy.GetApplicationOutput{
Application: &codedeployTypes.ApplicationInfo{
ApplicationName: aws.String("application1"),
ApplicationId: aws.String("application1"),
CreateTime: aws.Time(time.Now()),
GitHubAccountName: aws.String("github"),
LinkedToGitHub: true,
},
}, nil
}
func (m *MockedCodedeployClient) GetDeployment(ctx context.Context, input *codedeploy.GetDeploymentInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentOutput, error) {
return &codedeploy.GetDeploymentOutput{
DeploymentInfo: &codedeployTypes.DeploymentInfo{
ApplicationName: aws.String("application1"),
CreateTime: aws.Time(time.Now()),
DeploymentId: aws.String("deployment1"),
DeploymentOverview: &codedeployTypes.DeploymentOverview{
Failed: 0,
InProgress: 0,
Pending: 0,
Ready: 0,
Skipped: 0,
Succeeded: 0,
},
DeploymentConfigName: aws.String("deploymentConfig1"),
Status: codedeployTypes.DeploymentStatusSucceeded,
},
}, nil
}
func (m *MockedCodedeployClient) GetDeploymentConfig(ctx context.Context, input *codedeploy.GetDeploymentConfigInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentConfigOutput, error) {
return &codedeploy.GetDeploymentConfigOutput{
DeploymentConfigInfo: &codedeployTypes.DeploymentConfigInfo{
DeploymentConfigName: aws.String("deploymentConfig1"),
ComputePlatform: codedeployTypes.ComputePlatformServer,
MinimumHealthyHosts: &codedeployTypes.MinimumHealthyHosts{
Type: codedeployTypes.MinimumHealthyHostsTypeFleetPercent,
Value: 100,
},
},
}, 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/datapipeline"
dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types"
"github.com/patrickmn/go-cache"
)
type AWSDataPipelineClientInterface interface {
ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, opts ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error)
}
func init() {
gob.Register([]dataPipelineTypes.PipelineIdName{})
}
func CachedDataPipelineListPipelines(client AWSDataPipelineClientInterface, accountID string, region string) ([]dataPipelineTypes.PipelineIdName, error) {
var PaginationControl *string
var pipelines []dataPipelineTypes.PipelineIdName
cacheKey := fmt.Sprintf("%s-datapipeline-ListPipelines-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]dataPipelineTypes.PipelineIdName), nil
}
for {
ListPipelines, err := client.ListPipelines(
context.TODO(),
&datapipeline.ListPipelinesInput{
Marker: PaginationControl,
},
func(o *datapipeline.Options) {
o.Region = region
},
)
if err != nil {
return pipelines, err
}
pipelines = append(pipelines, ListPipelines.PipelineIdList...)
//pagination
if ListPipelines.Marker == nil {
break
}
PaginationControl = ListPipelines.Marker
}
internal.Cache.Set(cacheKey, pipelines, cache.DefaultExpiration)
return pipelines, nil
}
+27
View File
@@ -0,0 +1,27 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/datapipeline"
dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types"
)
type MockedDataPipelineClient struct {
}
func (m *MockedDataPipelineClient) ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, options ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error) {
return &datapipeline.ListPipelinesOutput{
PipelineIdList: []dataPipelineTypes.PipelineIdName{
{
Id: aws.String("pipeline1"),
Name: aws.String("pipeline1"),
},
{
Id: aws.String("pipeline2"),
Name: aws.String("pipeline2"),
},
},
}, nil
}
+1 -1
View File
@@ -19,7 +19,7 @@ type DocDBClientInterface interface {
DescribeDBInstances(context.Context, *docdb.DescribeDBInstancesInput, ...func(*docdb.Options)) (*docdb.DescribeDBInstancesOutput, error)
}
func RegisterDocDBTypes() {
func init() {
gob.Register([]docdbTypes.GlobalCluster{})
gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBInstance{})
+25
View File
@@ -0,0 +1,25 @@
package sdk
import (
"context"
"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"
)
type MockedAWSDocsDBClient struct {
}
func (m *MockedAWSDocsDBClient) DescribeDBClusters(ctx context.Context, input *docdb.DescribeDBClustersInput, options ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error) {
return &docdb.DescribeDBClustersOutput{
DBClusters: []docdbTypes.DBCluster{
{
DBClusterIdentifier: aws.String("cluster1"),
},
{
DBClusterIdentifier: aws.String("cluster2"),
},
},
}, nil
}
+1 -1
View File
@@ -16,7 +16,7 @@ type DynamoDBClientInterface interface {
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
}
func RegisterDynamoDBTypes() {
func init() {
gob.Register([]string{})
gob.Register(dynamoDBTypes.TableDescription{})
}
+23
View File
@@ -0,0 +1,23 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamodbTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
type MockedAWSDynamoDBClient struct {
}
func (m *MockedAWSDynamoDBClient) ListTables() ([]string, error) {
return []string{"table1", "table2"}, nil
}
func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, options ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) {
return &dynamodb.DescribeTableOutput{
Table: &dynamodbTypes.TableDescription{
TableName: input.TableName,
},
}, nil
}
+173 -3
View File
@@ -6,20 +6,31 @@ import (
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/patrickmn/go-cache"
)
type EC2ClientInterface interface {
type AWSEC2ClientInterface interface {
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
DescribeNetworkInterfaces(context.Context, *ec2.DescribeNetworkInterfacesInput, ...func(*ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error)
DescribeSnapshots(context.Context, *ec2.DescribeSnapshotsInput, ...func(*ec2.Options)) (*ec2.DescribeSnapshotsOutput, error)
DescribeVolumes(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error)
DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error)
DescribeInstanceAttribute(context.Context, *ec2.DescribeInstanceAttributeInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error)
}
func RegisterEC2Types() {
func init() {
gob.Register([]ec2Types.Instance{})
gob.Register([]ec2Types.NetworkInterface{})
gob.Register([]ec2Types.Snapshot{})
gob.Register([]ec2Types.Volume{})
gob.Register([]ec2Types.Image{})
}
func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
func CachedEC2DescribeInstances(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
var PaginationControl *string
var instances []ec2Types.Instance
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstances-%s", accountID, region)
@@ -56,3 +67,162 @@ func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, reg
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
func CachedEC2DescribeInstanceAttributeUserData(client AWSEC2ClientInterface, accountID string, region string, instanceID string) (string, error) {
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstanceAttributeUserData-%s-%s", accountID, region, instanceID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
DescribeInstanceAttribute, err := client.DescribeInstanceAttribute(
context.TODO(),
&ec2.DescribeInstanceAttributeInput{
Attribute: ec2Types.InstanceAttributeNameUserData,
InstanceId: &instanceID,
},
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return "", err
}
internal.Cache.Set(cacheKey, aws.ToString(DescribeInstanceAttribute.UserData.Value), cache.DefaultExpiration)
return aws.ToString(DescribeInstanceAttribute.UserData.Value), nil
}
func CachedEC2DescribeNetworkInterfaces(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.NetworkInterface, error) {
var PaginationControl *string
var NetworkInterfaces []ec2Types.NetworkInterface
cacheKey := fmt.Sprintf("%s-ec2-DescribeNetworkInterfaces-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.NetworkInterface), nil
}
for {
DescribeNetworkInterfaces, err := client.DescribeNetworkInterfaces(
context.TODO(),
&(ec2.DescribeNetworkInterfacesInput{
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return NetworkInterfaces, err
}
NetworkInterfaces = append(NetworkInterfaces, DescribeNetworkInterfaces.NetworkInterfaces...)
if DescribeNetworkInterfaces.NextToken == nil {
break
}
PaginationControl = DescribeNetworkInterfaces.NextToken
}
internal.Cache.Set(cacheKey, NetworkInterfaces, cache.DefaultExpiration)
return NetworkInterfaces, nil
}
func CachedEC2DescribeSnapshots(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Snapshot, error) {
var PaginationControl *string
var Snapshots []ec2Types.Snapshot
cacheKey := fmt.Sprintf("%s-ec2-DescribeSnapshots-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Snapshot), nil
}
for {
DescribeSnapshots, err := client.DescribeSnapshots(
context.TODO(),
&(ec2.DescribeSnapshotsInput{
NextToken: PaginationControl,
OwnerIds: []string{accountID},
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return Snapshots, err
}
Snapshots = append(Snapshots, DescribeSnapshots.Snapshots...)
if DescribeSnapshots.NextToken == nil {
break
}
PaginationControl = DescribeSnapshots.NextToken
}
internal.Cache.Set(cacheKey, Snapshots, cache.DefaultExpiration)
return Snapshots, nil
}
func CachedEC2DescribeVolumes(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Volume, error) {
var PaginationControl *string
var Volumes []ec2Types.Volume
cacheKey := fmt.Sprintf("%s-ec2-DescribeVolumes-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Volume), nil
}
for {
DescribeVolumes, err := client.DescribeVolumes(
context.TODO(),
&(ec2.DescribeVolumesInput{
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return Volumes, err
}
Volumes = append(Volumes, DescribeVolumes.Volumes...)
if DescribeVolumes.NextToken == nil {
break
}
PaginationControl = DescribeVolumes.NextToken
}
internal.Cache.Set(cacheKey, Volumes, cache.DefaultExpiration)
return Volumes, nil
}
func CachedEC2DescribeImages(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Image, error) {
var PaginationControl *string
var Images []ec2Types.Image
cacheKey := fmt.Sprintf("%s-ec2-DescribeImages-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Image), nil
}
for {
DescribeImages, err := client.DescribeImages(
context.TODO(),
&(ec2.DescribeImagesInput{
Owners: []string{accountID},
NextToken: PaginationControl,
}),
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return Images, err
}
Images = append(Images, DescribeImages.Images...)
if DescribeImages.NextToken == nil {
break
}
PaginationControl = DescribeImages.NextToken
}
internal.Cache.Set(cacheKey, Images, cache.DefaultExpiration)
return Images, nil
}
+388
View File
@@ -0,0 +1,388 @@
package sdk
// create test for the Cached EC2 functions. Create a mocked client and mocked functions for each of the methods used in the ec2.go file
import (
"context"
"encoding/json"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
const DESCRIBE_NETWORK_INTEFACES_TEST_FILE = "./test-data/describe-network-interfaces.json"
const DESCRIBE_INSTANCES_TEST_FILE = "./test-data/ec2-describeInstances.json"
const DESCRIBE_SNAPSHOTS_TEST_FILE = "./test-data/ec2-describeSnapshots.json"
const DESCRIBE_VOLUMES_TEST_FILE = "./test-data/ec2-describeVolumes.json"
const DESCRIBE_IMAGES_TEST_FILE = "./test-data/ec2-describeImages.json"
type MockedEC2Client2 struct {
describeNetworkInterfaces DescribeNetworkInterfaces
describeInstances DescribeInstances
describeSnapshots DescribeSnapshots
describeVolumes DescribeVolumes
describeImages DescribeImages
}
type DescribeNetworkInterfaces struct {
NetworkInterfaces []struct {
Status string `json:"Status"`
MacAddress string `json:"MacAddress"`
SourceDestCheck bool `json:"SourceDestCheck"`
VpcID string `json:"VpcId"`
Description string `json:"Description"`
Association struct {
PublicIP string `json:"PublicIp"`
AssociationID string `json:"AssociationId"`
PublicDNSName string `json:"PublicDnsName"`
IPOwnerID string `json:"IpOwnerId"`
} `json:"Association"`
NetworkInterfaceID string `json:"NetworkInterfaceId"`
PrivateIPAddresses []struct {
PrivateDNSName string `json:"PrivateDnsName"`
Association struct {
PublicIP string `json:"PublicIp"`
AssociationID string `json:"AssociationId"`
PublicDNSName string `json:"PublicDnsName"`
IPOwnerID string `json:"IpOwnerId"`
} `json:"Association"`
Primary bool `json:"Primary"`
PrivateIPAddress string `json:"PrivateIpAddress"`
} `json:"PrivateIpAddresses"`
RequesterManaged bool `json:"RequesterManaged"`
Ipv6Addresses []interface{} `json:"Ipv6Addresses"`
PrivateDNSName string `json:"PrivateDnsName,omitempty"`
AvailabilityZone string `json:"AvailabilityZone"`
Attachment struct {
Status string `json:"Status"`
DeviceIndex int `json:"DeviceIndex"`
AttachTime time.Time `json:"AttachTime"`
InstanceID string `json:"InstanceId"`
DeleteOnTermination bool `json:"DeleteOnTermination"`
AttachmentID string `json:"AttachmentId"`
InstanceOwnerID string `json:"InstanceOwnerId"`
} `json:"Attachment"`
Groups []struct {
GroupName string `json:"GroupName"`
GroupID string `json:"GroupId"`
} `json:"Groups"`
SubnetID string `json:"SubnetId"`
OwnerID string `json:"OwnerId"`
TagSet []interface{} `json:"TagSet"`
PrivateIPAddress string `json:"PrivateIpAddress"`
} `json:"NetworkInterfaces"`
}
type DescribeInstances struct {
Reservations []struct {
Instances []struct {
AmiLaunchIndex int `json:"AmiLaunchIndex"`
ImageID string `json:"ImageId"`
InstanceID string `json:"InstanceId"`
InstanceType string `json:"InstanceType"`
KernelID string `json:"KernelId"`
KeyName string `json:"KeyName"`
LaunchTime string `json:"LaunchTime"`
Monitoring struct {
State string `json:"State"`
} `json:"Monitoring"`
Placement struct {
AvailabilityZone string `json:"AvailabilityZone"`
GroupName string `json:"GroupName"`
Tenancy string `json:"Tenancy"`
} `json:"Placement"`
Platform string `json:"Platform"`
PrivateDNS string `json:"PrivateDnsName"`
PrivateIP string `json:"PrivateIpAddress"`
PublicDNS string `json:"PublicDnsName"`
PublicIP string `json:"PublicIpAddress"`
State string `json:"State"`
SubnetID string `json:"SubnetId"`
VpcID string `json:"VpcId"`
Architecture string `json:"Architecture"`
BlockDeviceMappings []struct {
DeviceName string `json:"DeviceName"`
Ebs struct {
AttachTime time.Time `json:"AttachTime"`
DeleteOnTermination bool `json:"DeleteOnTermination"`
Status string `json:"Status"`
VolumeID string `json:"VolumeId"`
} `json:"Ebs"`
} `json:"BlockDeviceMappings"`
ClientToken string `json:"ClientToken"`
EbsOptimized bool `json:"EbsOptimized"`
Hypervisor string `json:"Hypervisor"`
IamInstanceProfile struct {
Arn string `json:"Arn"`
ID string `json:"Id"`
} `json:"IamInstanceProfile"`
NetworkInterfaces []struct {
Attachment struct {
AttachTime time.Time `json:"AttachTime"`
AttachmentID string `json:"AttachmentId"`
DeleteOnTermination bool `json:"DeleteOnTermination"`
DeviceIndex int `json:"DeviceIndex"`
Status string `json:"Status"`
} `json:"Attachment"`
Description string `json:"Description"`
Groups []struct {
GroupName string `json:"GroupName"`
GroupID string `json:"GroupId"`
} `json:"Groups"`
MacAddress string `json:"MacAddress"`
NetworkInterfaceID string `json:"NetworkInterfaceId"`
OwnerID string `json:"OwnerId"`
PrivateDNSName string `json:"PrivateDnsName"`
PrivateIPAddress string `json:"PrivateIpAddress"`
PrivateIPAddresses []struct {
Association struct {
IPOwnerID string `json:"IpOwnerId"`
PublicDNSName string `json:"PublicDnsName"`
PublicIP string `json:"PublicIp"`
} `json:"Association"`
Primary bool `json:"Primary"`
PrivateDNSName string `json:"PrivateDnsName"`
PrivateIPAddress string `json:"PrivateIpAddress"`
} `json:"PrivateIpAddresses"`
SourceDestCheck bool `json:"SourceDestCheck"`
Status string `json:"Status"`
SubnetID string `json:"SubnetId"`
VpcID string `json:"VpcId"`
} `json:"NetworkInterfaces"`
RootDeviceName string `json:"RootDeviceName"`
RootDeviceType string `json:"RootDeviceType"`
SecurityGroups []struct {
GroupName string `json:"GroupName"`
GroupID string `json:"GroupId"`
} `json:"SecurityGroups"`
SourceDestCheck bool `json:"SourceDestCheck"`
StateReason struct {
Code string `json:"Code"`
Message string `json:"Message"`
} `json:"StateReason"`
Tags []struct {
Key string `json:"Key"`
Value string `json:"Value"`
} `json:"Tags"`
VirtualizationType string `json:"VirtualizationType"`
} `json:"Instances"`
OwnerID string `json:"OwnerId"`
RequesterID string `json:"RequesterId"`
ReservationID string `json:"ReservationId"`
} `json:"Reservations"`
}
type DescribeImages struct {
Images []struct {
Architecture string `json:"Architecture"`
BlockDeviceMappings []struct {
DeviceName string `json:"DeviceName"`
Ebs struct {
DeleteOnTermination bool `json:"DeleteOnTermination"`
SnapshotID string `json:"SnapshotId"`
VolumeSize int `json:"VolumeSize"`
VolumeType string `json:"VolumeType"`
} `json:"Ebs"`
} `json:"BlockDeviceMappings"`
CreationDate string `json:"CreationDate"`
Description string `json:"Description"`
EnaSupport bool `json:"EnaSupport"`
Hypervisor string `json:"Hypervisor"`
ImageID string `json:"ImageId"`
ImageLocation string `json:"ImageLocation"`
ImageType string `json:"ImageType"`
KernelID string `json:"KernelId"`
Name string `json:"Name"`
OwnerAlias string `json:"OwnerAlias"`
OwnerID string `json:"OwnerId"`
Platform string `json:"Platform"`
ProductCodes []struct {
ProductCodeID string `json:"ProductCodeId"`
ProductCodeType string `json:"ProductCodeType"`
} `json:"ProductCodes"`
Public bool `json:"Public"`
RamdiskID string `json:"RamdiskId"`
RootDeviceName string `json:"RootDeviceName"`
RootDeviceType string `json:"RootDeviceType"`
SriovNetSupport string `json:"SriovNetSupport"`
State string `json:"State"`
StateReason struct {
Code string `json:"Code"`
Message string `json:"Message"`
} `json:"StateReason"`
Tags []struct {
Key string `json:"Key"`
Value string `json:"Value"`
} `json:"Tags"`
VirtualizationType string `json:"VirtualizationType"`
} `json:"Images"`
NextToken string `json:"NextToken"`
}
type DescribeSnapshots struct {
Snapshots []struct {
DataEncryptionKeyID string `json:"DataEncryptionKeyId"`
Description string `json:"Description"`
Encrypted bool `json:"Encrypted"`
KMSKeyID string `json:"KmsKeyId"`
OwnerAlias string `json:"OwnerAlias"`
OwnerID string `json:"OwnerId"`
Progress string `json:"Progress"`
SnapshotID string `json:"SnapshotId"`
StartTime time.Time `json:"StartTime"`
State string `json:"State"`
StateMessage string `json:"StateMessage"`
TagSet []struct {
Key string `json:"Key"`
Value string `json:"Value"`
} `json:"Tags"`
VolumeID string `json:"VolumeId"`
VolumeSize int `json:"VolumeSize"`
} `json:"Snapshots"`
}
type DescribeVolumes struct {
Volumes []struct {
Attachments []struct {
AttachTime time.Time `json:"AttachTime"`
DeleteOnTermination bool `json:"DeleteOnTermination"`
Device string `json:"Device"`
InstanceID string `json:"InstanceId"`
State string `json:"State"`
VolumeID string `json:"VolumeId"`
} `json:"Attachments"`
AvailabilityZone string `json:"AvailabilityZone"`
CreateTime string `json:"CreateTime"`
Encrypted bool `json:"Encrypted"`
KMSKeyID string `json:"KmsKeyId"`
Size int `json:"Size"`
SnapshotID string `json:"SnapshotId"`
State string `json:"State"`
VolumeID string `json:"VolumeId"`
} `json:"Volumes"`
}
// DescribeInstanceAttribute is a struct for the DescribeInstanceAttribute function - include userdata
type DescribeInstanceAttribute struct {
UserData string `json:"UserData"`
InstanceID string `json:"InstanceId"`
}
func (c *MockedEC2Client2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
var nics []ec2types.NetworkInterface
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
}
for _, mockednic := range c.describeNetworkInterfaces.NetworkInterfaces {
nics = append(nics, ec2types.NetworkInterface{
Association: &ec2types.NetworkInterfaceAssociation{
PublicIp: aws.String(mockednic.Association.PublicIP),
},
NetworkInterfaceId: aws.String(mockednic.NetworkInterfaceID),
PrivateIpAddress: aws.String(mockednic.PrivateIPAddress),
VpcId: aws.String(mockednic.VpcID),
Attachment: &ec2types.NetworkInterfaceAttachment{InstanceId: aws.String(mockednic.Attachment.InstanceID)},
Description: aws.String(mockednic.Description),
})
}
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
}
func (c *MockedEC2Client2) DescribeInstances(ctx context.Context, input *ec2.DescribeInstancesInput, f ...func(o *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
var instances []ec2types.Instance
err := json.Unmarshal(readTestFile(DESCRIBE_INSTANCES_TEST_FILE), &c.describeInstances)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_INSTANCES_TEST_FILE)
}
for _, mockedReservation := range c.describeInstances.Reservations {
for _, mockedInstance := range mockedReservation.Instances {
for _, inputInstanceID := range input.InstanceIds {
if mockedInstance.InstanceID == inputInstanceID {
instances = append(instances, ec2types.Instance{
InstanceId: aws.String(mockedInstance.InstanceID),
InstanceType: ec2types.InstanceType(mockedInstance.InstanceType),
ImageId: aws.String(mockedInstance.ImageID),
})
}
}
}
}
return &ec2.DescribeInstancesOutput{Reservations: []ec2types.Reservation{
{Instances: instances},
}}, nil
}
func (c *MockedEC2Client2) DescribeVolumes(ctx context.Context, input *ec2.DescribeVolumesInput, f ...func(o *ec2.Options)) (*ec2.DescribeVolumesOutput, error) {
var volumes []ec2types.Volume
err := json.Unmarshal(readTestFile(DESCRIBE_VOLUMES_TEST_FILE), &c.describeVolumes)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_VOLUMES_TEST_FILE)
}
for _, mockedVolume := range c.describeVolumes.Volumes {
for _, inputVolumeID := range input.VolumeIds {
if mockedVolume.VolumeID == inputVolumeID {
volumes = append(volumes, ec2types.Volume{
VolumeId: aws.String(mockedVolume.VolumeID),
Size: aws.Int32(int32(mockedVolume.Size)),
Attachments: []ec2types.VolumeAttachment{
{
InstanceId: aws.String(mockedVolume.Attachments[0].InstanceID),
},
},
})
}
}
}
return &ec2.DescribeVolumesOutput{Volumes: volumes}, nil
}
func (c *MockedEC2Client2) DescribeSnapshots(ctx context.Context, input *ec2.DescribeSnapshotsInput, f ...func(o *ec2.Options)) (*ec2.DescribeSnapshotsOutput, error) {
var snapshots []ec2types.Snapshot
err := json.Unmarshal(readTestFile(DESCRIBE_SNAPSHOTS_TEST_FILE), &c.describeSnapshots)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_SNAPSHOTS_TEST_FILE)
}
for _, mockedSnapshot := range c.describeSnapshots.Snapshots {
for _, inputSnapshotID := range input.SnapshotIds {
if mockedSnapshot.SnapshotID == inputSnapshotID {
snapshots = append(snapshots, ec2types.Snapshot{
SnapshotId: aws.String(mockedSnapshot.SnapshotID),
VolumeId: aws.String(mockedSnapshot.VolumeID),
State: ec2types.SnapshotState(mockedSnapshot.State),
})
}
}
}
return &ec2.DescribeSnapshotsOutput{Snapshots: snapshots}, nil
}
func (c *MockedEC2Client2) DescribeImages(ctx context.Context, input *ec2.DescribeImagesInput, f ...func(o *ec2.Options)) (*ec2.DescribeImagesOutput, error) {
var images []ec2types.Image
err := json.Unmarshal(readTestFile(DESCRIBE_IMAGES_TEST_FILE), &c.describeImages)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_IMAGES_TEST_FILE)
}
for _, mockedImage := range c.describeImages.Images {
for _, inputImageID := range input.ImageIds {
if mockedImage.ImageID == inputImageID {
images = append(images, ec2types.Image{
ImageId: aws.String(mockedImage.ImageID),
Name: aws.String(mockedImage.Name),
})
}
}
}
return &ec2.DescribeImagesOutput{Images: images}, nil
}
func (c *MockedEC2Client2) DescribeInstanceAttribute(ctx context.Context, input *ec2.DescribeInstanceAttributeInput, f ...func(o *ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error) {
return &ec2.DescribeInstanceAttributeOutput{
UserData: &ec2types.AttributeValue{
Value: aws.String("userdata"),
},
}, nil
}
+3 -2
View File
@@ -19,9 +19,10 @@ type AWSECRClientInterface interface {
GetRepositoryPolicy(ctx context.Context, params *ecr.GetRepositoryPolicyInput, optFns ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error)
}
func RegisterECRTypes() {
func init() {
gob.Register([]ecrTypes.Repository{})
gob.Register([]ecrTypes.ImageDetail{})
gob.Register(ecrTypes.Repository{})
}
@@ -69,7 +70,7 @@ func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID st
func CachedECRDescribeImages(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) ([]ecrTypes.ImageDetail, error) {
var PaginationControl *string
var images []ecrTypes.ImageDetail
cacheKey := fmt.Sprintf("%s-efs-DescribImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cacheKey := fmt.Sprintf("%s-ecr-DescribeImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Images data")
+109
View File
@@ -0,0 +1,109 @@
package sdk
import (
"context"
"time"
"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"
)
type MockedECRClient struct {
}
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, input *ecr.DescribeRepositoriesInput, options ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []ecrTypes.Repository{
{
RepositoryName: aws.String("repo1"),
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1"),
},
{
RepositoryName: aws.String("repo2"),
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2"),
},
},
}, nil
}
func (m *MockedECRClient) DescribeImages(ctx context.Context, input *ecr.DescribeImagesInput, options ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
if aws.ToString(input.RepositoryName) == "repo1" {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"customtag",
"tag2",
},
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(123456),
},
},
}, nil
} else if aws.ToString(input.RepositoryName) == "repo2" {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"latest",
},
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(2222222),
},
},
}, nil
} else {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"customtag",
"tag2",
},
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(111),
},
{
ImageTags: []string{
"latest",
},
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
ImageSizeInBytes: aws.Int64(333),
},
},
}, nil
}
}
func (m *MockedECRClient) GetRepositoryPolicy(ctx context.Context, input *ecr.GetRepositoryPolicyInput, options ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
return &ecr.GetRepositoryPolicyOutput{
PolicyText: aws.String(`{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowPushPull",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:root",
"arn:aws:iam::123456789012:user/MyUser"
]
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
}
]
}`),
}, nil
}
+222
View File
@@ -0,0 +1,222 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/ecs"
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/patrickmn/go-cache"
)
type AWSECSClientInterface interface {
ListClusters(ctx context.Context, params *ecs.ListClustersInput, optFns ...func(*ecs.Options)) (*ecs.ListClustersOutput, error)
ListTasks(ctx context.Context, params *ecs.ListTasksInput, optFns ...func(*ecs.Options)) (*ecs.ListTasksOutput, error)
ListServices(ctx context.Context, params *ecs.ListServicesInput, optFns ...func(*ecs.Options)) (*ecs.ListServicesOutput, error)
DescribeTasks(ctx context.Context, params *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error)
DescribeTaskDefinition(ctx context.Context, params *ecs.DescribeTaskDefinitionInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
}
func init() {
gob.Register([]string{})
gob.Register(ecsTypes.Task{})
gob.Register([]ecsTypes.Task{})
gob.Register(ecsTypes.TaskDefinition{})
}
func CachedECSListClusters(ECSClient AWSECSClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var clusters []string
cacheKey := fmt.Sprintf("%s-ecs-ListClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS clusters data")
return cached.([]string), nil
}
for {
ListClusters, err := ECSClient.ListClusters(
context.TODO(),
&ecs.ListClustersInput{
NextToken: PaginationControl,
},
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
clusters = append(clusters, ListClusters.ClusterArns...)
// Pagination control.
if ListClusters.NextToken != nil {
PaginationControl = ListClusters.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
func CachedECSListTasks(ECSClient AWSECSClientInterface, accountID string, region string, cluster string) ([]string, error) {
var PaginationControl *string
var tasks []string
//grab cluster name from AWS ARN
clusterName := cluster[strings.LastIndex(cluster, "/")+1:]
cacheKey := fmt.Sprintf("%s-ecs-ListTasks-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS tasks data")
return cached.([]string), nil
}
for {
ListTasks, err := ECSClient.ListTasks(
context.TODO(),
&ecs.ListTasksInput{
Cluster: &cluster,
NextToken: PaginationControl,
},
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
tasks = append(tasks, ListTasks.TaskArns...)
// Pagination control.
if ListTasks.NextToken != nil {
PaginationControl = ListTasks.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, tasks, cache.DefaultExpiration)
return tasks, nil
}
func CachedECSDescribeTasks(ECSClient AWSECSClientInterface, accountID string, region string, cluster string, tasks []string) ([]ecsTypes.Task, error) {
var taskDetails []ecsTypes.Task
//replace semi-colons with underscores in task definition name
clusterFileSystemSafe := strings.ReplaceAll(cluster, ":", "_")
clusterFileSystemSafe = strings.ReplaceAll(clusterFileSystemSafe, "/", "_")
cacheKey := fmt.Sprintf("%s-ecs-DescribeTasks-%s-%s", accountID, region, clusterFileSystemSafe)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS task details data")
return cached.([]ecsTypes.Task), nil
}
DescribeTasks, err := ECSClient.DescribeTasks(
context.TODO(),
&ecs.DescribeTasksInput{
Cluster: &cluster,
Tasks: tasks,
},
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return []ecsTypes.Task{}, err
}
taskDetails = append(taskDetails, DescribeTasks.Tasks...)
internal.Cache.Set(cacheKey, taskDetails, cache.DefaultExpiration)
return taskDetails, nil
}
func CachedECSDescribeTaskDefinition(ECSClient AWSECSClientInterface, accountID string, region string, taskDefinition string) (ecsTypes.TaskDefinition, error) {
var taskDefinitionDetails ecsTypes.TaskDefinition
//replace semi-colons with underscores in task definition name
taskDefinitionFileSystemSafe := strings.ReplaceAll(taskDefinition, ":", "_")
taskDefinitionFileSystemSafe = strings.ReplaceAll(taskDefinitionFileSystemSafe, "/", "_")
cacheKey := fmt.Sprintf("%s-ecs-DescribeTaskDefinition-%s-%s", accountID, region, taskDefinitionFileSystemSafe)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS task definition details data")
return cached.(ecsTypes.TaskDefinition), nil
}
DescribeTaskDefinition, err := ECSClient.DescribeTaskDefinition(
context.TODO(),
&ecs.DescribeTaskDefinitionInput{
TaskDefinition: &taskDefinition,
},
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return ecsTypes.TaskDefinition{}, err
}
taskDefinitionDetails = *DescribeTaskDefinition.TaskDefinition
internal.Cache.Set(cacheKey, taskDefinitionDetails, cache.DefaultExpiration)
return taskDefinitionDetails, nil
}
func CachedECSListServices(ECSClient AWSECSClientInterface, accountID string, region string, cluster string) ([]string, error) {
var PaginationControl *string
var services []string
//grab cluster name from AWS ARN
clusterName := cluster[strings.LastIndex(cluster, "/")+1:]
cacheKey := fmt.Sprintf("%s-ecs-ListServices-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECS services data")
return cached.([]string), nil
}
for {
ListServices, err := ECSClient.ListServices(
context.TODO(),
&ecs.ListServicesInput{
Cluster: &cluster,
NextToken: PaginationControl,
},
func(o *ecs.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
services = append(services, ListServices.ServiceArns...)
// Pagination control.
if ListServices.NextToken != nil {
PaginationControl = ListServices.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+156
View File
@@ -0,0 +1,156 @@
package sdk
import (
"context"
"encoding/json"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecs"
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
)
const DESCRIBE_TASKS_TEST_FILE = "./test-data/describe-tasks.json"
type MockedECSClient struct {
describeTasks DescribeTasks
}
type ListTasks struct {
TaskArns []string `json:"taskArns"`
}
type DescribeTasks struct {
Tasks []struct {
Attachments []struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Details []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"details"`
} `json:"attachments"`
Attributes []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"attributes"`
AvailabilityZone string `json:"availabilityZone"`
ClusterArn string `json:"clusterArn"`
Connectivity string `json:"connectivity"`
ConnectivityAt string `json:"connectivityAt"`
Containers []struct {
ContainerArn string `json:"containerArn"`
TaskArn string `json:"taskArn"`
Name string `json:"name"`
Image string `json:"image"`
RuntimeID string `json:"runtimeId"`
LastStatus string `json:"lastStatus"`
NetworkBindings []interface{} `json:"networkBindings"`
NetworkInterfaces []struct {
AttachmentID string `json:"attachmentId"`
PrivateIpv4Address string `json:"privateIpv4Address"`
} `json:"networkInterfaces"`
HealthStatus string `json:"healthStatus"`
CPU string `json:"cpu"`
Memory string `json:"memory"`
} `json:"containers"`
CPU string `json:"cpu"`
CreatedAt string `json:"createdAt"`
DesiredStatus string `json:"desiredStatus"`
EnableExecuteCommand bool `json:"enableExecuteCommand"`
Group string `json:"group"`
HealthStatus string `json:"healthStatus"`
LastStatus string `json:"lastStatus"`
LaunchType string `json:"launchType"`
Memory string `json:"memory"`
Overrides struct {
ContainerOverrides []struct {
Name string `json:"name"`
} `json:"containerOverrides"`
InferenceAcceleratorOverrides []interface{} `json:"inferenceAcceleratorOverrides"`
} `json:"overrides"`
PlatformVersion string `json:"platformVersion"`
PlatformFamily string `json:"platformFamily"`
PullStartedAt string `json:"pullStartedAt"`
PullStoppedAt string `json:"pullStoppedAt"`
StartedAt string `json:"startedAt"`
StartedBy string `json:"startedBy"`
Tags []interface{} `json:"tags"`
TaskArn string `json:"taskArn"`
TaskDefinitionArn string `json:"taskDefinitionArn"`
Version int `json:"version"`
EphemeralStorage struct {
SizeInGiB int `json:"sizeInGiB"`
} `json:"ephemeralStorage"`
} `json:"tasks"`
Failures []interface{} `json:"failures"`
}
func (c *MockedECSClient) ListClusters(context.Context, *ecs.ListClustersInput, ...func(*ecs.Options)) (*ecs.ListClustersOutput, error) {
return &ecs.ListClustersOutput{ClusterArns: []string{
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster",
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster2",
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster3",
}}, nil
}
func (c *MockedECSClient) ListTasks(ctx context.Context, input *ecs.ListTasksInput, f ...func(*ecs.Options)) (*ecs.ListTasksOutput, error) {
return &ecs.ListTasksOutput{TaskArns: []string{
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/74de0355a10a4f979ac495c14EXAMPLE",
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/d789e94343414c25b9f6bd59eEXAMPLE",
}}, nil
}
func (c *MockedECSClient) ListServices(ctx context.Context, input *ecs.ListServicesInput, f ...func(*ecs.Options)) (*ecs.ListServicesOutput, error) {
return &ecs.ListServicesOutput{ServiceArns: []string{
"arn:aws:ecs:us-east-1:123456789012:service/MyService",
"arn:aws:ecs:us-east-1:123456789012:service/MyService2",
"arn:aws:ecs:us-east-1:123456789012:service/MyService3",
}}, nil
}
func (c *MockedECSClient) DescribeTasks(ctx context.Context, input *ecs.DescribeTasksInput, f ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_TASKS_TEST_FILE), &c.describeTasks)
if err != nil {
log.Fatalf("can't unmarshall file %s", DESCRIBE_TASKS_TEST_FILE)
}
var tasks []ecsTypes.Task
for _, mockedTask := range c.describeTasks.Tasks {
if mockedTask.ClusterArn == aws.ToString(input.Cluster) {
for _, inputTask := range input.Tasks {
if mockedTask.TaskArn == inputTask {
var attachments []ecsTypes.Attachment
for _, a := range mockedTask.Attachments {
var deets []ecsTypes.KeyValuePair
for _, detail := range a.Details {
deets = append(deets, ecsTypes.KeyValuePair{
Name: aws.String(detail.Name),
Value: aws.String(detail.Value)})
}
attachments = append(attachments, ecsTypes.Attachment{
Type: aws.String(a.Type),
Details: deets,
Id: aws.String(a.ID),
Status: aws.String(a.Status),
})
}
tasks = append(tasks, ecsTypes.Task{
ClusterArn: aws.String(mockedTask.ClusterArn),
TaskDefinitionArn: aws.String(mockedTask.TaskDefinitionArn),
LaunchType: ecsTypes.LaunchType(*aws.String(mockedTask.LaunchType)),
TaskArn: aws.String(mockedTask.TaskArn),
Attachments: attachments,
})
}
}
}
}
return &ecs.DescribeTasksOutput{Tasks: tasks}, nil
}
func (c *MockedECSClient) DescribeTaskDefinition(ctx context.Context, input *ecs.DescribeTaskDefinitionInput, f ...func(o *ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) {
testTaskDefinition := ecsTypes.TaskDefinition{}
testTaskDefinition.TaskRoleArn = aws.String("test123")
return &ecs.DescribeTaskDefinitionOutput{TaskDefinition: &testTaskDefinition}, nil
}
+1 -1
View File
@@ -20,7 +20,7 @@ type AWSEFSClientInterface interface {
DescribeFileSystemPolicy(ctx context.Context, params *efs.DescribeFileSystemPolicyInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemPolicyOutput, error)
}
func RegisterEFSTypes() {
func init() {
gob.Register([]efsTypes.FileSystemDescription{})
gob.Register([]efsTypes.MountTargetDescription{})
gob.Register([]efsTypes.AccessPointDescription{})
+67
View File
@@ -0,0 +1,67 @@
package sdk
import (
"context"
"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"
)
type MockedEfsClient struct {
}
func (m *MockedEfsClient) DescribeFileSystems(ctx context.Context, input *efs.DescribeFileSystemsInput, options ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error) {
return &efs.DescribeFileSystemsOutput{
FileSystems: []efsTypes.FileSystemDescription{
{
FileSystemId: aws.String("fs-12345678"),
},
{
FileSystemId: aws.String("fs-87654321"),
},
},
}, nil
}
func (m *MockedEfsClient) DescribeMountTargets(ctx context.Context, input *efs.DescribeMountTargetsInput, options ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error) {
return &efs.DescribeMountTargetsOutput{
MountTargets: []efsTypes.MountTargetDescription{
{
MountTargetId: aws.String("fsmt-12345678"),
FileSystemId: aws.String("fs-12345678"),
IpAddress: aws.String("10.1.1.1"),
},
{
MountTargetId: aws.String("fsmt-87654321"),
FileSystemId: aws.String("fs-87654321"),
IpAddress: aws.String("10.2.2.2.2"),
},
},
}, nil
}
func (m *MockedEfsClient) DescribeAccessPoints(ctx context.Context, input *efs.DescribeAccessPointsInput, options ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error) {
return &efs.DescribeAccessPointsOutput{
AccessPoints: []efsTypes.AccessPointDescription{
{
AccessPointId: aws.String("fsap-12345678"),
FileSystemId: aws.String("fs-12345678"),
Name: aws.String("fsap-12345678"),
PosixUser: &efsTypes.PosixUser{
Gid: aws.Int64(1000),
Uid: aws.Int64(1000),
},
},
{
AccessPointId: aws.String("fsap-87654321"),
FileSystemId: aws.String("fs-87654321"),
Name: aws.String("fsap-12345679"),
PosixUser: &efsTypes.PosixUser{
Gid: aws.Int64(1000),
Uid: aws.Int64(1000),
},
},
},
}, nil
}
+2 -2
View File
@@ -18,7 +18,7 @@ type EKSClientInterface interface {
ListNodegroups(context.Context, *eks.ListNodegroupsInput, ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error)
}
func RegisterEKSTypes() {
func init() {
gob.Register([]string{})
gob.Register(eksTypes.Cluster{})
gob.Register(eksTypes.Nodegroup{})
@@ -126,7 +126,7 @@ func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region
func CachedEKSDescribeNodeGroup(client EKSClientInterface, accountID string, region string, clusterName string, nodeGroupName string) (eksTypes.Nodegroup, error) {
var nodeGroup eksTypes.Nodegroup
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, nodeGroupName)
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, clusterName, nodeGroupName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Nodegroup), nil
+54
View File
@@ -0,0 +1,54 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/eks"
eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
)
type MockedAWSEksClient struct {
}
func (m *MockedAWSEksClient) ListClusters(ctx context.Context, input *eks.ListClustersInput, options ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
return &eks.ListClustersOutput{
Clusters: []string{
"cluster1",
"cluster2",
},
}, nil
}
func (m *MockedAWSEksClient) DescribeCluster(ctx context.Context, input *eks.DescribeClusterInput, options ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
return &eks.DescribeClusterOutput{
Cluster: &eksTypes.Cluster{
Arn: aws.String("arn:aws:eks:us-east-1:123456789012:cluster/cluster1"),
Endpoint: aws.String("https://cluster1.us-east-1.eks.amazonaws.com"),
Name: aws.String("cluster1"),
PlatformVersion: aws.String("eks.1"),
Version: aws.String("1.18"),
RoleArn: aws.String("arn:aws:iam::123456789012:role/eks-role"),
},
}, nil
}
func (m *MockedAWSEksClient) ListNodegroups(ctx context.Context, input *eks.ListNodegroupsInput, options ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
return &eks.ListNodegroupsOutput{
Nodegroups: []string{
"nodegroup1",
"nodegroup2",
},
}, nil
}
func (m *MockedAWSEksClient) DescribeNodegroup(ctx context.Context, input *eks.DescribeNodegroupInput, options ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
return &eks.DescribeNodegroupOutput{
Nodegroup: &eksTypes.Nodegroup{
ClusterName: aws.String("cluster1"),
NodeRole: aws.String("arn:aws:iam::123456789012:role/eks-role"),
NodegroupName: aws.String("nodegroup1"),
NodegroupArn: aws.String("arn:aws:eks:us-east-1:123456789012:nodegroup/cluster1/nodegroup1"),
},
}, 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/elasticache"
elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types"
"github.com/patrickmn/go-cache"
)
type AWSElastiCacheClientInterface interface {
DescribeCacheClusters(context.Context, *elasticache.DescribeCacheClustersInput, ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error)
}
func init() {
gob.Register([]elasticacheTypes.CacheCluster{})
}
func CachedElastiCacheDescribeCacheClusters(client AWSElastiCacheClientInterface, accountID string, region string) ([]elasticacheTypes.CacheCluster, error) {
var PaginationControl *string
var clusters []elasticacheTypes.CacheCluster
cacheKey := fmt.Sprintf("%s-elasticache-DescribeCacheClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elasticacheTypes.CacheCluster), nil
}
for {
DescribeCacheClusters, err := client.DescribeCacheClusters(
context.TODO(),
&elasticache.DescribeCacheClustersInput{
Marker: PaginationControl,
},
func(o *elasticache.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, DescribeCacheClusters.CacheClusters...)
//pagination
if DescribeCacheClusters.Marker == nil {
break
}
PaginationControl = DescribeCacheClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
+40
View File
@@ -0,0 +1,40 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/elasticache"
elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types"
)
type MockedElasticacheClient struct {
}
func (m *MockedElasticacheClient) DescribeCacheClusters(ctx context.Context, input *elasticache.DescribeCacheClustersInput, options ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error) {
return &elasticache.DescribeCacheClustersOutput{
CacheClusters: []elasticacheTypes.CacheCluster{
{
CacheClusterId: aws.String("test"),
ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:cluster:myCluster"),
Engine: aws.String("redis"),
EngineVersion: aws.String("6.x"),
CacheNodeType: aws.String("cache.t3.micro"),
NumCacheNodes: aws.Int32(1),
CacheClusterStatus: aws.String("available"),
PreferredAvailabilityZone: aws.String("us-east-1a"),
CacheSubnetGroupName: aws.String("default"),
ReplicationGroupId: aws.String("test"),
SecurityGroups: []elasticacheTypes.SecurityGroupMembership{
{
SecurityGroupId: aws.String("test"),
Status: aws.String("active"),
},
},
AutoMinorVersionUpgrade: aws.Bool(true),
PreferredMaintenanceWindow: aws.String("sun:05:00-sun:06:00"),
},
},
}, nil
}
+35
View File
@@ -0,0 +1,35 @@
package sdk
import (
"context"
"encoding/gob"
"github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk"
elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types"
)
type AWSElasticBeanstalkClientInterface interface {
DescribeApplications(context.Context, *elasticbeanstalk.DescribeApplicationsInput, ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error)
}
func init() {
gob.Register([]elasticbeanstalkTypes.ApplicationDescription{})
}
func CachedElasticBeanstalkDescribeApplications(client AWSElasticBeanstalkClientInterface, accountID string, region string) ([]elasticbeanstalkTypes.ApplicationDescription, error) {
var applications []elasticbeanstalkTypes.ApplicationDescription
DescribeApplications, err := client.DescribeApplications(
context.TODO(),
&elasticbeanstalk.DescribeApplicationsInput{},
func(o *elasticbeanstalk.Options) {
o.Region = region
},
)
if err != nil {
return applications, err
}
applications = append(applications, DescribeApplications.Applications...)
return applications, nil
}
+33
View File
@@ -0,0 +1,33 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk"
elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types"
)
type MockedElasticBeanstalkClient struct {
}
func (m *MockedElasticBeanstalkClient) DescribeApplications(ctx context.Context, input *elasticbeanstalk.DescribeApplicationsInput, options ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error) {
return &elasticbeanstalk.DescribeApplicationsOutput{
Applications: []elasticbeanstalkTypes.ApplicationDescription{
{
ApplicationName: aws.String("app1"),
ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app1"),
ConfigurationTemplates: []string{
"template1",
},
},
{
ApplicationName: aws.String("app2"),
ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app2"),
ConfigurationTemplates: []string{
"template2",
},
},
},
}, nil
}
+1 -1
View File
@@ -15,7 +15,7 @@ type ELBClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error)
}
func RegisterELBTypes() {
func init() {
gob.Register([]elbTypes.LoadBalancerDescription{})
}
+37
View File
@@ -0,0 +1,37 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
)
type MockedElbClient struct {
}
func (m *MockedElbClient) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancing.DescribeLoadBalancersInput, options ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error) {
return &elasticloadbalancing.DescribeLoadBalancersOutput{
LoadBalancerDescriptions: []elbTypes.LoadBalancerDescription{
{
LoadBalancerName: aws.String("elb1"),
DNSName: aws.String("elb1"),
Instances: []elbTypes.Instance{
{
InstanceId: aws.String("i-1234567890abcdef0"),
},
},
},
{
LoadBalancerName: aws.String("elb2"),
DNSName: aws.String("elb2"),
Instances: []elbTypes.Instance{
{
InstanceId: aws.String("i-1234567890abcdef1"),
},
},
},
},
}, nil
}
+1 -1
View File
@@ -15,7 +15,7 @@ type ELBv2ClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)
}
func RegisterELBv2Types() {
func init() {
gob.Register([]elbV2Types.LoadBalancer{})
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbv2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
)
type MockedElbv2Client struct {
}
func (m *MockedElbv2Client) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancersInput, options ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) {
return &elasticloadbalancingv2.DescribeLoadBalancersOutput{
LoadBalancers: []elbv2Types.LoadBalancer{
{
LoadBalancerArn: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"),
DNSName: aws.String("my-load-balancer-424835706.us-east-1.elb.amazonaws.com"),
CanonicalHostedZoneId: aws.String("Z2P70J7EXAMPLE"),
CreatedTime: aws.Time(time.Now()),
LoadBalancerName: aws.String("my-load-balancer"),
Scheme: elbv2Types.LoadBalancerSchemeEnumInternetFacing,
VpcId: aws.String("vpc-3ac0fb5f"),
State: &elbv2Types.LoadBalancerState{
Code: elbv2Types.LoadBalancerStateEnumActive,
Reason: aws.String(""),
},
Type: elbv2Types.LoadBalancerTypeEnumApplication,
AvailabilityZones: []elbv2Types.AvailabilityZone{
{
ZoneName: aws.String("us-east-1a"),
SubnetId: aws.String("subnet-8360a9e7"),
LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{
{
IpAddress: aws.String("1.2.3.4"),
},
},
},
{
ZoneName: aws.String("us-east-1b"),
SubnetId: aws.String("subnet-b7d581c0"),
LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{
{
IpAddress: aws.String("2.3.4.5"),
},
},
},
},
SecurityGroups: []string{
"sg-5943793c",
},
IpAddressType: elbv2Types.IpAddressTypeDualstack,
},
},
}, nil
}
+98
View File
@@ -0,0 +1,98 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/emr"
emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types"
"github.com/patrickmn/go-cache"
)
type AWSEMRClientInterface interface {
ListClusters(context.Context, *emr.ListClustersInput, ...func(*emr.Options)) (*emr.ListClustersOutput, error)
ListInstances(context.Context, *emr.ListInstancesInput, ...func(*emr.Options)) (*emr.ListInstancesOutput, error)
}
func init() {
gob.Register([]emrTypes.ClusterSummary{})
//need to do this to avoid conflicts with the Instance type in the ec2 package
type EMRInstance emrTypes.Instance
gob.Register([]EMRInstance{})
}
func CachedEMRListClusters(client AWSEMRClientInterface, accountID string, region string) ([]emrTypes.ClusterSummary, error) {
var PaginationControl *string
var clusters []emrTypes.ClusterSummary
cacheKey := fmt.Sprintf("%s-emr-ListClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]emrTypes.ClusterSummary), nil
}
for {
ListClusters, err := client.ListClusters(
context.TODO(),
&emr.ListClustersInput{
Marker: PaginationControl,
},
func(o *emr.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
//pagination
if ListClusters.Marker == nil {
break
}
PaginationControl = ListClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
func CachedEMRListInstances(client AWSEMRClientInterface, accountID string, region string, clusterID string) ([]emrTypes.Instance, error) {
var PaginationControl *string
var instances []emrTypes.Instance
cacheKey := fmt.Sprintf("%s-emr-ListInstances-%s-%s", accountID, region, clusterID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]emrTypes.Instance), nil
}
for {
ListInstances, err := client.ListInstances(
context.TODO(),
&emr.ListInstancesInput{
ClusterId: &clusterID,
Marker: PaginationControl,
},
func(o *emr.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
instances = append(instances, ListInstances.Instances...)
//pagination
if ListInstances.Marker == nil {
break
}
PaginationControl = ListInstances.Marker
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
+44
View File
@@ -0,0 +1,44 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/emr"
emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types"
)
type MockedEMRClient struct {
}
func (m *MockedEMRClient) ListClusters(ctx context.Context, input *emr.ListClustersInput, options ...func(*emr.Options)) (*emr.ListClustersOutput, error) {
return &emr.ListClustersOutput{
Clusters: []emrTypes.ClusterSummary{
{
Id: aws.String("cluster1"),
},
{
Id: aws.String("cluster2"),
},
},
}, nil
}
func (m *MockedEMRClient) ListInstances(ctx context.Context, input *emr.ListInstancesInput, options ...func(*emr.Options)) (*emr.ListInstancesOutput, error) {
return &emr.ListInstancesOutput{
Instances: []emrTypes.Instance{
{
Id: aws.String("instance1"),
InstanceType: aws.String("m5.xlarge"),
Ec2InstanceId: aws.String("i-1234567890"),
PrivateDnsName: aws.String("ip-10-0-0-1.ec2.internal"),
PublicDnsName: aws.String("ec2-1-2-3-4.compute-1.amazonaws.com"),
PrivateIpAddress: aws.String("10.0.0.1"),
PublicIpAddress: aws.String("1.2.3.4"),
},
{
Id: aws.String("instance2"),
},
},
}, nil
}
+248
View File
@@ -0,0 +1,248 @@
package sdk
import (
"context"
"encoding/gob"
"errors"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/glue"
glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types"
"github.com/patrickmn/go-cache"
)
type AWSGlueClientInterface interface {
ListDevEndpoints(ctx context.Context, params *glue.ListDevEndpointsInput, optFns ...func(*glue.Options)) (*glue.ListDevEndpointsOutput, error)
ListJobs(ctx context.Context, params *glue.ListJobsInput, optFns ...func(*glue.Options)) (*glue.ListJobsOutput, error)
GetTables(ctx context.Context, params *glue.GetTablesInput, optFns ...func(*glue.Options)) (*glue.GetTablesOutput, error)
GetDatabases(ctx context.Context, params *glue.GetDatabasesInput, optFns ...func(*glue.Options)) (*glue.GetDatabasesOutput, error)
GetResourcePolicies(ctx context.Context, params *glue.GetResourcePoliciesInput, optFns ...func(*glue.Options)) (*glue.GetResourcePoliciesOutput, error)
}
func init() {
gob.Register([]string{})
gob.Register(glueTypes.DevEndpoint{})
gob.Register(glueTypes.Job{})
gob.Register([]glueTypes.Table{})
gob.Register([]glueTypes.Database{})
gob.Register([]policy.Policy{})
}
func CachedGlueListDevEndpoints(GlueClient AWSGlueClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var devEndpoints []string
cacheKey := "glue-ListDevEndpoints-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue dev endpoints data")
return cached.([]string), nil
}
for {
ListDevEndpoints, err := GlueClient.ListDevEndpoints(
context.TODO(),
&glue.ListDevEndpointsInput{
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
devEndpoints = append(devEndpoints, ListDevEndpoints.DevEndpointNames...)
// Pagination control.
if aws.ToString(ListDevEndpoints.NextToken) != "" {
PaginationControl = ListDevEndpoints.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, devEndpoints, cache.DefaultExpiration)
return devEndpoints, nil
}
func CachedGlueListJobs(GlueClient AWSGlueClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var jobs []string
cacheKey := "glue-ListJobs-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue jobs data")
return cached.([]string), nil
}
for {
ListJobs, err := GlueClient.ListJobs(
context.TODO(),
&glue.ListJobsInput{
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
jobs = append(jobs, ListJobs.JobNames...)
// Pagination control.
if ListJobs.NextToken != nil {
PaginationControl = ListJobs.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, jobs, cache.DefaultExpiration)
return jobs, nil
}
func CachedGlueGetTables(GlueClient AWSGlueClientInterface, accountID string, region string, dbName string) ([]glueTypes.Table, error) {
var PaginationControl *string
var tables []glueTypes.Table
cacheKey := "glue-GetTables-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue tables data")
return cached.([]glueTypes.Table), nil
}
for {
GetTables, err := GlueClient.GetTables(
context.TODO(),
&glue.GetTablesInput{
DatabaseName: &dbName,
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
tables = append(tables, GetTables.TableList...)
// Pagination control.
if GetTables.NextToken != nil {
PaginationControl = GetTables.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration)
return tables, nil
}
func CachedGlueGetDatabases(GlueClient AWSGlueClientInterface, accountID string, region string) ([]glueTypes.Database, error) {
var PaginationControl *string
var databases []glueTypes.Database
cacheKey := "glue-GetDatabases-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue databases data")
return cached.([]glueTypes.Database), nil
}
for {
GetDatabases, err := GlueClient.GetDatabases(
context.TODO(),
&glue.GetDatabasesInput{
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
databases = append(databases, GetDatabases.DatabaseList...)
// Pagination control.
if GetDatabases.NextToken != nil {
PaginationControl = GetDatabases.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, databases, cache.DefaultExpiration)
return databases, nil
}
func CachedGlueGetResourcePolicies(GlueClient AWSGlueClientInterface, accountID string, region string) ([]policy.Policy, error) {
var PaginationControl *string
var GluePolicy policy.Policy
var policies []policy.Policy
var policyJSON string
cacheKey := "glue-GetResourcePolicies-" + accountID + "-" + region
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Glue resource policies data")
return cached.([]policy.Policy), nil
}
for {
GetResourcePolicies, err := GlueClient.GetResourcePolicies(
context.TODO(),
&glue.GetResourcePoliciesInput{
NextToken: PaginationControl,
},
func(o *glue.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
for _, policyPointer := range GetResourcePolicies.GetResourcePoliciesResponseList {
policyJSON = aws.ToString(policyPointer.PolicyInJson)
GluePolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return policies, errors.New("error parsing Glue policy")
}
policies = append(policies, GluePolicy)
}
// Pagination control.
if GetResourcePolicies.NextToken != nil {
PaginationControl = GetResourcePolicies.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, policies, cache.DefaultExpiration)
return policies, nil
}
// in the resource trust command, need to parse the actual resource policies to determine the resources for the cloudfox table. crazy pants
+80
View File
@@ -0,0 +1,80 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/glue"
glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types"
)
type MockedGlueClient struct {
}
func (m *MockedGlueClient) ListDevEndpoints(ctx context.Context, input *glue.ListDevEndpointsInput, options ...func(*glue.Options)) (*glue.ListDevEndpointsOutput, error) {
return &glue.ListDevEndpointsOutput{
DevEndpointNames: []string{
"devendpoint1",
"devendpoint2",
},
}, nil
}
func (m *MockedGlueClient) ListJobs(ctx context.Context, input *glue.ListJobsInput, options ...func(*glue.Options)) (*glue.ListJobsOutput, error) {
return &glue.ListJobsOutput{
JobNames: []string{
"job1",
"job2",
},
}, nil
}
func (m *MockedGlueClient) GetTables(ctx context.Context, input *glue.GetTablesInput, options ...func(*glue.Options)) (*glue.GetTablesOutput, error) {
return &glue.GetTablesOutput{
TableList: []glueTypes.Table{
{
Name: aws.String("table1"),
DatabaseName: aws.String("database1"),
Description: aws.String("description1"),
Parameters: map[string]string{
"param1": "value1",
"param2": "value2",
},
},
{
Name: aws.String("table2"),
DatabaseName: aws.String("database2"),
Description: aws.String("description2"),
Parameters: map[string]string{
"param1": "value1",
"param2": "value2",
},
},
},
}, nil
}
func (m *MockedGlueClient) GetDatabases(ctx context.Context, input *glue.GetDatabasesInput, options ...func(*glue.Options)) (*glue.GetDatabasesOutput, error) {
return &glue.GetDatabasesOutput{
DatabaseList: []glueTypes.Database{
{
Name: aws.String("database1"),
Description: aws.String("description1"),
LocationUri: aws.String("s3://bucket1"),
Parameters: map[string]string{
"param1": "value1",
"param2": "value2",
},
},
{
Name: aws.String("database2"),
Description: aws.String("description2"),
LocationUri: aws.String("s3://bucket2"),
Parameters: map[string]string{
"param1": "value1",
"param2": "value2",
},
},
},
}, nil
}
+1 -1
View File
@@ -15,7 +15,7 @@ type GrafanaClientInterface interface {
ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error)
}
func RegisterGrafanaTypes() {
func init() {
gob.Register([]grafanaTypes.WorkspaceSummary{})
}
+42
View File
@@ -0,0 +1,42 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/grafana"
grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
)
type MockedGrafanaClient struct {
}
func (m *MockedGrafanaClient) ListWorkspaces(ctx context.Context, input *grafana.ListWorkspacesInput, options ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error) {
return &grafana.ListWorkspacesOutput{
Workspaces: []grafanaTypes.WorkspaceSummary{
{
Authentication: &grafanaTypes.AuthenticationSummary{
Providers: []grafanaTypes.AuthenticationProviderTypes{
grafanaTypes.AuthenticationProviderTypesAwsSso,
},
},
Created: aws.Time(time.Now()),
Id: aws.String("workspace1"),
Name: aws.String("workspace1"),
Status: grafanaTypes.WorkspaceStatusActive,
},
{
Authentication: &grafanaTypes.AuthenticationSummary{
Providers: []grafanaTypes.AuthenticationProviderTypes{
grafanaTypes.AuthenticationProviderTypesAwsSso,
},
},
Created: aws.Time(time.Now()),
Id: aws.String("workspace2"),
Name: aws.String("workspace2"),
Status: grafanaTypes.WorkspaceStatusActive,
},
},
}, nil
}
+40 -2
View File
@@ -22,12 +22,14 @@ type AWSIAMClientInterface interface {
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)
ListGroups(ctx context.Context, params *iam.ListGroupsInput, optFns ...func(*iam.Options)) (*iam.ListGroupsOutput, error)
}
func RegisterIamTypes() {
func init() {
gob.Register([]iamTypes.User{})
gob.Register([]iamTypes.AccessKeyMetadata{})
gob.Register([]iamTypes.Role{})
gob.Register([]iamTypes.Group{})
gob.Register([]iamTypes.PolicyDetail{})
gob.Register([]iamTypes.InstanceProfile{})
gob.Register([]iamTypes.EvaluationResult{})
@@ -205,7 +207,7 @@ func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID
var EvaluationResults []iamTypes.EvaluationResult
md5hashedActionNames := md5.Sum([]byte(strings.Join(actionNames, "")))
md5hashedResourceArns := md5.Sum([]byte(strings.Join(resourceArns, "")))
// proccess arn and get the name of the resource
// process arn and get the name of the resource
arn, err := arn.Parse(*principal)
if err != nil {
return EvaluationResults, err
@@ -250,3 +252,39 @@ func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID
internal.Cache.Set(cacheKey, EvaluationResults, cache.DefaultExpiration)
return EvaluationResults, nil
}
func CachedIamListGroups(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.Group, error) {
var PaginationControl *string
var Groups []iamTypes.Group
cacheKey := fmt.Sprintf("%s-iam-ListGroups", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.Group), nil
}
for {
ListGroups, err := IAMClient.ListGroups(
context.TODO(),
&iam.ListGroupsInput{
Marker: PaginationControl,
},
)
if err != nil {
return Groups, err
}
Groups = append(Groups, ListGroups.Groups...)
// Pagination control.
if ListGroups.Marker != nil {
PaginationControl = ListGroups.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, Groups, cache.DefaultExpiration)
return Groups, nil
}
+222
View File
@@ -0,0 +1,222 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
)
type MockedIAMClient struct {
}
func (m *MockedIAMClient) ListUsers(ctx context.Context, input *iam.ListUsersInput, options ...func(*iam.Options)) (*iam.ListUsersOutput, error) {
return &iam.ListUsersOutput{
Users: []iamTypes.User{
{
Arn: aws.String("arn:aws:iam::123456789012:user/user1"),
CreateDate: aws.Time(time.Now()),
Path: aws.String("/"),
UserId: aws.String("123456789012"),
UserName: aws.String("user1"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:user/user2"),
CreateDate: aws.Time(time.Now()),
Path: aws.String("/"),
UserId: aws.String("123456789012"),
UserName: aws.String("user2"),
},
},
}, nil
}
func (m *MockedIAMClient) ListAccessKeys(ctx context.Context, input *iam.ListAccessKeysInput, options ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error) {
return &iam.ListAccessKeysOutput{
AccessKeyMetadata: []iamTypes.AccessKeyMetadata{
{
AccessKeyId: aws.String("accesskey1"),
CreateDate: aws.Time(time.Now()),
Status: iamTypes.StatusTypeActive,
UserName: aws.String("user1"),
},
{
AccessKeyId: aws.String("accesskey2"),
CreateDate: aws.Time(time.Now()),
Status: iamTypes.StatusTypeActive,
UserName: aws.String("user2"),
},
},
}, nil
}
func (m *MockedIAMClient) ListGroups(ctx context.Context, input *iam.ListGroupsInput, options ...func(*iam.Options)) (*iam.ListGroupsOutput, error) {
return &iam.ListGroupsOutput{
Groups: []iamTypes.Group{
{
Arn: aws.String("arn:aws:iam::123456789012:group/group1"),
CreateDate: aws.Time(time.Now()),
GroupId: aws.String("123456789012"),
GroupName: aws.String("group1"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:group/group2"),
CreateDate: aws.Time(time.Now()),
GroupId: aws.String("123456789012"),
GroupName: aws.String("group2"),
Path: aws.String("/"),
},
},
}, nil
}
func (m *MockedIAMClient) ListRoles(ctx context.Context, input *iam.ListRolesInput, options ...func(*iam.Options)) (*iam.ListRolesOutput, error) {
return &iam.ListRolesOutput{
Roles: []iamTypes.Role{
{
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role1"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:role/role2"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role2"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:role/role3"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role3"),
Path: aws.String("/"),
},
},
}, nil
}
func (m *MockedIAMClient) GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error) {
return &iam.GetAccountAuthorizationDetailsOutput{
GroupDetailList: []iamTypes.GroupDetail{
{
Arn: aws.String("arn:aws:iam::123456789012:group/group1"),
CreateDate: aws.Time(time.Now()),
GroupId: aws.String("123456789012"),
GroupName: aws.String("group1"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:group/group2"),
CreateDate: aws.Time(time.Now()),
GroupId: aws.String("123456789012"),
GroupName: aws.String("group2"),
Path: aws.String("/"),
},
},
RoleDetailList: []iamTypes.RoleDetail{
{
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role1"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:role/role2"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role2"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:role/role3"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role3"),
Path: aws.String("/"),
},
},
UserDetailList: []iamTypes.UserDetail{
{
Arn: aws.String("arn:aws:iam::123456789012:user/user1"),
CreateDate: aws.Time(time.Now()),
UserId: aws.String("123456789012"),
UserName: aws.String("user1"),
Path: aws.String("/"),
},
{
Arn: aws.String("arn:aws:iam::123456789012:user/user2"),
CreateDate: aws.Time(time.Now()),
UserId: aws.String("123456789012"),
UserName: aws.String("user2"),
Path: aws.String("/"),
},
},
}, nil
}
func (m *MockedIAMClient) SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error) {
return &iam.SimulatePrincipalPolicyOutput{
EvaluationResults: []iamTypes.EvaluationResult{
{
EvalActionName: aws.String("sts:AssumeRole"),
EvalDecision: iamTypes.PolicyEvaluationDecisionTypeAllowed,
EvalResourceName: aws.String("arn:aws:iam::123456789012:role/role1"),
MatchedStatements: []iamTypes.Statement{
{
SourcePolicyId: aws.String("PolicyForRole1"),
SourcePolicyType: iamTypes.PolicySourceTypeUser,
StartPosition: &iamTypes.Position{
Column: 0,
Line: 0,
},
},
},
},
},
}, nil
}
func (m *MockedIAMClient) ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error) {
return &iam.ListInstanceProfilesOutput{
InstanceProfiles: []iamTypes.InstanceProfile{
{
Arn: aws.String("arn:aws:iam::123456789012:instance-profile/instance-profile1"),
CreateDate: aws.Time(time.Now()),
InstanceProfileId: aws.String("123456789012"),
InstanceProfileName: aws.String("instance-profile1"),
Path: aws.String("/"),
Roles: []iamTypes.Role{
{
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
CreateDate: aws.Time(time.Now()),
RoleId: aws.String("123456789012"),
RoleName: aws.String("role1"),
Path: aws.String("/"),
},
},
},
},
}, nil
}
+55
View File
@@ -0,0 +1,55 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/kinesis"
"github.com/patrickmn/go-cache"
)
type AWSKinesisClientInterface interface {
ListStreams(context.Context, *kinesis.ListStreamsInput, ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error)
}
func init() {
gob.Register([]string{})
}
func CachedKinesisListStreams(client AWSKinesisClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var streams []string
cacheKey := fmt.Sprintf("%s-kinesis-ListStreams-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListStreams, err := client.ListStreams(
context.TODO(),
&kinesis.ListStreamsInput{
NextToken: PaginationControl,
},
func(o *kinesis.Options) {
o.Region = region
},
)
if err != nil {
return streams, err
}
streams = append(streams, ListStreams.StreamNames...)
//pagination
if ListStreams.NextToken == nil {
break
}
PaginationControl = ListStreams.NextToken
}
internal.Cache.Set(cacheKey, streams, cache.DefaultExpiration)
return streams, nil
}
+21
View File
@@ -0,0 +1,21 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/kinesis"
)
type MockedKinesisClient struct {
}
func (m *MockedKinesisClient) ListStreams(ctx context.Context, input *kinesis.ListStreamsInput, options ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error) {
return &kinesis.ListStreamsOutput{
HasMoreStreams: aws.Bool(false),
StreamNames: []string{
"stream1",
"stream2",
},
}, nil
}
+1 -1
View File
@@ -17,7 +17,7 @@ type LambdaClientInterface interface {
GetFunctionUrlConfig(context.Context, *lambda.GetFunctionUrlConfigInput, ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error)
}
func RegisterLambdaTypes() {
func init() {
gob.Register([]lambdaTypes.FunctionConfiguration{})
gob.Register(customGetFuntionURLOutput{})
}
+62
View File
@@ -0,0 +1,62 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/lambda"
lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
)
type MockedLambdaClient struct {
}
func (m *MockedLambdaClient) ListFunctions(ctx context.Context, input *lambda.ListFunctionsInput, options ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
return &lambda.ListFunctionsOutput{
Functions: []lambdaTypes.FunctionConfiguration{
{
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
FunctionName: aws.String("my-function"),
Handler: aws.String("index.handler"),
Runtime: lambdaTypes.RuntimeNodejs18x,
Environment: &lambdaTypes.EnvironmentResponse{
Variables: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
{
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function2"),
FunctionName: aws.String("my-function2"),
Handler: aws.String("index.handler"),
Runtime: lambdaTypes.RuntimeNodejs18x,
},
},
}, nil
}
func (m *MockedLambdaClient) GetFunction(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error) {
return &lambda.GetFunctionOutput{
Configuration: &lambdaTypes.FunctionConfiguration{
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
FunctionName: aws.String("my-function"),
Handler: aws.String("index.handler"),
Runtime: lambdaTypes.RuntimeNodejs18x,
Environment: &lambdaTypes.EnvironmentResponse{
Variables: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
}, nil
}
func (m *MockedLambdaClient) GetFunctionUrlConfig(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error) {
return &lambda.GetFunctionUrlConfigOutput{
FunctionUrl: aws.String("https://my-function.us-east-1.amazonaws.com/Prod/"),
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
AuthType: lambdaTypes.FunctionUrlAuthTypeNone,
}, nil
}
+6 -2
View File
@@ -16,8 +16,12 @@ type lightsailClientInterface interface {
GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error)
}
func RegisterLightsailTypes() {
//gob.Register([]lightsailTypes.Instance{})
func init() {
//need to do this to avoid conflicts with the Instance type in the ec2 package
type lightsailInstance lightsailTypes.Instance
gob.Register([]lightsailInstance{})
gob.Register([]lightsailTypes.ContainerService{})
}
+80
View File
@@ -0,0 +1,80 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types"
)
type MockedLightsailClient struct {
}
func (m *MockedLightsailClient) GetInstances(ctx context.Context, input *lightsail.GetInstancesInput, options ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error) {
return &lightsail.GetInstancesOutput{
Instances: []lightsailTypes.Instance{
{
BlueprintId: aws.String("blueprint1"),
BundleId: aws.String("bundle1"),
CreatedAt: aws.Time(time.Now()),
Location: &lightsailTypes.ResourceLocation{
AvailabilityZone: aws.String("us-east-1a"),
RegionName: lightsailTypes.RegionNameUsEast1,
},
Name: aws.String("instance1"),
Networking: &lightsailTypes.InstanceNetworking{
MonthlyTransfer: &lightsailTypes.MonthlyTransfer{
GbPerMonthAllocated: aws.Int32(1),
},
},
PrivateIpAddress: aws.String("10.1.1.1"),
PublicIpAddress: aws.String("1.2.3.4"),
},
{
BlueprintId: aws.String("blueprint2"),
BundleId: aws.String("bundle2"),
CreatedAt: aws.Time(time.Now()),
Location: &lightsailTypes.ResourceLocation{
AvailabilityZone: aws.String("us-east-1b"),
RegionName: lightsailTypes.RegionNameUsEast1,
},
Name: aws.String("instance2"),
Networking: &lightsailTypes.InstanceNetworking{
MonthlyTransfer: &lightsailTypes.MonthlyTransfer{
GbPerMonthAllocated: aws.Int32(2),
},
},
PrivateIpAddress: aws.String("10.2.2.2"),
PublicIpAddress: aws.String("2.3.4.4"),
},
},
}, nil
}
func (m *MockedLightsailClient) GetContainerServices(ctx context.Context, input *lightsail.GetContainerServicesInput, options ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error) {
return &lightsail.GetContainerServicesOutput{
ContainerServices: []lightsailTypes.ContainerService{
{
Arn: aws.String("arn1"),
Location: &lightsailTypes.ResourceLocation{
AvailabilityZone: aws.String("us-east-1a"),
RegionName: lightsailTypes.RegionNameUsEast1,
},
Url: aws.String("https://container1"),
PrivateDomainName: aws.String("container1"),
},
{
Arn: aws.String("arn2"),
Location: &lightsailTypes.ResourceLocation{
AvailabilityZone: aws.String("us-east-1a"),
RegionName: lightsailTypes.RegionNameUsEast1,
},
Url: aws.String("https://container2"),
PrivateDomainName: aws.String("container2"),
},
},
}, nil
}
+1 -1
View File
@@ -15,7 +15,7 @@ type MQClientInterface interface {
ListBrokers(context.Context, *mq.ListBrokersInput, ...func(*mq.Options)) (*mq.ListBrokersOutput, error)
}
func RegisterMQTypes() {
func init() {
gob.Register([]mqTypes.BrokerSummary{})
}
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff. Show more