mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 09:56:01 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ae745e8e | ||
|
|
dc0396ff33 | ||
|
|
c11ebf9a07 | ||
|
|
6c8936cfe4 | ||
|
|
fca0c4da14 | ||
|
|
2c5fec49c3 | ||
|
|
a9b95d9142 | ||
|
|
0a74adea9a | ||
|
|
adb9bcd55a | ||
|
|
03177e8726 | ||
|
|
1fb9f4c3e0 | ||
|
|
3c6c59ec4b | ||
|
|
3f8b828a6c | ||
|
|
b8bf12f1c4 | ||
|
|
690b3c8c77 | ||
|
|
845b69e9a0 | ||
|
|
fc7a57129f | ||
|
|
f6ec6a12f7 | ||
|
|
56075588d5 | ||
|
|
7b51d9da96 | ||
|
|
a0a64c579c | ||
|
|
2b294e5845 | ||
|
|
d5956cebb0 | ||
|
|
9581425231 | ||
|
|
ce6c3f76fa | ||
|
|
497c7940d2 | ||
|
|
b29e4ea6b3 | ||
|
|
2756cd0f45 | ||
|
|
c0597764a9 | ||
|
|
ef1b37cef7 | ||
|
|
8e9bd42a1e | ||
|
|
8b2f13ca88 | ||
|
|
8722782eae | ||
|
|
0397b5df5f | ||
|
|
5f7596b497 | ||
|
|
0276ce25a4 | ||
|
|
7490d1aaa1 | ||
|
|
8ef71a6388 | ||
|
|
365286665c | ||
|
|
537d064c6b | ||
|
|
766fe55484 | ||
|
|
51f0b65087 | ||
|
|
186cd04405 | ||
|
|
56247f40d4 | ||
|
|
bc91da000c | ||
|
|
1aca8cf4cf | ||
|
|
7f413fc9d1 | ||
|
|
f2f93779ca | ||
|
|
59157b40d1 |
No files matched your search
@@ -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
@@ -66,4 +66,7 @@ output/*
|
||||
cloudfox
|
||||
*.log
|
||||
*.bak
|
||||
table_output.txt
|
||||
*.txt
|
||||
*.json
|
||||
*.csv
|
||||
*.log
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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: "",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+73
-98
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/apprunner"
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+54
-12
@@ -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
@@ -210,10 +210,26 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
}
|
||||
|
||||
if len(m.IPv4_Private) > 0 || len(m.IPv4_Public) > 0 || len(m.IPv6) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
m.writeLoot(m.output.FilePath)
|
||||
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.Verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
m.writeLoot(o.Table.DirectoryName)
|
||||
fmt.Printf("[%s][%s] %s network services found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/organizations/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/kyokomi/emoji"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type OrgModule struct {
|
||||
OrganizationsClient sdk.OrganizationsClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
WrapTable bool
|
||||
DescribeOrgOutput *types.Organization
|
||||
|
||||
// Main module data
|
||||
Accounts []Account
|
||||
Orgs map[string]Org
|
||||
CommandCounter internal.CommandCounter
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type Org struct {
|
||||
OrgId string
|
||||
MgmtAccount string
|
||||
ChildAccounts []Account
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
ProfileName string
|
||||
isManagementAccount bool
|
||||
Name string
|
||||
Id string
|
||||
Email string
|
||||
Arn string
|
||||
Status string
|
||||
OrgId string
|
||||
}
|
||||
|
||||
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These 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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -115,10 +115,26 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
//m.writeLoot(o.Table.DirectoryName, verbosity)
|
||||
fmt.Printf("[%s][%s] %s resources found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
@@ -0,0 +1,739 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/BishopFox/cloudfox/internal/aws/policy"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/bishopfox/awsservicemap"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ResourceTrustsModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
AWSProfile string
|
||||
CloudFoxVersion string
|
||||
|
||||
Resources2 []Resource2
|
||||
CommandCounter internal.CommandCounter
|
||||
|
||||
output internal.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type Resource2 struct {
|
||||
AccountID string
|
||||
Name string
|
||||
ARN string
|
||||
Region string
|
||||
Policy policy.Policy
|
||||
PolicyJSON string
|
||||
ResourcePolicySummary string
|
||||
Public string
|
||||
Interesting string
|
||||
}
|
||||
|
||||
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These 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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package sdk
|
||||
|
||||
import "github.com/BishopFox/cloudfox/internal"
|
||||
|
||||
var sharedLogger = internal.TxtLogger()
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -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"
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: os.Args[0],
|
||||
Version: "1.10.1",
|
||||
Version: "1.11.2",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user