Compare commits

...
Author SHA1 Message Date
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
sethsec-bf c11ebf9a07 fixed newly introduced eks bug in the caching function (region and cluster name were passed in the wrong order) 2023-06-14 17:01:53 -04:00
sethsec-bf 6c8936cfe4 fixed two segfault issues due to not checking if fields within a struct are nil 2023-06-14 16:24:19 -04:00
Seth Art fca0c4da14 Merge pull request #44 from BishopFox/seth-dev
Switched default behavior on loading cached data from disk.
2023-06-14 09:49:58 -07:00
sethsec-bf 2c5fec49c3 loading cache from files by default was a bad idea. changed default to not load cache from disk, and if the -c flag is used, then cache is loaded from disk 2023-06-14 12:46:04 -04:00
sethsec-bf a9b95d9142 merged from main 2023-06-14 12:33:05 -04:00
Seth Art 0a74adea9a Merge pull request #43 from wdahlenburg/api-gw-patch
API Gateway Path Fix
2023-06-14 09:31:02 -07:00
Wyatt Dahlenburg adb9bcd55a Handle stage in apigatewayv2 2023-06-13 16:00:02 -06:00
Wyatt Dahlenburg 03177e8726 Fix paths on API Gateways & V2. Clean up path for S3 bucket endpoints as well 2023-06-13 15:01:54 -06:00
sethsec-bf 1fb9f4c3e0 fixed services table to sort by service which is way more helpful 2023-06-10 23:16:45 -04:00
Seth Art 3c6c59ec4b Update README.md 2023-06-09 14:33:51 -04:00
Seth Art 3f8b828a6c Merge pull request #41 from BishopFox/seth-dev
found and fixed some bugs in the new resource-trusts command
2023-06-09 14:32:01 -04:00
sethsec-bf b8bf12f1c4 found and fixed some bugs in the new resource-trusts command 2023-06-09 14:30:39 -04:00
sethsec-bf 690b3c8c77 found and fixed some bugs in the new resource-trusts command 2023-06-09 14:29:33 -04:00
Seth Art 845b69e9a0 Merge pull request #40 from BishopFox/seth-dev
updated lambda header to name instead of arn
2023-06-09 10:46:46 -04:00
sethsec-bf fc7a57129f updated lambda header to name instead of arn 2023-06-09 10:46:13 -04:00
Seth Art f6ec6a12f7 Merge pull request #39 from BishopFox/seth-dev
Version 1.11.0
2023-06-08 21:49:55 -04:00
sethsec-bf 56075588d5 bumped version to 1.11.0 2023-06-08 21:45:17 -04:00
sethsec-bf 7b51d9da96 updated tests 2023-06-08 21:43:42 -04:00
sethsec-bf a0a64c579c bumped go version for releaser 2023-06-08 21:43:20 -04:00
sethsec-bf 2b294e5845 updated wrapping for files written to disk, updated go modules 2023-06-08 21:33:20 -04:00
sethsec-bf d5956cebb0 tweak to database command cols 2023-06-08 21:05:40 -04:00
sethsec-bf 9581425231 updates to resource-trust command 2023-06-08 16:19:57 -04:00
sethsec-bf ce6c3f76fa migrated ecr and a few other functions in the endpoints and env-vars commands to use the cached functions 2023-06-03 12:41:10 -04:00
sethsec-bf 497c7940d2 migrated ecr to cached functions 2023-05-31 12:56:31 -04:00
sethsec-bf b29e4ea6b3 changed output directory, added beginning phases of cached support - both within one single run, but also loading cached data from previous runs, added codebuild command, added orgs command, added databases command, switched everything to use carlos's output2 functions, changed the output flag from type (csv,table) to be like kubectl with wide/narrow 2023-05-30 10:55:39 -04:00
sethsec-bf 2756cd0f45 changed default output dir to ~/.cloudfox/cloudfox-output 2023-05-23 15:32:43 -04:00
sethsec-bf c0597764a9 reworked the option to specify a principal so it works with groups also. i don't think it was working before 2023-05-18 15:58:27 -04:00
sethsec-bf ef1b37cef7 Added conditions col to the output to at least say if conditions exist on a permission 2023-05-10 20:34:03 -04:00
sethsec-bf 8e9bd42a1e finally added some logic so that if the user submits the name of the user/role/group instead of hte arn it will work anyway 2023-04-27 09:59:59 -04:00
sethsec-bf 8b2f13ca88 added ssm and ec2-instanceconnect commands to instances loot 2023-04-13 11:45:06 -04:00
sethsec-bf 8722782eae merged from main 2023-04-04 21:26:37 -04:00
sethsec-bf 5f7596b497 more draft work on the orgs command 2023-04-04 16:11:08 -04:00
sethsec-bf 0276ce25a4 initial draft of the orgs command 2023-03-30 07:35:48 -04:00
sethsec-bf 7490d1aaa1 fixed bug that wrote buckets, sns, sqs files in the wrong place when using environment variables 2023-03-28 15:18:01 -04:00
sethsec-bf 766fe55484 added more to resource trusts module, started to move commands to shared initalizers 2023-03-26 15:15:31 -04:00
sethsec-bf 186cd04405 Finished codebuild test, added codeguild to resource-trusts command, updated ecr test to add new function to the client interface 2023-03-21 12:09:45 -04:00
sethsec-bf 56247f40d4 first draft of resource-trusts command 2023-03-18 07:52:27 -04:00
sethsec-bf bc91da000c fixed bug in permissions command where it was choking on statements with conditions 2023-03-18 07:50:43 -04:00
sethsec-bf 1aca8cf4cf fixed bug in filesystems command related mount targets 2023-03-13 13:22:58 -04:00
sethsec-bf 7f413fc9d1 fixed bug in filesystems command 2023-03-09 13:31:34 -05:00
sethsec-bf f2f93779ca updated inventory to include codebuild and started to break it out a bit into api call functions 2023-03-08 09:19:19 -05:00
sethsec-bf 59157b40d1 updated EFS to show file permissions and root directory info 2023-02-28 18:17:50 -05:00
155 changed files with 15676 additions and 4617 deletions

No files matched your search

+4 -4
View File
@@ -15,10 +15,10 @@ jobs:
steps:
- name: Go 1.18
uses: actions/setup-go@v2
- name: Go 1.21
uses: actions/setup-go@v4
with:
go-version: ^1.18
go-version: ^1.20
id: go
- id: install-secret-key
@@ -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"
+5 -1
View File
@@ -66,4 +66,8 @@ output/*
cloudfox
*.log
*.bak
table_output.txt
*.txt
*.json
*.csv
*.log
dist/
+11 -4
View File
@@ -39,7 +39,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
| Provider| CloudFox Commands |
| - | - |
| AWS | 26 |
| AWS | 30 |
| Azure | 4 |
| GCP | Support Planned |
| Kubernetes | Support Planned |
@@ -49,11 +49,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 +71,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 +145,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.
+40 -46
View File
@@ -1,22 +1,21 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type AccessKeysModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSProfile string
OutputFormat string
@@ -38,7 +37,7 @@ type UserKeys struct {
}
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat 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 = "access-keys"
@@ -79,12 +78,27 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
fmt.Printf("[%s][%s] Only active access keys are shown.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Preparing output.\n\n")
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
//m.writeLoot(m.output.FilePath, verbosity)
fmt.Printf("[%s][%s] %s access keys found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No access keys found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -116,50 +130,30 @@ func (m *AccessKeysModule) writeLoot(outputDirectory string, verbosity int) {
func (m *AccessKeysModule) getAccessKeysForAllUsers() {
// The "PaginationControl" value remains true until all data is received (default is 100 results per page).
// "" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
PaginationControl := true
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for PaginationControl {
// added this to break out if there no users
if len(ListUsers) != 0 {
for _, user := range ListUsers {
IAMQuery, err := m.IAMClient.ListUsers(context.TODO(), &iam.ListUsersInput{Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
results, err := sdk.CachedIamListAccessKeys(m.IAMClient, aws.ToString(m.Caller.Account), aws.ToString(user.UserName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// added this to break out if there no users
if len(IAMQuery.Users) == 0 {
break
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results {
// This for loop iterates through all users.
for PaginationControl {
PaginationMarker = nil
for _, user := range IAMQuery.Users {
results, err := m.IAMClient.ListAccessKeys(context.TODO(), &iam.ListAccessKeysInput{UserName: user.UserName, Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results.AccessKeyMetadata {
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
}
PaginationControl = results.IsTruncated
PaginationMarker = results.Marker
}
}
PaginationControl = IAMQuery.IsTruncated
PaginationMarker = IAMQuery.Marker
}
}
+35 -108
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,37 +9,24 @@ import (
"sync"
"unicode/utf8"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type AWSS3Client interface {
ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error)
GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error)
GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error)
GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error)
}
type BucketsModule struct {
// General configuration data
S3Client AWSS3Client
//BucketsS3Client CloudFoxS3Client
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
// This interface is used for unit testing
// S3ClientListBucketsInterface AWSS3Client
// S3ClientGetBucketPolicyInterface AWSS3Client
// S3ClientGetBucketLocationInterface AWSS3Client
// S3ClientGetPublicAccessBlockInterface AWSS3Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
@@ -68,18 +54,18 @@ type Bucket struct {
}
func (m *BucketsModule) PrintBuckets(outputFormat 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 = "buckets"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating buckets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -134,8 +120,25 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
}
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)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s buckets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Bucket policies written to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
@@ -225,7 +228,9 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}()
var region string = "Global"
var name string
ListBuckets, err := m.listBuckets()
ListBuckets, err := sdk.CachedListBuckets(m.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
@@ -236,13 +241,13 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
Name: aws.ToString(b.Name),
AWSService: "S3",
}
region, err = m.getBucketRegion(aws.ToString(b.Name))
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
}
bucket.Region = region
policyJSON, err := m.getBucketPolicy(aws.ToString(b.Name))
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 {
@@ -270,86 +275,8 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}
func (m *BucketsModule) listBuckets() ([]types.Bucket, error) {
var buckets []types.Bucket
ListBuckets, err := m.S3Client.ListBuckets(
context.TODO(),
&s3.ListBucketsInput{},
)
if err != nil {
m.modLog.Error(err.Error())
return buckets, err
}
buckets = append(buckets, ListBuckets.Buckets...)
return buckets, nil
}
func (m *BucketsModule) getBucketRegion(bucketName string) (string, error) {
GetBucketRegion, err := m.S3Client.GetBucketLocation(
context.TODO(),
&s3.GetBucketLocationInput{
Bucket: &bucketName,
},
)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
location := string(GetBucketRegion.LocationConstraint)
if location == "" {
location = "us-east-1"
}
return location, err
}
func (m *BucketsModule) getBucketPolicy(bucketName string) (string, error) {
r, err := m.getBucketRegion(bucketName)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
BucketPolicyObject, err := m.S3Client.GetBucketPolicy(
context.TODO(),
&s3.GetBucketPolicyInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
return *BucketPolicyObject.Policy, nil
}
func (m *BucketsModule) getPublicAccessBlock(bucketName string) (*types.PublicAccessBlockConfiguration, error) {
r, err := m.getBucketRegion(bucketName)
PublicAccessBlock, err := m.S3Client.GetPublicAccessBlock(
context.TODO(),
&s3.GetPublicAccessBlockInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
return nil, err
}
return PublicAccessBlock.PublicAccessBlockConfiguration, err
}
func (m *BucketsModule) isPublicAccessBlocked(bucketName string) bool {
publicAccessBlock, err := m.getPublicAccessBlock(bucketName)
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
publicAccessBlock, err := sdk.CachedGetPublicAccessBlock(m.S3Client, aws.ToString(m.Caller.Account), r, bucketName)
if err != nil {
return false
}
@@ -360,7 +287,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string) bool {
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
m.storeAccessPolicy(bucket)
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name) {
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
bucket.IsPublic = "YES"
}
+20 -18
View File
@@ -16,11 +16,10 @@ import (
"github.com/spf13/afero"
)
type MockedS3Buckets struct {
AWSRegions []string
type MockedS3Client struct {
}
func (m *MockedS3Buckets) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
func (m *MockedS3Client) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
buckets := make([]types.Bucket, 1)
mockBucket := types.Bucket{
CreationDate: aws.Time(time.Now()),
@@ -34,7 +33,7 @@ func (m *MockedS3Buckets) ListBuckets(ctx context.Context, params *s3.ListBucket
}
func (m *MockedS3Buckets) GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) {
func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) {
bucketPolicy := `{
"Version": "2012-10-17",
"Statement": [
@@ -53,7 +52,7 @@ func (m *MockedS3Buckets) GetBucketPolicy(ctx context.Context, params *s3.GetBuc
return output, nil
}
func (m *MockedS3Buckets) GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) {
func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) {
locationConstraint := types.BucketLocationConstraint("us-east-2")
output := &s3.GetBucketLocationOutput{
LocationConstraint: locationConstraint,
@@ -61,7 +60,7 @@ func (m *MockedS3Buckets) GetBucketLocation(ctx context.Context, params *s3.GetB
return output, nil
}
func (m *MockedS3Buckets) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
output := &s3.GetPublicAccessBlockOutput{
PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{
BlockPublicAcls: true,
@@ -74,6 +73,19 @@ func (m *MockedS3Buckets) GetPublicAccessBlock(ctx context.Context, params *s3.G
}
func TestListBuckets(t *testing.T) {
m := BucketsModule{
S3Client: &MockedS3Client{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
Goroutines: 3,
}
subtests := []struct {
name string
outputDirectory string
@@ -85,17 +97,7 @@ func TestListBuckets(t *testing.T) {
name: "test1",
outputDirectory: ".",
verbosity: 2,
testModule: BucketsModule{
S3Client: &MockedS3Buckets{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "unittesting",
Goroutines: 30,
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
},
testModule: m,
expectedResult: []Bucket{{
Name: "mockBucket123",
}},
@@ -110,7 +112,7 @@ func TestListBuckets(t *testing.T) {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
for index, expectedBucket := range subtest.expectedResult {
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/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)
+126
View File
@@ -0,0 +1,126 @@
package aws
import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/fsx"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/organizations"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
iamSimMod := IamSimulatorModule{
IAMClient: iamSimPPClient,
Caller: caller,
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return iamSimMod
}
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
cloudFoxSNSClient := SNSModule{
SNSClient: sns.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
}
return cloudFoxSNSClient
}
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
cloudFoxS3Client := BucketsModule{
S3Client: s3.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return cloudFoxS3Client
}
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
sqsClient := SQSModule{
SQSClient: sqs.NewFromConfig(AWSConfig),
Caller: caller,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return sqsClient
}
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
lambdaClient := LambdasModule{
LambdaClient: lambda.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return lambdaClient
}
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
codeBuildClient := CodeBuildModule{
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return codeBuildClient
}
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
ecrClient := ECRModule{
ECRClient: ecr.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return ecrClient
}
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
fileSystemsClient := FilesystemsModule{
EFSClient: efs.NewFromConfig(AWSConfig),
FSxClient: fsx.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return fileSystemsClient
}
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
orgClient := OrgModule{
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return orgClient
}
+24 -71
View File
@@ -1,29 +1,24 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CloudFormationGetTemplateAPIClient interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
}
type CloudformationModule struct {
// General configuration data
CloudFormationDescribeStacksInterface cloudformation.DescribeStacksAPIClient
CloudFormationGetTemplateInterface CloudFormationGetTemplateAPIClient
CloudFormationClient sdk.CloudFormationClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -51,7 +46,7 @@ type CFStack struct {
}
func (m *CloudformationModule) PrintCloudformationStacks(outputFormat 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 = "cloudformation"
@@ -135,11 +130,27 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s cloudformation stacks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No cloudformation stacks found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -251,7 +262,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}()
var stackTemplateBody string = ""
DescribeStacks, err := m.describeStacks(r)
DescribeStacks, err := sdk.CachedCloudFormationDescribeStacks(m.CloudFormationClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -263,7 +274,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
stackOutputs := stack.Outputs
stackParameters := stack.Parameters
stackTemplateBody, err = m.getTemplateBody(r, stackName)
stackTemplateBody, err = sdk.CachedCloudFormationGetTemplate(m.CloudFormationClient, aws.ToString(m.Caller.Account), r, stackName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -281,61 +292,3 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}
}
func (m *CloudformationModule) describeStacks(r string) ([]types.Stack, error) {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var stacks []types.Stack
for {
DescribeStacks, err := m.CloudFormationDescribeStacksInterface.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
// The "NextToken" value is nil when there's no more data to return.
if DescribeStacks.NextToken != nil {
PaginationControl = DescribeStacks.NextToken
} else {
PaginationControl = nil
break
}
}
return stacks, nil
}
func (m *CloudformationModule) getTemplateBody(r string, stackName string) (string, error) {
var stackTemplateBody string
GetTemplate, err := m.CloudFormationGetTemplateInterface.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
return stackTemplateBody, nil
}
+7 -73
View File
@@ -1,80 +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 MockedCloudformationClientDescribeStacks struct {
describeStacks Stacks
}
type MockedCloudformationClientGetTemplate struct {
getTemplateBody TemplateBody
}
func (m *MockedCloudformationClientDescribeStacks) 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 *MockedCloudformationClientGetTemplate) 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 TestCloudFormation(t *testing.T) {
subtests := []struct {
name string
@@ -88,13 +23,12 @@ func TestCloudFormation(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: CloudformationModule{
CloudFormationDescribeStacksInterface: &MockedCloudformationClientDescribeStacks{},
CloudFormationGetTemplateInterface: &MockedCloudformationClientGetTemplate{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
CloudFormationClient: &sdk.MockedCloudformationClient{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
expectedResult: []CFStack{{
Name: "myteststack",
+252
View File
@@ -0,0 +1,252 @@
package aws
import (
"fmt"
"path/filepath"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CodeBuildModule struct {
// General configuration data
CodeBuildClient sdk.CodeBuildClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Main module data
Projects []Project
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Project struct {
Region string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
}
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "codebuild"
localAdminMap := make(map[string]bool)
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating CodeBuild projects for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
//create a channel to receive the objects
dataReceiver := make(chan Project)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
// Perform role analysis
if m.pmapperError == nil {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Projects[i].Role)
}
} else {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Projects[i].Role, m.iamSimClient, localAdminMap)
}
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
// Table rows
for i := range m.Projects {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
},
)
}
}
var seen []string
for _, project := range m.Projects {
if !internal.Contains(project.Name, seen) {
seen = append(seen, project.Name)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
//fmt.Printf("[%s][%s] %d projects with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d projects found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
} else {
fmt.Printf("[%s][%s] No projects found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
}
func (m *CodeBuildModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getcodeBuildProjectsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *CodeBuildModule) Receiver(receiver chan Project, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Projects = append(m.Projects, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *CodeBuildModule) getcodeBuildProjectsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
projects, err := sdk.CachedCodeBuildListProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
}
for _, project := range projects {
details, err := sdk.CachedCodeBuildBatchGetProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r, project)
if err != nil {
sharedLogger.Error(err.Error())
}
dataReceiver <- Project{
Name: aws.ToString(details.Name),
Region: r,
Role: aws.ToString(details.ServiceRole),
Admin: "",
CanPrivEsc: "",
}
}
}
+150
View File
@@ -0,0 +1,150 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
type mockedCodeBuildClient struct {
}
func (m *mockedCodeBuildClient) ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) {
out := codebuild.ListProjectsOutput{
Projects: []string{"test1", "test2"},
}
return &out, nil
}
func (m *mockedCodeBuildClient) BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error) {
project1out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test1"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test1"),
Source: &types.ProjectSource{
Type: "CODECOMMIT",
Location: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test1"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test1"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
project2out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test2"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test2"),
Source: &types.ProjectSource{
Type: "GITHUB",
Location: aws.String("https://test.github.com/test2"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test2"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
if params.Names[0] == "test1" {
return &project1out, nil
} else if params.Names[0] == "test2" {
return &project2out, nil
} else {
return nil, nil
}
}
func (m *mockedCodeBuildClient) GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
out := codebuild.GetResourcePolicyOutput{
Policy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"codebuild:StartBuild",
"codebuild:BatchGetBuilds",
"codebuild:StopBuild",
"codebuild:ListProjects",
"codebuild:BatchGetProjects"
],
"Resource": "*"
}
]
}`),
}
return &out, nil
}
func TestCodeBuildProjects(t *testing.T) {
m := CodeBuildModule{
CodeBuildClient: &mockedCodeBuildClient{},
AWSProfile: "unittesting",
AWSRegions: []string{"us-west-1"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
WrapTable: true,
SkipAdminCheck: true,
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintCodeBuildProjects("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/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)
}
expectedResults := strings.TrimLeft(`
╭───────────┬───────┬──────────────────────────────────────┬──────────────╮
│ Region │ Name │ Role │ IsAdminRole? │
├───────────┼───────┼──────────────────────────────────────┼──────────────┤
│ us-west-1 │ test1 │ arn:aws:iam::123456789012:role/test1 │ Skipped │
│ us-west-1 │ test2 │ arn:aws:iam::123456789012:role/test2 │ Skipped │
╰───────────┴───────┴──────────────────────────────────────┴──────────────╯
`, "\n")
fmt.Println(expectedResults)
fmt.Println("results file")
fmt.Println(string(resultsFile))
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
}
+451
View File
@@ -0,0 +1,451 @@
package aws
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type DatabasesModule struct {
RDSClient sdk.RDSClientInterface
RedshiftClient sdk.AWSRedShiftClientInterface
DynamoDBClient sdk.DynamoDBClientInterface
DocDBClient sdk.DocDBClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Databases []Database
CommandCounter internal.CommandCounter
Errors []string
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Database struct {
AWSService string
Region string
Engine string
Name string
Arn string
UserName string
Endpoint string
Port int32
Protocol string
Public string
Size string
}
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "databases"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating databases for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: RDS, DynamoDB\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Database)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
//execute regional checks
for _, region := range m.AWSRegions {
wg.Add(1)
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
sort.Slice(m.Databases, func(i, j int) bool {
return m.Databases[i].AWSService < m.Databases[j].AWSService
})
m.output.Headers = []string{
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
//"Port",
//"Protocol",
//"Public",
}
// Table rows
for i := range m.Databases {
m.output.Body = append(
m.output.Body,
[]string{
m.Databases[i].AWSService,
m.Databases[i].Engine,
m.Databases[i].Region,
m.Databases[i].Name,
m.Databases[i].Size,
m.Databases[i].UserName,
m.Databases[i].Endpoint,
// strconv.Itoa(int(m.Databases[i].Port)),
// m.Databases[i].Protocol,
// m.Databases[i].Public,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s databases found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No databases found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *DatabasesModule) Receiver(receiver chan Database, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Databases = append(m.Databases, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *DatabasesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("rds", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getRdsClustersPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("redshift", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getRedshiftDatabasesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("dynamodb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDynamoDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("docdb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDocDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
f := filepath.Join(path, "databases-UrlsOnly.txt")
var out string
for _, database := range m.Databases {
out = out + fmt.Sprintln(database.Endpoint)
}
err = os.WriteFile(f, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed this databases into nmap and something like gowitness/aquatone for screenshots."))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
}
func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
m.CommandCounter.Pending--
m.CommandCounter.Executing++
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var public string
for _, instance := range DBInstances {
if instance.Endpoint != nil {
name := aws.ToString(instance.DBInstanceIdentifier)
port := instance.Endpoint.Port
endpoint := aws.ToString(instance.Endpoint.Address)
engine := aws.ToString(instance.Engine)
if instance.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
dataReceiver <- Database{
AWSService: "RDS",
Region: r,
Name: name,
Engine: engine,
Endpoint: endpoint,
UserName: aws.ToString(instance.MasterUsername),
Port: port,
Protocol: aws.ToString(instance.Engine),
Public: public,
}
}
}
}
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "Redshift"
protocol := "https"
Clusters, err := sdk.CachedRedShiftDescribeClusters(m.RedshiftClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var public string
for _, cluster := range Clusters {
name := aws.ToString(cluster.DBName)
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
port := cluster.Endpoint.Port
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
}
}
}
func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DynamoDB"
Tables, err := sdk.CachedDynamoDBListTables(m.DynamoDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, table := range Tables {
//get size of dynamodb table
TableOutput, err := sdk.CachedDynamoDBDescribeTable(m.DynamoDBClient, aws.ToString(m.Caller.Account), r, table)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
size := aws.ToInt64(TableOutput.TableSizeBytes)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: table,
Size: strconv.Itoa(int(size)),
UserName: "N/A",
Endpoint: "N/A",
}
}
}
func (m *DatabasesModule) getDocDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DocDB"
Clusters, err := sdk.CachedDocDBDescribeDBClusters(m.DocDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, cluster := range Clusters {
name := aws.ToString(cluster.DBClusterIdentifier)
endpoint := aws.ToString(cluster.Endpoint)
port := aws.ToInt32(cluster.Port)
//size := aws.ToInt64(TableOutput.Table.TableSizeBytes)
userName := aws.ToString(cluster.MasterUsername)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
UserName: userName,
//Size: strconv.Itoa(int(size)),
}
}
}
+119 -108
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,9 +9,10 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
@@ -21,11 +21,7 @@ import (
type ECRModule struct {
// General configuration data
ECRClient *ecr.Client
// These interfaces are used for unit testing
ECRClientDescribeReposInterface ecr.DescribeRepositoriesAPIClient
ECRClientDescribeImagesInterface ecr.DescribeImagesAPIClient
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -49,10 +45,12 @@ type Repository struct {
PushedAt string
ImageTags string
ImageSize int64
Policy policy.Policy
PolicyJSON string
}
func (m *ECRModule) PrintECR(outputFormat 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 = "ecr"
@@ -125,11 +123,27 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s repositories found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No repositories found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -224,107 +238,104 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
defer func() {
<-semaphore
}()
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var PaginationControl2 *string
for {
DescribeRepositories, err := m.ECRClientDescribeReposInterface.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = r
},
)
var allImages []types.ImageDetail
var repoURI string
var repoName string
DescribeRepositories, err := m.describeRepositories(r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, repo := range DescribeRepositories {
repoName = aws.ToString(repo.RepositoryName)
repoURI = aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
images, err := m.describeImages(r, repoName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, repo := range DescribeRepositories.Repositories {
repoName := aws.ToString(repo.RepositoryName)
repoURI := aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
var images []types.ImageDetail
for {
DescribeImages, err := m.ECRClientDescribeImagesInterface.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
RepositoryName: &repoName,
NextToken: PaginationControl2,
},
func(o *ecr.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
//images := DescribeImages.ImageDetails
images = append(images, DescribeImages.ImageDetails...)
if DescribeImages.NextToken != nil {
PaginationControl2 = DescribeImages.NextToken
} else {
// not sure if this is the right way to do this, but adding this code here was the only way i could
// sort the results from all pages to look for the latest push.
PaginationControl2 = nil
sort.Slice(images, func(i, j int) bool {
return images[i].ImagePushedAt.Format("2006-01-02 15:04:05") < images[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
if len(images) > 1 {
image = images[len(images)-1]
} else if len(images) == 1 {
image = images[0]
} else {
break
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
// }
break
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
return
}
allImages = append(allImages, images...)
}
sort.Slice(allImages, func(i, j int) bool {
return allImages[i].ImagePushedAt.Format("2006-01-02 15:04:05") < allImages[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
if len(allImages) > 1 {
image = allImages[len(allImages)-1]
} else if len(allImages) == 1 {
image = allImages[0]
} else {
return
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
}
func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
var repositories []types.Repository
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
repositories = append(repositories, Repositories...)
return repositories, nil
}
func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
var images []types.ImageDetail
ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
images = append(images, ImageDetails...)
return images, nil
}
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
var repoPolicy policy.Policy
Policy, err := sdk.CachedECRGetRepositoryPolicy(m.ECRClient, aws.ToString(m.Caller.Account), r, repository)
if err != nil {
m.CommandCounter.Error++
return repoPolicy, err
}
repoPolicy, err = policy.ParseJSONPolicy([]byte(Policy))
if err != nil {
return repoPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", repository, err)
}
return repoPolicy, nil
}
+19 -15
View File
@@ -15,15 +15,10 @@ import (
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
type MockedECRClientDescribeRepos struct {
AWSRegions []string
type MockedECRClient struct {
}
type MockedECRClientDescribeImages struct {
AWSRegions []string
}
func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []types.Repository{
{
@@ -34,7 +29,7 @@ func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context,
}, nil
}
func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []types.ImageDetail{
{
@@ -46,7 +41,14 @@ func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.Des
}, nil
}
func (m *MockedECRClient) GetRepositoryPolicy(context.Context, *ecr.GetRepositoryPolicyInput, ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
return &ecr.GetRepositoryPolicyOutput{
PolicyText: aws.String("policyText"),
}, nil
}
func TestDescribeRepos(t *testing.T) {
subtests := []struct {
name string
outputDirectory string
@@ -59,13 +61,15 @@ func TestDescribeRepos(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ECRModule{
ECRClientDescribeReposInterface: &MockedECRClientDescribeRepos{},
ECRClientDescribeImagesInterface: &MockedECRClientDescribeImages{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
ECRClient: &MockedECRClient{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
expectedResult: []Repository{{
Name: "test1",
+121 -125
View File
@@ -2,6 +2,7 @@ package aws
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -9,27 +10,20 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type DescribeTasksDefinitionAPIClient interface {
DescribeTaskDefinition(context.Context, *ecs.DescribeTaskDefinitionInput, ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
}
type ECSTasksModule struct {
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
DescribeTasksClient ecs.DescribeTasksAPIClient
ListTasksClient ecs.ListTasksAPIClient
ListClustersClient ecs.ListClustersAPIClient
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
ECSClient sdk.AWSECSClientInterface
EC2Client sdk.AWSEC2ClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -50,15 +44,16 @@ type ECSTasksModule struct {
}
type MappedECSTask struct {
Cluster string
TaskDefinition string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
Cluster string
TaskDefinitionName string
TaskDefinitionContent string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
}
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
@@ -77,7 +72,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -124,7 +119,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
receiverDone <- true
<-receiverDone
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver)
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -141,7 +136,7 @@ func (m *ECSTasksModule) Receiver(receiver chan MappedECSTask, receiverDone chan
}
}
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask) {
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
if m.pmapperError == nil {
m.output.Headers = []string{
"Cluster",
@@ -174,7 +169,7 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinition,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
@@ -191,7 +186,7 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
m.output.Body,
[]string{
ecsTask.Cluster,
ecsTask.TaskDefinition,
ecsTask.TaskDefinitionName,
ecsTask.LaunchType,
ecsTask.ID,
ecsTask.ExternalIP,
@@ -205,11 +200,26 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s ECS tasks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -250,6 +260,24 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
m.CommandCounter.Error++
}
for _, task := range m.MappedECSTasks {
if task.TaskDefinitionContent != "" {
path := filepath.Join(path, "task-definitions")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
taskDefinitionFilename := filepath.Join(path, task.TaskDefinitionName+".json")
err = os.WriteFile(taskDefinitionFilename, []byte(task.TaskDefinitionContent), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
@@ -278,75 +306,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) {
@@ -355,16 +345,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++
@@ -372,7 +353,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)
@@ -385,14 +366,22 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
return
}
for _, task := range DescribeTasks.Tasks {
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++
return
}
mappedTask := MappedECSTask{
Cluster: getNameFromARN(clusterARN),
TaskDefinition: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: m.getTaskRole(aws.ToString(task.TaskDefinitionArn), region),
Cluster: getNameFromARN(clusterARN),
TaskDefinitionName: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
TaskDefinitionContent: getTaskDefinitionContent(taskDefinition),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: getTaskRole(taskDefinition),
}
eniID := getElasticNetworkInterfaceIDOfECSTask(task)
@@ -404,8 +393,22 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
}
func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) string {
DescribeTaskDefinition, err := m.DescribeTaskDefinitionClient.DescribeTaskDefinition(
func getTaskRole(taskDefinition types.TaskDefinition) string {
return aws.ToString(taskDefinition.TaskRoleArn)
}
func getTaskDefinitionContent(taskDefinition types.TaskDefinition) string {
// return taskDefinition as a json string
taskDefinitionContent, err := json.Marshal(taskDefinition)
if err != nil {
return ""
}
return string(taskDefinitionContent)
}
func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region string) (types.TaskDefinition, error) {
DescribeTaskDefinition, err := m.ECSClient.DescribeTaskDefinition(
context.TODO(),
&ecs.DescribeTaskDefinitionInput{
TaskDefinition: &taskDefinitionArn,
@@ -417,9 +420,9 @@ func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) st
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return ""
return types.TaskDefinition{}, err
}
return aws.ToString(DescribeTaskDefinition.TaskDefinition.TaskRoleArn)
return *DescribeTaskDefinition.TaskDefinition, nil
}
/* UNUSED CODE BLOCK - PLEASE REVIEW AND DELETE IF NOT NEEDED
@@ -455,20 +458,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)
}
+8 -246
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",
+39 -127
View File
@@ -1,18 +1,15 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -21,11 +18,8 @@ import (
type EKSModule struct {
// General configuration data
// These interfaces are used for unit testing
EKSClientListClustersInterface eks.ListClustersAPIClient
EKSClientDescribeClusterInterface eks.DescribeClusterAPIClient
EKSClientListNodeGroupsInterface eks.ListNodegroupsAPIClient
EKSClientDescribeNodeGroupInterface eks.DescribeNodegroupAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
EKSClient sdk.EKSClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -59,7 +53,7 @@ type Cluster struct {
}
func (m *EKSModule) EKS(outputFormat 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 = "eks"
@@ -76,7 +70,7 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -204,11 +198,27 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d clusters with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
} else {
fmt.Printf("[%s][%s] No clusters found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -304,16 +314,19 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
<-semaphore
}()
var clusters []string
var role string
clusters, err := m.listClusters(r)
clusters, err := sdk.CachedEKSListClusters(m.EKSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, clusterName := range clusters {
clusterDetails, err := m.describeCluster(clusterName, r)
var role string
var oidc string = ""
var publicEndpoint = ""
clusterDetails, err := sdk.CachedEKSDescribeCluster(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -321,20 +334,25 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
//nodeGroups = append(nodeGroups, DescribeCluster.Cluster.)
endpoint := aws.ToString(clusterDetails.Endpoint)
oidc := aws.ToString(clusterDetails.Identity.Oidc.Issuer)
publicEndpoint := strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
if clusterDetails.Identity != nil && clusterDetails.Identity.Oidc != nil {
oidc = aws.ToString(clusterDetails.Identity.Oidc.Issuer)
}
if clusterDetails.ResourcesVpcConfig != nil {
publicEndpoint = strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
}
// if DescribeCluster.Cluster.ResourcesVpcConfig.PublicAccessCidrs[0] == "0.0.0.0/0" {
// publicCIDRs := "0.0.0.0/0"
// } else {
// publicCIDRs := "specific IPs"
// }
ListNodeGroups := m.listNodeGroups(clusterName, r)
ListNodeGroups, err := sdk.CachedEKSListNodeGroups(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if len(ListNodeGroups) > 0 {
for _, nodeGroup := range ListNodeGroups {
nodeGroupDetails, err := m.describeNodegroup(clusterName, nodeGroup, r)
nodeGroupDetails, err := sdk.CachedEKSDescribeNodeGroup(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, nodeGroup, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -375,109 +393,3 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
}
}
func (m *EKSModule) listClusters(r string) ([]string, error) {
var PaginationControl *string
var clusters []string
for {
ListClusters, err := m.EKSClientListClustersInterface.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
// The "NextToken" value is nil when there's no more data to return.
if ListClusters.NextToken != nil {
PaginationControl = ListClusters.NextToken
} else {
PaginationControl = nil
break
}
}
return clusters, nil
}
func (m *EKSModule) listNodeGroups(clusterName string, r string) []string {
var PaginationControl *string
var nodeGroups []string
for {
ListNodeGroups, err := m.EKSClientListNodeGroupsInterface.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
return nodeGroups
}
func (m *EKSModule) describeCluster(clusterName string, r string) (*types.Cluster, error) {
var err error
//var clusterDetails types.Cluster
DescribeCluster, err := m.EKSClientDescribeClusterInterface.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeCluster.Cluster, err
}
func (m *EKSModule) describeNodegroup(clusterName string, nodeGroup string, r string) (*types.Nodegroup, error) {
DescribeNodegroup, err := m.EKSClientDescribeNodeGroupInterface.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroup,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeNodegroup.Nodegroup, err
}
+6 -21
View File
@@ -12,22 +12,10 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sts"
)
type MockedEKSClientListClusters struct {
type MockedEKSClientInterface struct {
}
type MockedEKSClientDescribeCluster struct {
}
type MockedEKSClientListNodegroups struct {
}
type MockedEKSClientDescribeNodegroup struct {
}
type MockedIAMSimulatePrincipalPolicy struct {
}
func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
func (m *MockedEKSClientInterface) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
testClusters := []string{}
testClusters = append(testClusters, "test1")
@@ -38,7 +26,7 @@ func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *
return testListClustersOutput, nil
}
func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
func (m *MockedEKSClientInterface) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
testIdentity := types.Identity{
Oidc: &types.OIDC{
Issuer: aws.String("abc123"),
@@ -61,7 +49,7 @@ func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.D
return testDescribeClusterOutput, nil
}
func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
func (m *MockedEKSClientInterface) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
testNodegroups := []string{}
testNodegroups = append(testNodegroups, "test1")
@@ -72,7 +60,7 @@ func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, para
return testListNodegroupsOutput, nil
}
func (m *MockedEKSClientDescribeNodegroup) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
func (m *MockedEKSClientInterface) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
testNodegroup := types.Nodegroup{}
testNodegroup.NodeRole = aws.String("roleABC")
testDescribeNodegroupOutput := &eks.DescribeNodegroupOutput{
@@ -95,10 +83,7 @@ func TestEks(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: EKSModule{
EKSClientListClustersInterface: &MockedEKSClientListClusters{},
EKSClientDescribeClusterInterface: &MockedEKSClientDescribeCluster{},
EKSClientListNodeGroupsInterface: &MockedEKSClientListNodegroups{},
EKSClientDescribeNodeGroupInterface: &MockedEKSClientDescribeNodegroup{},
EKSClient: &MockedEKSClientInterface{},
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
+59 -50
View File
@@ -1,16 +1,15 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/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,8 +17,7 @@ import (
)
type ElasticNetworkInterfacesModule struct {
//EC2Client *ec2.Client
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
EC2Client sdk.AWSEC2ClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -84,7 +82,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
receiverDone <- true
<-receiverDone
m.printENIsData(outputFormat, outputDirectory, dataReceiver)
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -101,7 +99,7 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
}
}
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI) {
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
m.output.Headers = []string{
"ID",
"Type",
@@ -126,11 +124,27 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s elastic network interfaces found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -197,50 +211,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("table", ".", 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)
}
+776 -650
View File
File diff suppressed because it is too large. Load diff
+70 -98
View File
@@ -8,6 +8,7 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
@@ -65,7 +66,7 @@ type EnvironmentVariable struct {
}
func (m *EnvsModule) PrintEnvs(outputFormat 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 = "env-vars"
@@ -139,10 +140,25 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] %s environment variables found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -328,38 +344,18 @@ func (m *EnvsModule) getLambdaEnvironmentVariablesPerRegion(region string, wg *s
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
// This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop).
for {
functions, err := m.LambdaClient.ListFunctions(
context.TODO(),
&lambda.ListFunctionsInput{
Marker: PaginationMarker,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, function := range functions.Functions {
m.getLambdaEnvironmentVariablesPerFunction(function, region, dataReceiver)
}
// Pagination control. After the last page of output, the for loop exits.
if functions.NextMarker != nil {
PaginationMarker = functions.NextMarker
} else {
PaginationMarker = nil
break
}
Functions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, function := range Functions {
m.getLambdaEnvironmentVariablesPerFunction(function, region, dataReceiver)
}
}
func (m *EnvsModule) getLambdaEnvironmentVariablesPerFunction(function lambdaTypes.FunctionConfiguration, region string, dataReceiver chan EnvironmentVariable) {
@@ -390,69 +386,51 @@ func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *syn
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListServices, err := m.AppRunnerClient.ListServices(
context.TODO(),
&(apprunner.ListServicesInput{
NextToken: PaginationControl,
}),
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
if len(ListServices.ServiceSummaryList) > 0 {
ServiceSummaryList, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
for _, service := range ListServices.ServiceSummaryList {
name := aws.ToString(service.ServiceName)
arn := aws.ToString(service.ServiceArn)
awsService := "App Runner"
if err != nil {
//modLog.Error(err.Error())
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
if len(ServiceSummaryList) > 0 {
DescribeService, err := m.AppRunnerClient.DescribeService(
context.TODO(),
&apprunner.DescribeServiceInput{
ServiceArn: &arn,
},
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
for _, service := range ServiceSummaryList {
name := aws.ToString(service.ServiceName)
arn := aws.ToString(service.ServiceArn)
awsService := "App Runner"
}
if len(DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables) > 0 {
for k, v := range DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables {
//fmt.Printf("%s - %s", k, v)
dataReceiver <- EnvironmentVariable{
service: awsService,
name: name,
region: r,
environmentVarName: k,
environmentVarValue: v,
}
}
}
DescribeService, err := m.AppRunnerClient.DescribeService(
context.TODO(),
&apprunner.DescribeServiceInput{
ServiceArn: &arn,
},
func(o *apprunner.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
}
// The "NextToken" value is nil when there's no more data to return.
if ListServices.NextToken != nil {
PaginationControl = ListServices.NextToken
} else {
PaginationControl = nil
break
if len(DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables) > 0 {
for k, v := range DescribeService.Service.SourceConfiguration.ImageRepository.ImageConfiguration.RuntimeEnvironmentVariables {
//fmt.Printf("%s - %s", k, v)
dataReceiver <- EnvironmentVariable{
service: awsService,
name: name,
region: r,
environmentVarName: k,
environmentVarValue: v,
}
}
}
}
}
}
@@ -473,13 +451,7 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
m.CommandCounter.Executing++
awsService := "Lightsail [Container]"
GetContainerServices, err := m.LightsailClient.GetContainerServices(
context.TODO(),
&(lightsail.GetContainerServicesInput{}),
func(o *lightsail.Options) {
o.Region = r
},
)
ContainerServices, err := sdk.CachedLightsailGetContainerServices(m.LightsailClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
@@ -489,9 +461,9 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
if err == nil {
if len(GetContainerServices.ContainerServices) > 0 {
if len(ContainerServices) > 0 {
for _, containerService := range GetContainerServices.ContainerServices {
for _, containerService := range ContainerServices {
for _, container := range containerService.CurrentDeployment.Containers {
for k, v := range container.Environment {
name := aws.ToString(containerService.ContainerServiceName)
+96 -81
View File
@@ -9,19 +9,18 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/aws/aws-sdk-go-v2/service/fsx"
fsxTypes "github.com/aws/aws-sdk-go-v2/service/fsx/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
)
var green = color.New(color.FgGreen).SprintFunc()
type FilesystemsModule struct {
// General configuration data
@@ -53,10 +52,11 @@ type FilesystemObject struct {
IP string
Policy string
MountTarget string
Permissions string
}
func (m *FilesystemsModule) PrintFilesystems(outputFormat 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 = "filesystems"
@@ -114,6 +114,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
//"IP",
"Mount Target",
"Policy",
"Permissions",
}
// Table rows
@@ -128,17 +129,34 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
//m.Filesystems[i].IP,
m.Filesystems[i].MountTarget,
m.Filesystems[i].Policy,
m.Filesystems[i].Permissions,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s filesystems found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -183,7 +201,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
}
f := filepath.Join(path, "filesystems-mount-commands.txt")
@@ -194,8 +213,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
switch m.Filesystems[i].AWSService {
case "EFS":
out = out + fmt.Sprintf("########## Mount instructions for %s - %s ##########\n", m.Filesystems[i].AWSService, m.Filesystems[i].Name)
out = out + fmt.Sprintf("mkdir -p /efs/%s/\n", m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport %s:/ /efs/%s\n\n", m.Filesystems[i].DnsName, m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("mkdir -p /efs%s/\n", m.Filesystems[i].MountTarget)
out = out + fmt.Sprintf("sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport %s:/ /efs%s\n\n", m.Filesystems[i].DnsName, m.Filesystems[i].MountTarget)
case "FSx [LUSTRE]":
out = out + fmt.Sprintf("########## Mount instructions for %s - %s ##########\n", m.Filesystems[i].AWSService, m.Filesystems[i].Name)
out = out + fmt.Sprintln("#sudo amazon-linux-extras install -y lustre2.10")
@@ -220,7 +239,8 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
err = os.WriteFile(f, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
@@ -243,94 +263,75 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
var PaginationMarker2 *string
var policy string
// This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop).
for {
DescribeFileSystems, err := m.EFSClient.DescribeFileSystems(
DescribeFileSystems, err := sdk.CachedDescribeFileSystems(m.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, filesystem := range DescribeFileSystems {
name := filesystem.Name
id := aws.ToString(filesystem.FileSystemId)
_, err := m.EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemsInput{
Marker: PaginationMarker,
&efs.DescribeFileSystemPolicyInput{
FileSystemId: filesystem.FileSystemId,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
policy = "Default (No IAM auth)"
} else {
policy = "IAM Authenticated"
}
DescribeMountTargets, err := sdk.CachedDescribeMountTargets(m.EFSClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, filesystem := range DescribeFileSystems.FileSystems {
name := filesystem.Name
id := filesystem.FileSystemId
//dnsName := fmt.Sprintf("%s.efs.%s.amazonaws.com", *id, r)
for _, mountTarget := range DescribeMountTargets {
ip := *mountTarget.IpAddress
mountTarget := aws.ToString(mountTarget.MountTargetId)
awsService := "EFS"
_, err := m.EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemPolicyInput{
FileSystemId: id,
},
func(o *efs.Options) {
o.Region = r
},
)
accessPoints, err := sdk.CachedDescribeAccessPoints(m.EFSClient, aws.ToString(m.Caller.Account), r, id)
if err != nil {
policy = "Default (No IAM auth)"
}
sharedLogger.Error(err.Error())
for {
DescribeMountTargets, err := m.EFSClient.DescribeMountTargets(
context.TODO(),
&efs.DescribeMountTargetsInput{
FileSystemId: id,
Marker: PaginationMarker2,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, mountTarget := range DescribeMountTargets.MountTargets {
mountTargetId := mountTarget.MountTargetId
ip := *mountTarget.IpAddress
awsService := "EFS"
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: aws.ToString(mountTargetId),
}
}
if DescribeMountTargets.NextMarker != nil {
PaginationMarker2 = DescribeMountTargets.NextMarker
} else {
PaginationMarker2 = nil
break
m.CommandCounter.Error++
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: mountTarget,
Permissions: "access denied",
}
}
//awsService := fmt.Sprintf("EFS [%s]", fsType)
for _, accessPoint := range accessPoints {
path, permissions := m.getEFSfilesystemPermissions(accessPoint)
dataReceiver <- FilesystemObject{
AWSService: awsService,
Region: r,
Name: aws.ToString(name),
DnsName: ip,
Policy: policy,
MountTarget: path,
Permissions: permissions,
}
}
// Pagination control. After the last page of output, the for loop exits.
if DescribeFileSystems.NextMarker != nil {
PaginationMarker = DescribeFileSystems.NextMarker
} else {
PaginationMarker = nil
break
}
}
}
@@ -365,7 +366,8 @@ func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup,
},
)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
break
}
@@ -480,3 +482,16 @@ func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup,
}
}
func (m *FilesystemsModule) getEFSfilesystemPermissions(accessPoint types.AccessPointDescription) (string, string) {
var path string
var permissions string
if accessPoint.AccessPointId != nil && accessPoint.RootDirectory != nil && accessPoint.RootDirectory.CreationInfo != nil {
path = aws.ToString(accessPoint.RootDirectory.Path)
permissions = aws.ToString(accessPoint.RootDirectory.CreationInfo.Permissions)
return path, permissions
}
return "", ""
}
+85 -121
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
@@ -19,10 +20,7 @@ import (
type IamSimulatorModule struct {
// General configuration data
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListUsersClient iam.ListUsersAPIClient
IAMListRolesClient iam.ListRolesAPIClient
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -68,14 +66,14 @@ var (
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputFormat 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", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
var actionList []string
var pmapperCommands []string
var pmapperOutFileName string
@@ -173,15 +171,30 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
// for _, line := range pmapperCommands {
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
// }
m.writeLoot(m.output.FilePath, verbosity, pmapperCommands)
m.writeLoot(o.Table.DirectoryName, verbosity, pmapperCommands)
} else if principal != "" || action != "" {
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -249,47 +262,31 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListUsers, err := m.IAMListUsersClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
for _, user := range ListUsers.Users {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, user := range ListUsers {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
@@ -304,99 +301,66 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListRoles, err := m.IAMListRolesClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
for _, role := range ListRoles.Roles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
for _, role := range ListRoles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
func (m *IamSimulatorModule) getPolicySimulatorResult(principal *string, actionNames []string, resource string, dataReceiver chan SimulatorResult) {
var PaginationControl2 *string
//var policySourceArn = "*"
//var arn *string
var resourceArns []string
resourceArns = append(resourceArns, resource)
//var resourceArns = []string{"*"}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
EvaluationResults, err := sdk.CachedIamSimulatePrincipalPolicy(m.IAMClient, aws.ToString(m.Caller.Account), principal, actionNames, resourceArns)
break
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
return
}
for _, result := range SimulatePrincipalPolicy.EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
for _, result := range EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
}
// Pagination control. After the last page of output, the for loop exits.
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
}
func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
@@ -412,7 +376,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
"ssm:GetDocument",
}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
+100 -26
View File
@@ -10,6 +10,7 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
@@ -23,21 +24,20 @@ import (
type InstancesModule struct {
// General configuration data
EC2Client *ec2.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListInstanceProfilesClient iam.ListInstanceProfilesAPIClient
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
EC2Client *ec2.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Module's Results
MappedInstances []MappedInstance
@@ -64,7 +64,7 @@ type MappedInstance struct {
}
func (m *InstancesModule) Instances(filter string, outputFormat 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 = "instances"
@@ -93,7 +93,7 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -149,7 +149,7 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
if m.UserDataAttributesOnly {
m.printInstancesUserDataAttributesOnly(outputFormat, outputDirectory, dataReceiver)
} else {
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver)
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
}
@@ -173,7 +173,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}()
m.output.CallingModule = "instance-userdata"
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)), "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
@@ -203,7 +203,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
// only create a file if if there is at least one instance AND at least one instance had user-data.
if (len(m.MappedInstances) > 0) && (userDataOut != "=============================================\n") {
if m.output.Verbosity > 1 {
if m.output.Verbosity > 2 {
fmt.Printf("%s", userDataOut)
}
err = os.WriteFile(userDataFileName, []byte(userDataOut), 0644)
@@ -217,7 +217,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
}
}
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance) {
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
// Prepare Table headers
//m.output.Headers = table.Row{
if m.pmapperError == nil {
@@ -296,12 +296,28 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
////m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s instances found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -309,7 +325,7 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
}
}
func (m *InstancesModule) writeLoot(outputDirectory string) {
func (m *InstancesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
@@ -318,9 +334,14 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
}
privateIPsFilename := filepath.Join(path, "instances-ec2PrivateIPs.txt")
publicIPsFilename := filepath.Join(path, "instances-ec2PublicIPs.txt")
ssmCommandsFilename := filepath.Join(path, "instances-ssmCommands.txt")
ec2InstanceConnectCommandsFilename := filepath.Join(path, "instances-ec2InstanceConnectCommands.txt")
var publicIPs string
var privateIPs string
var ssmCommands string
var ec2InstanceConnectCommands string
var headlineName string
for _, instance := range m.MappedInstances {
if instance.ExternalIP != "NoExternalIP" {
@@ -330,6 +351,32 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
privateIPs = privateIPs + fmt.Sprintln(instance.PrivateIP)
}
if instance.Name != "" {
headlineName = fmt.Sprintf("%s/%s", instance.Name, instance.ID)
} else {
headlineName = fmt.Sprintf("%s", instance.ID)
}
ssmCommands = ssmCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ssmCommands = ssmCommands + fmt.Sprintf("############## Instance: %s ##############\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ssmCommands = ssmCommands + fmt.Sprintf("### SSM start-session to %s ###\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("# You'll need the AWS CLI session manager plugin installed: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html\n")
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm start-session --target %s\n\n", instance.Region, instance.ID)
ssmCommands = ssmCommands + fmt.Sprintf("### SSM send-command to %s ###\n", headlineName)
ssmCommands = ssmCommands + fmt.Sprintf("# If you just want to run one command you can use send-command, but really, start-session is way easier\n")
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm send-command --instance-ids %s --document-name AWS-RunShellScript --parameters commands=\"aws sts get-caller-identity\" \n", instance.Region, instance.ID)
ssmCommands = ssmCommands + fmt.Sprintf("aws --profile $profile --region %s ssm get-command-invocation --output text --instance-id %s --command-id <command-id-from-previous-command>\n\n", instance.Region, instance.ID)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("############## Instance: %s ##############\n", headlineName)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("-----------------------------------------------------------------------\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("### EC2 Instance Connect to %s ###\n", instance.ID)
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("# You'll need to change the --instance-os-user and --ssh-public-key parameters to match your own setup\n")
ec2InstanceConnectCommands = ec2InstanceConnectCommands + fmt.Sprintf("aws --profile $profile --region %s ec2-instance-connect send-ssh-public-key --instance-id %s --instance-os-user ec2-user --ssh-public-key file://~/.ssh/id_rsa.pub\n\n", instance.Region, instance.ID)
}
err = os.WriteFile(privateIPsFilename, []byte(privateIPs), 0644)
if err != nil {
@@ -341,9 +388,35 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
err = os.WriteFile(ssmCommandsFilename, []byte(ssmCommands), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
err = os.WriteFile(ec2InstanceConnectCommandsFilename, []byte(ec2InstanceConnectCommands), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), ssmCommandsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), ec2InstanceConnectCommandsFilename)
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Loot file for instance command:"))
fmt.Printf("Private IPs:\n\n")
fmt.Print(privateIPs)
fmt.Printf("Public IPs:\n\n")
fmt.Print(publicIPs)
fmt.Printf("SSM Commands:\n\n")
fmt.Print(ssmCommands)
fmt.Printf("EC2 Instance Connect Commands:\n\n")
fmt.Print(ec2InstanceConnectCommands)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
}
}
@@ -479,6 +552,7 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(accountId), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
@@ -500,7 +574,7 @@ func (m *InstancesModule) getRolesFromInstanceProfiles() {
m.InstanceProfileToRolesMap = map[string][]iamTypes.Role{}
for PaginationControl {
ListInstanceProfiles, err := m.IAMListInstanceProfilesClient.ListInstanceProfiles(
ListInstanceProfiles, err := m.IAMClient.ListInstanceProfiles(
context.TODO(),
&(iam.ListInstanceProfilesInput{
Marker: PaginationMarker,
+2208 -1137
View File
File diff suppressed because it is too large. Load diff
+55 -13
View File
@@ -9,9 +9,10 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -21,8 +22,8 @@ import (
type LambdasModule struct {
// General configuration data
LambdaClient *lambda.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
LambdaClient *lambda.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -55,7 +56,8 @@ type Lambda struct {
}
func (m *LambdasModule) PrintLambdas(outputFormat 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 = "lambda"
@@ -70,7 +72,7 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
fmt.Printf("[%s][%s] Enumerating lambdas for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -125,8 +127,7 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
m.output.Headers = []string{
"Service",
"Region",
//"Type",
"Resource Arn",
"Resource",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
@@ -135,7 +136,7 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
m.output.Headers = []string{
"Service",
"Region",
"Resource Arn",
"Resource",
"Role",
"IsAdminRole?",
}
@@ -180,10 +181,25 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s lambdas found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No lambdas found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -245,7 +261,7 @@ func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
out = out + fmt.Sprintf("aws --profile $profile --region %s lambda get-function --function-name %s\n", function.Region, function.Name)
out = out + "# Download function code to to disk (requires jq and curl) \n"
out = out + fmt.Sprintf("mkdir -p ./lambdas/%s\n", function.Name)
out = out + fmt.Sprintf("url=`aws --profile $profile lambda get-function --region %s --function-name %s | jq .Code.Location | sed s/\"//g` && curl \"$url\" -o ./lambdas/%s.zip\n", function.Region, function.Name, function.Name)
out = out + fmt.Sprintf("url=`aws --profile $profile lambda get-function --region %s --function-name %s | jq .Code.Location | sed s/\\\"//g` && curl \"$url\" -o ./lambdas/%s.zip\n", function.Region, function.Name, function.Name)
}
err = os.WriteFile(pullFile, []byte(out), 0644)
if err != nil {
@@ -319,7 +335,7 @@ func (m *LambdasModule) listFunctions(r string) ([]types.FunctionConfiguration,
},
)
if err != nil {
m.modLog.Error(err.Error())
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return functions, err
}
@@ -334,3 +350,29 @@ func (m *LambdasModule) listFunctions(r string) ([]types.FunctionConfiguration,
}
return functions, nil
}
func (m *LambdasModule) getResourcePolicy(r string, functionName string) (policy.Policy, error) {
var projectPolicy policy.Policy
var policyJSON string
Policy, err := m.LambdaClient.GetPolicy(
context.TODO(),
&lambda.GetPolicyInput{
FunctionName: &functionName,
},
func(options *lambda.Options) {
options.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
return projectPolicy, err
}
policyJSON = aws.ToString(Policy.Policy)
projectPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return projectPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", functionName, err)
}
return projectPolicy, nil
}
+20 -4
View File
@@ -137,7 +137,7 @@ 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
// These struct values are used by the output module
m.output.Verbosity = m.Verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "network-ports"
@@ -210,10 +210,26 @@ 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", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
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)
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: m.Verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s network services found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+290
View File
@@ -0,0 +1,290 @@
package aws
import (
"fmt"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/organizations/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/kyokomi/emoji"
"github.com/sirupsen/logrus"
)
type OrgModule struct {
OrganizationsClient sdk.OrganizationsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
// Main module data
Accounts []Account
Orgs map[string]Org
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Org struct {
OrgId string
MgmtAccount string
ChildAccounts []Account
}
type Account struct {
ProfileName string
isManagementAccount bool
Name string
Id string
Email string
Arn string
Status string
OrgId string
}
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
var err error
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "org"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Checking if account %s is the management account in an organization.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.DescribeOrgOutput, err = sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
return
}
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
fmt.Printf("[%s][%s] Account %s is either not associated with an organization, or you do not have the organizations:DescribeOrganization permission.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
} else {
if m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account)) {
m.addOrgAccounts()
} else {
m.addOrgAccount()
//fmt.Printf("[%s][%s] Account %s is not the management account in an organization %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
}
m.output.Headers = []string{
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
// Table rows
for i := range m.Accounts {
m.output.Body = append(
m.output.Body,
[]string{
m.Accounts[i].Name,
m.Accounts[i].Id,
strconv.FormatBool(m.Accounts[i].isManagementAccount),
m.Accounts[i].Status,
m.Accounts[i].Email,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d accounts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
}
}
func (m *OrgModule) ProcessMultipleAccounts(AWSProfiles []string, version string) {
var seenOrgs []string
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Account)
// Create a channel to signal to stop
receiverDone := make(chan bool)
// Create a channel to signal to stop
go m.Receiver(dataReceiver, receiverDone)
for _, profile := range AWSProfiles {
wg.Add(1)
m.CommandCounter.Pending++
go m.FindMgmtAccounts(profile, version, wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
orgId := aws.ToString(m.DescribeOrgOutput.Id)
// if this orgId is not in seenOrgs, add it to seenOrgs
if !internal.Contains(orgId, seenOrgs) {
seenOrgs = append(seenOrgs, orgId)
// add this org to the Orgs map
Org := Org{
OrgId: orgId,
}
m.Orgs[orgId] = Org
} else {
continue
}
isMgmtAccount := m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account))
if isMgmtAccount {
mgmtAccount := aws.ToString(m.Caller.Account)
fmt.Printf("[%s] Found an Organization Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), mgmtAccount)
//mgmtAccounts[mgmtAccount] = append(mgmtAccounts[mgmtAccount], profile)
}
}
}
func (m *OrgModule) Receiver(receiver chan Account, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Accounts = append(m.Accounts, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *OrgModule) FindMgmtAccounts(profile string, version string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Account) {
var err error
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
dataReceiver <- Account{
isManagementAccount: m.IsManagementAccount(DescribeOrganization, aws.ToString(m.Caller.Account)),
Name: aws.ToString(m.Caller.Account),
Id: aws.ToString(m.Caller.Account),
Status: "ACTIVE",
Email: aws.ToString(m.Caller.Account),
OrgId: aws.ToString(DescribeOrganization.Id),
Arn: aws.ToString(m.Caller.Account),
}
}
func (m *OrgModule) IsManagementAccount(Organization *types.Organization, account string) bool {
// Check if the account is the management account
// https://docs.aws.amazon.com/organizations/latest/APIReference/API_DescribeOrganization.html
if aws.ToString(Organization.MasterAccountId) == account {
return true
}
return false
}
func (m *OrgModule) addOrgAccounts() {
accounts, err := sdk.CachedOrganizationsListAccounts(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, account := range accounts {
m.Accounts = append(m.Accounts, Account{
isManagementAccount: m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(account.Id)),
Name: aws.ToString(account.Name),
Id: aws.ToString(account.Id),
Email: aws.ToString(account.Email),
Arn: aws.ToString(account.Arn),
Status: string(account.Status),
})
}
}
func (m *OrgModule) addOrgAccount() {
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
m.Accounts = append(m.Accounts, Account{
isManagementAccount: true,
Name: "Mgmt Account",
Id: aws.ToString(DescribeOrganization.MasterAccountId),
Email: aws.ToString(DescribeOrganization.MasterAccountEmail),
Arn: aws.ToString(DescribeOrganization.MasterAccountArn),
Status: "ACTIVE",
})
m.Accounts = append(m.Accounts, Account{
isManagementAccount: false,
Name: "This account",
Id: aws.ToString(m.Caller.Account),
Email: "Unknown",
Arn: aws.ToString(m.Caller.Arn),
Status: "ACTIVE",
})
}
func (m *OrgModule) IsCallerAccountPartOfAnOrg() bool {
DescribeOrgOutput, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
return false
} else {
m.DescribeOrgOutput = DescribeOrgOutput
}
return true
}
+19 -3
View File
@@ -115,7 +115,7 @@ type CloudTrailEvent struct {
}
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFormat 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 = "outbound-assumed-roles"
@@ -190,10 +190,26 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s log entries found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
//m.writeLoot()
+247 -190
View File
@@ -1,18 +1,17 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
@@ -20,7 +19,7 @@ import (
type IamPermissionsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -80,10 +79,11 @@ type PermissionsRow struct {
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
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "permissions"
@@ -94,26 +94,27 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
if principal != "" {
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
}
m.getGAAD(principal)
m.parsePermissions()
m.getGAAD()
m.parsePermissions(principal)
m.output.Headers = []string{
"Service",
"Principal Type",
//"Service",
"Type",
"Name",
//"Arn",
"Policy Type",
"Policy",
"Policy Name",
"Effect",
"Action",
"Resource",
"Condition",
}
//Table rows
@@ -121,7 +122,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.output.Body = append(
m.output.Body,
[]string{
m.Rows[i].AWSService,
//m.Rows[i].AWSService,
m.Rows[i].Type,
m.Rows[i].Name,
//m.Rows[i].Arn,
@@ -130,16 +131,33 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.Rows[i].Effect,
m.Rows[i].Action,
m.Rows[i].Resource,
m.Rows[i].Condition,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector3(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -148,178 +166,233 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *IamPermissionsModule) getGAAD(principal string) {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
//var totalRoles int
// var attachedPolicies []types.AttachedPolicy
// var inlinePolicies []types.PolicyDetail
for {
GAAD, err := m.IAMClient.GetAccountAuthorizationDetails(
context.TODO(),
&iam.GetAccountAuthorizationDetailsInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, policy := range GAAD.Policies {
//var IAMtype = "Role"
arn := aws.ToString(policy.Arn)
name := aws.ToString(policy.PolicyName)
m.Policies = append(m.Policies, GAADPolicy{
Arn: arn,
Name: name,
PolicyVersionList: policy.PolicyVersionList,
})
}
for _, role := range GAAD.RoleDetailList {
arn := aws.ToString(role.Arn)
name := aws.ToString(role.RoleName)
if principal == "" {
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
} else {
if arn == principal {
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
}
}
}
// i think the error here is pagination!!
for _, user := range GAAD.UserDetailList {
//var IAMtype = "User"
arn := aws.ToString(user.Arn)
name := aws.ToString(user.UserName)
groupList := user.GroupList
if principal == "" {
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
} else {
if arn == principal {
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
}
}
}
for _, group := range GAAD.GroupDetailList {
arn := aws.ToString(group.Arn)
name := aws.ToString(group.GroupName)
if principal == "" {
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
} else {
if arn == principal {
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
}
}
}
// Pagination control. After the last page of output, the for loop exits.
if GAAD.Marker != nil {
PaginationControl = GAAD.Marker
} else {
PaginationControl = nil
break
}
func (m *IamPermissionsModule) getGAAD() {
GAAD, err := sdk.CachedIAMGetAccountAuthorizationDetails(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// // if user supplied a principal name without the arn, try to create the arn
// if !strings.Contains(principal, "arn:") {
// principal = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
// }
for _, policy := range GAAD.Policies {
//var IAMtype = "Role"
arn := aws.ToString(policy.Arn)
name := aws.ToString(policy.PolicyName)
m.Policies = append(m.Policies, GAADPolicy{
Arn: arn,
Name: name,
PolicyVersionList: policy.PolicyVersionList,
})
}
for _, role := range GAAD.RoleDetailList {
arn := aws.ToString(role.Arn)
name := aws.ToString(role.RoleName)
m.Roles = append(m.Roles, GAADRole{
Arn: arn,
Name: name,
AttachedPolicies: role.AttachedManagedPolicies,
InlinePolicies: role.RolePolicyList,
})
}
for _, user := range GAAD.UserDetailList {
//var IAMtype = "User"
arn := aws.ToString(user.Arn)
name := aws.ToString(user.UserName)
groupList := user.GroupList
m.Users = append(m.Users, GAADUser{
Arn: arn,
Name: name,
AttachedPolicies: user.AttachedManagedPolicies,
InlinePolicies: user.UserPolicyList,
GroupList: groupList,
})
}
for _, group := range GAAD.GroupDetailList {
arn := aws.ToString(group.Arn)
name := aws.ToString(group.GroupName)
m.Groups = append(m.Groups, GAADGroup{
Arn: arn,
Name: name,
AttachedPolicies: group.AttachedManagedPolicies,
InlinePolicies: group.GroupPolicyList,
})
}
}
func (m *IamPermissionsModule) parsePermissions() {
// create a function that will take a principal name and try to see if it is a role, user, or group and return the arn of the principal
func (m *IamPermissionsModule) getPrincipalArn(principal string) string {
var arn string
for _, role := range m.Roles {
if role.Name == principal {
arn = role.Arn
}
}
for _, user := range m.Users {
if user.Name == principal {
arn = user.Arn
}
}
for _, group := range m.Groups {
if group.Name == principal {
arn = group.Arn
}
}
return arn
}
func (m *IamPermissionsModule) parsePermissions(principal string) {
var inputArn string
for i := range m.Roles {
if principal == "" {
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
}
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Roles[i].Arn) == strings.ToLower(inputArn) {
for _, attachedPolicy := range m.Roles[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name)
}
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
}
}
for _, inlinePolicy := range m.Roles[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name)
}
}
for i := range m.Users {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
if principal == "" {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
// for each group in the user's group list, get the attached and inline policy names, and then get the permissions from those policies
for g := range m.Users[i].GroupList {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
if principal == "" {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
}
}
}
// for group := range m.Users[i].GroupList {
// for _, gaadGroup := range m.Groups {
// if gaadGroup.Name == group {
// for _, attachedPolicy := range m.Groups[j].AttachedPolicies {
// m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
// }
// for _, inlinePolicy := range m.Groups[j].InlinePolicies {
// m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
// }
// }
// }
// }
}
}
func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, attachedPolicy types.AttachedPolicy, IAMtype string, name string) {
//var policies []types.ManagedPolicyDetail
var s StatementEntry
var s policy.PolicyStatement
var AWSService = "IAM"
var hasConditions string
for _, p := range m.Policies {
if p.Name == aws.ToString(attachedPolicy.PolicyName) {
for _, d := range p.PolicyVersionList {
if d.IsDefaultVersion {
parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
//parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
document, _ := url.QueryUnescape(aws.ToString(d.Document))
parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document))
for _, s = range parsedPolicyDocument.Statement {
//version := parsedPolicyDocument.Version
effect := s.Effect
if s.Action != nil {
for _, action := range s.Action {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
@@ -332,6 +405,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Effect: effect,
Action: action,
Resource: resource,
Condition: hasConditions,
})
}
}
@@ -340,6 +414,11 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
if s.NotAction != nil {
for _, action := range s.NotAction {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
@@ -352,6 +431,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
Effect: effect,
Action: "[NotAction] " + action,
Resource: resource,
Condition: hasConditions,
})
}
}
@@ -366,16 +446,24 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inlinePolicy types.PolicyDetail, IAMtype string, name string) {
//var policies []types.ManagedPolicyDetail
var s StatementEntry
var s policy.PolicyStatement
var AWSService = "IAM"
var hasConditions string
//parsedPolicyDocument, _ := parsePolicyDocument(inlinePolicy.PolicyDocument)
document, _ := url.QueryUnescape(aws.ToString(inlinePolicy.PolicyDocument))
parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document))
parsedPolicyDocument, _ := parsePolicyDocument(inlinePolicy.PolicyDocument)
for _, s = range parsedPolicyDocument.Statement {
//version := parsedPolicyDocument.Version
effect := s.Effect
if s.Action != nil {
for _, action := range s.Action {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
@@ -388,6 +476,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
Effect: effect,
Action: action,
Resource: resource,
Condition: hasConditions,
})
}
}
@@ -395,6 +484,11 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
if s.NotAction != nil {
for _, action := range s.NotAction {
for _, resource := range s.Resource {
if s.Condition != nil {
hasConditions = "Yes"
} else {
hasConditions = "No"
}
m.Rows = append(
m.Rows,
PermissionsRow{
@@ -405,8 +499,9 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
PolicyType: "Inline",
PolicyName: aws.ToString(inlinePolicy.PolicyName),
Effect: effect,
Action: "notaction" + action,
Action: "[NotAction] " + action,
Resource: resource,
Condition: hasConditions,
})
}
}
@@ -414,41 +509,3 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
}
}
type policyDocument struct {
Version string `json:"Version"`
Statement []StatementEntry `json:"Statement"`
}
type StatementEntry struct {
Effect string `json:"Effect"`
Action ListOfItems `json:"Action,omitempty"`
NotAction ListOfItems `json:"NotAction,omitempty"`
Resource ListOfItems `json:"Resource"`
Condition ListOfItems `json:"Condition"`
}
func parsePolicyDocument(doc *string) (policyDocument, error) {
document, _ := url.QueryUnescape(aws.ToString(doc))
var parsedDocumentToJSON policyDocument
_ = json.Unmarshal([]byte(document), &parsedDocumentToJSON)
return parsedDocumentToJSON, nil
}
// A custom unmarshaller is necessary because the list of principals can be an array of strings or a string.
// https://stackoverflow.com/questions/65854778/parsing-arn-from-iam-policy-using-regex
type ListOfItems []string
func (r *ListOfItems) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err == nil {
*r = append(*r, s)
return nil
}
var ss []string
if err := json.Unmarshal(b, &ss); err == nil {
*r = ss
return nil
}
return errors.New("cannot unmarshal neither to a string nor a slice of strings")
}
+48 -15
View File
@@ -71,16 +71,7 @@ func (m *PmapperModule) initPmapperGraph() error {
return err
}
m.pmapperGraph = graph.New(graph.StringHash, graph.Directed())
for _, node := range m.Nodes {
_ = m.pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = m.pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
m.pmapperGraph = m.createAndPopulateGraph()
for i := range m.Nodes {
if m.doesNodeHavePathToAdmin(m.Nodes[i]) {
@@ -89,12 +80,38 @@ func (m *PmapperModule) initPmapperGraph() error {
} else {
m.Nodes[i].PathToAdmin = false
}
}
return nil
}
func (m *PmapperModule) createAndPopulateGraph() graph.Graph[string, string] {
pmapperGraph := graph.New(graph.StringHash, graph.Directed())
// having issues with caching the graph. will have to swing back and try again later
// gob.Register(pmapperGraph)
// cacheKey := fmt.Sprintf("%s-pmapperGraph", aws.ToString(m.Caller.Account))
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.(graph.Graph[string, string])
// }
for _, node := range m.Nodes {
_ = pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
//internal.Cache.Set(cacheKey, pmapperGraph, cache.DefaultExpiration)
return pmapperGraph
}
func (m *PmapperModule) DoesPrincipalHavePathToAdmin(principal string) bool {
for i := range m.Nodes {
if m.Nodes[i].Arn == principal {
@@ -120,7 +137,7 @@ func (m *PmapperModule) DoesPrincipalHaveAdmin(principal string) bool {
}
func (m *PmapperModule) PrintPmapperData(outputFormat 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 = "pmapper"
@@ -131,7 +148,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", 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 {
@@ -178,9 +195,25 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s principals who are admin or have a path to admin identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
+62 -83
View File
@@ -1,21 +1,20 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strconv"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type IamPrincipalsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -63,7 +62,7 @@ type Role struct {
}
func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat 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 = "principals"
@@ -87,8 +86,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
// done <- true
// <-done
m.getIAMUsers()
m.getIAMRoles()
m.addIAMUsersToTable()
m.addIAMRolesToTable()
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
@@ -135,10 +134,26 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s IAM principals found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -160,100 +175,64 @@ func (m *IamPrincipalsModule) executeChecks(wg *sync.WaitGroup) {
}
*/
func (m *IamPrincipalsModule) getIAMUsers() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMUsersToTable() {
var AWSService = "IAM"
var IAMtype = "User"
var attachedPolicies []string
var inlinePolicies []string
for {
ListUsers, err := m.IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, user := range ListUsers.Users {
arn := user.Arn
name := user.UserName
for _, user := range ListUsers {
arn := user.Arn
name := user.UserName
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
func (m *IamPrincipalsModule) getIAMRoles() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMRolesToTable() {
//var totalRoles int
var AWSService = "IAM"
var IAMtype = "Role"
var attachedPolicies []string
var inlinePolicies []string
for {
ListRoles, err := m.IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range ListRoles.Roles {
arn := role.Arn
name := role.RoleName
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
for _, role := range ListRoles {
arn := role.Arn
name := role.RoleName
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
//fmt.Printf("IAM Roles: %d\n\n", totalRoles)
break
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
+19 -3
View File
@@ -45,7 +45,7 @@ type Resource struct {
}
func (m *RAMModule) PrintRAM(outputFormat 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 = "ram"
@@ -115,10 +115,26 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resources found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
+739
View File
@@ -0,0 +1,739 @@
package aws
import (
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type ResourceTrustsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSProfile string
CloudFoxVersion string
Resources2 []Resource2
CommandCounter internal.CommandCounter
output internal.OutputData2
modLog *logrus.Entry
}
type Resource2 struct {
AccountID string
Name string
ARN string
Region string
Policy policy.Policy
PolicyJSON string
ResourcePolicySummary string
Public string
Interesting string
}
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
// These struct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "resource-trusts"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Lambda, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Resource2)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Add(1)
go m.getS3Buckets(wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
// Send a message to the data receiver goroutine to close the channel and stop
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
//"Account ID",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
// sort the table roles by Interesting
sort.Slice(m.Resources2, func(i, j int) bool {
return m.Resources2[j].Interesting > m.Resources2[i].Interesting
})
// Table rows
for i := range m.Resources2 {
m.output.Body = append(
m.output.Body,
[]string{
//m.Resources2[i].AccountID,
m.Resources2[i].ARN,
m.Resources2[i].Public,
m.Resources2[i].Interesting,
m.Resources2[i].ResourcePolicySummary,
},
)
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
//fmt.Printf("[%s][%s] Resource policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sns", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("sqs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSQSQueuesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("ecr", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getCodeBuildResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("lambda", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("efs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Resources2 = append(m.Resources2, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
ListTopics, err := cloudFoxSNSClient.listTopics(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, t := range ListTopics {
var statementSummaryInEnglish string
var isInteresting string = "No"
topic, err := cloudFoxSNSClient.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// check if topic is public or not
if topic.Policy.IsPublic() {
topic.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
topic.IsPublic = "No"
}
// If there is a resource policy, convert the resource policy into plain english
if !topic.Policy.IsEmpty() {
for i, statement := range topic.Policy.Statement {
var prefix string = ""
if len(topic.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = statementSummaryInEnglish + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(t.TopicArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: topic.IsPublic,
Region: parsedArn.Region,
Name: topic.Name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, b := range ListBuckets {
var statementSummaryInEnglish string
var isInteresting string = "No"
bucket := &Bucket{
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
}
name := aws.ToString(b.Name)
region, err := sdk.CachedGetBucketLocation(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), name)
if err != nil {
m.modLog.Error(err.Error())
continue
}
policyJSON, err := sdk.CachedGetBucketPolicy(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
continue
} else {
bucket.PolicyJSON = policyJSON
}
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
if policy.IsPublic() {
bucket.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
bucket.IsPublic = "No"
}
// If there is a resource policy, convert the resource policy into plain english
if !bucket.Policy.IsEmpty() {
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
if len(bucket.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = statementSummaryInEnglish + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: bucket.Arn,
ResourcePolicySummary: statementSummaryInEnglish,
Public: bucket.IsPublic,
Region: region,
Name: name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListQueues, err := cloudFoxSQSClient.listQueues(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, q := range ListQueues {
var statementSummaryInEnglish string
var isInteresting string = "No"
var isPublic string = "No"
queue, err := cloudFoxSQSClient.getQueueWithAttributes(q, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !queue.Policy.IsEmpty() {
if queue.Policy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
}
for i, statement := range queue.Policy.Statement {
prefix := ""
if len(queue.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(&queue.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: q,
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, repo := range DescribeRepositories {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
repoPolicy, err := cloudFoxECRClient.getECRRepositoryPolicy(r, aws.ToString(repo.RepositoryName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !repoPolicy.IsEmpty() {
if repoPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range repoPolicy.Statement {
prefix := ""
if len(repoPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(repo.RepositoryArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(repo.RepositoryName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, p := range ListProjects {
var projectPolicy policy.Policy
var statementSummaryInEnglish string
var isPublic string
var isInteresting string = "No"
project, err := sdk.CachedCodeBuildBatchGetProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
policyJSON, err := sdk.CachedCodeBuildGetResourcePolicy(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
sharedLogger.Error(err.Error())
continue
}
projectPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if !projectPolicy.IsEmpty() {
if projectPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range projectPolicy.Statement {
prefix := ""
if len(projectPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(project.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(project.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, f := range ListFunctions {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
functionPolicy, err := cloudFoxLambdaClient.getResourcePolicy(r, aws.ToString(f.FunctionName))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !functionPolicy.IsEmpty() {
if functionPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range functionPolicy.Statement {
prefix := ""
if len(functionPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(f.FunctionArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(f.FunctionName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, fs := range ListFileSystems {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
fsPolicy, err := sdk.CachedDescribeFileSystemPolicy(cloudFoxEFSClient.EFSClient, aws.ToString(fs.FileSystemId), r, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if fsPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !fsPolicy.IsEmpty() {
for i, statement := range fsPolicy.Statement {
prefix := ""
if len(fsPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(fs.FileSystemArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(fs.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func isResourcePolicyInteresting(statementSummaryInEnglish string) bool {
// check if the statement has any of the following items, but make sure the check is case insensitive
// if it does, then return true
// if it doesn't, then return false
if //(strings.Contains(strings.ToLower(statementSummaryInEnglish), "*") && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("Everyone")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgID")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalAccount")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgPaths")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("role")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("root")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceArn"))) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceAccount"))) {
return true
}
return false
}
+151 -97
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +11,7 @@ import (
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
@@ -22,8 +22,7 @@ import (
type RoleTrustsModule struct {
// General configuration data
IAMClientListRoles iam.ListRolesAPIClient
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
Caller sts.GetCallerIdentityOutput
@@ -101,7 +100,7 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
fmt.Printf("[%s][%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
//fmt.Printf("[%s][%s] Looking for pmapper data for this account and building a PrivEsc graph in golang if it exists.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
@@ -119,19 +118,67 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
}
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: principalsHeader,
Body: principalsBody,
Name: "role-trusts-principals",
})
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: servicesHeader,
Body: servicesBody,
Name: "role-trusts-services",
})
federatedHeader, federatedBody := m.printFederatedTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: federatedHeader,
Body: federatedBody,
Name: "role-trusts-federated",
})
o.WriteFullOutput(o.Table.TableFiles, nil)
if len(principalsBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(principalsBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(servicesBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(federatedBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
m.printPrincipalTrusts(outputFormat, outputDirectory)
m.printServiceTrusts(outputFormat, outputDirectory)
m.printFederatedTrusts(outputFormat, outputDirectory)
}
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-principals"
var column1 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
@@ -139,7 +186,7 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirec
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
@@ -152,41 +199,49 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirec
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := principal
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
////m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// ////m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// //internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
// fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-services"
var column1 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Service",
"ExternalID",
@@ -194,7 +249,7 @@ func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirecto
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Service",
"ExternalID",
@@ -206,42 +261,55 @@ func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirecto
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, service := range statement.Principal.Service {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := service
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// sort the rows based on column 2 (service)
sort.SliceStable(body, func(i, j int) bool {
return body[i][1] < body[j][1]
})
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
//m.sortTrustsTablePerTrustedPrincipal()
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-federated"
var column3, column2 string
var column1, column3, column2 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
@@ -249,7 +317,7 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirec
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
@@ -259,39 +327,44 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirec
}
for _, role := range m.AnalyzedRoles {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
for _, statement := range role.trustsDoc.Statement {
if len(statement.Principal.Federated) > 0 {
column2, column3 = parseFederatedTrustPolicy(statement)
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
return header, body
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, string) {
var column2, column3 string
if statement.Condition.StringLike.TokenActionsGithubusercontentComAud != "" || len(statement.Condition.StringLike.TokenActionsGithubusercontentComSub) > 0 {
column2 = "GitHub Actions (" + statement.Principal.Federated[0] + ")"
column2 = "GitHub Actions" // (" + statement.Principal.Federated[0] + ")"
trustedRepos := strings.Join(statement.Condition.StringLike.TokenActionsGithubusercontentComSub, "\n")
if trustedRepos == "" {
column3 = "ALL REPOS!!!"
@@ -300,13 +373,13 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
}
} else if statement.Condition.StringEquals.SAMLAud == "https://signin.aws.amazon.com/saml" {
if strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO (" + statement.Principal.Federated[0] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
} else if strings.Contains(statement.Principal.Federated[0], "Okta") {
column2 = "Okta (" + statement.Principal.Federated[0] + ")"
column2 = "Okta" // (" + statement.Principal.Federated[0] + ")"
}
column3 = "Not applicable"
} else if statement.Condition.StringEquals.OidcEksAud != "" || statement.Condition.StringEquals.OidcEksSub != "" || statement.Condition.StringLike.OidcEksAud != "" || statement.Condition.StringLike.OidcEksSub != "" {
column2 = "EKS (" + statement.Principal.Federated[0] + ")"
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.StringEquals.OidcEksSub != "" {
column3 = statement.Condition.StringEquals.OidcEksSub
} else if statement.Condition.StringLike.OidcEksSub != "" {
@@ -315,16 +388,16 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
column3 = "ALL SERVICE ACCOUNTS!"
}
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
column2 = "Cognito (" + statement.Principal.Federated[0] + ")"
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
}
} else {
if column2 == "" && strings.Contains(statement.Principal.Federated[0], "oidc.eks") {
column2 = "EKS (" + statement.Principal.Federated[0] + ")"
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
column3 = "ALL SERVICE ACCOUNTS!"
} else if column2 == "" && strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO (" + statement.Principal.Federated[0] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
}
}
@@ -341,50 +414,31 @@ func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
}
func (m *RoleTrustsModule) getAllRoleTrusts() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
// This for loop exits at the end depending on whether the output hits its last page (see pagination control block at the end of the loop).
for {
results, err := m.IAMClientListRoles.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationMarker,
},
)
for _, role := range ListRoles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range results.Roles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
// Pagination control. After the last page of output, the for loop exits.
if results.IsTruncated {
PaginationMarker = results.Marker
} else {
PaginationMarker = nil
break
}
}
}
func parseRoleTrustPolicyDocument(role types.Role) (trustPolicyDocument, error) {
+56 -61
View File
@@ -1,22 +1,21 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/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
@@ -44,7 +43,7 @@ type Record struct {
func (m *Route53Module) PrintRoute53(outputFormat 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 = "route53"
@@ -83,11 +82,27 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s DNS records found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -111,7 +126,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 {
@@ -160,71 +175,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
}
}
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
)
type APIGatewayClientInterface interface {
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
}
func init() {
gob.Register([]apiGatewayTypes.RestApi{})
}
// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.RestApi, error) {
var PaginationControl *string
var restAPIs []apiGatewayTypes.RestApi
cacheKey := fmt.Sprintf("%s-apigateway-GetRestApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.RestApi), nil
}
for {
GetRestApis, err := client.GetRestApis(
context.TODO(),
&apigateway.GetRestApisInput{
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return restAPIs, err
}
restAPIs = append(restAPIs, GetRestApis.Items...)
//pagination
if GetRestApis.Position == nil {
break
}
PaginationControl = GetRestApis.Position
}
return restAPIs, nil
}
+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
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
"github.com/patrickmn/go-cache"
)
type APIGatewayv2ClientInterface interface {
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
}
func init() {
gob.Register([]apiGatwayV2Types.Api{})
}
func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID string, region string) ([]apiGatwayV2Types.Api, error) {
var PaginationControl *string
var apis []apiGatwayV2Types.Api
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.Api), nil
}
for {
GetApis, err := client.GetApis(
context.TODO(),
&apigatewayv2.GetApisInput{
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return apis, err
}
apis = append(apis, GetApis.Items...)
//pagination
if GetApis.NextToken == nil {
break
}
PaginationControl = GetApis.NextToken
}
internal.Cache.Set(cacheKey, apis, cache.DefaultExpiration)
return apis, nil
}
+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
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types"
"github.com/patrickmn/go-cache"
)
type AppRunnerClientInterface interface {
ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error)
}
func init() {
gob.Register([]apprunnerTypes.Service{})
gob.Register([]apprunnerTypes.ServiceSummary{})
}
func CachedAppRunnerListServices(client AppRunnerClientInterface, accountID string, region string) ([]apprunnerTypes.ServiceSummary, error) {
var PaginationControl *string
var services []apprunnerTypes.ServiceSummary
cacheKey := fmt.Sprintf("%s-apprunner-ListServices-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apprunnerTypes.ServiceSummary), nil
}
for {
ListServices, err := client.ListServices(
context.TODO(),
&apprunner.ListServicesInput{
NextToken: PaginationControl,
},
func(o *apprunner.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, ListServices.ServiceSummaryList...)
//pagination
if ListServices.NextToken == nil {
break
}
PaginationControl = ListServices.NextToken
}
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+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
}
+119
View File
@@ -0,0 +1,119 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
cloudFormationTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/patrickmn/go-cache"
)
type CloudFormationClientInterface interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
DescribeStacks(context.Context, *cloudformation.DescribeStacksInput, ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error)
ListStacks(context.Context, *cloudformation.ListStacksInput, ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error)
}
func init() {
gob.Register([]cloudFormationTypes.Stack{})
gob.Register([]cloudFormationTypes.StackSummary{})
}
func CachedCloudFormationDescribeStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.Stack, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.Stack
cacheKey := fmt.Sprintf("%s-cloudformation-DescribeStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.Stack), nil
}
for {
DescribeStacks, err := client.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
//pagination
if DescribeStacks.NextToken == nil {
break
}
PaginationControl = DescribeStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
func CachedCloudFormationGetTemplate(client CloudFormationClientInterface, accountID string, region string, stackName string) (string, error) {
var stackTemplateBody string
cacheKey := fmt.Sprintf("%s-cloudformation-GetTemplate-%s-%s", accountID, region, stackName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetTemplate, err := client.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
}, func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
internal.Cache.Set(cacheKey, *GetTemplate.TemplateBody, cache.DefaultExpiration)
return stackTemplateBody, nil
}
func CachedCloudFormationListStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.StackSummary, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.StackSummary
cacheKey := fmt.Sprintf("%s-cloudformation-ListStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.StackSummary), nil
}
for {
ListStacks, err := client.ListStacks(
context.TODO(),
&cloudformation.ListStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, ListStacks.StackSummaries...)
//pagination
if ListStacks.NextToken == nil {
break
}
PaginationControl = ListStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
+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
}
+118
View File
@@ -0,0 +1,118 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
codeBuildTypes "github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/patrickmn/go-cache"
)
type CodeBuildClientInterface interface {
ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error)
BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error)
GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error)
}
func init() {
gob.Register(codeBuildTypes.Project{})
}
// create a CachedCodeBuildListProjects function that accepts a codebuild client, account id, and region. Make sure it handles the region option and pagination
func CachedCodeBuildListProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var projects []string
cacheKey := fmt.Sprintf("%s-codebuild-ListProjects", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListProjects, err := CodeBuildClient.ListProjects(
context.TODO(),
&codebuild.ListProjectsInput{
NextToken: PaginationControl,
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
projects = append(projects, ListProjects.Projects...)
// Pagination control.
if ListProjects.NextToken != nil {
PaginationControl = ListProjects.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, projects, cache.DefaultExpiration)
return projects, nil
}
// create a CachedCodeBuildBatchGetProjects function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildBatchGetProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (codeBuildTypes.Project, error) {
project := codeBuildTypes.Project{}
cacheKey := fmt.Sprintf("%s-codebuild-BatchGetProjects-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeBuildTypes.Project), nil
}
BatchGetProjects, err := CodeBuildClient.BatchGetProjects(
context.TODO(),
&codebuild.BatchGetProjectsInput{
Names: []string{projectID},
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return project, err
}
internal.Cache.Set(cacheKey, BatchGetProjects.Projects[0], cache.DefaultExpiration)
return BatchGetProjects.Projects[0], nil
}
// create a CachedCodeBuildGetResourcePolicy function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildGetResourcePolicy(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (string, error) {
cacheKey := fmt.Sprintf("%s-codebuild-GetResourcePolicy-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetResourcePolicy, err := CodeBuildClient.GetResourcePolicy(
context.TODO(),
&codebuild.GetResourcePolicyInput{
ResourceArn: aws.String("arn:aws:codebuild:" + region + ":" + accountID + ":project/" + projectID),
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
internal.Cache.Set(cacheKey, GetResourcePolicy, cache.DefaultExpiration)
return aws.ToString(GetResourcePolicy.Policy), nil
}
+19
View File
@@ -0,0 +1,19 @@
package sdk
import (
"context"
"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
}
+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
}
+141
View File
@@ -0,0 +1,141 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/docdb"
docdbTypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
"github.com/patrickmn/go-cache"
)
type DocDBClientInterface interface {
DescribeGlobalClusters(context.Context, *docdb.DescribeGlobalClustersInput, ...func(*docdb.Options)) (*docdb.DescribeGlobalClustersOutput, error)
DescribeDBClusters(context.Context, *docdb.DescribeDBClustersInput, ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error)
DescribeDBInstances(context.Context, *docdb.DescribeDBInstancesInput, ...func(*docdb.Options)) (*docdb.DescribeDBInstancesOutput, error)
}
func init() {
gob.Register([]docdbTypes.GlobalCluster{})
gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBInstance{})
}
func CachedDocDBDescribeGlobalClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.GlobalCluster, error) {
var PaginationControl *string
var globalClusters []docdbTypes.GlobalCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeGlobalClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.GlobalCluster), nil
}
for {
DescribeGlobalClusters, err := client.DescribeGlobalClusters(
context.TODO(),
&docdb.DescribeGlobalClustersInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return globalClusters, err
}
globalClusters = append(globalClusters, DescribeGlobalClusters.GlobalClusters...)
//pagination
if DescribeGlobalClusters.Marker == nil {
break
}
PaginationControl = DescribeGlobalClusters.Marker
}
internal.Cache.Set(cacheKey, globalClusters, cache.DefaultExpiration)
return globalClusters, nil
}
func CachedDocDBDescribeDBClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBCluster, error) {
var PaginationControl *string
var dbClusters []docdbTypes.DBCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBCluster), nil
}
for {
DescribeDBClusters, err := client.DescribeDBClusters(
context.TODO(),
&docdb.DescribeDBClustersInput{
Marker: PaginationControl,
Filters: []docdbTypes.Filter{
{
Name: aws.String("engine"),
Values: []string{"docdb"},
},
},
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbClusters, err
}
dbClusters = append(dbClusters, DescribeDBClusters.DBClusters...)
//pagination
if DescribeDBClusters.Marker == nil {
break
}
PaginationControl = DescribeDBClusters.Marker
}
internal.Cache.Set(cacheKey, dbClusters, cache.DefaultExpiration)
return dbClusters, nil
}
func CachedDocDBDescribeDBInstances(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBInstance, error) {
var PaginationControl *string
var dbInstances []docdbTypes.DBInstance
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBInstance), nil
}
for {
DescribeDBInstances, err := client.DescribeDBInstances(
context.TODO(),
&docdb.DescribeDBInstancesInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbInstances, err
}
dbInstances = append(dbInstances, DescribeDBInstances.DBInstances...)
//pagination
if DescribeDBInstances.Marker == nil {
break
}
PaginationControl = DescribeDBInstances.Marker
}
internal.Cache.Set(cacheKey, dbInstances, cache.DefaultExpiration)
return dbInstances, nil
}
+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
}
+85
View File
@@ -0,0 +1,85 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamoDBTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/patrickmn/go-cache"
)
type DynamoDBClientInterface interface {
ListTables(context.Context, *dynamodb.ListTablesInput, ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error)
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
}
func init() {
gob.Register([]string{})
gob.Register(dynamoDBTypes.TableDescription{})
}
func CachedDynamoDBListTables(client DynamoDBClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var tables []string
cacheKey := fmt.Sprintf("%s-dynamodb-ListTables-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListTables, err := client.ListTables(
context.TODO(),
&dynamodb.ListTablesInput{
ExclusiveStartTableName: PaginationControl,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tables, err
}
tables = append(tables, ListTables.TableNames...)
//pagination
if ListTables.LastEvaluatedTableName == nil {
break
}
PaginationControl = ListTables.LastEvaluatedTableName
}
internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration)
return tables, nil
}
func CachedDynamoDBDescribeTable(client DynamoDBClientInterface, accountID string, region string, tableName string) (dynamoDBTypes.TableDescription, error) {
var tableDescription dynamoDBTypes.TableDescription
cacheKey := fmt.Sprintf("%s-dynamodb-DescribeTable-%s-%s", accountID, region, tableName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(dynamoDBTypes.TableDescription), nil
}
DescribeTable, err := client.DescribeTable(
context.TODO(),
&dynamodb.DescribeTableInput{
TableName: &tableName,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tableDescription, err
}
tableDescription = *DescribeTable.Table
internal.Cache.Set(cacheKey, tableDescription, cache.DefaultExpiration)
return tableDescription, nil
}
+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
}
+206
View File
@@ -0,0 +1,206 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/patrickmn/go-cache"
)
type 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)
}
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 AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
var PaginationControl *string
var instances []ec2Types.Instance
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Instance), nil
}
for {
DescribeInstances, err := client.DescribeInstances(
context.TODO(),
&ec2.DescribeInstancesInput{
NextToken: PaginationControl,
},
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
for _, reservation := range DescribeInstances.Reservations {
instances = append(instances, reservation.Instances...)
}
//pagination
if DescribeInstances.NextToken == nil {
break
}
PaginationControl = DescribeInstances.NextToken
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
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
}
for _, networkInterface := range DescribeNetworkInterfaces.NetworkInterfaces {
NetworkInterfaces = append(NetworkInterfaces, networkInterface)
}
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
}
for _, snapshot := range DescribeSnapshots.Snapshots {
Snapshots = append(Snapshots, snapshot)
}
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
}
for _, volume := range DescribeVolumes.Volumes {
Volumes = append(Volumes, volume)
}
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
}
for _, image := range DescribeImages.Images {
Images = append(Images, image)
}
if DescribeImages.NextToken == nil {
break
}
PaginationControl = DescribeImages.NextToken
}
internal.Cache.Set(cacheKey, Images, cache.DefaultExpiration)
return Images, nil
}
+374
View File
@@ -0,0 +1,374 @@
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"`
}
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
}
+137
View File
@@ -0,0 +1,137 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/patrickmn/go-cache"
)
type AWSECRClientInterface interface {
DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error)
DescribeImages(ctx context.Context, params *ecr.DescribeImagesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error)
GetRepositoryPolicy(ctx context.Context, params *ecr.GetRepositoryPolicyInput, optFns ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error)
}
func init() {
gob.Register([]ecrTypes.Repository{})
gob.Register([]ecrTypes.ImageDetail{})
gob.Register(ecrTypes.Repository{})
}
// create a CachedECRDescribeRepositories function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID string, region string) ([]ecrTypes.Repository, error) {
var PaginationControl *string
var repositories []ecrTypes.Repository
cacheKey := fmt.Sprintf("%s-ecr-DescribeRepositories-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repositories data")
return cached.([]ecrTypes.Repository), nil
}
for {
DescribeRepositories, err := ECRClient.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
repositories = append(repositories, DescribeRepositories.Repositories...)
// Pagination control.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
return repositories, nil
}
// create a CachedECRDescribeImages function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRDescribeImages(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) ([]ecrTypes.ImageDetail, error) {
var PaginationControl *string
var images []ecrTypes.ImageDetail
cacheKey := fmt.Sprintf("%s-efs-DescribImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Images data")
return cached.([]ecrTypes.ImageDetail), nil
}
for {
DescribeImages, err := ECRClient.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
NextToken: PaginationControl,
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
images = append(images, DescribeImages.ImageDetails...)
// Pagination control.
if DescribeImages.NextToken != nil {
PaginationControl = DescribeImages.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, images, cache.DefaultExpiration)
return images, nil
}
// create a CachedECRGetRepositoryPolicy function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRGetRepositoryPolicy(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) (string, error) {
// in repositoryName, replace "/" with "-"
cacheKey := fmt.Sprintf("%s-ecr-GetRepositoryPolicy-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repository policy data")
return cached.(string), nil
}
GetRepositoryPolicy, err := ECRClient.GetRepositoryPolicy(
context.TODO(),
&ecr.GetRepositoryPolicyInput{
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
PolicyText := aws.ToString(GetRepositoryPolicy.PolicyText)
internal.Cache.Set(cacheKey, PolicyText, cache.DefaultExpiration)
return PolicyText, nil
}
+73
View File
@@ -0,0 +1,73 @@
package sdk
import (
"context"
"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"),
},
{
RepositoryName: aws.String("repo2"),
},
},
}, nil
}
func (m *MockedECRClient) DescribeImages(ctx context.Context, input *ecr.DescribeImagesInput, options ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []ecrTypes.ImageDetail{
{
ImageTags: []string{
"tag1",
"tag2",
},
},
{
ImageTags: []string{
"tag3", "tag4", "tag5", "tag6"},
},
},
}, 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
}
+215
View File
@@ -0,0 +1,215 @@
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.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
cacheKey := fmt.Sprintf("%s-ecs-DescribeTasks-%s-%s", accountID, region, cluster)
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
cacheKey := fmt.Sprintf("%s-ecs-DescribeTaskDefinition-%s-%s", accountID, region, taskDefinition)
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
}
+178
View File
@@ -0,0 +1,178 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/efs"
efsTypes "github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/patrickmn/go-cache"
)
type AWSEFSClientInterface interface {
DescribeFileSystems(ctx context.Context, params *efs.DescribeFileSystemsInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error)
DescribeMountTargets(ctx context.Context, params *efs.DescribeMountTargetsInput, optFns ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error)
DescribeAccessPoints(ctx context.Context, params *efs.DescribeAccessPointsInput, optFns ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error)
DescribeFileSystemPolicy(ctx context.Context, params *efs.DescribeFileSystemPolicyInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemPolicyOutput, error)
}
func init() {
gob.Register([]efsTypes.FileSystemDescription{})
gob.Register([]efsTypes.MountTargetDescription{})
gob.Register([]efsTypes.AccessPointDescription{})
gob.Register(policy.Policy{})
}
func CachedDescribeFileSystems(EFSClient AWSEFSClientInterface, accountID string, r string) ([]efsTypes.FileSystemDescription, error) {
var PaginationMarker *string
var filesystems []efsTypes.FileSystemDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeFileSystems-%s", accountID, r)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.FileSystemDescription), nil
}
for {
DescribeFileSystems, err := EFSClient.DescribeFileSystems(
context.TODO(),
&efs.DescribeFileSystemsInput{
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
filesystems = append(filesystems, DescribeFileSystems.FileSystems...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeFileSystems.Marker != nil {
PaginationMarker = DescribeFileSystems.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, filesystems, cache.DefaultExpiration)
return filesystems, err
}
func CachedDescribeMountTargets(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.MountTargetDescription, error) {
var PaginationMarker *string
var mountTargets []efsTypes.MountTargetDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeMountTargets-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.MountTargetDescription), nil
}
for {
DescribeMountTargets, err := EFSClient.DescribeMountTargets(
context.TODO(),
&efs.DescribeMountTargetsInput{
FileSystemId: aws.String(filesystemId),
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
mountTargets = append(mountTargets, DescribeMountTargets.MountTargets...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeMountTargets.Marker != nil {
PaginationMarker = DescribeMountTargets.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, mountTargets, cache.DefaultExpiration)
return mountTargets, err
}
func CachedDescribeAccessPoints(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.AccessPointDescription, error) {
var PaginationMarker *string
var accessPoints []efsTypes.AccessPointDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeAccessPoints-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.AccessPointDescription), nil
}
for {
DescribeAccessPoints, err := EFSClient.DescribeAccessPoints(
context.TODO(),
&efs.DescribeAccessPointsInput{
FileSystemId: aws.String(filesystemId),
NextToken: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
accessPoints = append(accessPoints, DescribeAccessPoints.AccessPoints...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeAccessPoints.NextToken != nil {
PaginationMarker = DescribeAccessPoints.NextToken
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, accessPoints, cache.DefaultExpiration)
return accessPoints, err
}
func CachedDescribeFileSystemPolicy(EFSClient AWSEFSClientInterface, filesystemId string, r string, accountID string) (policy.Policy, error) {
var efsPolicy policy.Policy
var policyJSON string
cacheKey := fmt.Sprintf("efs-%s-DescribeFileSystemPolicy-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(policy.Policy), nil
}
Policy, err := EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemPolicyInput{
FileSystemId: aws.String(filesystemId),
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return efsPolicy, err
}
policyJSON = aws.ToString(Policy.Policy)
efsPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return efsPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", filesystemId, err)
}
internal.Cache.Set(cacheKey, efsPolicy, cache.DefaultExpiration)
return efsPolicy, nil
}
+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
}
+152
View File
@@ -0,0 +1,152 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/eks"
eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/patrickmn/go-cache"
)
type EKSClientInterface interface {
DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error)
ListClusters(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) (*eks.ListClustersOutput, error)
DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error)
ListNodegroups(context.Context, *eks.ListNodegroupsInput, ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error)
}
func init() {
gob.Register([]string{})
gob.Register(eksTypes.Cluster{})
gob.Register(eksTypes.Nodegroup{})
}
func CachedEKSDescribeCluster(client EKSClientInterface, accountID string, clusterName string, region string) (eksTypes.Cluster, error) {
var cluster eksTypes.Cluster
cacheKey := fmt.Sprintf("%s-eks-DescribeCluster-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Cluster), nil
}
clusterOutput, err := client.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return cluster, err
}
cluster = *clusterOutput.Cluster
internal.Cache.Set(cacheKey, cluster, cache.DefaultExpiration)
return cluster, nil
}
func CachedEKSListClusters(client EKSClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var clusters []string
cacheKey := fmt.Sprintf("%s-eks-ListClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListClusters, err := client.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
//pagination
if ListClusters.NextToken == nil {
break
}
PaginationControl = ListClusters.NextToken
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region string, clusterName string) ([]string, error) {
var PaginationControl *string
var nodeGroups []string
cacheKey := fmt.Sprintf("%s-eks-ListNodeGroups-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListNodeGroups, err := client.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroups, err
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, nodeGroups, cache.DefaultExpiration)
return nodeGroups, nil
}
func CachedEKSDescribeNodeGroup(client EKSClientInterface, accountID string, region string, clusterName string, nodeGroupName string) (eksTypes.Nodegroup, error) {
var nodeGroup eksTypes.Nodegroup
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, nodeGroupName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Nodegroup), nil
}
nodeGroupOutput, err := client.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroupName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroup, err
}
nodeGroup = *nodeGroupOutput.Nodegroup
internal.Cache.Set(cacheKey, nodeGroup, cache.DefaultExpiration)
return nodeGroup, nil
}
+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: 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
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
"github.com/patrickmn/go-cache"
)
type ELBClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error)
}
func init() {
gob.Register([]elbTypes.LoadBalancerDescription{})
}
func CachedELBDescribeLoadBalancers(client ELBClientInterface, accountID string, region string) ([]elbTypes.LoadBalancerDescription, error) {
var PaginationControl *string
var loadbalancers []elbTypes.LoadBalancerDescription
cacheKey := fmt.Sprintf("%s-elb-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbTypes.LoadBalancerDescription), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancing.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancing.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancerDescriptions...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+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
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbV2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
"github.com/patrickmn/go-cache"
)
type ELBv2ClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)
}
func init() {
gob.Register([]elbV2Types.LoadBalancer{})
}
func CachedELBv2DescribeLoadBalancers(client ELBv2ClientInterface, accountID string, region string) ([]elbV2Types.LoadBalancer, error) {
var PaginationControl *string
var loadbalancers []elbV2Types.LoadBalancer
cacheKey := fmt.Sprintf("%s-elbv2-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbV2Types.LoadBalancer), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancingv2.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancingv2.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancers...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"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
}
+192
View File
@@ -0,0 +1,192 @@
package sdk
import (
"context"
"encoding/gob"
"github.com/BishopFox/cloudfox/internal"
"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)
}
func init() {
gob.Register([]string{})
gob.Register(glueTypes.DevEndpoint{})
gob.Register(glueTypes.Job{})
gob.Register([]glueTypes.Table{})
gob.Register([]glueTypes.Database{})
}
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
}
+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
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/grafana"
grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
"github.com/patrickmn/go-cache"
)
type GrafanaClientInterface interface {
ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error)
}
func init() {
gob.Register([]grafanaTypes.WorkspaceSummary{})
}
func CachedGrafanaListWorkspaces(client GrafanaClientInterface, accountID string, region string) ([]grafanaTypes.WorkspaceSummary, error) {
var PaginationControl *string
var workspaces []grafanaTypes.WorkspaceSummary
cacheKey := fmt.Sprintf("%s-grafana-ListWorkspaces-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]grafanaTypes.WorkspaceSummary), nil
}
for {
ListWorkspaces, err := client.ListWorkspaces(
context.TODO(),
&grafana.ListWorkspacesInput{
NextToken: PaginationControl,
},
func(o *grafana.Options) {
o.Region = region
},
)
if err != nil {
return workspaces, err
}
workspaces = append(workspaces, ListWorkspaces.Workspaces...)
//pagination
if ListWorkspaces.NextToken == nil {
break
}
PaginationControl = ListWorkspaces.NextToken
}
internal.Cache.Set(cacheKey, workspaces, cache.DefaultExpiration)
return workspaces, nil
}
+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
}
+290
View File
@@ -0,0 +1,290 @@
package sdk
import (
"context"
"crypto/md5"
"encoding/gob"
"encoding/hex"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/patrickmn/go-cache"
)
type AWSIAMClientInterface interface {
ListUsers(ctx context.Context, params *iam.ListUsersInput, optFns ...func(*iam.Options)) (*iam.ListUsersOutput, error)
ListAccessKeys(ctx context.Context, params *iam.ListAccessKeysInput, optFns ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error)
ListRoles(ctx context.Context, params *iam.ListRolesInput, optFns ...func(*iam.Options)) (*iam.ListRolesOutput, error)
GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error)
SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error)
ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
ListGroups(ctx context.Context, params *iam.ListGroupsInput, optFns ...func(*iam.Options)) (*iam.ListGroupsOutput, error)
}
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{})
gob.Register(customGAADOutput{})
}
func CachedIamListUsers(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.User, error) {
var PaginationControl *string
var users []iamTypes.User
cacheKey := fmt.Sprintf("%s-iam-ListUsers", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.User), nil
}
for {
ListUsers, err := IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
users = append(users, ListUsers.Users...)
// Pagination control.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, users, cache.DefaultExpiration)
return users, nil
}
func CachedIamListRoles(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.Role, error) {
var PaginationControl *string
var roles []iamTypes.Role
cacheKey := fmt.Sprintf("%s-iam-ListRoles", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.Role), nil
}
for {
ListRoles, err := IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
roles = append(roles, ListRoles.Roles...)
// Pagination control.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, roles, cache.DefaultExpiration)
return roles, nil
}
func CachedIamListAccessKeys(IAMClient AWSIAMClientInterface, accountID string, userName string) ([]iamTypes.AccessKeyMetadata, error) {
var PaginationControl *string
var accessKeys []iamTypes.AccessKeyMetadata
cacheKey := fmt.Sprintf("%s-iam-ListAccessKeys-%s", accountID, userName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.AccessKeyMetadata), nil
}
for {
ListAccessKeys, err := IAMClient.ListAccessKeys(
context.TODO(),
&iam.ListAccessKeysInput{
UserName: &userName,
Marker: PaginationControl,
},
)
if err != nil {
return accessKeys, err
}
accessKeys = append(accessKeys, ListAccessKeys.AccessKeyMetadata...)
// Pagination control.
if ListAccessKeys.Marker != nil {
PaginationControl = ListAccessKeys.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, accessKeys, cache.DefaultExpiration)
return accessKeys, nil
}
type customGAADOutput struct {
GroupDetailList []iamTypes.GroupDetail
UserDetailList []iamTypes.UserDetail
RoleDetailList []iamTypes.RoleDetail
Policies []iamTypes.ManagedPolicyDetail
}
func CachedIAMGetAccountAuthorizationDetails(IAMClient AWSIAMClientInterface, accountID string) (customGAADOutput, error) {
var PaginationControl *string
var GroupDetailList []iamTypes.GroupDetail
var UserDetailList []iamTypes.UserDetail
var RoleDetailList []iamTypes.RoleDetail
var Policies []iamTypes.ManagedPolicyDetail
var AllAccountAuthorizationDetails = customGAADOutput{
GroupDetailList: GroupDetailList,
UserDetailList: UserDetailList,
RoleDetailList: RoleDetailList,
Policies: Policies,
}
cacheKey := fmt.Sprintf("%s-iam-GetAccountAuthorizationDetails", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGAADOutput), nil
}
for {
GetAccountAuthorizationDetails, err := IAMClient.GetAccountAuthorizationDetails(
context.TODO(),
&iam.GetAccountAuthorizationDetailsInput{
Marker: PaginationControl,
},
)
if err != nil {
return AllAccountAuthorizationDetails, err
}
AllAccountAuthorizationDetails.GroupDetailList = append(AllAccountAuthorizationDetails.GroupDetailList, GetAccountAuthorizationDetails.GroupDetailList...)
AllAccountAuthorizationDetails.UserDetailList = append(AllAccountAuthorizationDetails.UserDetailList, GetAccountAuthorizationDetails.UserDetailList...)
AllAccountAuthorizationDetails.RoleDetailList = append(AllAccountAuthorizationDetails.RoleDetailList, GetAccountAuthorizationDetails.RoleDetailList...)
AllAccountAuthorizationDetails.Policies = append(AllAccountAuthorizationDetails.Policies, GetAccountAuthorizationDetails.Policies...)
if GetAccountAuthorizationDetails.Marker != nil {
PaginationControl = GetAccountAuthorizationDetails.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, AllAccountAuthorizationDetails, cache.DefaultExpiration)
return AllAccountAuthorizationDetails, nil
}
func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID string, principal *string, actionNames []string, resourceArns []string) ([]iamTypes.EvaluationResult, error) {
var PaginationControl2 *string
var EvaluationResults []iamTypes.EvaluationResult
md5hashedActionNames := md5.Sum([]byte(strings.Join(actionNames, "")))
md5hashedResourceArns := md5.Sum([]byte(strings.Join(resourceArns, "")))
// process arn and get the name of the resource
arn, err := arn.Parse(*principal)
if err != nil {
return EvaluationResults, err
}
name := arn.Resource
// if name has muliplte / then get the last one
if strings.Contains(arn.Resource, "/") {
name = arn.Resource[strings.LastIndex(arn.Resource, "/")+1:]
}
truncatedHashLength := 8
cacheKey := fmt.Sprintf("%s-iamSimulator-%s-%s-%s", accountID, name, hex.EncodeToString(md5hashedActionNames[:truncatedHashLength]), hex.EncodeToString(md5hashedResourceArns[:truncatedHashLength]))
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.EvaluationResult), nil
}
for {
SimulatePrincipalPolicy, err := IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
return EvaluationResults, err
}
EvaluationResults = append(EvaluationResults, SimulatePrincipalPolicy.EvaluationResults...)
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
internal.Cache.Set(cacheKey, EvaluationResults, cache.DefaultExpiration)
return EvaluationResults, nil
}
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
}
+147
View File
@@ -0,0 +1,147 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lambda"
lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/patrickmn/go-cache"
)
type LambdaClientInterface interface {
ListFunctions(context.Context, *lambda.ListFunctionsInput, ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error)
GetFunction(context.Context, *lambda.GetFunctionInput, ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error)
GetFunctionUrlConfig(context.Context, *lambda.GetFunctionUrlConfigInput, ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error)
}
func init() {
gob.Register([]lambdaTypes.FunctionConfiguration{})
gob.Register(customGetFuntionURLOutput{})
}
// create a CachedLambdaListFunctions function that accepts a lambda client, account id, region. Make sure it uses go-cache and handles the region option and pagination
func CachedLambdaListFunctions(client LambdaClientInterface, accountID string, region string) ([]lambdaTypes.FunctionConfiguration, error) {
var PaginationControl *string
var functions []lambdaTypes.FunctionConfiguration
cacheKey := fmt.Sprintf("%s-lambda-ListFunctions-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lambdaTypes.FunctionConfiguration), nil
}
for {
ListFunctions, err := client.ListFunctions(
context.TODO(),
&lambda.ListFunctionsInput{
Marker: PaginationControl,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functions, err
}
functions = append(functions, ListFunctions.Functions...)
//pagination
if ListFunctions.NextMarker == nil {
break
}
PaginationControl = ListFunctions.NextMarker
}
internal.Cache.Set(cacheKey, functions, cache.DefaultExpiration)
return functions, nil
}
type customGetFuntionURLOutput struct {
// The type of authentication that your function URL uses. Set to AWS_IAM if you
// want to restrict access to authenticated users only. Set to NONE if you want to
// bypass IAM authentication to create a public endpoint. For more information, see
// Security and auth model for Lambda function URLs (https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html)
// .
//
// This member is required.
AuthType lambdaTypes.FunctionUrlAuthType
// When the function URL was created, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
CreationTime *string
// The Amazon Resource Name (ARN) of your function.
//
// This member is required.
FunctionArn *string
// The HTTP URL endpoint for your function.
//
// This member is required.
FunctionUrl *string
// When the function URL configuration was last updated, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
LastModifiedTime *string
// The cross-origin resource sharing (CORS) (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
// settings for your function URL.
Cors *lambdaTypes.Cors
// Use one of the following options:
// - BUFFERED – This is the default option. Lambda invokes your function using
// the Invoke API operation. Invocation results are available when the payload is
// complete. The maximum payload size is 6 MB.
// - RESPONSE_STREAM – Your function streams payload results as they become
// available. Lambda invokes your function using the InvokeWithResponseStream API
// operation. The maximum response payload size is 20 MB, however, you can
// request a quota increase (https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html)
// .
InvokeMode lambdaTypes.InvokeMode
}
// create a CachedLambdaGetFunctionUrlConfig function that accepts a lambda client, account id, region, and function name. Make sure it uses go-cache and handles the region option
func CachedLambdaGetFunctionUrlConfig(client LambdaClientInterface, accountID string, region string, functionName string) (customGetFuntionURLOutput, error) {
var functionUrlConfigOutput customGetFuntionURLOutput
cacheKey := fmt.Sprintf("%s-lambda-GetFunctionUrlConfig-%s-%s", accountID, region, functionName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGetFuntionURLOutput), nil
}
GetFunctionUrlConfig, err := client.GetFunctionUrlConfig(
context.TODO(),
&lambda.GetFunctionUrlConfigInput{
FunctionName: &functionName,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functionUrlConfigOutput, err
}
functionUrlConfigOutput = customGetFuntionURLOutput{
AuthType: GetFunctionUrlConfig.AuthType,
CreationTime: GetFunctionUrlConfig.CreationTime,
FunctionArn: GetFunctionUrlConfig.FunctionArn,
FunctionUrl: GetFunctionUrlConfig.FunctionUrl,
LastModifiedTime: GetFunctionUrlConfig.LastModifiedTime,
Cors: GetFunctionUrlConfig.Cors,
InvokeMode: GetFunctionUrlConfig.InvokeMode,
}
internal.Cache.Set(cacheKey, functionUrlConfigOutput, cache.DefaultExpiration)
return functionUrlConfigOutput, nil
}
+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
}
+91
View File
@@ -0,0 +1,91 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types"
"github.com/patrickmn/go-cache"
)
type lightsailClientInterface interface {
GetInstances(context.Context, *lightsail.GetInstancesInput, ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error)
GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error)
}
func 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{})
}
func CachedLightsailGetInstances(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.Instance, error) {
var PaginationControl *string
var services []lightsailTypes.Instance
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
// cacheKey := fmt.Sprintf("%s-lightsail-GetInstances-%s", accountID, region)
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.([]lightsailTypes.Instance), nil
// }
for {
GetInstances, err := client.GetInstances(
context.TODO(),
&lightsail.GetInstancesInput{
PageToken: PaginationControl,
},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.Instances...)
//pagination
if GetInstances.NextPageToken == nil {
break
}
PaginationControl = GetInstances.NextPageToken
}
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
//internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
func CachedLightsailGetContainerServices(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.ContainerService, error) {
var services []lightsailTypes.ContainerService
cacheKey := fmt.Sprintf("%s-lightsail-GetContainerService-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lightsailTypes.ContainerService), nil
}
GetInstances, err := client.GetContainerServices(
context.TODO(),
&lightsail.GetContainerServicesInput{},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.ContainerServices...)
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+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
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/mq"
mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types"
"github.com/patrickmn/go-cache"
)
type MQClientInterface interface {
ListBrokers(context.Context, *mq.ListBrokersInput, ...func(*mq.Options)) (*mq.ListBrokersOutput, error)
}
func init() {
gob.Register([]mqTypes.BrokerSummary{})
}
// create CachedMQListBrokers function that uses go-cache and pagination
func CachedMQListBrokers(client MQClientInterface, accountID string, region string) ([]mqTypes.BrokerSummary, error) {
var PaginationControl *string
var brokers []mqTypes.BrokerSummary
cacheKey := fmt.Sprintf("%s-mq-ListBrokers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]mqTypes.BrokerSummary), nil
}
for {
ListBrokers, err := client.ListBrokers(
context.TODO(),
&mq.ListBrokersInput{
NextToken: PaginationControl,
},
func(o *mq.Options) {
o.Region = region
},
)
if err != nil {
return brokers, err
}
brokers = append(brokers, ListBrokers.BrokerSummaries...)
//pagination
if ListBrokers.NextToken == nil {
break
}
PaginationControl = ListBrokers.NextToken
}
internal.Cache.Set(cacheKey, brokers, cache.DefaultExpiration)
return brokers, nil
}
+40
View File
@@ -0,0 +1,40 @@
package sdk
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/mq"
mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types"
)
type MockedMQClient struct {
}
func (m *MockedMQClient) ListBrokers(ctx context.Context, input *mq.ListBrokersInput, options ...func(*mq.Options)) (*mq.ListBrokersOutput, error) {
return &mq.ListBrokersOutput{
BrokerSummaries: []mqTypes.BrokerSummary{
{
BrokerArn: aws.String("broker1"),
BrokerId: aws.String("broker1"),
BrokerName: aws.String("broker1"),
BrokerState: mqTypes.BrokerStateRunning,
Created: aws.Time(time.Now()),
DeploymentMode: mqTypes.DeploymentModeSingleInstance,
EngineType: mqTypes.EngineTypeRabbitmq,
HostInstanceType: aws.String("host1"),
},
{
BrokerArn: aws.String("broker2"),
BrokerId: aws.String("broker2"),
BrokerName: aws.String("broker2"),
BrokerState: mqTypes.BrokerStateRunning,
Created: aws.Time(time.Now()),
DeploymentMode: mqTypes.DeploymentModeSingleInstance,
EngineType: mqTypes.EngineTypeActivemq,
HostInstanceType: aws.String("host2"),
},
},
}, nil
}
+107
View File
@@ -0,0 +1,107 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/opensearch"
openSearchTypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
"github.com/patrickmn/go-cache"
)
type OpenSearchClientInterface interface {
ListDomainNames(context.Context, *opensearch.ListDomainNamesInput, ...func(*opensearch.Options)) (*opensearch.ListDomainNamesOutput, error)
DescribeDomainConfig(context.Context, *opensearch.DescribeDomainConfigInput, ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error)
DescribeDomain(context.Context, *opensearch.DescribeDomainInput, ...func(*opensearch.Options)) (*opensearch.DescribeDomainOutput, error)
}
func init() {
gob.Register([]openSearchTypes.DomainInfo{})
gob.Register(openSearchTypes.DomainConfig{})
gob.Register(openSearchTypes.DomainStatus{})
}
// create CachedOpenSearchListDomainNames function that uses go-cache and pagination
func CachedOpenSearchListDomainNames(client OpenSearchClientInterface, accountID string, region string) ([]openSearchTypes.DomainInfo, error) {
var domains []openSearchTypes.DomainInfo
cacheKey := fmt.Sprintf("%s-opensearch-ListDomainNames-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]openSearchTypes.DomainInfo), nil
}
ListDomainNames, err := client.ListDomainNames(
context.TODO(),
&opensearch.ListDomainNamesInput{},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return domains, err
}
domains = append(domains, ListDomainNames.DomainNames...)
internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration)
return domains, nil
}
// create CachedOpenSearchDescribeDomainConfig function that uses go-cache and pagination and supports region option
func CachedOpenSearchDescribeDomainConfig(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainConfig, error) {
var DomainConfig openSearchTypes.DomainConfig
cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomainConfig-%s-%s", accountID, region, domainName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(openSearchTypes.DomainConfig), nil
}
DescribeDomainConfig, err := client.DescribeDomainConfig(
context.TODO(),
&opensearch.DescribeDomainConfigInput{
DomainName: &domainName,
},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return DomainConfig, err
}
DomainConfig = *DescribeDomainConfig.DomainConfig
internal.Cache.Set(cacheKey, DomainConfig, cache.DefaultExpiration)
return DomainConfig, nil
}
// create CachedOpenSearchDescribeDomain function that uses go-cache and pagination and supports region option
func CachedOpenSearchDescribeDomain(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainStatus, error) {
var DomainStatus openSearchTypes.DomainStatus
cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomain-%s-%s", accountID, region, domainName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(openSearchTypes.DomainStatus), nil
}
DescribeDomain, err := client.DescribeDomain(
context.TODO(),
&opensearch.DescribeDomainInput{
DomainName: &domainName,
},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return DomainStatus, err
}
DomainStatus = *DescribeDomain.DomainStatus
internal.Cache.Set(cacheKey, DomainStatus, cache.DefaultExpiration)
return DomainStatus, nil
}
+63
View File
@@ -0,0 +1,63 @@
package sdk
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/opensearch"
openSearchTypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
)
type MockedOpenSearchClient struct {
}
func (m *MockedOpenSearchClient) ListDomainNames(ctx context.Context, input *opensearch.ListDomainNamesInput, options ...func(*opensearch.Options)) (*opensearch.ListDomainNamesOutput, error) {
return &opensearch.ListDomainNamesOutput{
DomainNames: []openSearchTypes.DomainInfo{
{
DomainName: aws.String("domain1"),
EngineType: openSearchTypes.EngineTypeOpenSearch,
},
{
DomainName: aws.String("domain2"),
EngineType: openSearchTypes.EngineTypeElasticsearch,
},
},
}, nil
}
func (m *MockedOpenSearchClient) DescribeDomainConfig(ctx context.Context, input *opensearch.DescribeDomainConfigInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error) {
return &opensearch.DescribeDomainConfigOutput{
DomainConfig: &openSearchTypes.DomainConfig{
EngineVersion: &openSearchTypes.VersionStatus{
Options: aws.String("OpenSearch-1.1"),
Status: &openSearchTypes.OptionStatus{
PendingDeletion: aws.Bool(false),
},
},
ClusterConfig: &openSearchTypes.ClusterConfigStatus{
Options: &openSearchTypes.ClusterConfig{
DedicatedMasterCount: aws.Int32(3),
DedicatedMasterEnabled: aws.Bool(true),
InstanceCount: aws.Int32(3),
WarmCount: aws.Int32(3),
WarmEnabled: aws.Bool(true),
},
},
DomainEndpointOptions: &openSearchTypes.DomainEndpointOptionsStatus{
Options: &openSearchTypes.DomainEndpointOptions{
EnforceHTTPS: aws.Bool(true),
},
},
},
}, nil
}
func (m *MockedOpenSearchClient) DescribeDomain(ctx context.Context, input *opensearch.DescribeDomainInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainOutput, error) {
return &opensearch.DescribeDomainOutput{
DomainStatus: &openSearchTypes.DomainStatus{
DomainName: aws.String("domain1"),
Endpoint: aws.String("https://domain1.us-east-1.es.amazonaws.com"),
},
}, nil
}
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff. Show more