mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 17:05:53 -10:00
Compare commits
77
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
187299ae82 | ||
|
|
8d145b135e | ||
|
|
729c59e198 | ||
|
|
6d1b47128e | ||
|
|
217ac7a018 | ||
|
|
4b0238c362 | ||
|
|
f78f1490d2 | ||
|
|
0159638ab7 | ||
|
|
1ff35a72ad | ||
|
|
29bfd2c863 | ||
|
|
40e0c92ef1 | ||
|
|
9f8ae08262 | ||
|
|
c85aa5de89 | ||
|
|
42d7cfef1e | ||
|
|
a5bce5ad8c | ||
|
|
8221b5ff6d | ||
|
|
aa42c8b808 | ||
|
|
5aab3b4bea | ||
|
|
01d320d64d | ||
|
|
5132622a13 | ||
|
|
08b5cd17af | ||
|
|
102b91e2c7 | ||
|
|
3a19fdb2fe | ||
|
|
d94fafe2b4 | ||
|
|
00ed8a7336 | ||
|
|
60daff386c | ||
|
|
cef1aa3a82 | ||
|
|
e88cb77394 | ||
|
|
fc52e01192 | ||
|
|
659b2e7248 | ||
|
|
06a7e7ec28 | ||
|
|
602332a0c5 | ||
|
|
471da59da2 | ||
|
|
d4ae745e8e | ||
|
|
dc0396ff33 | ||
|
|
c11ebf9a07 | ||
|
|
6c8936cfe4 | ||
|
|
fca0c4da14 | ||
|
|
2c5fec49c3 | ||
|
|
a9b95d9142 | ||
|
|
0a74adea9a | ||
|
|
adb9bcd55a | ||
|
|
03177e8726 | ||
|
|
1fb9f4c3e0 | ||
|
|
3c6c59ec4b | ||
|
|
3f8b828a6c | ||
|
|
b8bf12f1c4 | ||
|
|
690b3c8c77 | ||
|
|
845b69e9a0 | ||
|
|
fc7a57129f | ||
|
|
f6ec6a12f7 | ||
|
|
56075588d5 | ||
|
|
7b51d9da96 | ||
|
|
a0a64c579c | ||
|
|
2b294e5845 | ||
|
|
d5956cebb0 | ||
|
|
9581425231 | ||
|
|
ce6c3f76fa | ||
|
|
497c7940d2 | ||
|
|
b29e4ea6b3 | ||
|
|
2756cd0f45 | ||
|
|
c0597764a9 | ||
|
|
ef1b37cef7 | ||
|
|
8e9bd42a1e | ||
|
|
8b2f13ca88 | ||
|
|
8722782eae | ||
|
|
5f7596b497 | ||
|
|
0276ce25a4 | ||
|
|
7490d1aaa1 | ||
|
|
766fe55484 | ||
|
|
186cd04405 | ||
|
|
56247f40d4 | ||
|
|
bc91da000c | ||
|
|
1aca8cf4cf | ||
|
|
7f413fc9d1 | ||
|
|
f2f93779ca | ||
|
|
59157b40d1 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -66,4 +66,8 @@ output/*
|
||||
cloudfox
|
||||
*.log
|
||||
*.bak
|
||||
table_output.txt
|
||||
*.txt
|
||||
*.json
|
||||
*.csv
|
||||
*.log
|
||||
dist/
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+70
-98
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+55
-13
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user