Compare commits

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

No files matched your search

+2 -2
View File
@@ -15,10 +15,10 @@ jobs:
steps:
- name: Go 1.18
- name: Go 1.20
uses: actions/setup-go@v2
with:
go-version: ^1.18
go-version: ^1.20
id: go
- id: install-secret-key
+4 -1
View File
@@ -66,4 +66,7 @@ output/*
cloudfox
*.log
*.bak
table_output.txt
*.txt
*.json
*.csv
*.log
+1 -1
View File
@@ -39,7 +39,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
| Provider| CloudFox Commands |
| - | - |
| AWS | 26 |
| AWS | 30 |
| Azure | 4 |
| GCP | Support Planned |
| Kubernetes | Support Planned |
+39 -45
View File
@@ -1,22 +1,21 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type AccessKeysModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSProfile string
OutputFormat string
@@ -79,12 +78,27 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
fmt.Printf("[%s][%s] Only active access keys are shown.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
//fmt.Printf("[%s][%s] Preparing output.\n\n")
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
//m.writeLoot(m.output.FilePath, verbosity)
fmt.Printf("[%s][%s] %s access keys found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No access keys found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -116,50 +130,30 @@ func (m *AccessKeysModule) writeLoot(outputDirectory string, verbosity int) {
func (m *AccessKeysModule) getAccessKeysForAllUsers() {
// The "PaginationControl" value remains true until all data is received (default is 100 results per page).
// "" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
PaginationControl := true
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for PaginationControl {
// added this to break out if there no users
if len(ListUsers) != 0 {
for _, user := range ListUsers {
IAMQuery, err := m.IAMClient.ListUsers(context.TODO(), &iam.ListUsersInput{Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
results, err := sdk.CachedIamListAccessKeys(m.IAMClient, aws.ToString(m.Caller.Account), aws.ToString(user.UserName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// added this to break out if there no users
if len(IAMQuery.Users) == 0 {
break
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results {
// This for loop iterates through all users.
for PaginationControl {
PaginationMarker = nil
for _, user := range IAMQuery.Users {
results, err := m.IAMClient.ListAccessKeys(context.TODO(), &iam.ListAccessKeysInput{UserName: user.UserName, Marker: PaginationMarker})
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
// This for loop extracts relevant information for each access key and adds to output array
for _, key := range results.AccessKeyMetadata {
if key.Status == "Active" {
m.AnalyzedUsers = append(m.AnalyzedUsers, UserKeys{Username: *user.UserName, Key: *key.AccessKeyId})
}
}
PaginationControl = results.IsTruncated
PaginationMarker = results.Marker
}
}
PaginationControl = IAMQuery.IsTruncated
PaginationMarker = IAMQuery.Marker
}
}
+34 -107
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,37 +9,24 @@ import (
"sync"
"unicode/utf8"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type AWSS3Client interface {
ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error)
GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error)
GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error)
GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error)
}
type BucketsModule struct {
// General configuration data
S3Client AWSS3Client
//BucketsS3Client CloudFoxS3Client
S3Client sdk.AWSS3ClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
// This interface is used for unit testing
// S3ClientListBucketsInterface AWSS3Client
// S3ClientGetBucketPolicyInterface AWSS3Client
// S3ClientGetBucketLocationInterface AWSS3Client
// S3ClientGetPublicAccessBlockInterface AWSS3Client
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
// Main module data
@@ -75,11 +61,11 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating buckets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -134,8 +120,25 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s buckets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Bucket policies written to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
@@ -225,7 +228,9 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}()
var region string = "Global"
var name string
ListBuckets, err := m.listBuckets()
ListBuckets, err := sdk.CachedListBuckets(m.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
@@ -236,13 +241,13 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
Name: aws.ToString(b.Name),
AWSService: "S3",
}
region, err = m.getBucketRegion(aws.ToString(b.Name))
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
}
bucket.Region = region
policyJSON, err := m.getBucketPolicy(aws.ToString(b.Name))
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
} else {
@@ -270,86 +275,8 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
}
func (m *BucketsModule) listBuckets() ([]types.Bucket, error) {
var buckets []types.Bucket
ListBuckets, err := m.S3Client.ListBuckets(
context.TODO(),
&s3.ListBucketsInput{},
)
if err != nil {
m.modLog.Error(err.Error())
return buckets, err
}
buckets = append(buckets, ListBuckets.Buckets...)
return buckets, nil
}
func (m *BucketsModule) getBucketRegion(bucketName string) (string, error) {
GetBucketRegion, err := m.S3Client.GetBucketLocation(
context.TODO(),
&s3.GetBucketLocationInput{
Bucket: &bucketName,
},
)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
location := string(GetBucketRegion.LocationConstraint)
if location == "" {
location = "us-east-1"
}
return location, err
}
func (m *BucketsModule) getBucketPolicy(bucketName string) (string, error) {
r, err := m.getBucketRegion(bucketName)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
BucketPolicyObject, err := m.S3Client.GetBucketPolicy(
context.TODO(),
&s3.GetBucketPolicyInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
return "", err
}
return *BucketPolicyObject.Policy, nil
}
func (m *BucketsModule) getPublicAccessBlock(bucketName string) (*types.PublicAccessBlockConfiguration, error) {
r, err := m.getBucketRegion(bucketName)
PublicAccessBlock, err := m.S3Client.GetPublicAccessBlock(
context.TODO(),
&s3.GetPublicAccessBlockInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
return nil, err
}
return PublicAccessBlock.PublicAccessBlockConfiguration, err
}
func (m *BucketsModule) isPublicAccessBlocked(bucketName string) bool {
publicAccessBlock, err := m.getPublicAccessBlock(bucketName)
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
publicAccessBlock, err := sdk.CachedGetPublicAccessBlock(m.S3Client, aws.ToString(m.Caller.Account), r, bucketName)
if err != nil {
return false
}
@@ -360,7 +287,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string) bool {
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
m.storeAccessPolicy(bucket)
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name) {
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
bucket.IsPublic = "YES"
}
+20 -18
View File
@@ -16,11 +16,10 @@ import (
"github.com/spf13/afero"
)
type MockedS3Buckets struct {
AWSRegions []string
type MockedS3Client struct {
}
func (m *MockedS3Buckets) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
func (m *MockedS3Client) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) {
buckets := make([]types.Bucket, 1)
mockBucket := types.Bucket{
CreationDate: aws.Time(time.Now()),
@@ -34,7 +33,7 @@ func (m *MockedS3Buckets) ListBuckets(ctx context.Context, params *s3.ListBucket
}
func (m *MockedS3Buckets) GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) {
func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) {
bucketPolicy := `{
"Version": "2012-10-17",
"Statement": [
@@ -53,7 +52,7 @@ func (m *MockedS3Buckets) GetBucketPolicy(ctx context.Context, params *s3.GetBuc
return output, nil
}
func (m *MockedS3Buckets) GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) {
func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) {
locationConstraint := types.BucketLocationConstraint("us-east-2")
output := &s3.GetBucketLocationOutput{
LocationConstraint: locationConstraint,
@@ -61,7 +60,7 @@ func (m *MockedS3Buckets) GetBucketLocation(ctx context.Context, params *s3.GetB
return output, nil
}
func (m *MockedS3Buckets) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
output := &s3.GetPublicAccessBlockOutput{
PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{
BlockPublicAcls: true,
@@ -74,6 +73,19 @@ func (m *MockedS3Buckets) GetPublicAccessBlock(ctx context.Context, params *s3.G
}
func TestListBuckets(t *testing.T) {
m := BucketsModule{
S3Client: &MockedS3Client{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
Goroutines: 3,
}
subtests := []struct {
name string
outputDirectory string
@@ -85,17 +97,7 @@ func TestListBuckets(t *testing.T) {
name: "test1",
outputDirectory: ".",
verbosity: 2,
testModule: BucketsModule{
S3Client: &MockedS3Buckets{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "unittesting",
Goroutines: 30,
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
},
testModule: m,
expectedResult: []Bucket{{
Name: "mockBucket123",
}},
@@ -110,7 +112,7 @@ func TestListBuckets(t *testing.T) {
t.Run(subtest.name, func(t *testing.T) {
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
for index, expectedBucket := range subtest.expectedResult {
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting/table/buckets.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/buckets.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
+126
View File
@@ -0,0 +1,126 @@
package aws
import (
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/fsx"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/organizations"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
iamSimMod := IamSimulatorModule{
IAMClient: iamSimPPClient,
Caller: caller,
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return iamSimMod
}
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool) SNSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
cloudFoxSNSClient := SNSModule{
SNSClient: sns.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
}
return cloudFoxSNSClient
}
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
cloudFoxS3Client := BucketsModule{
S3Client: s3.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return cloudFoxS3Client
}
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
sqsClient := SQSModule{
SQSClient: sqs.NewFromConfig(AWSConfig),
Caller: caller,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return sqsClient
}
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
lambdaClient := LambdasModule{
LambdaClient: lambda.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return lambdaClient
}
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
codeBuildClient := CodeBuildModule{
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return codeBuildClient
}
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
ecrClient := ECRModule{
ECRClient: ecr.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return ecrClient
}
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
fileSystemsClient := FilesystemsModule{
EFSClient: efs.NewFromConfig(AWSConfig),
FSxClient: fsx.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return fileSystemsClient
}
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
orgClient := OrgModule{
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
Caller: caller,
AWSProfile: AWSProfile,
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
}
return orgClient
}
+23 -70
View File
@@ -1,29 +1,24 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CloudFormationGetTemplateAPIClient interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
}
type CloudformationModule struct {
// General configuration data
CloudFormationDescribeStacksInterface cloudformation.DescribeStacksAPIClient
CloudFormationGetTemplateInterface CloudFormationGetTemplateAPIClient
CloudFormationClient sdk.CloudFormationClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -135,11 +130,27 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s cloudformation stacks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No cloudformation stacks found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -251,7 +262,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}()
var stackTemplateBody string = ""
DescribeStacks, err := m.describeStacks(r)
DescribeStacks, err := sdk.CachedCloudFormationDescribeStacks(m.CloudFormationClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -263,7 +274,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
stackOutputs := stack.Outputs
stackParameters := stack.Parameters
stackTemplateBody, err = m.getTemplateBody(r, stackName)
stackTemplateBody, err = sdk.CachedCloudFormationGetTemplate(m.CloudFormationClient, aws.ToString(m.Caller.Account), r, stackName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -281,61 +292,3 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
}
}
func (m *CloudformationModule) describeStacks(r string) ([]types.Stack, error) {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var stacks []types.Stack
for {
DescribeStacks, err := m.CloudFormationDescribeStacksInterface.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
// The "NextToken" value is nil when there's no more data to return.
if DescribeStacks.NextToken != nil {
PaginationControl = DescribeStacks.NextToken
} else {
PaginationControl = nil
break
}
}
return stacks, nil
}
func (m *CloudformationModule) getTemplateBody(r string, stackName string) (string, error) {
var stackTemplateBody string
GetTemplate, err := m.CloudFormationGetTemplateInterface.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
},
func(o *cloudformation.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
return stackTemplateBody, nil
}
+14 -14
View File
@@ -38,15 +38,12 @@ type TemplateBody struct {
TemplateBody string `json:"TemplateBody"`
}
type MockedCloudformationClientDescribeStacks struct {
describeStacks Stacks
}
type MockedCloudformationClientGetTemplate struct {
type MockedCloudformationClient struct {
describeStacks Stacks
getTemplateBody TemplateBody
}
func (m *MockedCloudformationClientDescribeStacks) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
func (m *MockedCloudformationClient) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.describeStacks)
if err != nil {
@@ -66,7 +63,7 @@ func (m *MockedCloudformationClientDescribeStacks) DescribeStacks(ctx context.Co
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
}
func (m *MockedCloudformationClientGetTemplate) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
func (m *MockedCloudformationClient) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.getTemplateBody)
if err != nil {
log.Fatalf("can't unmarshall file %s", TEMPLATE_BODY_TEST_FILE)
@@ -75,6 +72,10 @@ func (m *MockedCloudformationClientGetTemplate) GetTemplate(ctx context.Context,
return &cloudformation.GetTemplateOutput{TemplateBody: &m.getTemplateBody.TemplateBody}, nil
}
func (m *MockedCloudformationClient) ListStacks(ctx context.Context, params *cloudformation.ListStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error) {
return &cloudformation.ListStacksOutput{}, nil
}
func TestCloudFormation(t *testing.T) {
subtests := []struct {
name string
@@ -88,13 +89,12 @@ func TestCloudFormation(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: CloudformationModule{
CloudFormationDescribeStacksInterface: &MockedCloudformationClientDescribeStacks{},
CloudFormationGetTemplateInterface: &MockedCloudformationClientGetTemplate{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
CloudFormationClient: &MockedCloudformationClient{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
expectedResult: []CFStack{{
Name: "myteststack",
+252
View File
@@ -0,0 +1,252 @@
package aws
import (
"fmt"
"path/filepath"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type CodeBuildModule struct {
// General configuration data
CodeBuildClient sdk.CodeBuildClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Main module data
Projects []Project
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Project struct {
Region string
Name string
Arn string
Role string
Admin string
CanPrivEsc string
}
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "codebuild"
localAdminMap := make(map[string]bool)
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating CodeBuild projects for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
//create a channel to receive the objects
dataReceiver := make(chan Project)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
// Perform role analysis
if m.pmapperError == nil {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Projects[i].Role)
}
} else {
for i := range m.Projects {
m.Projects[i].Admin, m.Projects[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Projects[i].Role, m.iamSimClient, localAdminMap)
}
}
// add - if struct is not empty do this. otherwise, dont write anything.
if m.pmapperError == nil {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
"Region",
"Name",
"Role",
"IsAdminRole?",
//"CanPrivEscToAdmin?",
}
}
// Table rows
for i := range m.Projects {
if m.pmapperError == nil {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
m.Projects[i].CanPrivEsc,
},
)
} else {
m.output.Body = append(
m.output.Body,
[]string{
m.Projects[i].Region,
m.Projects[i].Name,
m.Projects[i].Role,
m.Projects[i].Admin,
},
)
}
}
var seen []string
for _, project := range m.Projects {
if !internal.Contains(project.Name, seen) {
seen = append(seen, project.Name)
}
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
//fmt.Printf("[%s][%s] %d projects with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d projects found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
} else {
fmt.Printf("[%s][%s] No projects found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
}
func (m *CodeBuildModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getcodeBuildProjectsPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *CodeBuildModule) Receiver(receiver chan Project, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Projects = append(m.Projects, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *CodeBuildModule) getcodeBuildProjectsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Project) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
projects, err := sdk.CachedCodeBuildListProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
}
for _, project := range projects {
details, err := sdk.CachedCodeBuildBatchGetProjects(m.CodeBuildClient, aws.ToString(m.Caller.Account), r, project)
if err != nil {
sharedLogger.Error(err.Error())
}
dataReceiver <- Project{
Name: aws.ToString(details.Name),
Region: r,
Role: aws.ToString(details.ServiceRole),
Admin: "",
CanPrivEsc: "",
}
}
}
+150
View File
@@ -0,0 +1,150 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
"github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/afero"
)
type mockedCodeBuildClient struct {
}
func (m *mockedCodeBuildClient) ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) {
out := codebuild.ListProjectsOutput{
Projects: []string{"test1", "test2"},
}
return &out, nil
}
func (m *mockedCodeBuildClient) BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error) {
project1out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test1"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test1"),
Source: &types.ProjectSource{
Type: "CODECOMMIT",
Location: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test1"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test1"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
project2out := codebuild.BatchGetProjectsOutput{
Projects: []types.Project{
{
Name: aws.String("test2"),
Environment: &types.ProjectEnvironment{
Image: aws.String("aws/codebuild/standard:4.0"),
},
ServiceRole: aws.String("arn:aws:iam::123456789012:role/test2"),
Source: &types.ProjectSource{
Type: "GITHUB",
Location: aws.String("https://test.github.com/test2"),
},
Artifacts: &types.ProjectArtifacts{
Type: "S3",
Location: aws.String("s3://test2"),
},
TimeoutInMinutes: aws.Int32(60),
},
},
}
if params.Names[0] == "test1" {
return &project1out, nil
} else if params.Names[0] == "test2" {
return &project2out, nil
} else {
return nil, nil
}
}
func (m *mockedCodeBuildClient) GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
out := codebuild.GetResourcePolicyOutput{
Policy: aws.String(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"codebuild:StartBuild",
"codebuild:BatchGetBuilds",
"codebuild:StopBuild",
"codebuild:ListProjects",
"codebuild:BatchGetProjects"
],
"Resource": "*"
}
]
}`),
}
return &out, nil
}
func TestCodeBuildProjects(t *testing.T) {
m := CodeBuildModule{
CodeBuildClient: &mockedCodeBuildClient{},
AWSProfile: "unittesting",
AWSRegions: []string{"us-west-1"},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
WrapTable: true,
SkipAdminCheck: true,
}
fs := internal.MockFileSystem(true)
defer internal.MockFileSystem(false)
tmpDir := "."
// execute the module with verbosity set to 2
m.PrintCodeBuildProjects("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/codebuild.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭───────────┬───────┬──────────────────────────────────────┬──────────────╮
│ Region │ Name │ Role │ IsAdminRole? │
├───────────┼───────┼──────────────────────────────────────┼──────────────┤
│ us-west-1 │ test1 │ arn:aws:iam::123456789012:role/test1 │ Skipped │
│ us-west-1 │ test2 │ arn:aws:iam::123456789012:role/test2 │ Skipped │
╰───────────┴───────┴──────────────────────────────────────┴──────────────╯
`, "\n")
fmt.Println(expectedResults)
fmt.Println("results file")
fmt.Println(string(resultsFile))
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
}
}
+451
View File
@@ -0,0 +1,451 @@
package aws
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type DatabasesModule struct {
RDSClient sdk.RDSClientInterface
RedshiftClient sdk.RedShiftClientInterface
DynamoDBClient sdk.DynamoDBClientInterface
DocDBClient sdk.DocDBClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
Databases []Database
CommandCounter internal.CommandCounter
Errors []string
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Database struct {
AWSService string
Region string
Engine string
Name string
Arn string
UserName string
Endpoint string
Port int32
Protocol string
Public string
Size string
}
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "databases"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating databases for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: RDS, DynamoDB\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Database)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
//execute regional checks
for _, region := range m.AWSRegions {
wg.Add(1)
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
sort.Slice(m.Databases, func(i, j int) bool {
return m.Databases[i].AWSService < m.Databases[j].AWSService
})
m.output.Headers = []string{
"Service",
"Engine",
"Region",
"Name",
"Size",
"UserName",
"Endpoint",
//"Port",
//"Protocol",
//"Public",
}
// Table rows
for i := range m.Databases {
m.output.Body = append(
m.output.Body,
[]string{
m.Databases[i].AWSService,
m.Databases[i].Engine,
m.Databases[i].Region,
m.Databases[i].Name,
m.Databases[i].Size,
m.Databases[i].UserName,
m.Databases[i].Endpoint,
// strconv.Itoa(int(m.Databases[i].Port)),
// m.Databases[i].Protocol,
// m.Databases[i].Public,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s databases found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No databases found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *DatabasesModule) Receiver(receiver chan Database, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Databases = append(m.Databases, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *DatabasesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("rds", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
go m.getRdsClustersPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("redshift", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getRedshiftDatabasesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("dynamodb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDynamoDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("docdb", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getDocDBTablesPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
path := filepath.Join(outputDirectory, "loot")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
f := filepath.Join(path, "databases-UrlsOnly.txt")
var out string
for _, database := range m.Databases {
out = out + fmt.Sprintln(database.Endpoint)
}
err = os.WriteFile(f, []byte(out), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
panic(err.Error())
}
if verbosity > 2 {
fmt.Println()
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed this databases into nmap and something like gowitness/aquatone for screenshots."))
fmt.Print(out)
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
}
func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
m.CommandCounter.Pending--
m.CommandCounter.Executing++
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var public string
for _, instance := range DBInstances {
if instance.Endpoint != nil {
name := aws.ToString(instance.DBInstanceIdentifier)
port := instance.Endpoint.Port
endpoint := aws.ToString(instance.Endpoint.Address)
engine := aws.ToString(instance.Engine)
if instance.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
dataReceiver <- Database{
AWSService: "RDS",
Region: r,
Name: name,
Engine: engine,
Endpoint: endpoint,
UserName: aws.ToString(instance.MasterUsername),
Port: port,
Protocol: aws.ToString(instance.Engine),
Public: public,
}
}
}
}
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "Redshift"
protocol := "https"
Clusters, err := sdk.CachedRedShiftDescribeClusters(m.RedshiftClient, aws.ToString(m.Caller.Account), r)
if err != nil {
if errors.As(err, &oe) {
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
}
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
var public string
for _, cluster := range Clusters {
name := aws.ToString(cluster.DBName)
//id := workspace.Id
endpoint := aws.ToString(cluster.Endpoint.Address)
if cluster.PubliclyAccessible {
public = "True"
} else {
public = "False"
}
port := cluster.Endpoint.Port
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
Protocol: protocol,
Public: public,
}
}
}
func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DynamoDB"
Tables, err := sdk.CachedDynamoDBListTables(m.DynamoDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, table := range Tables {
//get size of dynamodb table
TableOutput, err := sdk.CachedDynamoDBDescribeTable(m.DynamoDBClient, aws.ToString(m.Caller.Account), r, table)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
size := aws.ToInt64(TableOutput.TableSizeBytes)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: table,
Size: strconv.Itoa(int(size)),
UserName: "N/A",
Endpoint: "N/A",
}
}
}
func (m *DatabasesModule) getDocDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
// m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
awsService := "DocDB"
Clusters, err := sdk.CachedDocDBDescribeDBClusters(m.DocDBClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, cluster := range Clusters {
name := aws.ToString(cluster.DBClusterIdentifier)
endpoint := aws.ToString(cluster.Endpoint)
port := aws.ToInt32(cluster.Port)
//size := aws.ToInt64(TableOutput.Table.TableSizeBytes)
userName := aws.ToString(cluster.MasterUsername)
dataReceiver <- Database{
AWSService: awsService,
Region: r,
Name: name,
Endpoint: endpoint,
Port: port,
UserName: userName,
//Size: strconv.Itoa(int(size)),
}
}
}
+118 -107
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -10,9 +9,10 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
@@ -21,11 +21,7 @@ import (
type ECRModule struct {
// General configuration data
ECRClient *ecr.Client
// These interfaces are used for unit testing
ECRClientDescribeReposInterface ecr.DescribeRepositoriesAPIClient
ECRClientDescribeImagesInterface ecr.DescribeImagesAPIClient
ECRClient sdk.AWSECRClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -49,6 +45,8 @@ type Repository struct {
PushedAt string
ImageTags string
ImageSize int64
Policy policy.Policy
PolicyJSON string
}
func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbosity int) {
@@ -125,11 +123,27 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s repositories found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No repositories found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -224,107 +238,104 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
defer func() {
<-semaphore
}()
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
var PaginationControl2 *string
for {
DescribeRepositories, err := m.ECRClientDescribeReposInterface.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = r
},
)
var allImages []types.ImageDetail
var repoURI string
var repoName string
DescribeRepositories, err := m.describeRepositories(r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, repo := range DescribeRepositories {
repoName = aws.ToString(repo.RepositoryName)
repoURI = aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
images, err := m.describeImages(r, repoName)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, repo := range DescribeRepositories.Repositories {
repoName := aws.ToString(repo.RepositoryName)
repoURI := aws.ToString(repo.RepositoryUri)
//created := *repo.CreatedAt
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
var images []types.ImageDetail
for {
DescribeImages, err := m.ECRClientDescribeImagesInterface.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
RepositoryName: &repoName,
NextToken: PaginationControl2,
},
func(o *ecr.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
//images := DescribeImages.ImageDetails
images = append(images, DescribeImages.ImageDetails...)
if DescribeImages.NextToken != nil {
PaginationControl2 = DescribeImages.NextToken
} else {
// not sure if this is the right way to do this, but adding this code here was the only way i could
// sort the results from all pages to look for the latest push.
PaginationControl2 = nil
sort.Slice(images, func(i, j int) bool {
return images[i].ImagePushedAt.Format("2006-01-02 15:04:05") < images[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
if len(images) > 1 {
image = images[len(images)-1]
} else if len(images) == 1 {
image = images[0]
} else {
break
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
// }
break
}
}
}
// The "NextToken" value is nil when there's no more data to return.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
return
}
allImages = append(allImages, images...)
}
sort.Slice(allImages, func(i, j int) bool {
return allImages[i].ImagePushedAt.Format("2006-01-02 15:04:05") < allImages[j].ImagePushedAt.Format("2006-01-02 15:04:05")
})
var image types.ImageDetail
var imageTags string
if len(allImages) > 1 {
image = allImages[len(allImages)-1]
} else if len(allImages) == 1 {
image = allImages[0]
} else {
return
}
if len(image.ImageTags) > 0 {
imageTags = image.ImageTags[0]
}
//imageTags := image.ImageTags[0]
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
imageSize := aws.ToInt64(image.ImageSizeInBytes)
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
dataReceiver <- Repository{
AWSService: "ECR",
Name: repoName,
Region: r,
URI: pullURI,
PushedAt: pushedAt,
ImageTags: imageTags,
ImageSize: imageSize,
}
}
func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
var repositories []types.Repository
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
repositories = append(repositories, Repositories...)
return repositories, nil
}
func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
var images []types.ImageDetail
ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
if err != nil {
m.CommandCounter.Error++
return nil, err
}
images = append(images, ImageDetails...)
return images, nil
}
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
var repoPolicy policy.Policy
Policy, err := sdk.CachedECRGetRepositoryPolicy(m.ECRClient, aws.ToString(m.Caller.Account), r, repository)
if err != nil {
m.CommandCounter.Error++
return repoPolicy, err
}
repoPolicy, err = policy.ParseJSONPolicy([]byte(Policy))
if err != nil {
return repoPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", repository, err)
}
return repoPolicy, nil
}
+19 -15
View File
@@ -15,15 +15,10 @@ import (
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
type MockedECRClientDescribeRepos struct {
AWSRegions []string
type MockedECRClient struct {
}
type MockedECRClientDescribeImages struct {
AWSRegions []string
}
func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
return &ecr.DescribeRepositoriesOutput{
Repositories: []types.Repository{
{
@@ -34,7 +29,7 @@ func (m *MockedECRClientDescribeRepos) DescribeRepositories(ctx context.Context,
}, nil
}
func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
return &ecr.DescribeImagesOutput{
ImageDetails: []types.ImageDetail{
{
@@ -46,7 +41,14 @@ func (m *MockedECRClientDescribeImages) DescribeImages(context.Context, *ecr.Des
}, nil
}
func (m *MockedECRClient) GetRepositoryPolicy(context.Context, *ecr.GetRepositoryPolicyInput, ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
return &ecr.GetRepositoryPolicyOutput{
PolicyText: aws.String("policyText"),
}, nil
}
func TestDescribeRepos(t *testing.T) {
subtests := []struct {
name string
outputDirectory string
@@ -59,13 +61,15 @@ func TestDescribeRepos(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ECRModule{
ECRClientDescribeReposInterface: &MockedECRClientDescribeRepos{},
ECRClientDescribeImagesInterface: &MockedECRClientDescribeImages{},
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
ECRClient: &MockedECRClient{},
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
OutputFormat: "table",
AWSProfile: "test",
Goroutines: 30,
AWSRegions: AWSRegions,
},
expectedResult: []Repository{{
Name: "test1",
+90 -34
View File
@@ -2,6 +2,7 @@ package aws
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -9,12 +10,12 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -24,12 +25,12 @@ type DescribeTasksDefinitionAPIClient interface {
DescribeTaskDefinition(context.Context, *ecs.DescribeTaskDefinitionInput, ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
}
type ECSTasksModule struct {
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
DescribeTasksClient ecs.DescribeTasksAPIClient
ListTasksClient ecs.ListTasksAPIClient
ListClustersClient ecs.ListClustersAPIClient
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
DescribeTasksClient ecs.DescribeTasksAPIClient
ListTasksClient ecs.ListTasksAPIClient
ListClustersClient ecs.ListClustersAPIClient
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -50,15 +51,16 @@ type ECSTasksModule struct {
}
type MappedECSTask struct {
Cluster string
TaskDefinition string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
Cluster string
TaskDefinitionName string
TaskDefinitionContent string
LaunchType string
ID string
ExternalIP string
PrivateIP string
Role string
Admin string
CanPrivEsc string
}
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
@@ -77,7 +79,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 +126,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
receiverDone <- true
<-receiverDone
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver)
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -141,7 +143,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 +176,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 +193,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 +207,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 +267,24 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
m.CommandCounter.Error++
}
for _, task := range m.MappedECSTasks {
if task.TaskDefinitionContent != "" {
path := filepath.Join(path, "task-definitions")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
taskDefinitionFilename := filepath.Join(path, task.TaskDefinitionName+".json")
err = os.WriteFile(taskDefinitionFilename, []byte(task.TaskDefinitionContent), 0644)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
}
}
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
@@ -386,13 +421,20 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
for _, task := range DescribeTasks.Tasks {
taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
mappedTask := MappedECSTask{
Cluster: getNameFromARN(clusterARN),
TaskDefinition: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: m.getTaskRole(aws.ToString(task.TaskDefinitionArn), region),
Cluster: getNameFromARN(clusterARN),
TaskDefinitionName: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
TaskDefinitionContent: getTaskDefinitionContent(taskDefinition),
LaunchType: string(task.LaunchType),
ID: getIDFromECSTask(aws.ToString(task.TaskArn)),
PrivateIP: getPrivateIPv4AddressFromECSTask(task),
Role: getTaskRole(taskDefinition),
}
eniID := getElasticNetworkInterfaceIDOfECSTask(task)
@@ -404,7 +446,21 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
}
}
func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) string {
func getTaskRole(taskDefinition types.TaskDefinition) string {
return aws.ToString(taskDefinition.TaskRoleArn)
}
func getTaskDefinitionContent(taskDefinition types.TaskDefinition) string {
// return taskDefinition as a json string
taskDefinitionContent, err := json.Marshal(taskDefinition)
if err != nil {
return ""
}
return string(taskDefinitionContent)
}
func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region string) (types.TaskDefinition, error) {
DescribeTaskDefinition, err := m.DescribeTaskDefinitionClient.DescribeTaskDefinition(
context.TODO(),
&ecs.DescribeTaskDefinitionInput{
@@ -417,9 +473,9 @@ func (m *ECSTasksModule) getTaskRole(taskDefinitionArn string, region string) st
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return ""
return types.TaskDefinition{}, err
}
return aws.ToString(DescribeTaskDefinition.TaskDefinition.TaskRoleArn)
return *DescribeTaskDefinition.TaskDefinition, nil
}
/* UNUSED CODE BLOCK - PLEASE REVIEW AND DELETE IF NOT NEEDED
+38 -126
View File
@@ -1,18 +1,15 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -21,11 +18,8 @@ import (
type EKSModule struct {
// General configuration data
// These interfaces are used for unit testing
EKSClientListClustersInterface eks.ListClustersAPIClient
EKSClientDescribeClusterInterface eks.DescribeClusterAPIClient
EKSClientListNodeGroupsInterface eks.ListNodegroupsAPIClient
EKSClientDescribeNodeGroupInterface eks.DescribeNodegroupAPIClient
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
EKSClient sdk.EKSClientInterface
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -76,7 +70,7 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
// Initialized the tools we'll need to check if any workload roles are admin or can privesc to admin
//fmt.Printf("[%s][%s] Attempting to build a PrivEsc graph in memory using local pmapper data if it exists on the filesystem.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -204,11 +198,27 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d clusters with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
} else {
fmt.Printf("[%s][%s] No clusters found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -304,16 +314,19 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
<-semaphore
}()
var clusters []string
var role string
clusters, err := m.listClusters(r)
clusters, err := sdk.CachedEKSListClusters(m.EKSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, clusterName := range clusters {
clusterDetails, err := m.describeCluster(clusterName, r)
var role string
var oidc string = ""
var publicEndpoint = ""
clusterDetails, err := sdk.CachedEKSDescribeCluster(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -321,20 +334,25 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
//nodeGroups = append(nodeGroups, DescribeCluster.Cluster.)
endpoint := aws.ToString(clusterDetails.Endpoint)
oidc := aws.ToString(clusterDetails.Identity.Oidc.Issuer)
publicEndpoint := strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
if clusterDetails.Identity != nil && clusterDetails.Identity.Oidc != nil {
oidc = aws.ToString(clusterDetails.Identity.Oidc.Issuer)
}
if clusterDetails.ResourcesVpcConfig != nil {
publicEndpoint = strconv.FormatBool(clusterDetails.ResourcesVpcConfig.EndpointPublicAccess)
}
// if DescribeCluster.Cluster.ResourcesVpcConfig.PublicAccessCidrs[0] == "0.0.0.0/0" {
// publicCIDRs := "0.0.0.0/0"
// } else {
// publicCIDRs := "specific IPs"
// }
ListNodeGroups := m.listNodeGroups(clusterName, r)
ListNodeGroups, err := sdk.CachedEKSListNodeGroups(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
if len(ListNodeGroups) > 0 {
for _, nodeGroup := range ListNodeGroups {
nodeGroupDetails, err := m.describeNodegroup(clusterName, nodeGroup, r)
nodeGroupDetails, err := sdk.CachedEKSDescribeNodeGroup(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, nodeGroup, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
@@ -375,109 +393,3 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
}
}
func (m *EKSModule) listClusters(r string) ([]string, error) {
var PaginationControl *string
var clusters []string
for {
ListClusters, err := m.EKSClientListClustersInterface.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
// The "NextToken" value is nil when there's no more data to return.
if ListClusters.NextToken != nil {
PaginationControl = ListClusters.NextToken
} else {
PaginationControl = nil
break
}
}
return clusters, nil
}
func (m *EKSModule) listNodeGroups(clusterName string, r string) []string {
var PaginationControl *string
var nodeGroups []string
for {
ListNodeGroups, err := m.EKSClientListNodeGroupsInterface.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
return nodeGroups
}
func (m *EKSModule) describeCluster(clusterName string, r string) (*types.Cluster, error) {
var err error
//var clusterDetails types.Cluster
DescribeCluster, err := m.EKSClientDescribeClusterInterface.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeCluster.Cluster, err
}
func (m *EKSModule) describeNodegroup(clusterName string, nodeGroup string, r string) (*types.Nodegroup, error) {
DescribeNodegroup, err := m.EKSClientDescribeNodeGroupInterface.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroup,
},
func(o *eks.Options) {
o.Region = r
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
return DescribeNodegroup.Nodegroup, err
}
+6 -21
View File
@@ -12,22 +12,10 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sts"
)
type MockedEKSClientListClusters struct {
type MockedEKSClientInterface struct {
}
type MockedEKSClientDescribeCluster struct {
}
type MockedEKSClientListNodegroups struct {
}
type MockedEKSClientDescribeNodegroup struct {
}
type MockedIAMSimulatePrincipalPolicy struct {
}
func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
func (m *MockedEKSClientInterface) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
testClusters := []string{}
testClusters = append(testClusters, "test1")
@@ -38,7 +26,7 @@ func (m *MockedEKSClientListClusters) ListClusters(ctx context.Context, params *
return testListClustersOutput, nil
}
func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
func (m *MockedEKSClientInterface) DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
testIdentity := types.Identity{
Oidc: &types.OIDC{
Issuer: aws.String("abc123"),
@@ -61,7 +49,7 @@ func (m *MockedEKSClientDescribeCluster) DescribeCluster(context.Context, *eks.D
return testDescribeClusterOutput, nil
}
func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
func (m *MockedEKSClientInterface) ListNodegroups(ctx context.Context, params *eks.ListNodegroupsInput, optFns ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
testNodegroups := []string{}
testNodegroups = append(testNodegroups, "test1")
@@ -72,7 +60,7 @@ func (m *MockedEKSClientListNodegroups) ListNodegroups(ctx context.Context, para
return testListNodegroupsOutput, nil
}
func (m *MockedEKSClientDescribeNodegroup) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
func (m *MockedEKSClientInterface) DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
testNodegroup := types.Nodegroup{}
testNodegroup.NodeRole = aws.String("roleABC")
testDescribeNodegroupOutput := &eks.DescribeNodegroupOutput{
@@ -95,10 +83,7 @@ func TestEks(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: EKSModule{
EKSClientListClustersInterface: &MockedEKSClientListClusters{},
EKSClientDescribeClusterInterface: &MockedEKSClientDescribeCluster{},
EKSClientListNodeGroupsInterface: &MockedEKSClientListNodegroups{},
EKSClientDescribeNodeGroupInterface: &MockedEKSClientDescribeNodegroup{},
EKSClient: &MockedEKSClientInterface{},
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
OutputFormat: "table",
+21 -5
View File
@@ -84,7 +84,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
receiverDone <- true
<-receiverDone
m.printENIsData(outputFormat, outputDirectory, dataReceiver)
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
}
@@ -101,7 +101,7 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
}
}
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI) {
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
m.output.Headers = []string{
"ID",
"Type",
@@ -126,11 +126,27 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s elastic network interfaces found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+774 -648
View File
File diff suppressed because it is too large. Load diff
+73 -98
View File
@@ -8,6 +8,7 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
@@ -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)
@@ -750,7 +722,10 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
m.CommandCounter.Error++
break
}
if err == nil {
// find the number of environment variables in the model in a pointer safe way
if DescribeModel.PrimaryContainer != nil {
if len(DescribeModel.PrimaryContainer.Environment) > 0 {
name := fmt.Sprintf("[Model] %s", aws.ToString(DescribeModel.ModelName))
+95 -80
View File
@@ -9,19 +9,18 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/efs"
"github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/aws/aws-sdk-go-v2/service/fsx"
fsxTypes "github.com/aws/aws-sdk-go-v2/service/fsx/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
)
var green = color.New(color.FgGreen).SprintFunc()
type FilesystemsModule struct {
// General configuration data
@@ -53,6 +52,7 @@ type FilesystemObject struct {
IP string
Policy string
MountTarget string
Permissions string
}
func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirectory string, verbosity int) {
@@ -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 "", ""
}
+84 -120
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
@@ -19,10 +20,7 @@ import (
type IamSimulatorModule struct {
// General configuration data
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListUsersClient iam.ListUsersAPIClient
IAMListRolesClient iam.ListRolesAPIClient
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
@@ -75,7 +73,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
var actionList []string
var pmapperCommands []string
var pmapperOutFileName string
@@ -173,15 +171,30 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
// for _, line := range pmapperCommands {
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
// }
m.writeLoot(m.output.FilePath, verbosity, pmapperCommands)
m.writeLoot(o.Table.DirectoryName, verbosity, pmapperCommands)
} else if principal != "" || action != "" {
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -249,47 +262,31 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
m.CommandCounter.Total++
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListUsers, err := m.IAMListUsersClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
for _, user := range ListUsers.Users {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, user := range ListUsers {
//name := user.UserName
principal := user.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
@@ -304,99 +301,66 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
m.CommandCounter.Pending--
m.CommandCounter.Executing++
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
for {
ListRoles, err := m.IAMListRolesClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
for _, role := range ListRoles.Roles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
for _, role := range ListRoles {
//name := user.UserName
principal := role.Arn
adminCheckResult := m.isPrincipalAnAdmin(principal)
if adminCheckResult {
query := "Appears to be an administrator"
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
func (m *IamSimulatorModule) getPolicySimulatorResult(principal *string, actionNames []string, resource string, dataReceiver chan SimulatorResult) {
var PaginationControl2 *string
//var policySourceArn = "*"
//var arn *string
var resourceArns []string
resourceArns = append(resourceArns, resource)
//var resourceArns = []string{"*"}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
EvaluationResults, err := sdk.CachedIamSimulatePrincipalPolicy(m.IAMClient, aws.ToString(m.Caller.Account), principal, actionNames, resourceArns)
break
}
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
return
}
for _, result := range SimulatePrincipalPolicy.EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
for _, result := range EvaluationResults {
evalName := result.EvalActionName
query := fmt.Sprintf("can %s on %s", *evalName, resource)
decision := result.EvalDecision
if decision == "allowed" {
dataReceiver <- SimulatorResult{
AWSService: "IAM",
Principal: aws.ToString(principal),
Query: query,
Decision: string(decision),
}
}
// Pagination control. After the last page of output, the for loop exits.
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
}
func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
@@ -412,7 +376,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
"ssm:GetDocument",
}
for {
SimulatePrincipalPolicy, err := m.IAMSimulatePrincipalPolicyClient.SimulatePrincipalPolicy(
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
+99 -25
View File
@@ -10,6 +10,7 @@ import (
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
@@ -23,21 +24,20 @@ import (
type InstancesModule struct {
// General configuration data
EC2Client *ec2.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
IAMListInstanceProfilesClient iam.ListInstanceProfilesAPIClient
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
EC2Client *ec2.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
UserDataAttributesOnly bool
AWSProfile string
WrapTable bool
InstanceProfileToRolesMap map[string][]iamTypes.Role
SkipAdminCheck bool
pmapperMod PmapperModule
pmapperError error
iamSimClient IamSimulatorModule
// Module's Results
MappedInstances []MappedInstance
@@ -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,
+474 -607
View File
File diff suppressed because it is too large. Load diff
+54 -12
View File
@@ -9,9 +9,10 @@ import (
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/lambda"
"github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
@@ -21,8 +22,8 @@ import (
type LambdasModule struct {
// General configuration data
LambdaClient *lambda.Client
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
LambdaClient *lambda.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -55,6 +56,7 @@ type Lambda struct {
}
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
@@ -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
}
+19 -3
View File
@@ -210,10 +210,26 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
}
if len(m.IPv4_Private) > 0 || len(m.IPv4_Public) > 0 || len(m.IPv6) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath)
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath)
o := internal.OutputClient{
Verbosity: m.Verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName)
fmt.Printf("[%s][%s] %s network services found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+290
View File
@@ -0,0 +1,290 @@
package aws
import (
"fmt"
"path/filepath"
"strconv"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/organizations/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/kyokomi/emoji"
"github.com/sirupsen/logrus"
)
type OrgModule struct {
OrganizationsClient sdk.OrganizationsClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
SkipAdminCheck bool
WrapTable bool
DescribeOrgOutput *types.Organization
// Main module data
Accounts []Account
Orgs map[string]Org
CommandCounter internal.CommandCounter
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
}
type Org struct {
OrgId string
MgmtAccount string
ChildAccounts []Account
}
type Account struct {
ProfileName string
isManagementAccount bool
Name string
Id string
Email string
Arn string
Status string
OrgId string
}
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
var err error
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "org"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Checking if account %s is the management account in an organization.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.DescribeOrgOutput, err = sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
return
}
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
fmt.Printf("[%s][%s] Account %s is either not associated with an organization, or you do not have the organizations:DescribeOrganization permission.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
} else {
if m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account)) {
m.addOrgAccounts()
} else {
m.addOrgAccount()
//fmt.Printf("[%s][%s] Account %s is not the management account in an organization %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
}
m.output.Headers = []string{
"Name",
"ID",
"isManagementAccount?",
"Status",
"Email",
}
// Table rows
for i := range m.Accounts {
m.output.Body = append(
m.output.Body,
[]string{
m.Accounts[i].Name,
m.Accounts[i].Id,
strconv.FormatBool(m.Accounts[i].isManagementAccount),
m.Accounts[i].Status,
m.Accounts[i].Email,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %d accounts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body))
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
}
}
func (m *OrgModule) ProcessMultipleAccounts(AWSProfiles []string, version string) {
var seenOrgs []string
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Account)
// Create a channel to signal to stop
receiverDone := make(chan bool)
// Create a channel to signal to stop
go m.Receiver(dataReceiver, receiverDone)
for _, profile := range AWSProfiles {
wg.Add(1)
m.CommandCounter.Pending++
go m.FindMgmtAccounts(profile, version, wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
receiverDone <- true
<-receiverDone
orgId := aws.ToString(m.DescribeOrgOutput.Id)
// if this orgId is not in seenOrgs, add it to seenOrgs
if !internal.Contains(orgId, seenOrgs) {
seenOrgs = append(seenOrgs, orgId)
// add this org to the Orgs map
Org := Org{
OrgId: orgId,
}
m.Orgs[orgId] = Org
} else {
continue
}
isMgmtAccount := m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(m.Caller.Account))
if isMgmtAccount {
mgmtAccount := aws.ToString(m.Caller.Account)
fmt.Printf("[%s] Found an Organization Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), mgmtAccount)
//mgmtAccounts[mgmtAccount] = append(mgmtAccounts[mgmtAccount], profile)
}
}
}
func (m *OrgModule) Receiver(receiver chan Account, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Accounts = append(m.Accounts, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *OrgModule) FindMgmtAccounts(profile string, version string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Account) {
var err error
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
dataReceiver <- Account{
isManagementAccount: m.IsManagementAccount(DescribeOrganization, aws.ToString(m.Caller.Account)),
Name: aws.ToString(m.Caller.Account),
Id: aws.ToString(m.Caller.Account),
Status: "ACTIVE",
Email: aws.ToString(m.Caller.Account),
OrgId: aws.ToString(DescribeOrganization.Id),
Arn: aws.ToString(m.Caller.Account),
}
}
func (m *OrgModule) IsManagementAccount(Organization *types.Organization, account string) bool {
// Check if the account is the management account
// https://docs.aws.amazon.com/organizations/latest/APIReference/API_DescribeOrganization.html
if aws.ToString(Organization.MasterAccountId) == account {
return true
}
return false
}
func (m *OrgModule) addOrgAccounts() {
accounts, err := sdk.CachedOrganizationsListAccounts(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, account := range accounts {
m.Accounts = append(m.Accounts, Account{
isManagementAccount: m.IsManagementAccount(m.DescribeOrgOutput, aws.ToString(account.Id)),
Name: aws.ToString(account.Name),
Id: aws.ToString(account.Id),
Email: aws.ToString(account.Email),
Arn: aws.ToString(account.Arn),
Status: string(account.Status),
})
}
}
func (m *OrgModule) addOrgAccount() {
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
m.CommandCounter.Error++
return
}
m.Accounts = append(m.Accounts, Account{
isManagementAccount: true,
Name: "Mgmt Account",
Id: aws.ToString(DescribeOrganization.MasterAccountId),
Email: aws.ToString(DescribeOrganization.MasterAccountEmail),
Arn: aws.ToString(DescribeOrganization.MasterAccountArn),
Status: "ACTIVE",
})
m.Accounts = append(m.Accounts, Account{
isManagementAccount: false,
Name: "This account",
Id: aws.ToString(m.Caller.Account),
Email: "Unkonwn",
Arn: aws.ToString(m.Caller.Arn),
Status: "ACTIVE",
})
}
func (m *OrgModule) IsCallerAccountPartOfAnOrg() bool {
DescribeOrgOutput, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Errorf("Failed to describe organization: %s", err)
return false
} else {
m.DescribeOrgOutput = DescribeOrgOutput
}
return true
}
+18 -2
View File
@@ -190,10 +190,26 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s log entries found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
//m.writeLoot()
+248 -191
View File
@@ -1,18 +1,17 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
@@ -20,7 +19,7 @@ import (
type IamPermissionsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -80,6 +79,7 @@ type PermissionsRow struct {
Effect string
Action string
Resource string
Condition string
}
func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDirectory string, verbosity int, principal string) {
@@ -94,26 +94,27 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
if principal != "" {
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
}
m.getGAAD(principal)
m.parsePermissions()
m.getGAAD()
m.parsePermissions(principal)
m.output.Headers = []string{
"Service",
"Principal Type",
//"Service",
"Type",
"Name",
//"Arn",
"Policy Type",
"Policy",
"Policy Name",
"Effect",
"Action",
"Resource",
"Condition",
}
//Table rows
@@ -121,7 +122,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.output.Body = append(
m.output.Body,
[]string{
m.Rows[i].AWSService,
//m.Rows[i].AWSService,
m.Rows[i].Type,
m.Rows[i].Name,
//m.Rows[i].Arn,
@@ -130,16 +131,33 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
m.Rows[i].Effect,
m.Rows[i].Action,
m.Rows[i].Resource,
m.Rows[i].Condition,
},
)
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector3(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -148,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)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, attachedPolicy := range m.Users[i].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
for _, inlinePolicy := range m.Users[i].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
for j, group := range m.Users[i].GroupList {
for _, gaadGroup := range m.Groups {
if gaadGroup.Name == group {
for _, attachedPolicy := range m.Groups[j].AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
// for each group in the user's group list, get the attached and inline policy names, and then get the permissions from those policies
for g := range m.Users[i].GroupList {
if principal == "" {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
for _, inlinePolicy := range m.Groups[j].InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
} else {
// if user supplied a principal name without the arn, try to create the arn
if !strings.Contains(principal, "arn:") {
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
} else {
inputArn = principal
}
if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) {
for _, group := range m.Groups {
if group.Name == m.Users[i].GroupList[g] {
for _, attachedPolicy := range group.AttachedPolicies {
m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
}
for _, inlinePolicy := range group.InlinePolicies {
m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
}
}
}
}
}
}
// for group := range m.Users[i].GroupList {
// for _, gaadGroup := range m.Groups {
// if gaadGroup.Name == group {
// for _, attachedPolicy := range m.Groups[j].AttachedPolicies {
// m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name)
// }
// for _, inlinePolicy := range m.Groups[j].InlinePolicies {
// m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name)
// }
// }
// }
// }
}
}
func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, attachedPolicy types.AttachedPolicy, IAMtype string, name string) {
//var policies []types.ManagedPolicyDetail
var s StatementEntry
var s policy.PolicyStatement
var AWSService = "IAM"
var hasConditions string
for _, p := range m.Policies {
if p.Name == aws.ToString(attachedPolicy.PolicyName) {
for _, d := range p.PolicyVersionList {
if d.IsDefaultVersion {
parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
//parsedPolicyDocument, _ := parsePolicyDocument(d.Document)
document, _ := url.QueryUnescape(aws.ToString(d.Document))
parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document))
for _, s = range parsedPolicyDocument.Statement {
//version := parsedPolicyDocument.Version
effect := s.Effect
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")
}
+47 -14
View File
@@ -71,16 +71,7 @@ func (m *PmapperModule) initPmapperGraph() error {
return err
}
m.pmapperGraph = graph.New(graph.StringHash, graph.Directed())
for _, node := range m.Nodes {
_ = m.pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = m.pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
m.pmapperGraph = m.createAndPopulateGraph()
for i := range m.Nodes {
if m.doesNodeHavePathToAdmin(m.Nodes[i]) {
@@ -89,12 +80,38 @@ func (m *PmapperModule) initPmapperGraph() error {
} else {
m.Nodes[i].PathToAdmin = false
}
}
return nil
}
func (m *PmapperModule) createAndPopulateGraph() graph.Graph[string, string] {
pmapperGraph := graph.New(graph.StringHash, graph.Directed())
// having issues with caching the graph. will have to swing back and try again later
// gob.Register(pmapperGraph)
// cacheKey := fmt.Sprintf("%s-pmapperGraph", aws.ToString(m.Caller.Account))
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.(graph.Graph[string, string])
// }
for _, node := range m.Nodes {
_ = pmapperGraph.AddVertex(node.Arn)
}
for _, edge := range m.Edges {
_ = pmapperGraph.AddEdge(edge.Source, edge.Destination)
}
//internal.Cache.Set(cacheKey, pmapperGraph, cache.DefaultExpiration)
return pmapperGraph
}
func (m *PmapperModule) DoesPrincipalHavePathToAdmin(principal string) bool {
for i := range m.Nodes {
if m.Nodes[i].Arn == principal {
@@ -131,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)))
+61 -82
View File
@@ -1,21 +1,20 @@
package aws
import (
"context"
"fmt"
"path/filepath"
"strconv"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type IamPrincipalsModule struct {
// General configuration data
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
Caller sts.GetCallerIdentityOutput
AWSRegions []string
@@ -87,8 +86,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
// done <- true
// <-done
m.getIAMUsers()
m.getIAMRoles()
m.addIAMUsersToTable()
m.addIAMRolesToTable()
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
@@ -135,10 +134,26 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s IAM principals found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
@@ -160,100 +175,64 @@ func (m *IamPrincipalsModule) executeChecks(wg *sync.WaitGroup) {
}
*/
func (m *IamPrincipalsModule) getIAMUsers() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMUsersToTable() {
var AWSService = "IAM"
var IAMtype = "User"
var attachedPolicies []string
var inlinePolicies []string
for {
ListUsers, err := m.IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
for _, user := range ListUsers.Users {
arn := user.Arn
name := user.UserName
for _, user := range ListUsers {
arn := user.Arn
name := user.UserName
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
// Pagination control. After the last page of output, the for loop exits.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
m.Users = append(
m.Users,
User{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
func (m *IamPrincipalsModule) getIAMRoles() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationControl *string
func (m *IamPrincipalsModule) addIAMRolesToTable() {
//var totalRoles int
var AWSService = "IAM"
var IAMtype = "Role"
var attachedPolicies []string
var inlinePolicies []string
for {
ListRoles, err := m.IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range ListRoles.Roles {
arn := role.Arn
name := role.RoleName
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
for _, role := range ListRoles {
arn := role.Arn
name := role.RoleName
// Pagination control. After the last page of output, the for loop exits.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
//fmt.Printf("IAM Roles: %d\n\n", totalRoles)
break
}
m.Roles = append(
m.Roles,
Role{
AWSService: AWSService,
Arn: aws.ToString(arn),
Name: aws.ToString(name),
Type: IAMtype,
AttachedPolicies: attachedPolicies,
InlinePolicies: inlinePolicies,
})
}
}
+18 -2
View File
@@ -115,10 +115,26 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resources found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
+739
View File
@@ -0,0 +1,739 @@
package aws
import (
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
)
type ResourceTrustsModule struct {
// General configuration data
Caller sts.GetCallerIdentityOutput
AWSRegions []string
Goroutines int
WrapTable bool
AWSProfile string
CloudFoxVersion string
Resources2 []Resource2
CommandCounter internal.CommandCounter
output internal.OutputData2
modLog *logrus.Entry
}
type Resource2 struct {
AccountID string
Name string
ARN string
Region string
Policy policy.Policy
PolicyJSON string
ResourcePolicySummary string
Public string
Interesting string
}
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
// These stuct values are used by the output module
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "resource-trusts"
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
}
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Lambda, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
wg := new(sync.WaitGroup)
semaphore := make(chan struct{}, m.Goroutines)
// Create a channel to signal the spinner aka task status goroutine to finish
spinnerDone := make(chan bool)
//fire up the the task status spinner/updated
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
//create a channel to receive the objects
dataReceiver := make(chan Resource2)
// Create a channel to signal to stop
receiverDone := make(chan bool)
go m.Receiver(dataReceiver, receiverDone)
for _, region := range m.AWSRegions {
wg.Add(1)
m.CommandCounter.Pending++
go m.executeChecks(region, wg, semaphore, dataReceiver)
}
wg.Add(1)
go m.getS3Buckets(wg, semaphore, dataReceiver)
wg.Wait()
// Send a message to the spinner goroutine to close the channel and stop
spinnerDone <- true
<-spinnerDone
// Send a message to the data receiver goroutine to close the channel and stop
receiverDone <- true
<-receiverDone
// add - if struct is not empty do this. otherwise, dont write anything.
m.output.Headers = []string{
//"Account ID",
"ARN",
"Public",
"Interesting",
"Resource Policy Summary",
}
// sort the table roles by Interesting
sort.Slice(m.Resources2, func(i, j int) bool {
return m.Resources2[j].Interesting > m.Resources2[i].Interesting
})
// Table rows
for i := range m.Resources2 {
m.output.Body = append(
m.output.Body,
[]string{
//m.Resources2[i].AccountID,
m.Resources2[i].ARN,
m.Resources2[i].Public,
m.Resources2[i].Interesting,
m.Resources2[i].ResourcePolicySummary,
},
)
}
if len(m.output.Body) > 0 {
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
//fmt.Printf("[%s][%s] Resource policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sns", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("sqs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getSQSQueuesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("ecr", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("codebuild", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getCodeBuildResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("lambda", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver)
}
res, err = servicemap.IsServiceInRegion("efs", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone chan bool) {
defer close(receiverDone)
for {
select {
case data := <-receiver:
m.Resources2 = append(m.Resources2, data)
case <-receiverDone:
receiverDone <- true
return
}
}
}
func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
ListTopics, err := cloudFoxSNSClient.listTopics(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, t := range ListTopics {
var statementSummaryInEnglish string
var isInteresting string = "No"
topic, err := cloudFoxSNSClient.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// check if topic is public or not
if topic.Policy.IsPublic() {
topic.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
topic.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
if !topic.Policy.IsEmpty() {
for i, statement := range topic.Policy.Statement {
var prefix string = ""
if len(topic.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = statementSummaryInEnglish + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(t.TopicArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: topic.IsPublic,
Region: parsedArn.Region,
Name: topic.Name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, b := range ListBuckets {
var statementSummaryInEnglish string
var isInteresting string = "No"
bucket := &Bucket{
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
}
name := aws.ToString(b.Name)
region, err := sdk.CachedGetBucketLocation(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), name)
if err != nil {
m.modLog.Error(err.Error())
continue
}
policyJSON, err := sdk.CachedGetBucketPolicy(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
if err != nil {
m.modLog.Error(err.Error())
continue
} else {
bucket.PolicyJSON = policyJSON
}
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
} else {
bucket.Policy = policy
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
if policy.IsPublic() {
bucket.IsPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
bucket.IsPublic = "No"
}
// If there is a resouce policy, convert the resource policy into plain english
if !bucket.Policy.IsEmpty() {
for i, statement := range bucket.Policy.Statement {
var prefix string = ""
if len(bucket.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = statementSummaryInEnglish + prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: bucket.Arn,
ResourcePolicySummary: statementSummaryInEnglish,
Public: bucket.IsPublic,
Region: region,
Name: name,
Interesting: isInteresting,
}
}
}
}
func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListQueues, err := cloudFoxSQSClient.listQueues(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, q := range ListQueues {
var statementSummaryInEnglish string
var isInteresting string = "No"
var isPublic string = "No"
queue, err := cloudFoxSQSClient.getQueueWithAttributes(q, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !queue.Policy.IsEmpty() {
if queue.Policy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
}
for i, statement := range queue.Policy.Statement {
prefix := ""
if len(queue.Policy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(&queue.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: q,
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, repo := range DescribeRepositories {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
repoPolicy, err := cloudFoxECRClient.getECRRepositoryPolicy(r, aws.ToString(repo.RepositoryName))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !repoPolicy.IsEmpty() {
if repoPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range repoPolicy.Statement {
prefix := ""
if len(repoPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(repo.RepositoryArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(repo.RepositoryName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, p := range ListProjects {
var projectPolicy policy.Policy
var statementSummaryInEnglish string
var isPublic string
var isInteresting string = "No"
project, err := sdk.CachedCodeBuildBatchGetProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
continue
}
policyJSON, err := sdk.CachedCodeBuildGetResourcePolicy(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
if err != nil {
sharedLogger.Error(err.Error())
continue
}
projectPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if !projectPolicy.IsEmpty() {
if projectPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range projectPolicy.Statement {
prefix := ""
if len(projectPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(project.Arn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(project.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, f := range ListFunctions {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
functionPolicy, err := cloudFoxLambdaClient.getResourcePolicy(r, aws.ToString(f.FunctionName))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if !functionPolicy.IsEmpty() {
if functionPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
for i, statement := range functionPolicy.Statement {
prefix := ""
if len(functionPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(f.FunctionArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(f.FunctionName),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}
for _, fs := range ListFileSystems {
var isPublic string
var statementSummaryInEnglish string
var isInteresting string = "No"
fsPolicy, err := sdk.CachedDescribeFileSystemPolicy(cloudFoxEFSClient.EFSClient, aws.ToString(fs.FileSystemId), r, aws.ToString(m.Caller.Account))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
continue
}
if fsPolicy.IsPublic() {
isPublic = magenta("Yes")
isInteresting = magenta("Yes")
} else {
isPublic = "No"
}
if !fsPolicy.IsEmpty() {
for i, statement := range fsPolicy.Statement {
prefix := ""
if len(fsPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: aws.ToString(fs.FileSystemArn),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(fs.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}
}
func isResourcePolicyInteresting(statementSummaryInEnglish string) bool {
// check if the statement has any of the following items, but make sure the check is case insensitive
// if it does, then return true
// if it doesn't, then return false
if //(strings.Contains(strings.ToLower(statementSummaryInEnglish), "*") && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("Everyone")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), "denied")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgID")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalAccount")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:PrincipalOrgPaths")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("role")) ||
strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("root")) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceArn"))) ||
(strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("aws:arn")) && !strings.Contains(strings.ToLower(statementSummaryInEnglish), strings.ToLower("SourceAccount"))) {
return true
}
return false
}
+151 -97
View File
@@ -1,7 +1,6 @@
package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +11,7 @@ import (
"strconv"
"strings"
"github.com/BishopFox/cloudfox/aws/sdk"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
@@ -22,8 +22,7 @@ import (
type RoleTrustsModule struct {
// General configuration data
IAMClientListRoles iam.ListRolesAPIClient
IAMClient *iam.Client
IAMClient sdk.AWSIAMClientInterface
IAMSimulatePrincipalPolicyClient iam.SimulatePrincipalPolicyAPIClient
Caller sts.GetCallerIdentityOutput
@@ -101,7 +100,7 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
fmt.Printf("[%s][%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
//fmt.Printf("[%s][%s] Looking for pmapper data for this account and building a PrivEsc graph in golang if it exists.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMSimulatePrincipalPolicyClient, m.Caller, m.AWSProfile, m.Goroutines)
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// if m.pmapperError != nil {
// fmt.Printf("[%s][%s] No pmapper data found for this account. Using cloudfox's iam-simulator for role analysis\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// } else {
@@ -119,19 +118,67 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
}
}
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: principalsHeader,
Body: principalsBody,
Name: "role-trusts-principals",
})
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: servicesHeader,
Body: servicesBody,
Name: "role-trusts-services",
})
federatedHeader, federatedBody := m.printFederatedTrusts(outputFormat, outputDirectory)
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: federatedHeader,
Body: federatedBody,
Name: "role-trusts-federated",
})
o.WriteFullOutput(o.Table.TableFiles, nil)
if len(principalsBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(principalsBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(servicesBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
if len(federatedBody) > 0 {
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
m.printPrincipalTrusts(outputFormat, outputDirectory)
m.printServiceTrusts(outputFormat, outputDirectory)
m.printFederatedTrusts(outputFormat, outputDirectory)
}
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-principals"
var column1 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
@@ -139,7 +186,7 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirec
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Principal",
"ExternalID",
@@ -152,41 +199,49 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirec
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := principal
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
////m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// ////m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// //internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
// fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-services"
var column1 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Service",
"ExternalID",
@@ -194,7 +249,7 @@ func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirecto
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Service",
"ExternalID",
@@ -206,42 +261,55 @@ func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirecto
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, service := range statement.Principal.Service {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
column2 := service
column3 := statement.Condition.StringEquals.StsExternalID
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// sort the rows based on column 2 (service)
sort.SliceStable(body, func(i, j int) bool {
return body[i][1] < body[j][1]
})
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
//m.sortTrustsTablePerTrustedPrincipal()
return header, body
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) {
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
var header []string
var body [][]string
m.output.FullFilename = ""
m.output.Body = nil
m.output.CallingModule = "role-trusts"
m.output.FullFilename = "role-trusts-federated"
var column3, column2 string
var column1, column3, column2 string
if m.pmapperError == nil {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
@@ -249,7 +317,7 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirec
"CanPrivEscToAdmin?",
}
} else {
m.output.Headers = []string{
header = []string{
"Role",
"Trusted Provider",
"Trusted Subject",
@@ -259,39 +327,44 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirec
}
for _, role := range m.AnalyzedRoles {
column1 := aws.ToString(role.roleARN)
if outputFormat == "wide" {
column1 = aws.ToString(role.roleARN)
} else {
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
}
for _, statement := range role.trustsDoc.Statement {
if len(statement.Principal.Federated) > 0 {
column2, column3 = parseFederatedTrustPolicy(statement)
column4 := role.Admin
column5 := role.CanPrivEsc
if m.pmapperError == nil {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4, column5})
body = append(body, []string{column1, column2, column3, column4, column5})
} else {
m.output.Body = append(m.output.Body, []string{column1, column2, column3, column4})
body = append(body, []string{column1, column2, column3, column4})
}
}
}
}
m.sortTrustsTablePerTrustedPrincipal()
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
return header, body
} else {
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
}
// if len(m.output.Body) > 0 {
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
// //m.output.OutputSelector(outputFormat)
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
// } else {
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
// }
}
func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, string) {
var column2, column3 string
if statement.Condition.StringLike.TokenActionsGithubusercontentComAud != "" || len(statement.Condition.StringLike.TokenActionsGithubusercontentComSub) > 0 {
column2 = "GitHub Actions (" + statement.Principal.Federated[0] + ")"
column2 = "GitHub Actions" // (" + statement.Principal.Federated[0] + ")"
trustedRepos := strings.Join(statement.Condition.StringLike.TokenActionsGithubusercontentComSub, "\n")
if trustedRepos == "" {
column3 = "ALL REPOS!!!"
@@ -300,13 +373,13 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
}
} else if statement.Condition.StringEquals.SAMLAud == "https://signin.aws.amazon.com/saml" {
if strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO (" + statement.Principal.Federated[0] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
} else if strings.Contains(statement.Principal.Federated[0], "Okta") {
column2 = "Okta (" + statement.Principal.Federated[0] + ")"
column2 = "Okta" // (" + statement.Principal.Federated[0] + ")"
}
column3 = "Not applicable"
} else if statement.Condition.StringEquals.OidcEksAud != "" || statement.Condition.StringEquals.OidcEksSub != "" || statement.Condition.StringLike.OidcEksAud != "" || statement.Condition.StringLike.OidcEksSub != "" {
column2 = "EKS (" + statement.Principal.Federated[0] + ")"
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.StringEquals.OidcEksSub != "" {
column3 = statement.Condition.StringEquals.OidcEksSub
} else if statement.Condition.StringLike.OidcEksSub != "" {
@@ -315,16 +388,16 @@ func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, strin
column3 = "ALL SERVICE ACCOUNTS!"
}
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
column2 = "Cognito (" + statement.Principal.Federated[0] + ")"
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
}
} else {
if column2 == "" && strings.Contains(statement.Principal.Federated[0], "oidc.eks") {
column2 = "EKS (" + statement.Principal.Federated[0] + ")"
column2 = "EKS" // (" + statement.Principal.Federated[0] + ")"
column3 = "ALL SERVICE ACCOUNTS!"
} else if column2 == "" && strings.Contains(statement.Principal.Federated[0], "AWSSSO") {
column2 = "AWS SSO (" + statement.Principal.Federated[0] + ")"
column2 = "AWS SSO" // (" + statement.Principal.Federated[0] + ")"
}
}
@@ -341,50 +414,31 @@ func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
}
func (m *RoleTrustsModule) getAllRoleTrusts() {
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
var PaginationMarker *string
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
}
// This for loop exits at the end depending on whether the output hits its last page (see pagination control block at the end of the loop).
for {
results, err := m.IAMClientListRoles.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationMarker,
},
)
for _, role := range ListRoles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
for _, role := range results.Roles {
trustsdoc, err := parseRoleTrustPolicyDocument(role)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
if role.Arn != nil {
m.AnalyzedRoles = append(m.AnalyzedRoles, AnalyzedRole{
roleARN: role.Arn,
trustsDoc: trustsdoc,
Admin: "",
CanPrivEsc: "",
})
}
// Pagination control. After the last page of output, the for loop exits.
if results.IsTruncated {
PaginationMarker = results.Marker
} else {
PaginationMarker = nil
break
}
}
}
func parseRoleTrustPolicyDocument(role types.Role) (trustPolicyDocument, error) {
+19 -3
View File
@@ -83,11 +83,27 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s DNS records found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
)
type APIGatewayClientInterface interface {
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
}
func RegisterApiGatewayTypes() {
gob.Register([]apiGatewayTypes.RestApi{})
}
// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.RestApi, error) {
var PaginationControl *string
var restAPIs []apiGatewayTypes.RestApi
cacheKey := fmt.Sprintf("%s-apigateway-GetRestApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatewayTypes.RestApi), nil
}
for {
GetRestApis, err := client.GetRestApis(
context.TODO(),
&apigateway.GetRestApisInput{
Position: PaginationControl,
},
func(o *apigateway.Options) {
o.Region = region
},
)
if err != nil {
return restAPIs, err
}
restAPIs = append(restAPIs, GetRestApis.Items...)
//pagination
if GetRestApis.Position == nil {
break
}
PaginationControl = GetRestApis.Position
}
return restAPIs, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
"github.com/patrickmn/go-cache"
)
type APIGatewayv2ClientInterface interface {
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
}
func RegisterApiGatewayV2Types() {
gob.Register([]apiGatwayV2Types.Api{})
}
func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID string, region string) ([]apiGatwayV2Types.Api, error) {
var PaginationControl *string
var apis []apiGatwayV2Types.Api
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetApis-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apiGatwayV2Types.Api), nil
}
for {
GetApis, err := client.GetApis(
context.TODO(),
&apigatewayv2.GetApisInput{
NextToken: PaginationControl,
},
func(o *apigatewayv2.Options) {
o.Region = region
},
)
if err != nil {
return apis, err
}
apis = append(apis, GetApis.Items...)
//pagination
if GetApis.NextToken == nil {
break
}
PaginationControl = GetApis.NextToken
}
internal.Cache.Set(cacheKey, apis, cache.DefaultExpiration)
return apis, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/apprunner"
apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types"
"github.com/patrickmn/go-cache"
)
type AppRunnerClientInterface interface {
ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error)
}
func RegisterApprunnerTypes() {
gob.Register([]apprunnerTypes.Service{})
gob.Register([]apprunnerTypes.ServiceSummary{})
}
func CachedAppRunnerListServices(client AppRunnerClientInterface, accountID string, region string) ([]apprunnerTypes.ServiceSummary, error) {
var PaginationControl *string
var services []apprunnerTypes.ServiceSummary
cacheKey := fmt.Sprintf("%s-apprunner-ListServices-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]apprunnerTypes.ServiceSummary), nil
}
for {
ListServices, err := client.ListServices(
context.TODO(),
&apprunner.ListServicesInput{
NextToken: PaginationControl,
},
func(o *apprunner.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, ListServices.ServiceSummaryList...)
//pagination
if ListServices.NextToken == nil {
break
}
PaginationControl = ListServices.NextToken
}
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+119
View File
@@ -0,0 +1,119 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
cloudFormationTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/patrickmn/go-cache"
)
type CloudFormationClientInterface interface {
GetTemplate(context.Context, *cloudformation.GetTemplateInput, ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
DescribeStacks(context.Context, *cloudformation.DescribeStacksInput, ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error)
ListStacks(context.Context, *cloudformation.ListStacksInput, ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error)
}
func RegisterCloudFormationTypes() {
gob.Register([]cloudFormationTypes.Stack{})
gob.Register([]cloudFormationTypes.StackSummary{})
}
func CachedCloudFormationDescribeStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.Stack, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.Stack
cacheKey := fmt.Sprintf("%s-cloudformation-DescribeStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.Stack), nil
}
for {
DescribeStacks, err := client.DescribeStacks(
context.TODO(),
&cloudformation.DescribeStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, DescribeStacks.Stacks...)
//pagination
if DescribeStacks.NextToken == nil {
break
}
PaginationControl = DescribeStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
func CachedCloudFormationGetTemplate(client CloudFormationClientInterface, accountID string, region string, stackName string) (string, error) {
var stackTemplateBody string
cacheKey := fmt.Sprintf("%s-cloudformation-GetTemplate-%s-%s", accountID, region, stackName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetTemplate, err := client.GetTemplate(
context.TODO(),
&cloudformation.GetTemplateInput{
StackName: &stackName,
}, func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stackTemplateBody, err
}
stackTemplateBody = aws.ToString(GetTemplate.TemplateBody)
internal.Cache.Set(cacheKey, *GetTemplate.TemplateBody, cache.DefaultExpiration)
return stackTemplateBody, nil
}
func CachedCloudFormationListStacks(client CloudFormationClientInterface, accountID string, region string) ([]cloudFormationTypes.StackSummary, error) {
var PaginationControl *string
var stacks []cloudFormationTypes.StackSummary
cacheKey := fmt.Sprintf("%s-cloudformation-ListStacks-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]cloudFormationTypes.StackSummary), nil
}
for {
ListStacks, err := client.ListStacks(
context.TODO(),
&cloudformation.ListStacksInput{
NextToken: PaginationControl,
},
func(o *cloudformation.Options) {
o.Region = region
},
)
if err != nil {
return stacks, err
}
stacks = append(stacks, ListStacks.StackSummaries...)
//pagination
if ListStacks.NextToken == nil {
break
}
PaginationControl = ListStacks.NextToken
}
internal.Cache.Set(cacheKey, stacks, cache.DefaultExpiration)
return stacks, nil
}
+118
View File
@@ -0,0 +1,118 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codebuild"
codeBuildTypes "github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/patrickmn/go-cache"
)
type CodeBuildClientInterface interface {
ListProjects(ctx context.Context, params *codebuild.ListProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error)
BatchGetProjects(ctx context.Context, params *codebuild.BatchGetProjectsInput, optFns ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error)
GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error)
}
func RegisterCodeBuildTypes() {
gob.Register(codeBuildTypes.Project{})
}
// create a CachedCodeBuildListProjects function that accepts a codebuild client, account id, and region. Make sure it handles the region option and pagination
func CachedCodeBuildListProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var projects []string
cacheKey := fmt.Sprintf("%s-codebuild-ListProjects", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListProjects, err := CodeBuildClient.ListProjects(
context.TODO(),
&codebuild.ListProjectsInput{
NextToken: PaginationControl,
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
projects = append(projects, ListProjects.Projects...)
// Pagination control.
if ListProjects.NextToken != nil {
PaginationControl = ListProjects.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, projects, cache.DefaultExpiration)
return projects, nil
}
// create a CachedCodeBuildBatchGetProjects function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildBatchGetProjects(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (codeBuildTypes.Project, error) {
project := codeBuildTypes.Project{}
cacheKey := fmt.Sprintf("%s-codebuild-BatchGetProjects-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(codeBuildTypes.Project), nil
}
BatchGetProjects, err := CodeBuildClient.BatchGetProjects(
context.TODO(),
&codebuild.BatchGetProjectsInput{
Names: []string{projectID},
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return project, err
}
internal.Cache.Set(cacheKey, BatchGetProjects.Projects[0], cache.DefaultExpiration)
return BatchGetProjects.Projects[0], nil
}
// create a CachedCodeBuildGetResourcePolicy function that accepts a codebuild client, account id, region, and a single projectId. Make sure it handles the region option and pagination
func CachedCodeBuildGetResourcePolicy(CodeBuildClient CodeBuildClientInterface, accountID string, region string, projectID string) (string, error) {
cacheKey := fmt.Sprintf("%s-codebuild-GetResourcePolicy-%s-%s", accountID, region, projectID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(string), nil
}
GetResourcePolicy, err := CodeBuildClient.GetResourcePolicy(
context.TODO(),
&codebuild.GetResourcePolicyInput{
ResourceArn: aws.String("arn:aws:codebuild:" + region + ":" + accountID + ":project/" + projectID),
},
func(o *codebuild.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
internal.Cache.Set(cacheKey, GetResourcePolicy, cache.DefaultExpiration)
return aws.ToString(GetResourcePolicy.Policy), nil
}
+141
View File
@@ -0,0 +1,141 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/docdb"
docdbTypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
"github.com/patrickmn/go-cache"
)
type DocDBClientInterface interface {
DescribeGlobalClusters(context.Context, *docdb.DescribeGlobalClustersInput, ...func(*docdb.Options)) (*docdb.DescribeGlobalClustersOutput, error)
DescribeDBClusters(context.Context, *docdb.DescribeDBClustersInput, ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error)
DescribeDBInstances(context.Context, *docdb.DescribeDBInstancesInput, ...func(*docdb.Options)) (*docdb.DescribeDBInstancesOutput, error)
}
func RegisterDocDBTypes() {
gob.Register([]docdbTypes.GlobalCluster{})
gob.Register([]docdbTypes.DBCluster{})
//gob.Register([]docdbTypes.DBInstance{})
}
func CachedDocDBDescribeGlobalClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.GlobalCluster, error) {
var PaginationControl *string
var globalClusters []docdbTypes.GlobalCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeGlobalClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.GlobalCluster), nil
}
for {
DescribeGlobalClusters, err := client.DescribeGlobalClusters(
context.TODO(),
&docdb.DescribeGlobalClustersInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return globalClusters, err
}
globalClusters = append(globalClusters, DescribeGlobalClusters.GlobalClusters...)
//pagination
if DescribeGlobalClusters.Marker == nil {
break
}
PaginationControl = DescribeGlobalClusters.Marker
}
internal.Cache.Set(cacheKey, globalClusters, cache.DefaultExpiration)
return globalClusters, nil
}
func CachedDocDBDescribeDBClusters(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBCluster, error) {
var PaginationControl *string
var dbClusters []docdbTypes.DBCluster
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBCluster), nil
}
for {
DescribeDBClusters, err := client.DescribeDBClusters(
context.TODO(),
&docdb.DescribeDBClustersInput{
Marker: PaginationControl,
Filters: []docdbTypes.Filter{
{
Name: aws.String("engine"),
Values: []string{"docdb"},
},
},
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbClusters, err
}
dbClusters = append(dbClusters, DescribeDBClusters.DBClusters...)
//pagination
if DescribeDBClusters.Marker == nil {
break
}
PaginationControl = DescribeDBClusters.Marker
}
internal.Cache.Set(cacheKey, dbClusters, cache.DefaultExpiration)
return dbClusters, nil
}
func CachedDocDBDescribeDBInstances(client DocDBClientInterface, accountID string, region string) ([]docdbTypes.DBInstance, error) {
var PaginationControl *string
var dbInstances []docdbTypes.DBInstance
cacheKey := fmt.Sprintf("%s-docdb-DescribeDBInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]docdbTypes.DBInstance), nil
}
for {
DescribeDBInstances, err := client.DescribeDBInstances(
context.TODO(),
&docdb.DescribeDBInstancesInput{
Marker: PaginationControl,
},
func(o *docdb.Options) {
o.Region = region
},
)
if err != nil {
return dbInstances, err
}
dbInstances = append(dbInstances, DescribeDBInstances.DBInstances...)
//pagination
if DescribeDBInstances.Marker == nil {
break
}
PaginationControl = DescribeDBInstances.Marker
}
internal.Cache.Set(cacheKey, dbInstances, cache.DefaultExpiration)
return dbInstances, nil
}
+85
View File
@@ -0,0 +1,85 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamoDBTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/patrickmn/go-cache"
)
type DynamoDBClientInterface interface {
ListTables(context.Context, *dynamodb.ListTablesInput, ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error)
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
}
func RegisterDynamoDBTypes() {
gob.Register([]string{})
gob.Register(dynamoDBTypes.TableDescription{})
}
func CachedDynamoDBListTables(client DynamoDBClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var tables []string
cacheKey := fmt.Sprintf("%s-dynamodb-ListTables-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListTables, err := client.ListTables(
context.TODO(),
&dynamodb.ListTablesInput{
ExclusiveStartTableName: PaginationControl,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tables, err
}
tables = append(tables, ListTables.TableNames...)
//pagination
if ListTables.LastEvaluatedTableName == nil {
break
}
PaginationControl = ListTables.LastEvaluatedTableName
}
internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration)
return tables, nil
}
func CachedDynamoDBDescribeTable(client DynamoDBClientInterface, accountID string, region string, tableName string) (dynamoDBTypes.TableDescription, error) {
var tableDescription dynamoDBTypes.TableDescription
cacheKey := fmt.Sprintf("%s-dynamodb-DescribeTable-%s-%s", accountID, region, tableName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(dynamoDBTypes.TableDescription), nil
}
DescribeTable, err := client.DescribeTable(
context.TODO(),
&dynamodb.DescribeTableInput{
TableName: &tableName,
},
func(o *dynamodb.Options) {
o.Region = region
},
)
if err != nil {
return tableDescription, err
}
tableDescription = *DescribeTable.Table
internal.Cache.Set(cacheKey, tableDescription, cache.DefaultExpiration)
return tableDescription, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/patrickmn/go-cache"
)
type EC2ClientInterface interface {
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
}
func RegisterEC2Types() {
gob.Register([]ec2Types.Instance{})
}
func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
var PaginationControl *string
var instances []ec2Types.Instance
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]ec2Types.Instance), nil
}
for {
DescribeInstances, err := client.DescribeInstances(
context.TODO(),
&ec2.DescribeInstancesInput{
NextToken: PaginationControl,
},
func(o *ec2.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
for _, reservation := range DescribeInstances.Reservations {
instances = append(instances, reservation.Instances...)
}
//pagination
if DescribeInstances.NextToken == nil {
break
}
PaginationControl = DescribeInstances.NextToken
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
+136
View File
@@ -0,0 +1,136 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/patrickmn/go-cache"
)
type AWSECRClientInterface interface {
DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error)
DescribeImages(ctx context.Context, params *ecr.DescribeImagesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error)
GetRepositoryPolicy(ctx context.Context, params *ecr.GetRepositoryPolicyInput, optFns ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error)
}
func RegisterECRTypes() {
gob.Register([]ecrTypes.Repository{})
gob.Register([]ecrTypes.ImageDetail{})
}
// create a CachedECRDescribeRepositories function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID string, region string) ([]ecrTypes.Repository, error) {
var PaginationControl *string
var repositories []ecrTypes.Repository
cacheKey := fmt.Sprintf("%s-ecr-DescribeRepositories-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repositories data")
return cached.([]ecrTypes.Repository), nil
}
for {
DescribeRepositories, err := ECRClient.DescribeRepositories(
context.TODO(),
&ecr.DescribeRepositoriesInput{
NextToken: PaginationControl,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
repositories = append(repositories, DescribeRepositories.Repositories...)
// Pagination control.
if DescribeRepositories.NextToken != nil {
PaginationControl = DescribeRepositories.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
return repositories, nil
}
// create a CachedECRDescribeImages function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRDescribeImages(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) ([]ecrTypes.ImageDetail, error) {
var PaginationControl *string
var images []ecrTypes.ImageDetail
cacheKey := fmt.Sprintf("%s-efs-DescribImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached Images data")
return cached.([]ecrTypes.ImageDetail), nil
}
for {
DescribeImages, err := ECRClient.DescribeImages(
context.TODO(),
&ecr.DescribeImagesInput{
NextToken: PaginationControl,
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
images = append(images, DescribeImages.ImageDetails...)
// Pagination control.
if DescribeImages.NextToken != nil {
PaginationControl = DescribeImages.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, images, cache.DefaultExpiration)
return images, nil
}
// create a CachedECRGetRepositoryPolicy function that uses go-cache line the other Cached* functions. It should accept a ecr client, account id, and region. Make sure it handles the region option and pagination if needed
func CachedECRGetRepositoryPolicy(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) (string, error) {
// in repositoryName, replace "/" with "-"
cacheKey := fmt.Sprintf("%s-ecr-GetRepositoryPolicy-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached ECR repository policy data")
return cached.(string), nil
}
GetRepositoryPolicy, err := ECRClient.GetRepositoryPolicy(
context.TODO(),
&ecr.GetRepositoryPolicyInput{
RepositoryName: &repositoryName,
},
func(o *ecr.Options) {
o.Region = region
},
)
if err != nil {
sharedLogger.Error(err.Error())
return "", err
}
PolicyText := aws.ToString(GetRepositoryPolicy.PolicyText)
internal.Cache.Set(cacheKey, PolicyText, cache.DefaultExpiration)
return PolicyText, nil
}
+178
View File
@@ -0,0 +1,178 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/BishopFox/cloudfox/internal/aws/policy"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/efs"
efsTypes "github.com/aws/aws-sdk-go-v2/service/efs/types"
"github.com/patrickmn/go-cache"
)
type AWSEFSClientInterface interface {
DescribeFileSystems(ctx context.Context, params *efs.DescribeFileSystemsInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error)
DescribeMountTargets(ctx context.Context, params *efs.DescribeMountTargetsInput, optFns ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error)
DescribeAccessPoints(ctx context.Context, params *efs.DescribeAccessPointsInput, optFns ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error)
DescribeFileSystemPolicy(ctx context.Context, params *efs.DescribeFileSystemPolicyInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemPolicyOutput, error)
}
func RegisterEFSTypes() {
gob.Register([]efsTypes.FileSystemDescription{})
gob.Register([]efsTypes.MountTargetDescription{})
gob.Register([]efsTypes.AccessPointDescription{})
gob.Register(policy.Policy{})
}
func CachedDescribeFileSystems(EFSClient AWSEFSClientInterface, accountID string, r string) ([]efsTypes.FileSystemDescription, error) {
var PaginationMarker *string
var filesystems []efsTypes.FileSystemDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeFileSystems-%s", accountID, r)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.FileSystemDescription), nil
}
for {
DescribeFileSystems, err := EFSClient.DescribeFileSystems(
context.TODO(),
&efs.DescribeFileSystemsInput{
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
filesystems = append(filesystems, DescribeFileSystems.FileSystems...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeFileSystems.Marker != nil {
PaginationMarker = DescribeFileSystems.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, filesystems, cache.DefaultExpiration)
return filesystems, err
}
func CachedDescribeMountTargets(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.MountTargetDescription, error) {
var PaginationMarker *string
var mountTargets []efsTypes.MountTargetDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeMountTargets-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.MountTargetDescription), nil
}
for {
DescribeMountTargets, err := EFSClient.DescribeMountTargets(
context.TODO(),
&efs.DescribeMountTargetsInput{
FileSystemId: aws.String(filesystemId),
Marker: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
mountTargets = append(mountTargets, DescribeMountTargets.MountTargets...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeMountTargets.Marker != nil {
PaginationMarker = DescribeMountTargets.Marker
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, mountTargets, cache.DefaultExpiration)
return mountTargets, err
}
func CachedDescribeAccessPoints(EFSClient AWSEFSClientInterface, accountID string, r string, filesystemId string) ([]efsTypes.AccessPointDescription, error) {
var PaginationMarker *string
var accessPoints []efsTypes.AccessPointDescription
var err error
cacheKey := fmt.Sprintf("%s-efs-DescribeAccessPoints-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]efsTypes.AccessPointDescription), nil
}
for {
DescribeAccessPoints, err := EFSClient.DescribeAccessPoints(
context.TODO(),
&efs.DescribeAccessPointsInput{
FileSystemId: aws.String(filesystemId),
NextToken: PaginationMarker,
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return nil, err
}
accessPoints = append(accessPoints, DescribeAccessPoints.AccessPoints...)
// Pagination control. After the last page of output, the for loop exits.
if DescribeAccessPoints.NextToken != nil {
PaginationMarker = DescribeAccessPoints.NextToken
} else {
PaginationMarker = nil
break
}
}
internal.Cache.Set(cacheKey, accessPoints, cache.DefaultExpiration)
return accessPoints, err
}
func CachedDescribeFileSystemPolicy(EFSClient AWSEFSClientInterface, filesystemId string, r string, accountID string) (policy.Policy, error) {
var efsPolicy policy.Policy
var policyJSON string
cacheKey := fmt.Sprintf("efs-%s-DescribeFileSystemPolicy-%s-%s", accountID, r, filesystemId)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(policy.Policy), nil
}
Policy, err := EFSClient.DescribeFileSystemPolicy(
context.TODO(),
&efs.DescribeFileSystemPolicyInput{
FileSystemId: aws.String(filesystemId),
},
func(o *efs.Options) {
o.Region = r
},
)
if err != nil {
sharedLogger.Error(err.Error())
return efsPolicy, err
}
policyJSON = aws.ToString(Policy.Policy)
efsPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
if err != nil {
return efsPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", filesystemId, err)
}
internal.Cache.Set(cacheKey, efsPolicy, cache.DefaultExpiration)
return efsPolicy, nil
}
+152
View File
@@ -0,0 +1,152 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/eks"
eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
"github.com/patrickmn/go-cache"
)
type EKSClientInterface interface {
DescribeCluster(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error)
ListClusters(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) (*eks.ListClustersOutput, error)
DescribeNodegroup(context.Context, *eks.DescribeNodegroupInput, ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error)
ListNodegroups(context.Context, *eks.ListNodegroupsInput, ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error)
}
func RegisterEKSTypes() {
gob.Register([]string{})
gob.Register(eksTypes.Cluster{})
gob.Register(eksTypes.Nodegroup{})
}
func CachedEKSDescribeCluster(client EKSClientInterface, accountID string, clusterName string, region string) (eksTypes.Cluster, error) {
var cluster eksTypes.Cluster
cacheKey := fmt.Sprintf("%s-eks-DescribeCluster-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Cluster), nil
}
clusterOutput, err := client.DescribeCluster(
context.TODO(),
&eks.DescribeClusterInput{
Name: &clusterName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return cluster, err
}
cluster = *clusterOutput.Cluster
internal.Cache.Set(cacheKey, cluster, cache.DefaultExpiration)
return cluster, nil
}
func CachedEKSListClusters(client EKSClientInterface, accountID string, region string) ([]string, error) {
var PaginationControl *string
var clusters []string
cacheKey := fmt.Sprintf("%s-eks-ListClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListClusters, err := client.ListClusters(
context.TODO(),
&eks.ListClustersInput{
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, ListClusters.Clusters...)
//pagination
if ListClusters.NextToken == nil {
break
}
PaginationControl = ListClusters.NextToken
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region string, clusterName string) ([]string, error) {
var PaginationControl *string
var nodeGroups []string
cacheKey := fmt.Sprintf("%s-eks-ListNodeGroups-%s-%s", accountID, region, clusterName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]string), nil
}
for {
ListNodeGroups, err := client.ListNodegroups(
context.TODO(),
&eks.ListNodegroupsInput{
ClusterName: &clusterName,
NextToken: PaginationControl,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroups, err
}
nodeGroups = append(nodeGroups, ListNodeGroups.Nodegroups...)
if ListNodeGroups.NextToken != nil {
PaginationControl = ListNodeGroups.NextToken
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, nodeGroups, cache.DefaultExpiration)
return nodeGroups, nil
}
func CachedEKSDescribeNodeGroup(client EKSClientInterface, accountID string, region string, clusterName string, nodeGroupName string) (eksTypes.Nodegroup, error) {
var nodeGroup eksTypes.Nodegroup
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, nodeGroupName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(eksTypes.Nodegroup), nil
}
nodeGroupOutput, err := client.DescribeNodegroup(
context.TODO(),
&eks.DescribeNodegroupInput{
ClusterName: &clusterName,
NodegroupName: &nodeGroupName,
},
func(o *eks.Options) {
o.Region = region
},
)
if err != nil {
return nodeGroup, err
}
nodeGroup = *nodeGroupOutput.Nodegroup
internal.Cache.Set(cacheKey, nodeGroup, cache.DefaultExpiration)
return nodeGroup, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
"github.com/patrickmn/go-cache"
)
type ELBClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error)
}
func RegisterELBTypes() {
gob.Register([]elbTypes.LoadBalancerDescription{})
}
func CachedELBDescribeLoadBalancers(client ELBClientInterface, accountID string, region string) ([]elbTypes.LoadBalancerDescription, error) {
var PaginationControl *string
var loadbalancers []elbTypes.LoadBalancerDescription
cacheKey := fmt.Sprintf("%s-elb-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbTypes.LoadBalancerDescription), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancing.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancing.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancerDescriptions...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbV2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
"github.com/patrickmn/go-cache"
)
type ELBv2ClientInterface interface {
DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)
}
func RegisterELBv2Types() {
gob.Register([]elbV2Types.LoadBalancer{})
}
func CachedELBv2DescribeLoadBalancers(client ELBv2ClientInterface, accountID string, region string) ([]elbV2Types.LoadBalancer, error) {
var PaginationControl *string
var loadbalancers []elbV2Types.LoadBalancer
cacheKey := fmt.Sprintf("%s-elbv2-DescribeLoadBalancers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]elbV2Types.LoadBalancer), nil
}
for {
DescribeLoadBalancers, err := client.DescribeLoadBalancers(
context.TODO(),
&elasticloadbalancingv2.DescribeLoadBalancersInput{
Marker: PaginationControl,
},
func(o *elasticloadbalancingv2.Options) {
o.Region = region
},
)
if err != nil {
return loadbalancers, err
}
loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancers...)
//pagination
if DescribeLoadBalancers.NextMarker == nil {
break
}
PaginationControl = DescribeLoadBalancers.NextMarker
}
internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration)
return loadbalancers, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/grafana"
grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
"github.com/patrickmn/go-cache"
)
type GrafanaClientInterface interface {
ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error)
}
func RegisterGrafanaTypes() {
gob.Register([]grafanaTypes.WorkspaceSummary{})
}
func CachedGrafanaListWorkspaces(client GrafanaClientInterface, accountID string, region string) ([]grafanaTypes.WorkspaceSummary, error) {
var PaginationControl *string
var workspaces []grafanaTypes.WorkspaceSummary
cacheKey := fmt.Sprintf("%s-grafana-ListWorkspaces-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]grafanaTypes.WorkspaceSummary), nil
}
for {
ListWorkspaces, err := client.ListWorkspaces(
context.TODO(),
&grafana.ListWorkspacesInput{
NextToken: PaginationControl,
},
func(o *grafana.Options) {
o.Region = region
},
)
if err != nil {
return workspaces, err
}
workspaces = append(workspaces, ListWorkspaces.Workspaces...)
//pagination
if ListWorkspaces.NextToken == nil {
break
}
PaginationControl = ListWorkspaces.NextToken
}
internal.Cache.Set(cacheKey, workspaces, cache.DefaultExpiration)
return workspaces, nil
}
+252
View File
@@ -0,0 +1,252 @@
package sdk
import (
"context"
"crypto/md5"
"encoding/gob"
"encoding/hex"
"fmt"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/patrickmn/go-cache"
)
type AWSIAMClientInterface interface {
ListUsers(ctx context.Context, params *iam.ListUsersInput, optFns ...func(*iam.Options)) (*iam.ListUsersOutput, error)
ListAccessKeys(ctx context.Context, params *iam.ListAccessKeysInput, optFns ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error)
ListRoles(ctx context.Context, params *iam.ListRolesInput, optFns ...func(*iam.Options)) (*iam.ListRolesOutput, error)
GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error)
SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error)
ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
}
func RegisterIamTypes() {
gob.Register([]iamTypes.User{})
gob.Register([]iamTypes.AccessKeyMetadata{})
gob.Register([]iamTypes.Role{})
gob.Register([]iamTypes.PolicyDetail{})
gob.Register([]iamTypes.InstanceProfile{})
gob.Register([]iamTypes.EvaluationResult{})
gob.Register(customGAADOutput{})
}
func CachedIamListUsers(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.User, error) {
var PaginationControl *string
var users []iamTypes.User
cacheKey := fmt.Sprintf("%s-iam-ListUsers", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.User), nil
}
for {
ListUsers, err := IAMClient.ListUsers(
context.TODO(),
&iam.ListUsersInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
users = append(users, ListUsers.Users...)
// Pagination control.
if ListUsers.Marker != nil {
PaginationControl = ListUsers.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, users, cache.DefaultExpiration)
return users, nil
}
func CachedIamListRoles(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.Role, error) {
var PaginationControl *string
var roles []iamTypes.Role
cacheKey := fmt.Sprintf("%s-iam-ListRoles", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.Role), nil
}
for {
ListRoles, err := IAMClient.ListRoles(
context.TODO(),
&iam.ListRolesInput{
Marker: PaginationControl,
},
)
if err != nil {
sharedLogger.Error(err.Error())
break
}
roles = append(roles, ListRoles.Roles...)
// Pagination control.
if ListRoles.Marker != nil {
PaginationControl = ListRoles.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, roles, cache.DefaultExpiration)
return roles, nil
}
func CachedIamListAccessKeys(IAMClient AWSIAMClientInterface, accountID string, userName string) ([]iamTypes.AccessKeyMetadata, error) {
var PaginationControl *string
var accessKeys []iamTypes.AccessKeyMetadata
cacheKey := fmt.Sprintf("%s-iam-ListAccessKeys-%s", accountID, userName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.AccessKeyMetadata), nil
}
for {
ListAccessKeys, err := IAMClient.ListAccessKeys(
context.TODO(),
&iam.ListAccessKeysInput{
UserName: &userName,
Marker: PaginationControl,
},
)
if err != nil {
return accessKeys, err
}
accessKeys = append(accessKeys, ListAccessKeys.AccessKeyMetadata...)
// Pagination control.
if ListAccessKeys.Marker != nil {
PaginationControl = ListAccessKeys.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, accessKeys, cache.DefaultExpiration)
return accessKeys, nil
}
type customGAADOutput struct {
GroupDetailList []iamTypes.GroupDetail
UserDetailList []iamTypes.UserDetail
RoleDetailList []iamTypes.RoleDetail
Policies []iamTypes.ManagedPolicyDetail
}
func CachedIAMGetAccountAuthorizationDetails(IAMClient AWSIAMClientInterface, accountID string) (customGAADOutput, error) {
var PaginationControl *string
var GroupDetailList []iamTypes.GroupDetail
var UserDetailList []iamTypes.UserDetail
var RoleDetailList []iamTypes.RoleDetail
var Policies []iamTypes.ManagedPolicyDetail
var AllAccountAuthorizationDetails = customGAADOutput{
GroupDetailList: GroupDetailList,
UserDetailList: UserDetailList,
RoleDetailList: RoleDetailList,
Policies: Policies,
}
cacheKey := fmt.Sprintf("%s-iam-GetAccountAuthorizationDetails", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGAADOutput), nil
}
for {
GetAccountAuthorizationDetails, err := IAMClient.GetAccountAuthorizationDetails(
context.TODO(),
&iam.GetAccountAuthorizationDetailsInput{
Marker: PaginationControl,
},
)
if err != nil {
return AllAccountAuthorizationDetails, err
}
AllAccountAuthorizationDetails.GroupDetailList = append(AllAccountAuthorizationDetails.GroupDetailList, GetAccountAuthorizationDetails.GroupDetailList...)
AllAccountAuthorizationDetails.UserDetailList = append(AllAccountAuthorizationDetails.UserDetailList, GetAccountAuthorizationDetails.UserDetailList...)
AllAccountAuthorizationDetails.RoleDetailList = append(AllAccountAuthorizationDetails.RoleDetailList, GetAccountAuthorizationDetails.RoleDetailList...)
AllAccountAuthorizationDetails.Policies = append(AllAccountAuthorizationDetails.Policies, GetAccountAuthorizationDetails.Policies...)
if GetAccountAuthorizationDetails.Marker != nil {
PaginationControl = GetAccountAuthorizationDetails.Marker
} else {
PaginationControl = nil
break
}
}
internal.Cache.Set(cacheKey, AllAccountAuthorizationDetails, cache.DefaultExpiration)
return AllAccountAuthorizationDetails, nil
}
func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID string, principal *string, actionNames []string, resourceArns []string) ([]iamTypes.EvaluationResult, error) {
var PaginationControl2 *string
var EvaluationResults []iamTypes.EvaluationResult
md5hashedActionNames := md5.Sum([]byte(strings.Join(actionNames, "")))
md5hashedResourceArns := md5.Sum([]byte(strings.Join(resourceArns, "")))
// proccess arn and get the name of the resource
arn, err := arn.Parse(*principal)
if err != nil {
return EvaluationResults, err
}
name := arn.Resource
// if name has muliplte / then get the last one
if strings.Contains(arn.Resource, "/") {
name = arn.Resource[strings.LastIndex(arn.Resource, "/")+1:]
}
truncatedHashLength := 8
cacheKey := fmt.Sprintf("%s-iamSimulator-%s-%s-%s", accountID, name, hex.EncodeToString(md5hashedActionNames[:truncatedHashLength]), hex.EncodeToString(md5hashedResourceArns[:truncatedHashLength]))
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]iamTypes.EvaluationResult), nil
}
for {
SimulatePrincipalPolicy, err := IAMClient.SimulatePrincipalPolicy(
context.TODO(),
&iam.SimulatePrincipalPolicyInput{
Marker: PaginationControl2,
ActionNames: actionNames,
PolicySourceArn: principal,
ResourceArns: resourceArns,
},
)
if err != nil {
return EvaluationResults, err
}
EvaluationResults = append(EvaluationResults, SimulatePrincipalPolicy.EvaluationResults...)
if SimulatePrincipalPolicy.Marker != nil {
PaginationControl2 = SimulatePrincipalPolicy.Marker
} else {
PaginationControl2 = nil
break
}
}
internal.Cache.Set(cacheKey, EvaluationResults, cache.DefaultExpiration)
return EvaluationResults, nil
}
+147
View File
@@ -0,0 +1,147 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lambda"
lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/patrickmn/go-cache"
)
type LambdaClientInterface interface {
ListFunctions(context.Context, *lambda.ListFunctionsInput, ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error)
GetFunction(context.Context, *lambda.GetFunctionInput, ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error)
GetFunctionUrlConfig(context.Context, *lambda.GetFunctionUrlConfigInput, ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error)
}
func RegisterLambdaTypes() {
gob.Register([]lambdaTypes.FunctionConfiguration{})
gob.Register(customGetFuntionURLOutput{})
}
// create a CachedLambdaListFunctions function that accepts a lambda client, account id, region. Make sure it uses go-cache and handles the region option and pagination
func CachedLambdaListFunctions(client LambdaClientInterface, accountID string, region string) ([]lambdaTypes.FunctionConfiguration, error) {
var PaginationControl *string
var functions []lambdaTypes.FunctionConfiguration
cacheKey := fmt.Sprintf("%s-lambda-ListFunctions-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lambdaTypes.FunctionConfiguration), nil
}
for {
ListFunctions, err := client.ListFunctions(
context.TODO(),
&lambda.ListFunctionsInput{
Marker: PaginationControl,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functions, err
}
functions = append(functions, ListFunctions.Functions...)
//pagination
if ListFunctions.NextMarker == nil {
break
}
PaginationControl = ListFunctions.NextMarker
}
internal.Cache.Set(cacheKey, functions, cache.DefaultExpiration)
return functions, nil
}
type customGetFuntionURLOutput struct {
// The type of authentication that your function URL uses. Set to AWS_IAM if you
// want to restrict access to authenticated users only. Set to NONE if you want to
// bypass IAM authentication to create a public endpoint. For more information, see
// Security and auth model for Lambda function URLs (https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html)
// .
//
// This member is required.
AuthType lambdaTypes.FunctionUrlAuthType
// When the function URL was created, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
CreationTime *string
// The Amazon Resource Name (ARN) of your function.
//
// This member is required.
FunctionArn *string
// The HTTP URL endpoint for your function.
//
// This member is required.
FunctionUrl *string
// When the function URL configuration was last updated, in ISO-8601 format (https://www.w3.org/TR/NOTE-datetime)
// (YYYY-MM-DDThh:mm:ss.sTZD).
//
// This member is required.
LastModifiedTime *string
// The cross-origin resource sharing (CORS) (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
// settings for your function URL.
Cors *lambdaTypes.Cors
// Use one of the following options:
// - BUFFERED – This is the default option. Lambda invokes your function using
// the Invoke API operation. Invocation results are available when the payload is
// complete. The maximum payload size is 6 MB.
// - RESPONSE_STREAM – Your function streams payload results as they become
// available. Lambda invokes your function using the InvokeWithResponseStream API
// operation. The maximum response payload size is 20 MB, however, you can
// request a quota increase (https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html)
// .
InvokeMode lambdaTypes.InvokeMode
}
// create a CachedLambdaGetFunctionUrlConfig function that accepts a lambda client, account id, region, and function name. Make sure it uses go-cache and handles the region option
func CachedLambdaGetFunctionUrlConfig(client LambdaClientInterface, accountID string, region string, functionName string) (customGetFuntionURLOutput, error) {
var functionUrlConfigOutput customGetFuntionURLOutput
cacheKey := fmt.Sprintf("%s-lambda-GetFunctionUrlConfig-%s-%s", accountID, region, functionName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(customGetFuntionURLOutput), nil
}
GetFunctionUrlConfig, err := client.GetFunctionUrlConfig(
context.TODO(),
&lambda.GetFunctionUrlConfigInput{
FunctionName: &functionName,
},
func(o *lambda.Options) {
o.Region = region
},
)
if err != nil {
return functionUrlConfigOutput, err
}
functionUrlConfigOutput = customGetFuntionURLOutput{
AuthType: GetFunctionUrlConfig.AuthType,
CreationTime: GetFunctionUrlConfig.CreationTime,
FunctionArn: GetFunctionUrlConfig.FunctionArn,
FunctionUrl: GetFunctionUrlConfig.FunctionUrl,
LastModifiedTime: GetFunctionUrlConfig.LastModifiedTime,
Cors: GetFunctionUrlConfig.Cors,
InvokeMode: GetFunctionUrlConfig.InvokeMode,
}
internal.Cache.Set(cacheKey, functionUrlConfigOutput, cache.DefaultExpiration)
return functionUrlConfigOutput, nil
}
+87
View File
@@ -0,0 +1,87 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types"
"github.com/patrickmn/go-cache"
)
type lightsailClientInterface interface {
GetInstances(context.Context, *lightsail.GetInstancesInput, ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error)
GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error)
}
func RegisterLightsailTypes() {
//gob.Register([]lightsailTypes.Instance{})
gob.Register([]lightsailTypes.ContainerService{})
}
func CachedLightsailGetInstances(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.Instance, error) {
var PaginationControl *string
var services []lightsailTypes.Instance
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
// cacheKey := fmt.Sprintf("%s-lightsail-GetInstances-%s", accountID, region)
// cached, found := internal.Cache.Get(cacheKey)
// if found {
// return cached.([]lightsailTypes.Instance), nil
// }
for {
GetInstances, err := client.GetInstances(
context.TODO(),
&lightsail.GetInstancesInput{
PageToken: PaginationControl,
},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.Instances...)
//pagination
if GetInstances.NextPageToken == nil {
break
}
PaginationControl = GetInstances.NextPageToken
}
// TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types
//internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
func CachedLightsailGetContainerServices(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.ContainerService, error) {
var services []lightsailTypes.ContainerService
cacheKey := fmt.Sprintf("%s-lightsail-GetContainerService-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]lightsailTypes.ContainerService), nil
}
GetInstances, err := client.GetContainerServices(
context.TODO(),
&lightsail.GetContainerServicesInput{},
func(o *lightsail.Options) {
o.Region = region
},
)
if err != nil {
return services, err
}
services = append(services, GetInstances.ContainerServices...)
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
return services, nil
}
+58
View File
@@ -0,0 +1,58 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/mq"
mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types"
"github.com/patrickmn/go-cache"
)
type MQClientInterface interface {
ListBrokers(context.Context, *mq.ListBrokersInput, ...func(*mq.Options)) (*mq.ListBrokersOutput, error)
}
func RegisterMQTypes() {
gob.Register([]mqTypes.BrokerSummary{})
}
// create CachedMQListBrokers function that uses go-cache and pagination
func CachedMQListBrokers(client MQClientInterface, accountID string, region string) ([]mqTypes.BrokerSummary, error) {
var PaginationControl *string
var brokers []mqTypes.BrokerSummary
cacheKey := fmt.Sprintf("%s-mq-ListBrokers-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]mqTypes.BrokerSummary), nil
}
for {
ListBrokers, err := client.ListBrokers(
context.TODO(),
&mq.ListBrokersInput{
NextToken: PaginationControl,
},
func(o *mq.Options) {
o.Region = region
},
)
if err != nil {
return brokers, err
}
brokers = append(brokers, ListBrokers.BrokerSummaries...)
//pagination
if ListBrokers.NextToken == nil {
break
}
PaginationControl = ListBrokers.NextToken
}
internal.Cache.Set(cacheKey, brokers, cache.DefaultExpiration)
return brokers, nil
}
+77
View File
@@ -0,0 +1,77 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/opensearch"
openSearchTypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
"github.com/patrickmn/go-cache"
)
type OpenSearchClientInterface interface {
ListDomainNames(context.Context, *opensearch.ListDomainNamesInput, ...func(*opensearch.Options)) (*opensearch.ListDomainNamesOutput, error)
DescribeDomainConfig(context.Context, *opensearch.DescribeDomainConfigInput, ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error)
}
func RegisterOpenSearchTypes() {
gob.Register([]openSearchTypes.DomainInfo{})
gob.Register(openSearchTypes.DomainConfig{})
}
// create CachedOpenSearchListDomainNames function that uses go-cache and pagination
func CachedOpenSearchListDomainNames(client OpenSearchClientInterface, accountID string, region string) ([]openSearchTypes.DomainInfo, error) {
var domains []openSearchTypes.DomainInfo
cacheKey := fmt.Sprintf("%s-opensearch-ListDomainNames-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]openSearchTypes.DomainInfo), nil
}
ListDomainNames, err := client.ListDomainNames(
context.TODO(),
&opensearch.ListDomainNamesInput{},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return domains, err
}
domains = append(domains, ListDomainNames.DomainNames...)
internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration)
return domains, nil
}
// create CachedOpenSearchDescribeDomainConfig function that uses go-cache and pagination and supports region option
func CachedOpenSearchDescribeDomainConfig(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainConfig, error) {
var DomainConfig openSearchTypes.DomainConfig
cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomainConfig-%s-%s", accountID, region, domainName)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(openSearchTypes.DomainConfig), nil
}
DescribeDomainConfig, err := client.DescribeDomainConfig(
context.TODO(),
&opensearch.DescribeDomainConfigInput{
DomainName: &domainName,
},
func(o *opensearch.Options) {
o.Region = region
},
)
if err != nil {
return DomainConfig, err
}
DomainConfig = *DescribeDomainConfig.DomainConfig
internal.Cache.Set(cacheKey, DomainConfig, cache.DefaultExpiration)
return DomainConfig, nil
}
+81
View File
@@ -0,0 +1,81 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/organizations"
orgTypes "github.com/aws/aws-sdk-go-v2/service/organizations/types"
"github.com/patrickmn/go-cache"
)
type OrganizationsClientInterface interface {
ListAccounts(ctx context.Context, params *organizations.ListAccountsInput, optFns ...func(*organizations.Options)) (*organizations.ListAccountsOutput, error)
DescribeOrganization(ctx context.Context, params *organizations.DescribeOrganizationInput, optFns ...func(*organizations.Options)) (*organizations.DescribeOrganizationOutput, error)
}
func RegisterOrganizationsTypes() {
gob.Register([]orgTypes.Account{})
//gob.Register(orgTypes.Organization{})
}
// create a CachedOrganizationsListAccounts function that uses go-cache and pagination and returns a list of accounts
func CachedOrganizationsListAccounts(client OrganizationsClientInterface, accountID string) ([]orgTypes.Account, error) {
var PaginationControl *string
var accounts []orgTypes.Account
cacheKey := fmt.Sprintf("%s-organizations-ListAccounts", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]orgTypes.Account), nil
}
for {
ListAccounts, err := client.ListAccounts(
context.TODO(),
&organizations.ListAccountsInput{
NextToken: PaginationControl,
},
)
if err != nil {
return accounts, err
}
accounts = append(accounts, ListAccounts.Accounts...)
//pagination
if ListAccounts.NextToken == nil {
break
}
PaginationControl = ListAccounts.NextToken
}
internal.Cache.Set(cacheKey, accounts, cache.DefaultExpiration)
return accounts, nil
}
// create a CachedOrganizationsDescribeOrganization function that uses go-cache and returns an organization
func CachedOrganizationsDescribeOrganization(client OrganizationsClientInterface, accountID string) (*orgTypes.Organization, error) {
var organization *orgTypes.Organization
cacheKey := fmt.Sprintf("%s-organizations-DescribeOrganization", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.(*orgTypes.Organization), nil
}
DescribeOrganization, err := client.DescribeOrganization(
context.TODO(),
&organizations.DescribeOrganizationInput{},
)
if err != nil {
return organization, err
}
organization = DescribeOrganization.Organization
internal.Cache.Set(cacheKey, organization, cache.DefaultExpiration)
return organization, nil
}
+57
View File
@@ -0,0 +1,57 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/rds"
rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/patrickmn/go-cache"
)
type RDSClientInterface interface {
DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error)
}
func RegisterRDSTypes() {
gob.Register([]rdsTypes.DBInstance{})
}
func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBInstance, error) {
var PaginationControl *string
var instances []rdsTypes.DBInstance
cacheKey := fmt.Sprintf("%s-rds-DescribeDBInstances-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]rdsTypes.DBInstance), nil
}
for {
DescribeDBInstances, err := client.DescribeDBInstances(
context.TODO(),
&rds.DescribeDBInstancesInput{
Marker: PaginationControl,
},
func(o *rds.Options) {
o.Region = region
},
)
if err != nil {
return instances, err
}
instances = append(instances, DescribeDBInstances.DBInstances...)
//pagination
if DescribeDBInstances.Marker == nil {
break
}
PaginationControl = DescribeDBInstances.Marker
}
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
return instances, nil
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/redshift"
redshiftTypes "github.com/aws/aws-sdk-go-v2/service/redshift/types"
"github.com/patrickmn/go-cache"
)
type RedShiftClientInterface interface {
DescribeClusters(context.Context, *redshift.DescribeClustersInput, ...func(*redshift.Options)) (*redshift.DescribeClustersOutput, error)
}
func RegisterRedShiftTypes() {
gob.Register([]redshiftTypes.Cluster{})
}
func CachedRedShiftDescribeClusters(client RedShiftClientInterface, accountID string, region string) ([]redshiftTypes.Cluster, error) {
var PaginationControl *string
var clusters []redshiftTypes.Cluster
cacheKey := fmt.Sprintf("%s-redshift-DescribeClusters-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]redshiftTypes.Cluster), nil
}
for {
DescribeClusters, err := client.DescribeClusters(
context.TODO(),
&redshift.DescribeClustersInput{
Marker: PaginationControl,
},
func(o *redshift.Options) {
o.Region = region
},
)
if err != nil {
return clusters, err
}
clusters = append(clusters, DescribeClusters.Clusters...)
//pagination
if DescribeClusters.Marker == nil {
break
}
PaginationControl = DescribeClusters.Marker
}
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
return clusters, nil
}
+124
View File
@@ -0,0 +1,124 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3Types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/patrickmn/go-cache"
)
type AWSS3ClientInterface interface {
ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error)
GetBucketPolicy(ctx context.Context, params *s3.GetBucketPolicyInput, optFns ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error)
GetBucketLocation(ctx context.Context, params *s3.GetBucketLocationInput, optFns ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error)
GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error)
}
func RegisterS3Types() {
gob.Register([]s3Types.Bucket{})
gob.Register(s3Types.Bucket{})
gob.Register(&s3Types.PublicAccessBlockConfiguration{})
}
func CachedListBuckets(S3Client AWSS3ClientInterface, accountID string) ([]s3Types.Bucket, error) {
var buckets []s3Types.Bucket
cacheKey := fmt.Sprintf("%s-s3-ListBuckets", accountID)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for ListBuckets data")
return cached.([]s3Types.Bucket), nil
}
ListBuckets, err := S3Client.ListBuckets(
context.TODO(),
&s3.ListBucketsInput{},
)
if err != nil {
return buckets, err
}
buckets = append(buckets, ListBuckets.Buckets...)
internal.Cache.Set(cacheKey, buckets, cache.DefaultExpiration)
return buckets, nil
}
func CachedGetBucketLocation(S3Client AWSS3ClientInterface, accountID string, bucketName string) (string, error) {
cacheKey := fmt.Sprintf("%s-s3-GetBucketLocation-%s", accountID, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetBucketLocation data")
return cached.(string), nil
}
GetBucketRegion, err := S3Client.GetBucketLocation(
context.TODO(),
&s3.GetBucketLocationInput{
Bucket: &bucketName,
},
)
if err != nil {
return "", err
}
location := string(GetBucketRegion.LocationConstraint)
if location == "" {
location = "us-east-1"
}
internal.Cache.Set(cacheKey, location, cache.DefaultExpiration)
return location, err
}
func CachedGetBucketPolicy(S3Client AWSS3ClientInterface, accountID string, r string, bucketName string) (string, error) {
cacheKey := fmt.Sprintf("%s-s3-GetBucketPolicy-%s-%s", accountID, r, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetBucketPolicy data")
return cached.(string), nil
}
BucketPolicyObject, err := S3Client.GetBucketPolicy(
context.TODO(),
&s3.GetBucketPolicyInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
return "", err
}
internal.Cache.Set(cacheKey, aws.ToString(BucketPolicyObject.Policy), cache.DefaultExpiration)
return *BucketPolicyObject.Policy, nil
}
func CachedGetPublicAccessBlock(S3Client AWSS3ClientInterface, accountID string, r string, bucketName string) (*s3Types.PublicAccessBlockConfiguration, error) {
cacheKey := fmt.Sprintf("%s-s3-GetPublicAccessBlock-%s-%s", accountID, r, bucketName)
cached, found := internal.Cache.Get(cacheKey)
if found {
sharedLogger.Debug("Using cached data for GetPublicAccessBlock data")
return cached.(*s3Types.PublicAccessBlockConfiguration), nil
}
PublicAccessBlock, err := S3Client.GetPublicAccessBlock(
context.TODO(),
&s3.GetPublicAccessBlockInput{
Bucket: &bucketName,
},
func(o *s3.Options) {
o.Region = r
},
)
if err != nil {
return nil, err
}
internal.Cache.Set(cacheKey, PublicAccessBlock.PublicAccessBlockConfiguration, cache.DefaultExpiration)
return PublicAccessBlock.PublicAccessBlockConfiguration, err
}
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
secretsmanagerTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
"github.com/patrickmn/go-cache"
)
type SecretsManagerClientInterface interface {
ListSecrets(context.Context, *secretsmanager.ListSecretsInput, ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error)
}
func RegisterSecretsManagerTypes() {
gob.Register([]secretsmanagerTypes.SecretListEntry{})
}
func CachedSecretsManagerListSecrets(client SecretsManagerClientInterface, accountID string, region string) ([]secretsmanagerTypes.SecretListEntry, error) {
var PaginationControl *string
var secrets []secretsmanagerTypes.SecretListEntry
cacheKey := fmt.Sprintf("%s-secretsmanager-ListSecrets-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]secretsmanagerTypes.SecretListEntry), nil
}
for {
ListSecrets, err := client.ListSecrets(
context.TODO(),
&secretsmanager.ListSecretsInput{
NextToken: PaginationControl,
},
func(o *secretsmanager.Options) {
o.Region = region
},
)
if err != nil {
return secrets, err
}
secrets = append(secrets, ListSecrets.SecretList...)
//pagination
if ListSecrets.NextToken == nil {
break
}
PaginationControl = ListSecrets.NextToken
}
internal.Cache.Set(cacheKey, secrets, cache.DefaultExpiration)
return secrets, nil
}
+5
View File
@@ -0,0 +1,5 @@
package sdk
import "github.com/BishopFox/cloudfox/internal"
var sharedLogger = internal.TxtLogger()
+56
View File
@@ -0,0 +1,56 @@
package sdk
import (
"context"
"encoding/gob"
"fmt"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/service/sfn"
sfnTypes "github.com/aws/aws-sdk-go-v2/service/sfn/types"
"github.com/patrickmn/go-cache"
)
type StepFunctionsClientInterface interface {
ListStateMachines(context.Context, *sfn.ListStateMachinesInput, ...func(*sfn.Options)) (*sfn.ListStateMachinesOutput, error)
}
func RegisterStepFunctionsTypes() {
gob.Register([]sfnTypes.StateMachineListItem{})
}
func CachedStepFunctionsListStateMachines(client StepFunctionsClientInterface, accountID string, region string) ([]sfnTypes.StateMachineListItem, error) {
var PaginationControl *string
var stateMachines []sfnTypes.StateMachineListItem
cacheKey := fmt.Sprintf("%s-stepfunctions-ListStateMachines-%s", accountID, region)
cached, found := internal.Cache.Get(cacheKey)
if found {
return cached.([]sfnTypes.StateMachineListItem), nil
}
for {
ListStateMachines, err := client.ListStateMachines(
context.TODO(),
&sfn.ListStateMachinesInput{
NextToken: PaginationControl,
},
func(o *sfn.Options) {
o.Region = region
},
)
if err != nil {
return stateMachines, err
}
stateMachines = append(stateMachines, ListStateMachines.StateMachines...)
//pagination
if ListStateMachines.NextToken == nil {
break
}
PaginationControl = ListStateMachines.NextToken
}
internal.Cache.Set(cacheKey, stateMachines, cache.DefaultExpiration)
return stateMachines, nil
}
+19 -3
View File
@@ -115,12 +115,28 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//m.output.OutputSelector(outputFormat)
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity)
fmt.Printf("[%s][%s] %s secrets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
} else {
+15 -12
View File
@@ -5,28 +5,23 @@ import (
"log"
"os"
"runtime"
"strings"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/fatih/color"
)
var cyan = color.New(color.FgCyan).SprintFunc()
var red = color.New(color.FgRed).SprintFunc()
var yellow = color.New(color.FgRed).SprintFunc()
var blue = color.New(color.FgBlue).SprintFunc()
var magenta = color.New(color.FgMagenta).SprintFunc()
func initIAMSimClient(iamSimPPClient iam.SimulatePrincipalPolicyAPIClient, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
var green = color.New(color.FgGreen).SprintFunc()
iamSimMod := IamSimulatorModule{
IAMSimulatePrincipalPolicyClient: iamSimPPClient,
Caller: caller,
AWSProfile: AWSProfile,
Goroutines: Goroutines,
}
return iamSimMod
}
var sharedLogger = internal.TxtLogger()
func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod IamSimulatorModule, localAdminMap map[string]bool) (string, string) {
var adminRole, canRolePrivEsc string
@@ -175,3 +170,11 @@ func GetPmapperResults(SkipAdminCheck bool, pmapperMod PmapperModule, roleArn *s
}
return adminRole, canRolePrivEsc
}
// take an arn and return the resource name
func GetResourceNameFromArn(arn string) string {
parts := strings.Split(arn, "/")
resourceName := parts[len(parts)-1]
return resourceName
}
+75 -46
View File
@@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/sns"
"github.com/aws/aws-sdk-go-v2/service/sns/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/bishopfox/awsservicemap"
"github.com/sirupsen/logrus"
@@ -23,16 +24,14 @@ import (
type SNSModule struct {
// General configuration data
SNSClient AWSSNSClient
SNSClient SNSClientInterface
AWSRegions []string
AWSProfile string
Caller sts.GetCallerIdentityOutput
StorePolicies bool
Caller sts.GetCallerIdentityOutput
AWSRegions []string
OutputFormat string
Goroutines int
AWSProfile string
WrapTable bool
OutputFormat string
Goroutines int
WrapTable bool
// Main module data
Topics []SNSTopic
@@ -42,7 +41,7 @@ type SNSModule struct {
modLog *logrus.Entry
}
type AWSSNSClient interface {
type SNSClientInterface interface {
ListTopics(ctx context.Context, params *sns.ListTopicsInput, optFns ...func(*sns.Options)) (*sns.ListTopicsOutput, error)
GetTopicAttributes(ctx context.Context, params *sns.GetTopicAttributesInput, optFns ...func(*sns.Options)) (*sns.GetTopicAttributesOutput, error)
}
@@ -70,10 +69,10 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
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 SNS topics for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -133,8 +132,24 @@ func (m *SNSModule) PrintSNS(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
//m.output.OutputSelector(outputFormat)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s topics found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Access policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
@@ -148,7 +163,7 @@ func (m *SNSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sns", r)
if err != nil {
@@ -211,7 +226,7 @@ func (m *SNSModule) writeLoot(outputDirectory string, verbosity int, profile str
out = out + fmt.Sprintln("# WARNING: messages are consumed, you should leave fuzzing to non-production environments.")
out = out + fmt.Sprintf("aws --profile $profile --region %s sns publish --topic-arn %s --message \"[INSERT MESSAGE BODY]\"\n\n", topic.Region, topic.ARN)
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# Send message to the queue with attributes file (You'll have to create and populate the file)")
out = out + fmt.Sprintln("# Send message to the topic with attributes file (You'll have to create and populate the file)")
out = out + fmt.Sprintln("")
out = out + fmt.Sprintln("# WARNING: The following command can cause adverse effects in the environment. Like fuzzing a web application, if you inject")
out = out + fmt.Sprintln("# WARNING: malicious data you might find a vulnerability, but you also might break something. Unless you really know how the")
@@ -247,8 +262,47 @@ func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphor
defer func() {
<-semaphore
}()
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
ListTopics, err := m.listTopics(r)
if err != nil {
m.modLog.Error(err.Error())
return
}
for _, t := range ListTopics {
topic, err := m.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
topic.IsPublic = "No"
if !topic.Policy.IsEmpty() {
m.analyseTopicPolicy(topic, dataReceiver)
} else {
// If the topic policy "resource policy" is empty, the only principals that have permisisons
// are those that are granted access by IAM policies
//topic.Access = "Private. Access allowed by IAM policies"
topic.Access = "Only intra-account access (via IAM) allowed"
dataReceiver <- *topic
}
}
}
func (m *SNSModule) listTopics(region string) ([]types.Topic, error) {
var PaginationControl *string
var topics []types.Topic
for {
ListTopics, err := m.SNSClient.ListTopics(
@@ -257,42 +311,15 @@ func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphor
NextToken: PaginationControl,
},
func(o *sns.Options) {
o.Region = r
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
return nil, err
}
for _, t := range ListTopics.Topics {
topic, err := m.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
if err != nil {
topic.Name = aws.ToString(t.TopicArn)
}
topic.Name = parsedArn.Resource
topic.Region = parsedArn.Region
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
topic.IsPublic = "No"
if !topic.Policy.IsEmpty() {
m.analyseTopicPolicy(topic, dataReceiver)
} else {
// If the topic policy "resource policy" is empty, the only principals that have permisisons
// are those that are granted access by IAM policies
//topic.Access = "Private. Access allowed by IAM policies"
topic.Access = "Only intra-account access (via IAM) allowed"
dataReceiver <- *topic
}
topics = append(topics, t)
}
// The "NextToken" value is nil when there's no more data to return.
@@ -303,6 +330,8 @@ func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphor
break
}
}
return topics, nil
}
func (m *SNSModule) getTopicWithAttributes(topicARN string, region string) (*SNSTopic, error) {
+6 -6
View File
@@ -26,15 +26,15 @@ func TestSNSQueues(t *testing.T) {
}
m := SNSModule{
SNSClient: c,
StorePolicies: true,
AWSProfile: "unittesting",
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
SNSClient: c,
Caller: sts.GetCallerIdentityOutput{
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Account: aws.String("123456789012"),
},
Goroutines: 3,
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
AWSProfile: "unittesting",
StorePolicies: true,
Goroutines: 3,
}
fs := internal.MockFileSystem(true)
@@ -44,7 +44,7 @@ func TestSNSQueues(t *testing.T) {
// execute the module with verbosity = 2
m.PrintSNS("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting/table/sns.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/sns.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
+63 -30
View File
@@ -72,10 +72,10 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
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 SQS queues for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
@@ -134,8 +134,24 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
}
if len(m.output.Body) > 0 {
//m.output.OutputSelector(outputFormat)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s queues found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
fmt.Printf("[%s][%s] Access policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
} else {
@@ -148,7 +164,7 @@ func (m *SQSModule) PrintSQS(outputFormat string, outputDirectory string, verbos
func (m *SQSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Queue) {
defer wg.Done()
servicemap := &awsservicemap.AwsServiceMap{
JsonFileSource: "EMBEDDED_IN_PACKAGE",
JsonFileSource: "DOWNLOAD_FROM_AWS",
}
res, err := servicemap.IsServiceInRegion("sqs", r)
if err != nil {
@@ -244,8 +260,44 @@ func (m *SQSModule) getSQSRecordsPerRegion(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.
ListQueues, err := m.listQueues(r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
return
}
for _, url := range ListQueues {
queue, err := m.getQueueWithAttributes(url, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
queue.IsPublic = "No"
if !queue.Policy.IsEmpty() {
m.analyseQueuePolicy(queue, dataReceiver)
} else {
// If the queue policy "resource policy" is empty, the only principals that have permisisons
// are those that are granted access by IAM policies
//queue.Access = "Private. Access allowed by IAM policies"
queue.Access = "Only intra-account access (via IAM) allowed"
dataReceiver <- *queue
}
}
}
func (m *SQSModule) listQueues(region string) ([]string, error) {
var PaginationControl *string
var queues []string
for {
ListQueues, err := m.SQSClient.ListQueues(
@@ -255,37 +307,15 @@ func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
NextToken: PaginationControl,
},
func(o *sqs.Options) {
o.Region = r
o.Region = region
},
)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
return nil, fmt.Errorf("ListQueues() failed: %s", err)
}
for _, url := range ListQueues.QueueUrls {
queue, err := m.getQueueWithAttributes(url, r)
if err != nil {
m.modLog.Error(err.Error())
m.CommandCounter.Error++
break
}
// easier to just set the default state to be no and only flip it to yes if we have a case that matches
queue.IsPublic = "No"
if !queue.Policy.IsEmpty() {
m.analyseQueuePolicy(queue, dataReceiver)
} else {
// If the queue policy "resource policy" is empty, the only principals that have permisisons
// are those that are granted access by IAM policies
//queue.Access = "Private. Access allowed by IAM policies"
queue.Access = "Only intra-account access (via IAM) allowed"
dataReceiver <- *queue
}
queues = append(queues, url)
}
// The "NextToken" value is nil when there's no more data to return.
@@ -296,6 +326,8 @@ func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
break
}
}
return queues, nil
}
func (m *SQSModule) getQueueWithAttributes(queueURL string, region string) (*Queue, error) {
@@ -358,6 +390,7 @@ func (m *SQSModule) analyseQueuePolicy(queue *Queue, dataReceiver chan Queue) {
} else {
queue.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}
queue.ResourcePolicySummary = strings.TrimSuffix(queue.ResourcePolicySummary, "\n")
}
dataReceiver <- *queue
+10 -14
View File
@@ -50,25 +50,21 @@ func TestSQSQueues(t *testing.T) {
// execute the module with verbosity set to 2
m.PrintSQS("table", tmpDir, 2)
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting/table/sqs.txt")
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/sqs.txt")
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
if err != nil {
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
}
expectedResults := strings.TrimLeft(`
╭────────────────────────────────────────────────────┬─────────┬────────────────────────────────────────────────────────────╮
│ Arn │ Public? │ Resource Policy Summary │
├────────────────────────────────────────────────────┼─────────┼────────────────────────────────────────────────────────────┤
│ arn:aws:sqs:us-east-1:123456789012:condition-queue │ YES │ Statement 0 says: s3.amazonaws.com can SQS:SendMessage │
│ │ │ │
│ │ │ Statement 1 says: arn:aws:iam::123456789012:root can SQS:* │
│ │ │ │
│ │ │ Statement 2 says: Everyone can perform 2 actions │
│ │ │ │
│ arn:aws:sqs:us-east-1:123456789012:policy-queue │ YES │ * can sqs:* │
│ │ │ │
│ arn:aws:sqs:us-east-1:123456789012:no-policy-queue │ No │ │
╰────────────────────────────────────────────────────┴─────────┴────────────────────────────────────────────────────────────╯
╭────────────────────────────────────────────────────┬─────────┬─────────────────────────────────────────────────────────────────────────╮
│ Arn │ Public? │ Resource Policy Summary │
├────────────────────────────────────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
│ arn:aws:sqs:us-east-1:123456789012:condition-queue │ YES │ Statement 0 says: s3.amazonaws.com can SQS:SendMessage │
│ │ │ Statement 1 says: arn:aws:iam::123456789012:root can SQS:* │
│ │ │ Statement 2 says: Everyone can SQS:SendMessage & can SQS:ReceiveMessage │
│ arn:aws:sqs:us-east-1:123456789012:policy-queue │ YES │ * can sqs:* │
│ arn:aws:sqs:us-east-1:123456789012:no-policy-queue │ No │ │
╰────────────────────────────────────────────────────┴─────────┴─────────────────────────────────────────────────────────────────────────╯
`, "\n")
if string(resultsFile) != expectedResults {
t.Fatalf("Unexpected results:\n%s\n", resultsFile)
+18 -2
View File
@@ -133,10 +133,26 @@ func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verb
}
if len(m.output.Body) > 0 {
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
//m.writeLoot(m.output.FilePath, verbosity)
o := internal.OutputClient{
Verbosity: verbosity,
CallingModule: m.output.CallingModule,
Table: internal.TableClient{
Wrap: m.WrapTable,
},
}
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
o.WriteFullOutput(o.Table.TableFiles, nil)
//m.writeLoot(o.Table.DirectoryName, verbosity, m.AWSProfile)
fmt.Printf("[%s][%s] %s tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
count := m.countUniqueResourcesWithTags()
fmt.Printf("[%s][%s] %d unique resources with tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), count)
+637 -290
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -2,5 +2,5 @@ package globals
const CLOUDFOX_USER_AGENT = "cloudfox"
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
const CLOUDFOX_BASE_DIRECTORY = ".cloudfox/cloudfox-output"
const LOOT_DIRECTORY_NAME = "loot"
+87 -82
View File
@@ -1,111 +1,116 @@
module github.com/BishopFox/cloudfox
go 1.18
go 1.20
require (
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1
github.com/Azure/go-autorest/autorest v0.11.28
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
github.com/aquasecurity/table v1.8.0
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.3
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0
github.com/aws/aws-sdk-go-v2/service/elasticache v1.26.0
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/config v1.18.25
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0
github.com/aws/smithy-go v1.13.5
github.com/bishopfox/awsservicemap v1.0.1
github.com/dominikbraun/graph v0.15.0
github.com/fatih/color v1.13.0
github.com/bishopfox/awsservicemap v1.0.2
github.com/dominikbraun/graph v0.22.2
github.com/fatih/color v1.15.0
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/sirupsen/logrus v1.9.0
github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.9.0
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.4 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.11.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
go.mongodb.org/mongo-driver v1.11.7 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
)
+191 -175
View File
@@ -36,28 +36,31 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw=
github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.4 h1:pqrAR74b6EoR4kcxF7L7Wg2B8Jgil9UUZtMvxhEFqWo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.4/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 h1:XUNQ4mw+zJmaA2KXzP9JlQiecy1SI+Eog7xVkPiqIbg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 h1:YvQv9Mz6T8oR5ypQOL6erY0Z5t71ak1uHV4QFokCOZk=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1/go.mod h1:c6WvOhtmjNUWbLfOG1qxM/q0SPvQNSVJvolm+C52dIU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8=
github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
@@ -74,125 +77,128 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0=
github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw=
github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 h1:RKci2D7tMwpvGpDNZnGQw9wk6v7o/xSwFcUAuNPoB8k=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9/go.mod h1:vCmV1q1VK8eoQJ5+aYE7PkK1K6v41qJ5pJdK3ggCDvg=
github.com/aws/aws-sdk-go-v2/config v1.18.3 h1:3kfBKcX3votFX84dm00U8RGA1sCCh3eRMOGzg5dCWfU=
github.com/aws/aws-sdk-go-v2/config v1.18.3/go.mod h1:BYdrbeCse3ZnOD5+2/VE/nATOK8fEUpBtmPMdKSyhMU=
github.com/aws/aws-sdk-go-v2/credentials v1.13.3 h1:ur+FHdp4NbVIv/49bUjBW+FE7e57HOo03ELodttmagk=
github.com/aws/aws-sdk-go-v2/credentials v1.13.3/go.mod h1:/rOMmqYBcFfNbRPU0iN9IgGqD5+V2yp3iWNmIlz0wI4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 h1:E3PXZSI3F2bzyj6XxUXdTIfvp425HHhwKsFvmzBwHgs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19/go.mod h1:VihW95zQpeKQWVPGkwT+2+WJNQV8UXFfMTWdU6VErL8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25/go.mod h1:Zb29PYkf42vVYQY6pvSyJCJcFHlPIiY+YKdPtwnvMkY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 h1:Mza+vlnZr+fPKFKRq/lKGVvM6B/8ZZmNdEopOwSQLms=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26/go.mod h1:Y2OJ+P+MC1u1VKnavT+PshiEuGPyh/7DqxoDNij4/bg=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 h1:2EXB7dtGwRYIN3XQ9qwIW504DVbKIw3r89xQnonGdsQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16/go.mod h1:XH+3h395e3WVdd6T2Z3mPxuI+x/HVtdqVOREkTiyubs=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25 h1:WCRj8JKifyZTZiFGY6ayyNypZOlHsPbCqv6Tqfjmn7s=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25/go.mod h1:3olVANhEv+CFhEvC/TTkqh+1kg+r0px3CbH5eRKx7J4=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23 h1:tRkvVyUDMi4DBV6+QKK9Vmi2QN/SHmmcQo21vACw1Tc=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23/go.mod h1:BEIWaGqO27qq9JeFeY746S4+SFmBajpV+yhGne2qbMo=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0 h1:bX586RbPhYHIZKQ01cEUImiHU5y/7Ob5R9c5N6jhGuc=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.15.0/go.mod h1:NgTcDA7LdWjNGGBhS09XMVuwr8MZSFy/4G+BwZASviE=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1 h1:k8/rAveUOcZV8Ex6WiCmvdtTcDtyf0QgvMmVFA9PRJ8=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.1/go.mod h1:AyrrIfauUrYfHqLrnroijTBBegQow3QIZTaLbQsauNk=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0 h1:3bV+nNw0PPxkHvdRWUogUMtikIgIo2bVj48MvxQvoHA=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.21.0/go.mod h1:fV9PzXIIT2xUvf5KXEGToDYxEXcqDyO33SRfv2rAs/8=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3 h1:syWZB25hdMayklHlAe195ertSget7wBEjExW/t+tfJ0=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.20.3/go.mod h1:G3xZtg7cjsJaJdl1oVkscYXbdDLZBfOHbE1JqcnZxOI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7 h1:/TwGWNd3vnjXaPMau8eY7s5j6Afe4WxnRfIB64r4jEk=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.7/go.mod h1:BiglbKCG56L8tmMnUEyEQo422BO9xnNR8vVHnOsByf8=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1 h1:iR8DtI9Jc9sMdOsvjiu6rs5jH+9csW88elgwpEMP8TU=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.72.1/go.mod h1:zul71QqzR4D1a90/5FloZiAnZ1CtuIjVH7R9MP997+A=
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23 h1:Da25Cw8xI2q9Gr+ekQvpqJpyqR61xcb9Fh1Tkr/Cv/w=
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.23/go.mod h1:kEVGiy2tACP0cegVqx4MrjsgQMSgrtgRq1fSa+Ix6F0=
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0 h1:tFOfeyENzdOM5zKwGXdDsqeVJEjav8YLTb7tDehJ/c0=
github.com/aws/aws-sdk-go-v2/service/ecs v1.20.0/go.mod h1:NpR78BP2STxvF/R1GXLDM4gAEfjz68W/h0nC5b6Jk3s=
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0 h1:06pqrWeT8jrj8NIE0kovrFY3KC0pBt913HyusYyFjm8=
github.com/aws/aws-sdk-go-v2/service/efs v1.18.0/go.mod h1:DdO6hLx0XiYuj2Tn8VsR8et3BW1aXiv74GfsjHvhN9U=
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0 h1:roxmtB8AB5b/COvsD75AnswtsQOxqbjjvowmnX8IKDE=
github.com/aws/aws-sdk-go-v2/service/eks v1.23.0/go.mod h1:bxjOnpk0lwAq4jmmTONfUGPjgO8sLAbflxTHsY7thkU=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.26.0 h1:hKb7jojTnBE4FWqO2VeQ/phXizYWi+LL4RSME2HQyq0=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.26.0/go.mod h1:gnN6CtMag9be9XGXsMenh084NcSy5pO0hriEYz/TERk=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23 h1:wJdl2doHQU+60ETqLHfXtY4ywmm36ZHiinA/rVkTtl8=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.23/go.mod h1:gHgx63dm28U/o5NotzfFWhxUASzcZuPgO8GDpsI1Aqw=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26 h1:gAy84CkCnK2vgh7Rj5tRzpqeATCPShVCIjJZ0g7iKNg=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.26/go.mod h1:uIsRP+M5F/Ch+21isqTg6u16FXl2yzupCX0Dli4eQEM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4 h1:cqb2+5MxHa+qdNWiu4giFuDdgBlyDzDawiROMYTuzBM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.25.4/go.mod h1:dOw+yZ4MsogGQIWd7z6jNb0Q9aSlSeTy3EZWKDGT/dM=
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0 h1:qs2mr/MDqcAkG2XimkX3PcwR5jwCCZ8r4/yAWOCjGJo=
github.com/aws/aws-sdk-go-v2/service/glue v1.35.0/go.mod h1:kgD6fBlQEkhJlffBbS8SGYtpjboavr9e8B1ZouD84pY=
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0 h1:Oi4le4KruAPyOzM5eIIUfUHMKnFKpyGAt5sglp+KIgw=
github.com/aws/aws-sdk-go-v2/service/grafana v1.10.0/go.mod h1:mvMvy3rVAodoUa9hx6vS8cooFkLlpkt46n7BWVZNyA0=
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23 h1:HOtW30EkfQevdv++mKguMyn8/agh1z2VuBGR4Hou/u8=
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23/go.mod h1:yQ92mKfw/Gg5AvgxGmfdufKEyVoa9RNBsdnB9j5Gzkk=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 h1:dpiPHgmFstgkLG07KaYAewvuptq5kvo52xn7tVSrtrQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10/go.mod h1:9cBNUHI2aW4ho0A5T87O294iPDuuUOSIEDjnd1Lq/z0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 h1:KSvtm1+fPXE0swe9GPjc6msyrdTT0LB/BP8eLugL1FI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20/go.mod h1:Mp4XI/CkWGD79AQxZ5lIFlgvC0A+gl+4BmyG1F+SfNc=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19 h1:V03dAtcAN4Qtly7H3/0B6m3t/cyl4FgyKFqK738fyJw=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19/go.mod h1:2WpVWFC5n4DYhjNXzObtge8xfgId9UP6GWca46KJFLo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 h1:GE25AWCdNUPh9AOJzI9KIJnja7IwUc1WyUqz/JTyJ/I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19/go.mod h1:02CP6iuYP+IVnBX5HULVdSAku/85eHB2Y9EsFhrkEwU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 h1:piDBAaWkaxkkVV3xJJbTehXCZRXYs49kvpi/LG6LR2o=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19/go.mod h1:BmQWRVkLTmyNzYPFAZgon53qKLWBNSvonugD1MrSWUs=
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1 h1:mmWZ5Hdc7hMHIUVLgOFLjQQvvUGZ3uhNO8mDGznqIiA=
github.com/aws/aws-sdk-go-v2/service/lambda v1.25.1/go.mod h1:2oqKd3SCTyhVaUei20xDUOOcqOAuAnbCy79w/t1dDVs=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0 h1:ILjBjigugB2APXvhntZU+XwDAcaTId22dE0OwIbi/2A=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.24.0/go.mod h1:LYCAGTJ4XgNZAESKxLC9L12F7IIQbJHccVxanY8DYzo=
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15 h1:K0UbxNfaPHoIXo0T4L4jrbE+TKcVPYp55nEnMLxVRPk=
github.com/aws/aws-sdk-go-v2/service/mq v1.13.15/go.mod h1:ycghPMXYItx5Y74iehFgGwDNUMXdq0xCxLaYC5uYZO0=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3 h1:ViGcWux+UbkS/JBLxPILq/Mevlf9RXSycGlPaVQON5Y=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3/go.mod h1:7mh4ks7MsQUuhHFCr10LIF9gKVS+0yylPUmCEM/kzJE=
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24 h1:pzB33ZEhd1szVKHkgYe4cXQ+kSLJEFGBLy+IpbAKY9Y=
github.com/aws/aws-sdk-go-v2/service/ram v1.16.24/go.mod h1:el3+/AiwdwKk5qZEcCQqm+t3b1A5QLZI2nr/OkhM/gc=
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0 h1:lx8pflhN7oogISvAorwqPGFHN7eiVlGEwk21ThYcyoA=
github.com/aws/aws-sdk-go-v2/service/rds v1.31.0/go.mod h1:wPFe1Cj3nZWmNWKKdkXw961l1dJheTZQ5JjPImqbMuI=
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16 h1:qPa2cNgLUAMavzTD9joFfjU/ue5GE1gFsJPLD13yomw=
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.16/go.mod h1:Ds+ElMoF3oxjYWJZofKeNeih3q3W7vzRCkRdnu/aOj8=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24 h1:CP4LqvOiNVTtWoUgISmhTzUinT7zvYd57C6Xey9w86k=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.24/go.mod h1:/WfhDm5Hmfy/3TSM/1m9ojM0IQsBuVGvd3vITQc86i0=
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0 h1:ubppi63qDFs3J7cg8uDOzyvlmKFQDoxL2tlHb7mfbR8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.25.0/go.mod h1:kUSK8EkGYdzFbTmADk0t7yRIoESH80xjWe8Bp6dQce8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4 h1:QgmmWifaYZZcpaw3y1+ccRlgH6jAvLm4K/MBGUc7cNM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.4/go.mod h1:/NHbqPRiwxSPVOB2Xr+StDEH+GWV/64WwnUjv4KYzV0=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0 h1:i0G6pXYPs6FsufUBOGXqE/ozlFxZUf+pwp5fhSEvs7E=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.55.0/go.mod h1:umigFBRAb3i0OTRXRYDLlCawnJPYCWLhod55+tZHOFA=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8 h1:Zw48FHykP40fKMxPmagkuzklpEuDPLhvUjKP8Ygrds0=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.8/go.mod h1:k6CPuxyzO247nYEM1baEwHH1kRtosRCvgahAepaaShw=
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6 h1:rfQqunscpnVmvK6O9B2DwrBzIMICSCKswPwkD2XDan8=
github.com/aws/aws-sdk-go-v2/service/sns v1.18.6/go.mod h1:2cPUjR63iE9MPMPJtSyzYmsTFCNrN/Xi9j0v9BL5OU0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15 h1:5PgOVgJWObGxve+0qU7T/C0reU6RxqpNwbuunLT9Vlc=
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.15/go.mod h1:DKX/7/ZiAzHO6p6AhArnGdrV4r+d461weby8KeVtvC4=
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1 h1:N4aPQGoAgdUr+3F1UcuW8/WE3aM7sxzOpzDP0hWkJCg=
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1/go.mod h1:rEsqsZrOp9YvSGPOrcL3pR9+i/QJaWRkAYbuxMa7yCU=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 h1:GFZitO48N/7EsFDt8fMa5iYdmWqkUDDB3Eje6z3kbG0=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25/go.mod h1:IARHuzTXmj1C0KS35vboR0FeJ89OkEy1M9mWbK2ifCI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 h1:jcw6kKZrtNfBPJkaHrscDOZoe5gvi9wjudnxvozYFJo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8/go.mod h1:er2JHN+kBY6FcMfcBBKNGCT3CarImmdFzishsqBmSRI=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5 h1:60SJ4lhvn///8ygCzYy2l53bFW/Q15bVfyjyAWo6zuw=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.5/go.mod h1:bXcN3koeVYiJcdDU89n3kCYILob7Y34AeLopUbZgLT4=
github.com/aws/smithy-go v1.13.4/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.25 h1:JuYyZcnMPBiFqn87L2cRppo+rNwgah6YwD3VuyvaW6Q=
github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4=
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 h1:PjiYyls3QdCrzqUN35jMWtUK1vqVZ+zLfdOa/UPFDp0=
github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 h1:AzwRi5OKKwo4QNqPf7TjeO+tK8AyOK3GVSwmRPo7/Cs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25/go.mod h1:SUbB4wcbSEyCvqBxv/O/IBf93RbEze7U7OnoTlpPB+g=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11 h1:90Jf9htQw8RA1FIujUc8ZM8AFWNySHZH1SKe2/0Kla8=
github.com/aws/aws-sdk-go-v2/service/apigateway v1.16.11/go.mod h1:GXjIkQpFivo8T4szSTIiNQBvONXQz/MbN+M251q9BPk=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11 h1:1L2042GftNVyI3TtWclGodfN5zBQjBNXsTQxDNaPXs8=
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.13.11/go.mod h1:Cs+mG0DXkVYPWsWIE8Ga78C/HeN5zFBbPHdOnJPwZ4M=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9 h1:Ddc9R2j1KQ4qhbbfwugcQ44LzmeieaUSlBGbAGASuLM=
github.com/aws/aws-sdk-go-v2/service/apprunner v1.17.9/go.mod h1:tKdk26xmO22Q+geNJM7/uMDdTMvPCFZM12H0m1jpsHM=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0 h1:MjDK6nt3iDPCk4CVLrc6GoxZIunzRnyIalTYwEUKb/E=
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.29.0/go.mod h1:YtA9SsNBWnaDpSECATt8ghAOUMcGeHcnY2kTENLNmO8=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6 h1:qDEv2jw4sFWOxpo++zFx76GQ7MEh+Q7gHg7/6PVFMJs=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.6/go.mod h1:VaCrMUb08RMUN5p3ss+92ZEGC/2NkWuiCNYET3xkirw=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0 h1:tPKUl76gWGmmdewjbAMlbL32hr6roDoE8xLiqKNGPfo=
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.26.0/go.mod h1:JJDbUhySZXBEEMEMtiNubwi7ooh7OXI8mb/ItEAShjs=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13 h1:EPmZulYYBXFr/TlIe4iPMm6nyR198VMJOur0EzHvTvI=
github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.13/go.mod h1:5Wsxbl13SRHZ3oUQqIjvn+wLwiknRA/Myi1F5fGW6mo=
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3 h1:3KemQEVSRTEMexSmRVgqYSLqClvhtETjmrxxQhNLmuo=
github.com/aws/aws-sdk-go-v2/service/docdb v1.21.3/go.mod h1:XC0nkyZ9G1cFKU/K0WS/Jhrz6fsfy411T2L0MEFIlMo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7 h1:yb2o8oh3Y+Gg2g+wlzrWS3pB89+dHrXayT/d9cs8McU=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.7/go.mod h1:1MNss6sqoIsFGisX92do/5doiUCBrN7EjhZCS/8DUjI=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11 h1:wlTgmb/sCmVRJrN5De3CiHj4v/bTCgL5+qpdEd0CPtw=
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11/go.mod h1:Ce1q2jlNm8BVpjLaOnwnm5v2RClAbK6txwPljFzyW6c=
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1 h1:54QSuWR3Pot7HqBRXd+c1yF97h2bqzDBID8qFSAkTlE=
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1/go.mod h1:SB6YszwN1iKvyt/Qk+ICeKsfBxjd0CTEwwkmej9qoa0=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1 h1:fJFdGkf0Xj04IBnps3YlL6mGPSjzfgAZ7ZX+mbsrPDc=
github.com/aws/aws-sdk-go-v2/service/efs v1.20.1/go.mod h1:7szMjYu35IWLaJEG23z6x/KOFckAF4/oQdNuDni99Pk=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12 h1:eKidf2ebtleLtH67x02syhO9t3FItv9a7/ep9KC3TAM=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.12/go.mod h1:ZoyBDE311XYRiJpofw4jorVH2u+UhFpzfkrxF3aWu0U=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0 h1:m0ZU6thCelJgtyNMnXYp39y/SY9qE4clxhs84sQM3jA=
github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.0/go.mod h1:uSAGbZNsmIG2EXhWd7+6iKtwF15wWNtR5HLuRamlY5Q=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10 h1:gGqHXu9rt/F+xGidPfFKVZUYEDZ3zKMMAOx1yVUr//U=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.15.10/go.mod h1:pidEyxe4u/vkB8wvbKRZ/r6IUJcyhQoTbSLA2HWR6cY=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11 h1:IN2XMTLmhIEL5e3o+tY9JsLFSAxmjgM8gI7W2+CPrpw=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.19.11/go.mod h1:oPHYtcocUcfHOE7qygtvyZMw82nedCKZSop/R9jxlAM=
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13 h1:lIVHlpyYIQPjFX47bsf6u0vfMLP1A4a7imoqzmIj66s=
github.com/aws/aws-sdk-go-v2/service/fsx v1.28.13/go.mod h1:jQE07g3nDTABqgZabycvM3LsgnUYRImP4zWPt4Sb9Bc=
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0 h1:GF6Lsy9g1+Ig2e1TpGygl00+oBcdYHIMyTHoKZa9VGE=
github.com/aws/aws-sdk-go-v2/service/glue v1.50.0/go.mod h1:agadckFdb7BwFqeN4CXt3yrMtoFvY/8b2F+8FNeHVOc=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1 h1:7njo8N0P8cfchwXTn1xefRbYCzjQN6jrqOATFoan37U=
github.com/aws/aws-sdk-go-v2/service/grafana v1.13.1/go.mod h1:FkpbGiG/BBvaZKQTy4eNxo04PJXOuGwIbVMuUM+xAUo=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0 h1:ywXSXkssdnuPlJyCZVO5kAUQhFm/RhsbvWRHklJ0uH4=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.0/go.mod h1:kAnokExGCYs7zfvZEZdFHvQ/x4ZKIci0Raps6mZI1Ag=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 h1:vGWm5vTpMr39tEZfQeDiDAMgk+5qsnvRny3FjLpnH5w=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27 h1:QmyPCRZNMR1pFbiOi9kBZWZuKrKB9LD4cxltxQk4tNE=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.27/go.mod h1:DfuVY36ixXnsG+uTqnoLWunXAKJ4qjccoFrXUPpj+hs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 h1:NbWkRxEEIRSCqxhsHQuMiTH7yo+JZW1gp8v3elSVMTQ=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2/go.mod h1:4tfW5l4IAB32VWCDEBxCRtR9T4BWy4I4kr1spr8NgZM=
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0 h1:iNLsDIOju/bbqw0mNaEXh+9Ms6Mm0RjcHPP9z4k9lUY=
github.com/aws/aws-sdk-go-v2/service/lambda v1.35.0/go.mod h1:i23nHcGEyswthctBfhEO1agGpM5Uyh83aSmSB6DmdCk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6 h1:QQE/ZcXSrPFGprrG8VFblHiMpenvzICT09YnaMmQEwk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.26.6/go.mod h1:L+JqH2pSCvKnCVJNKnU/8TTUfuNuTXSmXiS3F0zMvzQ=
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11 h1:eZ1ScRKjMgz9yHTFBivnnRW9eUAoGXeuvWmx28ACYbI=
github.com/aws/aws-sdk-go-v2/service/mq v1.14.11/go.mod h1:ladDWyHbOQSExyg5caiKPBAxkjZfAbb/wZcXpF80IB8=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0 h1:GAFqcrOqxNh/2FXEb07nBpqlv79hGxJoI1u+5Cl/aAU=
github.com/aws/aws-sdk-go-v2/service/opensearch v1.17.0/go.mod h1:a+o5iMtYkg8xyE1VLRmhXmWElEgugcSIlU3rCY9hL5g=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6 h1:wHV9iUDPdluHAkeJBP9exp4IO9KN+T7/UHgltB8Udsg=
github.com/aws/aws-sdk-go-v2/service/organizations v1.19.6/go.mod h1:zw4Ac19gtzc4cdtfBCTZa7FlrXYh8tbUl3Jr7movexs=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2 h1:taFrZuOwM1UiayFQhSXw1JX5v2wFM/qIBGxgN5UWsxU=
github.com/aws/aws-sdk-go-v2/service/ram v1.18.2/go.mod h1:lYxryEoqZQtttq5iIoA40FD079MxYvNCIzczvMD9iAM=
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0 h1:Yi23UNiGidNfT7tIW0lbE6JtRR1ZN+cNZGRTKLB+opk=
github.com/aws/aws-sdk-go-v2/service/rds v1.45.0/go.mod h1:rS6T0DrjdZ5LDr8ZC/J9iZdD1oSbie5reWWzqv5zLOw=
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11 h1:a6XfWSH3xO0+eEAT9ox6V13LNX09fQrs7QvU5+juxY0=
github.com/aws/aws-sdk-go-v2/service/redshift v1.27.11/go.mod h1:gEfU7N8/eImCoodwl5CKW7Mih19THtJhzQCdA5TZZQ0=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12 h1:/fvzwRqxxiNJwNK5XVE1NvnQFvNajNRYx82lkTqXlMM=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.12/go.mod h1:i/CXT+v8caoM60a7brYlrVG64srBa1Op0Q5Nl6askyo=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1 h1:8e1fgdyer5IqBPtiWNsVLY/XFucmNTtYMqADyCFXTgQ=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.1/go.mod h1:9SEpwqaALzp34eCT6w5PTh4SDDT84wxfMRx9VJSJPsk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1 h1:O+9nAy9Bb6bJFTpeNFtd9UfHbgxO1o4ZDAM9rQp5NsY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1/go.mod h1:J9kLNzEiHSeGMyN7238EjJmBpCniVzFda75Gxl/NqB8=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0 h1:8fsILyGP7AMoK4LkO09UE9x/6uj05i3N3//CqUE3z1E=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.83.0/go.mod h1:l4Wxb+ZaxOd9hpjFmzI28NgX8us5H3rv/QXg9q/T8tE=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 h1:eB91eEYUlh8+O2dXr189W8GJJd+/T8N/c5HocH2KzVo=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA=
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11 h1:A3Y64jN5O4kZMDpsddKgy7p5ZRmKae4Rd5JJglkIq5Q=
github.com/aws/aws-sdk-go-v2/service/sfn v1.17.11/go.mod h1:pZ4bJEoEyKsCxq1IJFbhiB3JKNr1VMvmI+ujmlwOiuU=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11 h1:kUKAkuOhCCq/Av372Dtzg0oaAD5VEUYdDtU4lGIYKkw=
github.com/aws/aws-sdk-go-v2/service/sns v1.20.11/go.mod h1:WjBcrd28zNbbuAcIRO/n89sSeOxTuOZPiuxNXU/2WrI=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0 h1:EgyGgs20+tdc2F2P7mKCD6SkWv/62fsGZlT3N5VFi5M=
github.com/aws/aws-sdk-go-v2/service/sqs v1.23.0/go.mod h1:ujUjm+PrcKUeIiKu2PT7MWjcyY0D6YZRZF3fSswiO+0=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4 h1:3AjvCuRS8OnNVRC/UBagp1Jo2feR94+VAIKO4lz8gOQ=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.4/go.mod h1:p6MaesK9061w6NTiFmZpUzEkKUY5blKlwD2zYyErxKA=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGXsLBI6al083tpjJzY=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 h1:2DQLAKDteoEDI8zpCzqBMaZlJuoE9iTYD0gFmXVax9E=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bishopfox/awsservicemap v1.0.1 h1:TBsT2GlG22se6GXkcYS3jrb/sew62LuwN4kYg8InnJ4=
github.com/bishopfox/awsservicemap v1.0.1/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
github.com/bishopfox/awsservicemap v1.0.2 h1:+NnuInVcQkja6y6h6eGseneU3m5Nzp9O++gR1s+FiyM=
github.com/bishopfox/awsservicemap v1.0.2/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -208,29 +214,28 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
github.com/dominikbraun/graph v0.15.0 h1:U6N0fWJTDsUO9R5NhFE1Xi0YGGQ1+nKV9KO5apClf3E=
github.com/dominikbraun/graph v0.15.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dominikbraun/graph v0.22.2 h1:rFtrufgqXJy7daEMTxHIQWQc12Y1XHelXi2Heo1Bfho=
github.com/dominikbraun/graph v0.22.2/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc=
github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -284,9 +289,9 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@@ -294,8 +299,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -315,54 +320,55 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -373,9 +379,9 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -389,12 +395,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -428,6 +434,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -460,8 +467,10 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -482,6 +491,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -495,7 +505,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -518,14 +527,20 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -534,8 +549,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -586,6 +602,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -686,7 +703,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+12
View File
@@ -270,3 +270,15 @@ func SpinUntil(callingModuleName string, counter *CommandCounter, done chan bool
}
}
}
func ReorganizeAWSProfiles(allProfiles []string, mgmtProfile string) []string {
// take the mgmt profile, move it from its current position to the front of the list
var newProfiles []string
newProfiles = append(newProfiles, mgmtProfile)
for _, profile := range allProfiles {
if profile != mgmtProfile {
newProfiles = append(newProfiles, profile)
}
}
return newProfiles
}
+34 -8
View File
@@ -10,6 +10,7 @@ type PolicyStatement struct {
Effect string `json:"Effect"`
Principal PolicyStatementPrincipal `json:"Principal,omitempty"`
Action ListOrString `json:"Action"`
NotAction ListOrString `json:"NotAction,omitempty"`
Resource ListOrString `json:"Resource,omitempty"`
Condition PolicyStatementCondition `json:"Condition,omitempty"`
}
@@ -29,7 +30,7 @@ func (ps *PolicyStatement) IsAllow() bool {
func (ps *PolicyStatement) GetAllActionsAsString() string {
actions := ""
if len(ps.Action) < 2 {
if len(ps.Action) < 3 {
for _, action := range ps.Action {
if ps.Effect == "Allow" {
actions = fmt.Sprintf("%scan %s & ", actions, action)
@@ -53,14 +54,34 @@ func (ps *PolicyStatement) GetAllActionsAsString() string {
func (ps *PolicyStatement) GetAllPrincipalsAsString() string {
principals := ""
// if len(ps.Principal.O.GetListOfPrincipals()) < 3 {
// for _, principal := range ps.Principal.O.GetListOfPrincipals() {
// principals = fmt.Sprintf("%s%s & ", principals, principal)
// }
// } else {
// principals = fmt.Sprintf("%s%d principals", principals, len(ps.Principal.O.GetListOfPrincipals()))
// }
// principals = strings.TrimSuffix(principals, " & ")
// principals = principals + "\n"
// return principals
for _, principal := range ps.Principal.O.GetListOfPrincipals() {
principals = fmt.Sprintf("%s%s & ", principals, principal)
if len(ps.Principal.O.GetListOfPrincipals()) > 1 {
principals = fmt.Sprintf("%s%s \n& ", principals, principal)
} else {
principals = fmt.Sprintf("%s%s", principals, principal)
}
}
if principals == "" && ps.Principal.S == "*" {
principals = "Everyone"
}
return strings.TrimSuffix(principals, " & ")
// replace " &\n--" in principals with just a newline
principals = strings.TrimSuffix(principals, " \n& ")
principals = principals + "\n"
return principals
}
func (ps *PolicyStatement) GetConditionsInEnglish(caller string) string {
@@ -77,10 +98,12 @@ func (ps *PolicyStatement) GetConditionsInEnglish(caller string) string {
if (k == "AWS:SourceOwner" || k == "AWS:SourceAccount") && contains(v, caller) {
conditionText = "Default resource policy: Not exploitable"
} else {
conditionText = fmt.Sprintf("-> Only when %s %s %s", k, condition, arn)
conditionText = fmt.Sprintf("-> Only when %s %s %s\n", k, condition, arn)
}
conditionTextAll = fmt.Sprintf("%s%s\n", conditionTextAll, conditionText)
conditionTextAll = fmt.Sprintf("%s%s", conditionTextAll, conditionText)
// trim the last newline character
//conditionTextAll = strings.TrimSuffix(conditionTextAll, "\n")
}
}
@@ -96,14 +119,17 @@ func (ps *PolicyStatement) GetStatementSummaryInEnglish(caller string) string {
principals := ps.GetAllPrincipalsAsString()
conditions := ps.GetConditionsInEnglish(caller)
if conditions == "Default resource policy: Not exploitable\n" {
if conditions == "Default resource policy: Not exploitable" {
statementSummary = "Default resource policy: Not exploitable\n" + "\n"
} else if conditions != "\n" && conditions != "" {
statementSummary = fmt.Sprintf("%s %s %s", strings.TrimSuffix(principals, "\n"), actions, conditions) + "\n"
} else if conditions != "\n" {
statementSummary = fmt.Sprintf("%s %s %s", strings.TrimSuffix(principals, "\n"), actions, conditions)
} else {
statementSummary = fmt.Sprintf("%s %s\n", strings.TrimSuffix(principals, "\n"), actions)
}
// trim the last newline character
//statementSummary = strings.TrimSuffix(statementSummary, "\n")
return statementSummary
}
+169
View File
@@ -0,0 +1,169 @@
package internal
import (
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/patrickmn/go-cache"
)
var Cache = cache.New(120*time.Minute, 0)
var sharedLogger = TxtLogger()
func SaveCacheToFiles(directory string, accountID string) error {
err := os.MkdirAll(directory, 0755)
if err != nil {
return err
}
for key, item := range Cache.Items() {
// only if the key contains the accountID
if accountID != "" && strings.Contains(key, accountID) {
entry := cacheEntry{
Value: item.Object,
Exp: item.Expiration,
}
jsonData, err := json.MarshalIndent(entry, "", " ")
if err != nil {
return err
}
filename := filepath.Join(directory, key+".json")
err = ioutil.WriteFile(filename, jsonData, 0644)
if err != nil {
return err
}
}
}
return nil
}
func LoadCacheFromFiles(directory string) error {
_, err := os.Stat(directory)
if os.IsNotExist(err) {
// Directory doesn't exist, skip loading cache
return nil
} else if err != nil {
return err
}
files, err := ioutil.ReadDir(directory)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
continue
}
filename := filepath.Join(directory, file.Name())
jsonData, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
var entry cacheEntry
err = json.Unmarshal(jsonData, &entry)
if err != nil {
return err
}
key := strings.TrimSuffix(file.Name(), ".json")
Cache.Set(key, entry.Value, time.Duration(entry.Exp-time.Now().UnixNano()))
}
fmt.Println("Cache loaded from files.")
return nil
}
type cacheEntry struct {
Value interface{}
Exp int64
}
func SaveCacheToGobFiles(directory string, accountID string) error {
err := os.MkdirAll(directory, 0755)
if err != nil {
return err
}
for key, item := range Cache.Items() {
// only if the key contains the accountID
if accountID != "" && strings.Contains(key, accountID) {
entry := cacheEntry{
Value: item.Object,
Exp: item.Expiration,
}
filename := filepath.Join(directory, key+".gob")
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(entry)
if err != nil {
sharedLogger.Errorf("Could not encode the following key: %s", key)
return err
}
}
}
return nil
}
var ErrDirectoryDoesNotExist = errors.New("directory does not exist")
func LoadCacheFromGobFiles(directory string) error {
_, err := os.Stat(directory)
if os.IsNotExist(err) {
// Directory doesn't exist, skip loading cache
return ErrDirectoryDoesNotExist
} else if err != nil {
return err
}
files, err := ioutil.ReadDir(directory)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
continue
}
filename := filepath.Join(directory, file.Name())
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
var entry cacheEntry
decoder := gob.NewDecoder(file)
err = decoder.Decode(&entry)
if err != nil {
sharedLogger.Errorf("Could not decode the following file: %s", filename)
return err
}
// the key should remove the directory and the .json suffix from the filename and also trim the first slash
key := strings.TrimSuffix(strings.TrimPrefix(filename, directory), ".gob")[1:]
Cache.Set(key, entry.Value, time.Duration(entry.Exp-time.Now().UnixNano()))
}
//fmt.Println("Cache loaded from files.")
return nil
}
+34
View File
@@ -2,6 +2,7 @@ package internal
import (
"encoding/csv"
"encoding/json"
"fmt"
"log"
"os"
@@ -40,6 +41,9 @@ func OutputSelector(verbosity int, outputType string, header []string, body [][]
// Add writeLootToScreen function here
}
switch outputType {
case "wide":
case "table":
fmt.Println("")
outputFileTable := createOutputFile(
@@ -78,6 +82,14 @@ func OutputSelector(verbosity int, outputType string, header []string, body [][]
printCSVtoFile(header, body, outputFileCSV)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileCSV.Name())
// Add writeLootToFile function here
outputFileJSON := createOutputFile(
ptr.String(filepath.Join(outputDirectory, "json")),
ptr.String(fmt.Sprintf("%s.json", fileName)),
outputType,
callingModule)
printJsonToFile(header, body, outputFileJSON)
fmt.Printf("[%s][%s] Output written to [%s]\n", cyan(callingModule), cyan(prefixIdentifier), outputFileJSON.Name())
}
}
@@ -85,14 +97,35 @@ func printCSVtoFile(header []string, body [][]string, outputFile afero.File) {
csvWriter := csv.NewWriter(outputFile)
csvWriter.Write(header)
for _, row := range body {
row = removeColorCodesFromSlice(row)
csvWriter.Write(row)
}
csvWriter.Flush()
}
func printJsonToFile(header []string, body [][]string, outputFile afero.File) {
body = removeColorCodesFromNestedSlice(body)
jsonData := make([]map[string]string, len(body))
for i, row := range body {
jsonData[i] = make(map[string]string)
for j, column := range row {
jsonData[i][header[j]] = column
}
}
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
fmt.Println("error marshalling json:", err)
return
}
outputFile.Write(jsonBytes)
}
func printTableToFile(header []string, body [][]string, wrapLines bool, outputFile afero.File) {
body = removeColorCodesFromNestedSlice(body)
standardColumnWidth := 1000
t := table.New(outputFile)
if wrapLines {
terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
@@ -106,6 +139,7 @@ func printTableToFile(header []string, body [][]string, wrapLines bool, outputFi
}
t.SetColumnMaxWidth(standardColumnWidth)
t.SetHeaders(header...)
t.AddRows(body...)
t.SetRowLines(false)
t.SetDividers(table.UnicodeRoundedDividers)
+146 -12
View File
@@ -2,15 +2,16 @@ package internal
import (
"encoding/csv"
"encoding/json"
"fmt"
"log"
"os"
"path"
"regexp"
"github.com/aquasecurity/table"
"github.com/fatih/color"
"github.com/spf13/afero"
"golang.org/x/crypto/ssh/terminal"
)
// Used for file system mocking with Afero library. Set:
@@ -39,6 +40,7 @@ type TableFile struct {
Name string
TableFilePointer afero.File
CSVFilePointer afero.File
JSONFilePointer afero.File
Header []string
Body [][]string
}
@@ -54,6 +56,43 @@ type LootFile struct {
Contents string
}
func removeColorCodes(input string) string {
// Regular expression to match ANSI color codes
ansiRegExp := regexp.MustCompile(`\x1b\[[0-9;]*m`)
return ansiRegExp.ReplaceAllString(input, "")
}
func removeColorCodesFromSlice(input []string) []string {
// Regular expression to match ANSI color codes
ansiRegExp := regexp.MustCompile(`\x1b\[[0-9;]*m`)
// Create a new slice to store the strings with color codes removed
noColorSlice := make([]string, len(input))
for i, str := range input {
noColorSlice[i] = ansiRegExp.ReplaceAllString(str, "")
}
return noColorSlice
}
func removeColorCodesFromNestedSlice(input [][]string) [][]string {
// Regular expression to match ANSI color codes
ansiRegExp := regexp.MustCompile(`\x1b\[[0-9;]*m`)
// Create a new slice to store the slices with color codes removed
noColorNestedSlice := make([][]string, len(input))
for i, strSlice := range input {
noColorNestedSlice[i] = make([]string, len(strSlice))
for j, str := range strSlice {
noColorNestedSlice[i][j] = ansiRegExp.ReplaceAllString(str, "")
}
}
return noColorNestedSlice
}
func (o *OutputClient) WriteFullOutput(tables []TableFile, lootFiles []LootFile) {
switch o.Verbosity {
@@ -71,9 +110,12 @@ func (o *OutputClient) WriteFullOutput(tables []TableFile, lootFiles []LootFile)
tableOutputPaths := o.Table.writeTableFiles(tables)
o.Table.createCSVFiles()
csvOutputPaths := o.Table.writeCSVFiles()
o.Table.createJSONFiles()
jsonOutputPaths := o.Table.writeJSONFiles()
var outputPaths []string
outputPaths = append(outputPaths, tableOutputPaths...)
outputPaths = append(outputPaths, csvOutputPaths...)
outputPaths = append(outputPaths, jsonOutputPaths...)
if lootFiles != nil {
o.Loot.createLootFiles(lootFiles)
@@ -140,19 +182,22 @@ func (b *TableClient) printTablesToScreen(tableFiles []TableFile) {
standardColumnWidth := 1000
t := table.New(os.Stdout)
if b.Wrap {
terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
fmt.Printf("error getting terminal size: %s, please set the --wrap flag to false\n", err)
return
}
columnCount := len(tf.Header)
// The offset value was defined by trial and error to get the best wrapping
trialAndErrorOffset := 1
standardColumnWidth = terminalWidth / (columnCount + trialAndErrorOffset)
// if b.Wrap {
// terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
// if err != nil {
// fmt.Printf("error getting terminal size: %s, please set the --wrap flag to false\n", err)
// return
// }
// columnCount := len(tf.Header)
// // The offset value was defined by trial and error to get the best wrapping
// trialAndErrorOffset := 1
// standardColumnWidth = terminalWidth / (columnCount + trialAndErrorOffset)
// }
if !b.Wrap {
t.SetColumnMaxWidth(standardColumnWidth)
}
t.SetColumnMaxWidth(standardColumnWidth)
//t.SetColumnMaxWidth(standardColumnWidth)
t.SetHeaders(tf.Header...)
t.AddRows(tf.Body...)
t.SetHeaderStyle(table.StyleBold)
@@ -200,9 +245,15 @@ func (b *TableClient) writeTableFiles(files []TableFile) []string {
var fullFilePaths []string
for _, file := range b.TableFiles {
standardColumnWidth := 1000
t := table.New(file.TableFilePointer)
if !b.Wrap {
t.SetColumnMaxWidth(standardColumnWidth)
}
t.SetHeaders(file.Header...)
file.Body = removeColorCodesFromNestedSlice(file.Body)
t.AddRows(file.Body...)
t.SetRowLines(false)
t.SetDividers(table.UnicodeRoundedDividers)
@@ -253,6 +304,7 @@ func (b *TableClient) writeCSVFiles() []string {
csvWriter := csv.NewWriter(file.CSVFilePointer)
csvWriter.Write(file.Header)
for _, row := range file.Body {
row = removeColorCodesFromSlice(row)
csvWriter.Write(row)
}
csvWriter.Flush()
@@ -263,3 +315,85 @@ func (b *TableClient) writeCSVFiles() []string {
return fullFilePaths
}
func (b *TableClient) createJSONFiles() {
for i, file := range b.TableFiles {
if b.DirectoryName == "" {
b.DirectoryName = "."
}
jsonDirectory := path.Join(b.DirectoryName, "json")
if _, err := fileSystem.Stat(jsonDirectory); os.IsNotExist(err) {
err = fileSystem.MkdirAll(jsonDirectory, 0700)
if err != nil {
log.Fatal(err)
}
}
if file.Name == "" {
log.Fatalf("error creating json file: no file name was specified")
}
fileNameWithExt := fmt.Sprintf("%s.json", file.Name)
filePointer, err := fileSystem.OpenFile(path.Join(jsonDirectory, fileNameWithExt), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("error creating json file: %s", err)
}
b.TableFiles[i].JSONFilePointer = filePointer
}
}
// func (b *TableClient) writeJSONFiles() []string {
// var fullFilePaths []string
// for _, file := range b.TableFiles {
// file.Body = removeColorCodesFromNestedSlice(file.Body)
// jsonBytes, err := json.Marshal(file.Body)
// if err != nil {
// log.Fatalf("error marshalling json: %s", err)
// }
// _, err = file.JSONFilePointer.Write(jsonBytes)
// if err != nil {
// log.Fatalf("error writing json: %s", err)
// }
// fullPath := path.Join(b.DirectoryName, "json", fmt.Sprintf("%s.json", file.Name))
// fullFilePaths = append(fullFilePaths, fullPath)
// }
// return fullFilePaths
// }
func (b *TableClient) writeJSONFiles() []string {
var fullFilePaths []string
for _, file := range b.TableFiles {
file.Body = removeColorCodesFromNestedSlice(file.Body)
jsonData := make([]map[string]string, len(file.Body))
for i, row := range file.Body {
jsonData[i] = make(map[string]string)
for j, column := range row {
jsonData[i][file.Header[j]] = column
}
}
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
fmt.Println("error marshalling json:", err)
}
_, err = file.JSONFilePointer.Write(jsonBytes)
if err != nil {
log.Fatalf("error writing json: %s", err)
}
fullPath := path.Join(b.DirectoryName, "json", fmt.Sprintf("%s.json", file.Name))
fullFilePaths = append(fullFilePaths, fullPath)
}
return fullFilePaths
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var (
rootCmd = &cobra.Command{
Use: os.Args[0],
Version: "1.10.1",
Version: "1.11.2",
}
)
+12 -2
View File
@@ -11,6 +11,9 @@
"cloudformation:ListStacks",
"cloudtrail:LookupEvents",
"cloudfront:ListDistributions",
"codebuild:ListProjects",
"codebuild:BatchGetProjects",
"codebuild:GetResourcePolicy",
"dynamodb:ListTables",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstances",
@@ -21,7 +24,7 @@
"ecs:DescribeTaskDefinition",
"ecs:ListClusters",
"ecs:ListTaskDefinitions",
"ecs:ListTasks",
"ecs:ListTasks",
"eks:DescribeCluster",
"eks:DescribeNodegroup",
"eks:ListClusters",
@@ -47,11 +50,13 @@
"iam:ListRoles",
"iam:SimulatePrincipalPolicy",
"lambda:GetFunctionUrlConfig",
"lambda:ListFunctions",
"lambda:ListFunctions",
"lambda:GetPolicy",
"lightsail:GetContainerServices",
"lightsail:GetInstances",
"mq:DescribeBroker",
"mq:ListBrokers",
"organizations:ListAccounts",
"es:DescribeDomain",
"es:ListDomainNames",
"ram:GetResourceShares",
@@ -63,6 +68,9 @@
"s3:GetBucketPolicyStatus",
"s3:GetBucketWebsite",
"s3:ListAllMyBuckets",
"s3:GetAccountPublicAccessBlock",
"s3:GetBucketLocation",
"s3:GetBucketPolicy",
"ssm:DescribeParameters",
"sagemaker:ListProcessingJobs",
"sagemaker:DescribeProcessingJob",
@@ -74,7 +82,9 @@
"sagemaker:DescribeModel",
"secretsmanager:ListSecrets",
"sns:ListTopics",
"sns:GetTopicAttributes",
"sqs:ListQueues",
"sqs:GetQueueAttributes",
"tag:GetResources"
],