mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-08 17:07:29 -10:00
Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db7091f33b | ||
|
|
465f08b530 | ||
|
|
8da636e294 | ||
|
|
6c7c26a53f | ||
|
|
2086a72275 | ||
|
|
ab6806d641 | ||
|
|
3e3608f774 | ||
|
|
7941a2c712 | ||
|
|
67feb23ad2 | ||
|
|
14bcd43158 | ||
|
|
0d3acc1006 | ||
|
|
f371bc7021 | ||
|
|
272bc0ad11 | ||
|
|
3cf38defdd | ||
|
|
6725aaaf45 | ||
|
|
b0fe4a290f | ||
|
|
20881183d1 | ||
|
|
8d989ccaf9 | ||
|
|
a91e0a891e | ||
|
|
386f3cb059 | ||
|
|
effb6a490e | ||
|
|
f6f78efd42 | ||
|
|
924fd3d89e | ||
|
|
60dbe425a5 | ||
|
|
bffa980d22 | ||
|
|
6665a9a5f0 | ||
|
|
50445263d7 | ||
|
|
ec422dcc6c | ||
|
|
416c93fdf1 | ||
|
|
9bd96cfb66 | ||
|
|
b38bf98072 | ||
|
|
7e209883fb | ||
|
|
991decf2d9 | ||
|
|
14132b3958 | ||
|
|
325b6ce892 | ||
|
|
b7cca44509 | ||
|
|
c2100cd927 | ||
|
|
71a60a6e67 | ||
|
|
04db8765a9 | ||
|
|
e5de75bd07 | ||
|
|
5b5a69a605 | ||
|
|
783524ba9c | ||
|
|
7410dab4f6 |
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
* @bishopfaure @dbravo-bishopfox
|
||||
@sethsec-bf carlosvendramini-bf
|
||||
@@ -15,7 +15,6 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.vscode
|
||||
.idea
|
||||
launch.json
|
||||
|
||||
######################################################
|
||||
|
||||
@@ -48,8 +48,8 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
|
||||
| Provider| CloudFox Commands |
|
||||
| - | - |
|
||||
| AWS | 33 |
|
||||
| Azure | 4 |
|
||||
| AWS | 30 |
|
||||
| Azure | 6 |
|
||||
| GCP | Support Planned |
|
||||
| Kubernetes | Support Planned |
|
||||
|
||||
@@ -149,11 +149,14 @@ Additional policy notes (as of 09/2022):
|
||||
| Azure | [inventory](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#inventory) | Display an inventory table of all resources per location. |
|
||||
| Azure | [rbac](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac) | Lists Azure RBAC role assignments at subscription or tenant level |
|
||||
| Azure | [storage](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#storage) | The storage command is still under development. Currently it only displays limited data about the storage accounts |
|
||||
| Azure | [nsg-links](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-links) | Lists subnets and network interfaces attached to Network Security Groups |
|
||||
| Azure | [nsg-rules](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-rules) | Lists rules in Network Security Groups |
|
||||
| Azure | [vms](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#vms) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
|
||||
|
||||
# Authors
|
||||
* [Carlos Vendramini](https://github.com/carlosvendramini-bf)
|
||||
* [Seth Art (@sethsec](https://twitter.com/sethsec))
|
||||
* [Bastien Faure](https://github.com/BastienFaure)
|
||||
|
||||
# Contributing
|
||||
[Wiki - How to Contribute](https://github.com/BishopFox/cloudfox/wiki#how-to-contribute)
|
||||
|
||||
+8
-1
@@ -2,6 +2,7 @@ package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -143,7 +144,7 @@ func (m *ApiGwModule) PrintApiGws(outputDirectory string, verbosity int) {
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
loot := m.writeLoot(filepath, verbosity)
|
||||
loot := m.writeLoot(o.Table.DirectoryName, verbosity)
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
|
||||
Name: m.output.CallingModule,
|
||||
Contents: loot,
|
||||
@@ -206,6 +207,12 @@ func (m *ApiGwModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
|
||||
func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
|
||||
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, "api-gws.txt")
|
||||
|
||||
var out string
|
||||
|
||||
+1
-3
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -30,8 +29,7 @@ func TestApiGw(t *testing.T) {
|
||||
|
||||
fs := internal.MockFileSystem(true)
|
||||
defer internal.MockFileSystem(false)
|
||||
//tmpDir := "~/.cloudfox/"
|
||||
tmpDir := ptr.ToString(internal.GetLogDirPath())
|
||||
tmpDir := "~/.cloudfox/"
|
||||
|
||||
m.PrintApiGws(tmpDir, 2)
|
||||
|
||||
|
||||
+130
-141
@@ -3,6 +3,7 @@ package aws
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -21,6 +22,7 @@ type DatabasesModule struct {
|
||||
RDSClient sdk.RDSClientInterface
|
||||
RedshiftClient sdk.AWSRedShiftClientInterface
|
||||
DynamoDBClient sdk.DynamoDBClientInterface
|
||||
DocDBClient sdk.DocDBClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
@@ -51,7 +53,6 @@ type Database struct {
|
||||
Protocol string
|
||||
Public string
|
||||
Size string
|
||||
Roles string
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int) {
|
||||
@@ -67,13 +68,13 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
}
|
||||
|
||||
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, Redshift, DynamoDB, DocumentDB, Neptune\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
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 task status spinner/updated
|
||||
//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
|
||||
@@ -111,8 +112,7 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
"Roles",
|
||||
//"Port",
|
||||
//"Protocol",
|
||||
//"Public",
|
||||
|
||||
@@ -139,8 +139,6 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
"Roles",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
@@ -152,7 +150,6 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,8 +166,7 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
m.Databases[i].Size,
|
||||
m.Databases[i].UserName,
|
||||
m.Databases[i].Endpoint,
|
||||
strconv.Itoa(int(m.Databases[i].Port)),
|
||||
m.Databases[i].Roles,
|
||||
// strconv.Itoa(int(m.Databases[i].Port)),
|
||||
// m.Databases[i].Protocol,
|
||||
// m.Databases[i].Public,
|
||||
},
|
||||
@@ -178,17 +174,13 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
filepath := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.WrapTable,
|
||||
DirectoryName: filepath,
|
||||
},
|
||||
Loot: internal.LootClient{
|
||||
DirectoryName: filepath,
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
@@ -198,15 +190,9 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
loot := m.writeLoot(filepath, verbosity)
|
||||
//o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.writeLoot(o.Table.DirectoryName, verbosity)
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
|
||||
Name: "databases-UrlsOnly.txt",
|
||||
Contents: loot,
|
||||
})
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
|
||||
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))
|
||||
@@ -231,74 +217,56 @@ func (m *DatabasesModule) Receiver(receiver chan Database, receiverDone chan boo
|
||||
func (m *DatabasesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer wg.Done()
|
||||
|
||||
serviceMap := &awsservicemap.AwsServiceMap{
|
||||
servicemap := &awsservicemap.AwsServiceMap{
|
||||
JsonFileSource: "DOWNLOAD_FROM_AWS",
|
||||
}
|
||||
m.executeRdsCheck(r, wg, semaphore, dataReceiver, serviceMap) // Also returns Neptune and DocDB
|
||||
m.executeRedshiftCheck(r, wg, semaphore, dataReceiver, serviceMap)
|
||||
m.executeDynamoDbCheck(r, wg, semaphore, dataReceiver, serviceMap)
|
||||
}
|
||||
|
||||
type check struct {
|
||||
region string
|
||||
wg *sync.WaitGroup
|
||||
semaphore chan struct{}
|
||||
dataReceiver chan Database
|
||||
serviceMap *awsservicemap.AwsServiceMap
|
||||
service string
|
||||
executor func(string, *sync.WaitGroup, chan struct{}, chan Database)
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) executeCheck(check check) {
|
||||
res, err := check.serviceMap.IsServiceInRegion(check.service, check.region)
|
||||
res, err := servicemap.IsServiceInRegion("rds", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
check.wg.Add(1)
|
||||
go check.executor(check.region, check.wg, check.semaphore, check.dataReceiver)
|
||||
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) executeRdsCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
|
||||
m.executeCheck(check{
|
||||
region: r,
|
||||
wg: wg,
|
||||
semaphore: semaphore,
|
||||
dataReceiver: dataReceiver,
|
||||
serviceMap: servicemap,
|
||||
service: "rds",
|
||||
executor: m.getRdsClustersPerRegion,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) executeRedshiftCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
|
||||
m.executeCheck(check{
|
||||
region: r,
|
||||
wg: wg,
|
||||
semaphore: semaphore,
|
||||
dataReceiver: dataReceiver,
|
||||
serviceMap: servicemap,
|
||||
service: "redshift",
|
||||
executor: m.getRedshiftDatabasesPerRegion,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) executeDynamoDbCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) {
|
||||
m.executeCheck(check{
|
||||
region: r,
|
||||
wg: wg,
|
||||
semaphore: semaphore,
|
||||
dataReceiver: dataReceiver,
|
||||
serviceMap: servicemap,
|
||||
service: "dynamodb",
|
||||
executor: m.getDynamoDBTablesPerRegion,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) string {
|
||||
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
|
||||
@@ -307,12 +275,12 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) strin
|
||||
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())
|
||||
// }
|
||||
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()
|
||||
@@ -323,8 +291,6 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) strin
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
|
||||
return out
|
||||
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
@@ -341,58 +307,45 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DBClusters, err := sdk.CachedRDSDescribeDBClusters(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
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
|
||||
}
|
||||
|
||||
for _, cluster := range DBClusters {
|
||||
var public string
|
||||
var service string
|
||||
var roles string
|
||||
if cluster.Endpoint == nil {
|
||||
continue
|
||||
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 aws.ToBool(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: aws.ToInt32(port),
|
||||
Protocol: aws.ToString(instance.Engine),
|
||||
Public: public,
|
||||
}
|
||||
}
|
||||
|
||||
name := aws.ToString(cluster.DBClusterIdentifier)
|
||||
port := cluster.Port
|
||||
endpoint := aws.ToString(cluster.Endpoint)
|
||||
engine := aws.ToString(cluster.Engine)
|
||||
|
||||
if aws.ToBool(cluster.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
}
|
||||
|
||||
if isNeptune(cluster.Engine) {
|
||||
service = "Neptune"
|
||||
} else if isDocDB(cluster.Engine) {
|
||||
service = "DocsDB"
|
||||
} else {
|
||||
service = "RDS"
|
||||
}
|
||||
|
||||
associatedRoles := cluster.AssociatedRoles
|
||||
for _, role := range associatedRoles {
|
||||
roles = roles + aws.ToString(role.RoleArn) + " "
|
||||
}
|
||||
|
||||
dataReceiver <- Database{
|
||||
AWSService: service,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Engine: engine,
|
||||
Endpoint: endpoint,
|
||||
UserName: aws.ToString(cluster.MasterUsername),
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: aws.ToString(cluster.Engine),
|
||||
Public: public,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
@@ -416,7 +369,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
|
||||
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.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++
|
||||
@@ -428,19 +381,19 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
name := aws.ToString(cluster.DBName)
|
||||
//id := workspace.Id
|
||||
endpoint := aws.ToString(cluster.Endpoint.Address)
|
||||
port := aws.ToInt32(cluster.Endpoint.Port)
|
||||
|
||||
if aws.ToBool(cluster.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
}
|
||||
port := cluster.Endpoint.Port
|
||||
dataReceiver <- Database{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
}
|
||||
@@ -489,12 +442,48 @@ func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGrou
|
||||
Endpoint: "N/A",
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func isNeptune(engine *string) bool {
|
||||
return *engine == "neptune"
|
||||
}
|
||||
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()
|
||||
|
||||
func isDocDB(engine *string) bool {
|
||||
return *engine == "docdb"
|
||||
}()
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func TestDatabasesCommand(t *testing.T) {
|
||||
|
||||
m := DatabasesModule{
|
||||
AWSProfile: "unittesting",
|
||||
AWSRegions: []string{"us-east-1"},
|
||||
Caller: sts.GetCallerIdentityOutput{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
|
||||
Account: aws.String("123456789012"),
|
||||
},
|
||||
Goroutines: 3,
|
||||
WrapTable: false,
|
||||
RDSClient: &sdk.MockedRDSClient{},
|
||||
DynamoDBClient: &sdk.MockedAWSDynamoDBClient{},
|
||||
RedshiftClient: &sdk.MockedRedshiftClient{},
|
||||
}
|
||||
|
||||
fs := internal.MockFileSystem(true)
|
||||
defer internal.MockFileSystem(false)
|
||||
tmpDir := ptr.ToString(internal.GetLogDirPath())
|
||||
|
||||
m.PrintDatabases(tmpDir, 2)
|
||||
//resultsFile, err := afero.ReadFile(fs, "table/databases.txt")
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/databases.txt")
|
||||
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read output file at %s: %s", resultsFile, err)
|
||||
}
|
||||
|
||||
expectedResults := []string{
|
||||
"db1.cluster-123456789012.us-west-2.rds.amazonaws.com",
|
||||
"db2.cluster-123456789012.us-west-2.rds.amazonaws.com",
|
||||
"db3.cluster-123456789012.us-west-2.neptune.amazonaws.com",
|
||||
"db4.cluster-123456789012.us-west-2.docdb.amazonaws.com",
|
||||
}
|
||||
|
||||
for _, expected := range expectedResults {
|
||||
if !strings.Contains(string(resultsFile), expected) {
|
||||
t.Errorf("Expected %s to be in the results file", expected)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -836,7 +836,7 @@ func (m *EnvsModule) interestingEnvVarsOnly() [][]string {
|
||||
for _, envVar := range m.EnvironmentVariables {
|
||||
if envVar.interesting {
|
||||
interestingBody = append(
|
||||
interestingBody, []string{
|
||||
m.output.Body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
|
||||
+148
-150
@@ -1,17 +1,21 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/bishopfox/knownawsaccountslookup"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -61,12 +65,46 @@ type RoleTrustRow struct {
|
||||
|
||||
type AnalyzedRole struct {
|
||||
roleARN *string
|
||||
trustsDoc policy.TrustPolicyDocument
|
||||
trustsDoc trustPolicyDocument
|
||||
// trustType string // UNUSED FIELD, PLEASE REVIEW
|
||||
Admin string
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
type trustPolicyDocument struct {
|
||||
Version string `json:"Version"`
|
||||
Statement []RoleTrustStatementEntry `json:"Statement"`
|
||||
}
|
||||
|
||||
type RoleTrustStatementEntry struct {
|
||||
Sid string `json:"Sid"`
|
||||
Effect string `json:"Effect"`
|
||||
Principal struct {
|
||||
AWS ListOfPrincipals `json:"AWS"`
|
||||
Service ListOfPrincipals `json:"Service"`
|
||||
Federated ListOfPrincipals `json:"Federated"`
|
||||
} `json:"Principal"`
|
||||
Action string `json:"Action"`
|
||||
Condition struct {
|
||||
StringEquals struct {
|
||||
StsExternalID string `json:"sts:ExternalId"`
|
||||
SAMLAud string `json:"SAML:aud"`
|
||||
OidcEksSub string `json:"OidcEksSub"`
|
||||
OidcEksAud string `json:"OidcEksAud"`
|
||||
CognitoAud string `json:"cognito-identity.amazonaws.com:aud"`
|
||||
} `json:"StringEquals"`
|
||||
StringLike struct {
|
||||
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
|
||||
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
|
||||
OidcEksSub string `json:"OidcEksSub"`
|
||||
OidcEksAud string `json:"OidcEksAud"`
|
||||
} `json:"StringLike"`
|
||||
ForAnyValueStringLike struct {
|
||||
CognitoAMR string `json:"cognito-identity.amazonaws.com:amr"`
|
||||
} `json:"ForAnyValue:StringLike"`
|
||||
} `json:"Condition"`
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int) {
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
@@ -102,7 +140,6 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -152,12 +189,12 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int
|
||||
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 service role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
|
||||
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 federated role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
|
||||
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))
|
||||
}
|
||||
@@ -204,34 +241,33 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
for _, principal := range statement.Principal.AWS {
|
||||
//check to see if the accountID is known
|
||||
if strings.Contains(principal, "arn:aws:iam::") || strings.Contains(principal, "root") {
|
||||
if strings.Contains(principal, ":root") {
|
||||
//check to see if the accountID is known
|
||||
accountID := strings.Split(principal, ":")[4]
|
||||
vendorName := m.vendors.GetVendorNameFromAccountID(accountID)
|
||||
if vendorName != "" {
|
||||
principal = fmt.Sprintf("%s (%s)", principal, vendorName)
|
||||
}
|
||||
}
|
||||
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedPrincipal,
|
||||
RoleTrustRow.ExternalID,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedPrincipal,
|
||||
RoleTrustRow.ExternalID,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
@@ -336,7 +372,7 @@ func (m *RoleTrustsModule) printServiceTrusts(outputDirectory string) ([]string,
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{"Account", "Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
tableCols = []string{"Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
@@ -404,7 +440,7 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]strin
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{"Account", "Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
tableCols = []string{"Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
@@ -417,25 +453,23 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]strin
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
if len(statement.Principal.Federated) > 0 {
|
||||
provider, subjects := parseFederatedTrustPolicy(statement)
|
||||
for _, subject := range subjects {
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedFederatedProvider: provider,
|
||||
TrustedFederatedSubject: subject,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedFederatedProvider,
|
||||
RoleTrustRow.TrustedFederatedSubject,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
provider, subject := parseFederatedTrustPolicy(statement)
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedFederatedProvider: provider,
|
||||
TrustedFederatedSubject: subject,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedFederatedProvider,
|
||||
RoleTrustRow.TrustedFederatedSubject,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -446,120 +480,47 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]strin
|
||||
|
||||
}
|
||||
|
||||
func parseFederatedTrustPolicy(statement policy.RoleTrustStatementEntry) (string, []string) {
|
||||
var provider string
|
||||
var subjects []string
|
||||
if len(statement.Principal.Federated) > 1 {
|
||||
sharedLogger.Warnf("Multiple federated providers found in the trust policy. This is not currently supported. Please review the trust policy for specifics.")
|
||||
provider = "Multiple Federated Providers"
|
||||
subjects = append(subjects, "Review policy for specifics\nand submit issue to cloudfox repo.")
|
||||
}
|
||||
|
||||
switch {
|
||||
// lets use the Federated field to determine the provider, then based on the provider we can grab the list of subjects
|
||||
case strings.Contains(statement.Principal.Federated[0], "token.actions.githubusercontent.com"):
|
||||
provider = "GitHub"
|
||||
if len(statement.Condition.StringLike.TokenActionsGithubusercontentComSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.TokenActionsGithubusercontentComSub...)
|
||||
} else if len(statement.Condition.StringEquals.TokenActionsGithubusercontentComSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.TokenActionsGithubusercontentComSub...)
|
||||
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] + ")"
|
||||
trustedRepos := strings.Join(statement.Condition.StringLike.TokenActionsGithubusercontentComSub, "\n")
|
||||
if trustedRepos == "" {
|
||||
column3 = "ALL REPOS!!!"
|
||||
} else {
|
||||
subjects = append(subjects, "ALL REPOS!!!")
|
||||
column3 = trustedRepos
|
||||
}
|
||||
case strings.Contains(statement.Principal.Federated[0], "oidc.eks"):
|
||||
provider = "EKS"
|
||||
if len(statement.Condition.StringLike.OidcEksSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.OidcEksSub...)
|
||||
} else if len(statement.Condition.StringEquals.OidcEksSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.OidcEksSub...)
|
||||
} 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] + ")"
|
||||
} else if strings.Contains(statement.Principal.Federated[0], "Okta") {
|
||||
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] + ")"
|
||||
if statement.Condition.StringEquals.OidcEksSub != "" {
|
||||
column3 = statement.Condition.StringEquals.OidcEksSub
|
||||
} else if statement.Condition.StringLike.OidcEksSub != "" {
|
||||
column3 = statement.Condition.StringLike.OidcEksSub
|
||||
} else {
|
||||
subjects = append(subjects, "ALL SERVICE ACCOUNTS!!!")
|
||||
column3 = "ALL SERVICE ACCOUNTS!"
|
||||
}
|
||||
// terraform case
|
||||
case strings.Contains(statement.Principal.Federated[0], "app.terraform.io"):
|
||||
provider = "Terraform Cloud"
|
||||
if len(statement.Condition.StringLike.TerraformSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.TerraformSub...)
|
||||
} else if len(statement.Condition.StringEquals.TerraformSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.TerraformSub...)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL WORKSPACES")
|
||||
}
|
||||
// Azure AD case
|
||||
case strings.Contains(statement.Principal.Federated[0], "http://sts.windows.net"):
|
||||
provider = "Azure AD"
|
||||
if len(statement.Condition.StringLike.AzureADIss) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.AzureADIss...)
|
||||
} else if len(statement.Condition.StringEquals.AzureADIss) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.AzureADIss...)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL ISSUERS")
|
||||
}
|
||||
|
||||
// AWS SSO case
|
||||
case strings.Contains(statement.Principal.Federated[0], "AWSSSO"):
|
||||
provider = "AWS SSO"
|
||||
subjects = append(subjects, "Not applicable")
|
||||
|
||||
// okta case
|
||||
case strings.Contains(statement.Principal.Federated[0], "Okta"):
|
||||
provider = "Okta"
|
||||
subjects = append(subjects, "Not applicable")
|
||||
|
||||
// cognito case
|
||||
case statement.Principal.Federated[0] == "cognito-identity.amazonaws.com":
|
||||
provider = "Cognito"
|
||||
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
|
||||
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
|
||||
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
|
||||
subjects = append(subjects, statement.Condition.ForAnyValueStringLike.CognitoAMR)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL IDENTITIES")
|
||||
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
|
||||
}
|
||||
// google workspace case
|
||||
case strings.Contains(statement.Principal.Federated[0], "workspace.google.com"):
|
||||
provider = "Google Workspace"
|
||||
if len(statement.Condition.StringLike.GoogleWorkspaceSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.GoogleWorkspaceSub...)
|
||||
} else if len(statement.Condition.StringEquals.GoogleWorkspaceSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.GoogleWorkspaceSub...)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL USERS")
|
||||
} else {
|
||||
if column2 == "" && strings.Contains(statement.Principal.Federated[0], "oidc.eks") {
|
||||
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] + ")"
|
||||
}
|
||||
// GCP case
|
||||
case strings.Contains(statement.Principal.Federated[0], "accounts.google.com"):
|
||||
provider = "GCP"
|
||||
if len(statement.Condition.StringLike.GCPSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.GCPSub...)
|
||||
} else if len(statement.Condition.StringEquals.GCPSub) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.GCPSub...)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL USERS")
|
||||
}
|
||||
// auth0 case
|
||||
//not ready yet
|
||||
// case strings.Contains(statement.Principal.Federated[0], "auth0.com"):
|
||||
// provider = "Auth0"
|
||||
// if len(statement.Condition.ForAnyValueStringLike.Auth0Amr) > 0 {
|
||||
// subjects = append(subjects, statement.Condition.ForAnyValueStringLike.Auth0Amr...)
|
||||
// } else {
|
||||
// subjects = append(subjects, "ALL GROUPS")
|
||||
// }
|
||||
// circleci case
|
||||
case strings.Contains(statement.Principal.Federated[0], "oidc.circleci.com"):
|
||||
provider = "CircleCI"
|
||||
if len(statement.Condition.StringLike.CircleCIAud) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringLike.CircleCIAud...)
|
||||
} else if len(statement.Condition.StringEquals.CircleCIAud) > 0 {
|
||||
subjects = append(subjects, statement.Condition.StringEquals.CircleCIAud...)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL PROJECTS")
|
||||
}
|
||||
|
||||
default:
|
||||
provider = "Unknown Federated Provider"
|
||||
subjects = append(subjects, "Review policy for specifics\nand submit issue to cloudfox repo.")
|
||||
|
||||
}
|
||||
return provider, subjects
|
||||
return column2, column3
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
|
||||
@@ -579,7 +540,7 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
|
||||
}
|
||||
|
||||
for _, role := range ListRoles {
|
||||
trustsdoc, err := policy.ParseRoleTrustPolicyDocument(role)
|
||||
trustsdoc, err := parseRoleTrustPolicyDocument(role)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
@@ -599,3 +560,40 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func parseRoleTrustPolicyDocument(role types.Role) (trustPolicyDocument, error) {
|
||||
document, _ := url.QueryUnescape(aws.ToString(role.AssumeRolePolicyDocument))
|
||||
|
||||
// These next six lines are a hack, needed because the EKS OIDC json field name is dynamic
|
||||
// and therefore can't be used to unmarshall in a predictable way. The hack involves replacing
|
||||
// the random pattern with a predictable one so that we can add the predictable one in the struct
|
||||
// used to unmarshall.
|
||||
pattern := `(\w+)\:`
|
||||
pattern2 := `".[a-zA-Z0-9\-\.]+/id/`
|
||||
var reEKSSub = regexp.MustCompile(pattern2 + pattern + "sub")
|
||||
var reEKSAud = regexp.MustCompile(pattern2 + pattern + "aud")
|
||||
document = reEKSSub.ReplaceAllString(document, "\"OidcEksSub")
|
||||
document = reEKSAud.ReplaceAllString(document, "\"OidcEksAud")
|
||||
|
||||
var parsedDocumentToJSON trustPolicyDocument
|
||||
_ = 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 ListOfPrincipals []string
|
||||
|
||||
func (r *ListOfPrincipals) 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")
|
||||
}
|
||||
+14
-96
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/apigateway"
|
||||
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type APIGatewayClientInterface interface {
|
||||
@@ -23,32 +22,13 @@ type APIGatewayClientInterface interface {
|
||||
GetUsagePlanKeys(context.Context, *apigateway.GetUsagePlanKeysInput, ...func(*apigateway.Options)) (*apigateway.GetUsagePlanKeysOutput, error)
|
||||
}
|
||||
|
||||
type CachedGetMethodOutput struct {
|
||||
ApiKeyRequired *bool
|
||||
AuthorizationScopes []string
|
||||
AuthorizationType *string
|
||||
AuthorizerId *string
|
||||
HttpMethod *string
|
||||
MethodIntegration *apiGatewayTypes.Integration
|
||||
MethodResponses map[string]apiGatewayTypes.MethodResponse
|
||||
OperationName *string
|
||||
RequestModels map[string]string
|
||||
RequestParameters map[string]bool
|
||||
RequestValidatorId *string
|
||||
ResourceId *string
|
||||
}
|
||||
|
||||
type CachedGetStagesOutput struct {
|
||||
Item []apiGatewayTypes.Stage
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]apiGatewayTypes.RestApi{})
|
||||
gob.Register(CachedGetStagesOutput{})
|
||||
gob.Register(apigateway.GetStagesOutput{})
|
||||
gob.Register([]apiGatewayTypes.Resource{})
|
||||
gob.Register([]apiGatewayTypes.DomainName{})
|
||||
gob.Register([]apiGatewayTypes.BasePathMapping{})
|
||||
gob.Register(CachedGetMethodOutput{})
|
||||
gob.Register(apigateway.GetMethodOutput{})
|
||||
gob.Register([]apiGatewayTypes.UsagePlan{})
|
||||
gob.Register([]apiGatewayTypes.UsagePlanKey{})
|
||||
}
|
||||
@@ -86,18 +66,16 @@ func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID str
|
||||
}
|
||||
PaginationControl = GetRestApis.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, restAPIs, cache.DefaultExpiration)
|
||||
|
||||
return restAPIs, nil
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetStages function that accepts a client, account id, region, and rest api id. Make sure it handles caching, the region option and pagination
|
||||
func CachedApiGatewayGetStages(client APIGatewayClientInterface, accountID string, region string, restAPIID string) (*apigateway.GetStagesOutput, error) {
|
||||
func CachedApiGatewayGetStages(client APIGatewayClientInterface, accountID string, region string, restAPIID string) (apigateway.GetStagesOutput, error) {
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetStages-%s-%s", accountID, region, restAPIID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
// Convert cached data back to GetStagesOutput before returning
|
||||
cachedOutput := cached.(*CachedGetStagesOutput) // Ensure this type assertion matches your caching logic
|
||||
return fromCachedGetStagesOutput(cachedOutput), nil
|
||||
return cached.(apigateway.GetStagesOutput), nil
|
||||
}
|
||||
|
||||
GetStages, err := client.GetStages(
|
||||
@@ -111,28 +89,10 @@ func CachedApiGatewayGetStages(client APIGatewayClientInterface, accountID strin
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return &apigateway.GetStagesOutput{}, err
|
||||
return apigateway.GetStagesOutput{}, err
|
||||
}
|
||||
|
||||
// Convert GetStagesOutput to CachedGetStagesOutput before caching
|
||||
cachedVersion := toCachedGetStagesOutput(GetStages)
|
||||
internal.Cache.Set(cacheKey, cachedVersion, cache.DefaultExpiration)
|
||||
return GetStages, err
|
||||
}
|
||||
|
||||
// Convert from AWS SDK type to custom type for caching
|
||||
func toCachedGetStagesOutput(gso *apigateway.GetStagesOutput) *CachedGetStagesOutput {
|
||||
return &CachedGetStagesOutput{
|
||||
Item: gso.Item,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to AWS SDK type after fetching from cache
|
||||
func fromCachedGetStagesOutput(cgso *CachedGetStagesOutput) *apigateway.GetStagesOutput {
|
||||
return &apigateway.GetStagesOutput{
|
||||
Item: cgso.Item,
|
||||
// Initialize ResultMetadata or leave it as zero value if it's not required for your use case
|
||||
}
|
||||
return *GetStages, err
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetResources function that accepts a client, account id, region, and rest api id. Make sure it handles caching, the region option and pagination
|
||||
@@ -169,7 +129,7 @@ func CachedApiGatewayGetResources(client APIGatewayClientInterface, accountID st
|
||||
}
|
||||
PaginationControl = GetResources.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, resources, cache.DefaultExpiration)
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
@@ -206,7 +166,7 @@ func CachedApiGatewayGetDomainNames(client APIGatewayClientInterface, accountID
|
||||
}
|
||||
PaginationControl = GetDomainNames.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, domainNames, cache.DefaultExpiration)
|
||||
|
||||
return domainNames, nil
|
||||
}
|
||||
|
||||
@@ -244,19 +204,17 @@ func CachedApiGatewayGetBasePathMappings(client APIGatewayClientInterface, accou
|
||||
}
|
||||
PaginationControl = GetBasePathMappings.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, basePathMappings, cache.DefaultExpiration)
|
||||
|
||||
return basePathMappings, nil
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetMethod function that accepts a client, account id, region, rest api id, and resource id. Make sure it handles caching, the region option and pagination if needed
|
||||
func CachedApiGatewayGetMethod(client APIGatewayClientInterface, accountID string, region string, restAPIID string, resourceID string, method string) (*apigateway.GetMethodOutput, error) {
|
||||
func CachedApiGatewayGetMethod(client APIGatewayClientInterface, accountID string, region string, restAPIID string, resourceID string, method string) (apigateway.GetMethodOutput, error) {
|
||||
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetMethod-%s-%s-%s-%s", accountID, region, restAPIID, resourceID, method)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
// Convert cached data back to GetMethodOutput before returning
|
||||
cachedOutput := cached.(*CachedGetMethodOutput) // Ensure this type assertion matches your caching logic
|
||||
return fromCachedGetMethodOutput(cachedOutput), nil
|
||||
return cached.(apigateway.GetMethodOutput), nil
|
||||
}
|
||||
|
||||
GetMethod, err := client.GetMethod(
|
||||
@@ -272,51 +230,13 @@ func CachedApiGatewayGetMethod(client APIGatewayClientInterface, accountID strin
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return &apigateway.GetMethodOutput{}, err
|
||||
return apigateway.GetMethodOutput{}, err
|
||||
}
|
||||
|
||||
// Convert GetMethodOutput to CachedGetMethodOutput before caching
|
||||
cachedVersion := toCachedGetMethodOutput(GetMethod)
|
||||
internal.Cache.Set(cacheKey, cachedVersion, cache.DefaultExpiration)
|
||||
return GetMethod, nil
|
||||
return *GetMethod, nil
|
||||
|
||||
}
|
||||
|
||||
// Convert from AWS SDK type to custom type for caching
|
||||
func toCachedGetMethodOutput(gmo *apigateway.GetMethodOutput) *CachedGetMethodOutput {
|
||||
return &CachedGetMethodOutput{
|
||||
ApiKeyRequired: gmo.ApiKeyRequired,
|
||||
AuthorizationScopes: gmo.AuthorizationScopes,
|
||||
AuthorizationType: gmo.AuthorizationType,
|
||||
AuthorizerId: gmo.AuthorizerId,
|
||||
HttpMethod: gmo.HttpMethod,
|
||||
MethodIntegration: gmo.MethodIntegration,
|
||||
MethodResponses: gmo.MethodResponses,
|
||||
OperationName: gmo.OperationName,
|
||||
RequestModels: gmo.RequestModels,
|
||||
RequestParameters: gmo.RequestParameters,
|
||||
RequestValidatorId: gmo.RequestValidatorId,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to AWS SDK type after fetching from cache
|
||||
func fromCachedGetMethodOutput(cgmo *CachedGetMethodOutput) *apigateway.GetMethodOutput {
|
||||
return &apigateway.GetMethodOutput{
|
||||
ApiKeyRequired: cgmo.ApiKeyRequired,
|
||||
AuthorizationScopes: cgmo.AuthorizationScopes,
|
||||
AuthorizationType: cgmo.AuthorizationType,
|
||||
AuthorizerId: cgmo.AuthorizerId,
|
||||
HttpMethod: cgmo.HttpMethod,
|
||||
MethodIntegration: cgmo.MethodIntegration,
|
||||
MethodResponses: cgmo.MethodResponses,
|
||||
OperationName: cgmo.OperationName,
|
||||
RequestModels: cgmo.RequestModels,
|
||||
RequestParameters: cgmo.RequestParameters,
|
||||
RequestValidatorId: cgmo.RequestValidatorId,
|
||||
// ResultMetadata: This will be missing or zero value; handle accordingly
|
||||
}
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetUsagePlans function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
|
||||
func CachedApiGatewayGetUsagePlans(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.UsagePlan, error) {
|
||||
var PaginationControl *string
|
||||
@@ -351,7 +271,6 @@ func CachedApiGatewayGetUsagePlans(client APIGatewayClientInterface, accountID s
|
||||
PaginationControl = GetUsagePlans.Position
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, usagePlans, cache.DefaultExpiration)
|
||||
return usagePlans, nil
|
||||
}
|
||||
|
||||
@@ -390,6 +309,5 @@ func CachedApiGatewayGetUsagePlanKeys(client APIGatewayClientInterface, accountI
|
||||
PaginationControl = GetUsagePlanKeys.Position
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, usagePlanKeys, cache.DefaultExpiration)
|
||||
return usagePlanKeys, nil
|
||||
}
|
||||
@@ -21,14 +21,13 @@ type APIGatewayv2ClientInterface interface {
|
||||
}
|
||||
|
||||
type domainName apiGatwayV2Types.DomainName
|
||||
type stage apiGatwayV2Types.Stage
|
||||
|
||||
func init() {
|
||||
gob.Register([]apiGatwayV2Types.Api{})
|
||||
//need to do this to avoid conflicts with the Instance type in the ec2 package
|
||||
gob.Register([]domainName{})
|
||||
gob.Register([]apiGatwayV2Types.ApiMapping{})
|
||||
gob.Register([]stage{})
|
||||
gob.Register([]apiGatwayV2Types.Stage{})
|
||||
gob.Register([]apiGatwayV2Types.Route{})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ type DocDBClientInterface interface {
|
||||
|
||||
func init() {
|
||||
gob.Register([]docdbTypes.GlobalCluster{})
|
||||
//gob.Register([]docdbTypes.DBCluster{})
|
||||
gob.Register([]docdbTypes.DBCluster{})
|
||||
//gob.Register([]docdbTypes.DBInstance{})
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import (
|
||||
type MockedAWSDynamoDBClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSDynamoDBClient) ListTables() ([]string, error) {
|
||||
return []string{"table1", "table2"}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, options ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) {
|
||||
return &dynamodb.DescribeTableOutput{
|
||||
Table: &dynamodbTypes.TableDescription{
|
||||
@@ -17,9 +21,3 @@ func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dyna
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSDynamoDBClient) ListTables(ctx context.Context, input *dynamodb.ListTablesInput, options ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) {
|
||||
return &dynamodb.ListTablesOutput{
|
||||
TableNames: []string{"table1", "table2"},
|
||||
}, nil
|
||||
}
|
||||
@@ -14,13 +14,10 @@ import (
|
||||
|
||||
type RDSClientInterface interface {
|
||||
DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error)
|
||||
DescribeDBClusters(context.Context, *rds.DescribeDBClustersInput, ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]rdsTypes.DBInstance{})
|
||||
gob.Register([]rdsTypes.DBCluster{})
|
||||
|
||||
}
|
||||
|
||||
func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBInstance, error) {
|
||||
@@ -58,39 +55,3 @@ func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, r
|
||||
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func CachedRDSDescribeDBClusters(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBCluster, error) {
|
||||
var PaginationControl *string
|
||||
var clusters []rdsTypes.DBCluster
|
||||
cacheKey := fmt.Sprintf("%s-rds-DescribeDBClusters-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]rdsTypes.DBCluster), nil
|
||||
}
|
||||
for {
|
||||
DescribeDBClusters, err := client.DescribeDBClusters(
|
||||
context.TODO(),
|
||||
&rds.DescribeDBClustersInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *rds.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return clusters, err
|
||||
}
|
||||
|
||||
clusters = append(clusters, DescribeDBClusters.DBClusters...)
|
||||
|
||||
//pagination
|
||||
if DescribeDBClusters.Marker == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeDBClusters.Marker
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
|
||||
return clusters, nil
|
||||
}
|
||||
+2
-52
@@ -9,10 +9,10 @@ import (
|
||||
rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
|
||||
)
|
||||
|
||||
type MockedRDSClient struct {
|
||||
type MOckedRDSClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.DescribeDBInstancesInput, options ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) {
|
||||
func (m *MOckedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.DescribeDBInstancesInput, options ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) {
|
||||
return &rds.DescribeDBInstancesOutput{
|
||||
DBInstances: []rdsTypes.DBInstance{
|
||||
{
|
||||
@@ -32,53 +32,3 @@ func (m *MockedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.De
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedRDSClient) DescribeDBClusters(ctx context.Context, input *rds.DescribeDBClustersInput, options ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error) {
|
||||
return &rds.DescribeDBClustersOutput{
|
||||
DBClusters: []rdsTypes.DBCluster{
|
||||
{
|
||||
DBClusterIdentifier: aws.String("db1"),
|
||||
Engine: aws.String("aurora-postgresql"),
|
||||
EngineVersion: aws.String("13.3"),
|
||||
Endpoint: aws.String("db1.cluster-123456789012.us-west-2.rds.amazonaws.com"),
|
||||
ClusterCreateTime: aws.Time(time.Now()),
|
||||
MasterUsername: aws.String("postgres"),
|
||||
Port: aws.Int32(5432),
|
||||
AssociatedRoles: nil,
|
||||
},
|
||||
{
|
||||
DBClusterIdentifier: aws.String("db2"),
|
||||
Engine: aws.String("aurora-postgresql"),
|
||||
EngineVersion: aws.String("13.3"),
|
||||
Endpoint: aws.String("db2.cluster-123456789012.us-west-2.rds.amazonaws.com"),
|
||||
ClusterCreateTime: aws.Time(time.Now()),
|
||||
MasterUsername: aws.String("postgres"),
|
||||
Port: aws.Int32(5432),
|
||||
AssociatedRoles: nil,
|
||||
},
|
||||
{
|
||||
DBClusterIdentifier: aws.String("db3"),
|
||||
Engine: aws.String("neptune"),
|
||||
EngineVersion: aws.String("1.2.1.0"),
|
||||
Endpoint: aws.String("db3.cluster-123456789012.us-west-2.neptune.amazonaws.com"),
|
||||
MasterUsername: aws.String("neptune"),
|
||||
Port: aws.Int32(8182),
|
||||
AssociatedRoles: []rdsTypes.DBClusterRole{
|
||||
{
|
||||
RoleArn: aws.String("arn:aws:iam::123456789012:role/NeptuneRole"),
|
||||
Status: aws.String("active"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DBClusterIdentifier: aws.String("db4"),
|
||||
Engine: aws.String("docsdb"),
|
||||
EngineVersion: aws.String("4.0.0"),
|
||||
Endpoint: aws.String("db4.cluster-123456789012.us-west-2.docdb.amazonaws.com"),
|
||||
MasterUsername: aws.String("docsdb"),
|
||||
Port: aws.Int32(27017),
|
||||
AssociatedRoles: nil,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+78
-74
@@ -7,107 +7,101 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzInventoryModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
|
||||
func (m *AzInventoryModule) AzInventoryCommand() error {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: "inventory",
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
if AzTenantID != "" && AzSubscriptionID == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
if AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
if m.AzClient.AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: "inventory",
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
m.Log.Infof(nil, "Gathering inventory for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
|
||||
|
||||
//populate the table data
|
||||
header, body, err := m.getInventoryInfoPerTenant(ptr.ToString(AzTenant.TenantID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(o.CallingModule)})
|
||||
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
} else {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runInventoryCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"[%s][%s] Gathering inventory for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
|
||||
//populate the table data
|
||||
header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(o.CallingModule)})
|
||||
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
} else {
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
|
||||
runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
}
|
||||
} else {
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runInventoryCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
} else if AzTenantID == "" && AzSubscriptionID != "" {
|
||||
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
runInventoryCommandForSingleSubscription(AzSubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
}
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
var err error
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
var AzSubscriptionInfo SubsriptionInfo
|
||||
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
tenantInfo := populateTenant(tenantID)
|
||||
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
o.PrefixIdentifier = *AzSubscription.DisplayName
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
|
||||
|
||||
fmt.Printf(
|
||||
"[%s][%s] Gathering inventory for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
m.Log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
|
||||
// populate the table data
|
||||
header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
|
||||
header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,8 +120,8 @@ func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDir
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, [][]string, error) {
|
||||
resources, err := getResources(tenantID, subscriptionID)
|
||||
func (m *AzInventoryModule) getInventoryInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, error) {
|
||||
resources, err := m.getResources(*AzSubscription.TenantID, *AzSubscription.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -139,14 +133,24 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string,
|
||||
for _, resource := range resources {
|
||||
resourceType := ptr.ToString(resource.Type)
|
||||
resourceLocation := ptr.ToString(resource.Location)
|
||||
|
||||
_, ok := inventory[resourceType]
|
||||
if len(m.AzClient.AzRGs) > 0 {
|
||||
for _, AzRG := range m.AzClient.AzRGs {
|
||||
metaResource, _ := azure.ParseResourceID(*resource.ID)
|
||||
if metaResource.ResourceGroup == *AzRG.Name {
|
||||
goto ADD_RESOURCE
|
||||
}
|
||||
}
|
||||
goto SKIP_RESOURCE
|
||||
}
|
||||
ADD_RESOURCE:
|
||||
if !ok {
|
||||
inventory[resourceType] = make(map[string]int)
|
||||
}
|
||||
inventory[resourceType][resourceLocation]++
|
||||
resourceTypes[resourceType] = true
|
||||
resourceLocations[resourceLocation] = true
|
||||
SKIP_RESOURCE:
|
||||
}
|
||||
|
||||
header := []string{"Resource Type"}
|
||||
@@ -173,14 +177,14 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string,
|
||||
return header, body, nil
|
||||
}
|
||||
|
||||
func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
|
||||
func (m *AzInventoryModule) getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
|
||||
|
||||
inventory := make(map[string]map[string]int)
|
||||
resourceTypes := make(map[string]bool)
|
||||
resourceLocations := make(map[string]bool)
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(tenantID) {
|
||||
resources, err := getResources(tenantID, ptr.ToString(s.SubscriptionID))
|
||||
resources, err := m.getResources(tenantID, ptr.ToString(s.SubscriptionID))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -223,7 +227,7 @@ func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
|
||||
return header, body, nil
|
||||
}
|
||||
|
||||
func getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) {
|
||||
func (m *AzInventoryModule) getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) {
|
||||
client := internal.GetARMresourcesClient(tenantID, subscriptionID)
|
||||
|
||||
var resources []*armresources.GenericResourceExpanded
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
package azure
|
||||
// https://learn.microsoft.com/en-us/azure/virtual-network/network-security-group-how-it-works
|
||||
// https://learn.microsoft.com/en-us/azure/virtual-network/application-security-groups
|
||||
// https://learn.microsoft.com/en-us/azure/network-watcher/diagnose-vm-network-traffic-filtering-problem?toc=%2Fazure%2Fvirtual-network%2Ftoc.json
|
||||
import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"strings"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"context"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type AzNetScanModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
|
||||
func (m *AzNetScanModule) AzNetScanCommand(sourceIP string) error {
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: "netscan",
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
if len(m.AzClient.AzResources) == 0 && sourceIP == "" {
|
||||
return fmt.Errorf("No resource ID or source IP address supplied")
|
||||
}
|
||||
if sourceIP != "" {
|
||||
m.Log.Warnf(nil, "Searching resources attached to IP %s, this might take a while", sourceIP)
|
||||
} else {
|
||||
for _, AzResource := range m.AzClient.AzResources {
|
||||
if AzResource.ResourceType == "virtualMachines" {
|
||||
m.Log.Infof(nil, "Starting network analysis for Virtual Machine %s", AzResource.ResourceName)
|
||||
m.processVM(AzResource.SubscriptionID, AzResource.ResourceGroup, AzResource.ResourceName)
|
||||
} else {
|
||||
m.Log.Errorf(nil, "Resource type %s / %s not implemented", AzResource.Provider, AzResource.ResourceType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName string, VMName string) {
|
||||
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
|
||||
vm, err := computeClient.Get(context.TODO(), resourceGroupName, VMName, "")
|
||||
targetSubnets := []string{}
|
||||
targetSubnetsAlt := make(map[string][]string)
|
||||
vmSubscriptionIDs := make(map[string]bool)
|
||||
vmSubscriptionIDs[subscriptionID] = true
|
||||
if err != nil {
|
||||
m.Log.Errorf([]string{VMName}, "Could not fetch virtual machine details")
|
||||
return
|
||||
} else {
|
||||
if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil {
|
||||
for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces {
|
||||
nic, err := getNICdetails(subscriptionID, resourceGroupName, nicReference, "networkSecurityGroup")
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{VMName}, "Could not get details of network interface %s", *nicReference.ID)
|
||||
continue
|
||||
}
|
||||
if nic.InterfacePropertiesFormat.IPConfigurations == nil {
|
||||
m.Log.Warn([]string{VMName, *nic.Name}, "Interface has no IP address configured")
|
||||
continue
|
||||
}
|
||||
if nic.InterfacePropertiesFormat.NetworkSecurityGroup != nil {
|
||||
m.Log.Infof([]string{VMName, *nic.Name}, "Linked to NSG %s", *nic.InterfacePropertiesFormat.NetworkSecurityGroup.Name)
|
||||
}
|
||||
//var subnet *network.Subnet
|
||||
for _, ip := range *nic.InterfacePropertiesFormat.IPConfigurations {
|
||||
if ip.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress == nil {
|
||||
m.Log.Warn([]string{VMName, *nic.Name}, "Empty IP configuration")
|
||||
continue
|
||||
}
|
||||
ipConfig := ip.InterfaceIPConfigurationPropertiesFormat
|
||||
m.Log.Infof([]string{VMName, *nic.Name}, "Interface assigned with address %s", *ipConfig.PrivateIPAddress)
|
||||
subnet := *ipConfig.Subnet
|
||||
subnetRGName := strings.Split(*subnet.ID, "/")[4]
|
||||
VNETName := strings.Split(*subnet.ID, "/")[8]
|
||||
subnetName := strings.Split(*subnet.ID, "/")[10]
|
||||
subnet, err := getSubnetDetails(subscriptionID, subnetRGName, VNETName, subnetName, "networkSecurityGroup")
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Unable to get subnet %s details", subnetName)
|
||||
continue
|
||||
}
|
||||
if subnet.SubnetPropertiesFormat.NetworkSecurityGroup != nil {
|
||||
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Linked to NSG %s through subnet %s", *subnet.SubnetPropertiesFormat.NetworkSecurityGroup.Name, *subnet.Name)
|
||||
}
|
||||
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Subnet %s, in Virtual Network %s", subnetName, VNETName)
|
||||
vnet, err := getVNET(subscriptionID, subnetRGName, VNETName, "")
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Unable to get Virtual Network %s details", VNETName)
|
||||
continue
|
||||
}
|
||||
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Virtual Network %s has %s subnets as potential targets", VNETName, len(*vnet.VirtualNetworkPropertiesFormat.Subnets))
|
||||
for _, subnet := range *vnet.VirtualNetworkPropertiesFormat.Subnets {
|
||||
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Potential target : %s", *subnet.SubnetPropertiesFormat.AddressPrefix)
|
||||
targetSubnets = append(targetSubnets, *subnet.SubnetPropertiesFormat.AddressPrefix)
|
||||
targetSubnetsAlt[*subnet.SubnetPropertiesFormat.AddressPrefix] = []string{}
|
||||
}
|
||||
for _, peering := range *vnet.VirtualNetworkPropertiesFormat.VirtualNetworkPeerings {
|
||||
remoteVnetID := *peering.VirtualNetworkPeeringPropertiesFormat.RemoteVirtualNetwork.ID
|
||||
remoteVnetResource, _ := azure.ParseResourceID(remoteVnetID)
|
||||
vmSubscriptionIDs[remoteVnetResource.SubscriptionID] = true
|
||||
if !*peering.VirtualNetworkPeeringPropertiesFormat.AllowForwardedTraffic {
|
||||
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is disabled", remoteVnetResource.ResourceName)
|
||||
} else {
|
||||
m.Log.Successf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is active : %s",
|
||||
remoteVnetResource.ResourceName, stringAndArrayToString(nil, peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes, " "))
|
||||
for _, addressPrefix := range *peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes {
|
||||
targetSubnets = append(targetSubnets, addressPrefix)
|
||||
targetSubnetsAlt[addressPrefix] = []string{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
m.Log.Info([]string{VMName}, "Narrowing down potential target subnets with running compute instances...")
|
||||
var vms []compute.VirtualMachine
|
||||
for subscriptionID, _ := range vmSubscriptionIDs {
|
||||
subscriptionVMs, err := getComputeVMsPerSubscription(subscriptionID)
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{VMName}, "Unable to enumerate VMs in subscription %s", subscriptionID)
|
||||
continue
|
||||
}
|
||||
vms = append(vms, subscriptionVMs...)
|
||||
}
|
||||
for i, vm := range vms {
|
||||
fmt.Printf("Processing machine %d%%\r", 100*i/len(vms))
|
||||
vmResource, _ := azure.ParseResourceID(*vm.ID)
|
||||
vmDetails, err := getComputeVmInstanceView(subscriptionID, vmResource.ResourceGroup, ptr.ToString(vm.Name))
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{VMName}, "Unable to fetch VM %s details", ptr.ToString(vm.Name))
|
||||
continue
|
||||
}
|
||||
if vmDetails.VirtualMachineProperties != nil && vmDetails.VirtualMachineProperties.InstanceView != nil {
|
||||
for _, status := range *vmDetails.VirtualMachineProperties.InstanceView.Statuses {
|
||||
if *status.Code == "PowerState/running" {
|
||||
privateIPs, _ := getIPs(subscriptionID, vmResource.ResourceGroup, vm)
|
||||
for targetSubnet, _ := range targetSubnetsAlt {
|
||||
_, ipnet, _ := net.ParseCIDR(targetSubnet)
|
||||
for _, privateIP := range privateIPs {
|
||||
ip := net.ParseIP(privateIP)
|
||||
if ipnet.Contains(ip) {
|
||||
targetSubnetsAlt[targetSubnet] = append(targetSubnetsAlt[targetSubnet], privateIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for targetSubnet, targets := range targetSubnetsAlt {
|
||||
m.Log.Infof([]string{VMName}, "%s subnet has %s addresses", targetSubnet, len(targets))
|
||||
}
|
||||
m.writeScanFile(VMName, targetSubnetsAlt)
|
||||
} else {
|
||||
m.Log.Errorf(nil, "Virtual Machine %s has no network interface", VMName)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AzNetScanModule) filterVM(vm compute.VirtualMachine) {
|
||||
|
||||
}
|
||||
func getComputeVMsPerSubscription(subscriptionID string) ([]compute.VirtualMachine, error) {
|
||||
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
|
||||
var vms []compute.VirtualMachine
|
||||
|
||||
for _, rg := range GetResourceGroups(subscriptionID) {
|
||||
for page, err := computeClient.List(context.TODO(), ptr.ToString(rg.Name), ""); page.NotDone(); page.Next() {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not enumerate resource group %s. %s", rg, err)
|
||||
} else {
|
||||
|
||||
vms = append(vms, page.Values()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
func (m *AzNetScanModule) writeScanFile(vm string, targetSubnetsAlt map[string][]string) error {
|
||||
lootDirectory := filepath.Join(m.AzClient.AzOutputDirectory, "loot")
|
||||
lootFilePath := filepath.Join(lootDirectory, fmt.Sprintf("scan-from-%s.txt", vm))
|
||||
err := os.MkdirAll(lootDirectory, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Create(lootFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
for targetSubnet, targets := range targetSubnetsAlt {
|
||||
cleanSubnet := strings.Replace(targetSubnet, "/", "_", -1)
|
||||
_, err := file.WriteString(fmt.Sprintf("nmap -p- --max-retries 1 -T4 -Pn -sV -sC -oA %s -iL %s.lst\n", cleanSubnet, cleanSubnet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsFile, err := os.Create(filepath.Join(lootDirectory, fmt.Sprintf("%s.lst", cleanSubnet)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetsFile.Close()
|
||||
for _, target := range targets {
|
||||
targetsFile.WriteString(fmt.Sprintf("%s\n", target))
|
||||
}
|
||||
}
|
||||
m.Log.Successf(nil, "Scan file written to %s", lootFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSubnetDetails(subscriptionID, resourceGroup, VNETName, subnetName, expand string) (network.Subnet, error) {
|
||||
client := internal.GetSubnetsClient(subscriptionID)
|
||||
subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, subnetName, expand)
|
||||
if err != nil {
|
||||
return network.Subnet{}, err
|
||||
}
|
||||
return subnet, nil
|
||||
}
|
||||
|
||||
func getVNET(subscriptionID, resourceGroup, VNETName, expand string) (network.VirtualNetwork, error) {
|
||||
client := internal.GetVNETsClient(subscriptionID)
|
||||
subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, expand)
|
||||
if err != nil {
|
||||
return network.VirtualNetwork{}, err
|
||||
}
|
||||
return subnet, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
)
|
||||
|
||||
type AzNSGModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
getNSGData func(string, *subscriptions.Subscription) error
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
|
||||
func (m *AzNSGModule) AzNSGCommand(data string) error {
|
||||
|
||||
if data == "links" {
|
||||
m.AzNSGLinksCommand()
|
||||
} else if data == "rules" {
|
||||
m.AzNSGRulesCommand()
|
||||
} else {
|
||||
return fmt.Errorf("Invalid data selection for NSG")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) getNSGInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
var err error
|
||||
|
||||
err = m.getNSGData(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) getNSG(subscriptionID string) (*[]network.SecurityGroup, error) {
|
||||
nsgClient := internal.GetNSGClient(subscriptionID)
|
||||
var networkSecurityGroups []network.SecurityGroup
|
||||
for page, err := nsgClient.ListAll(context.TODO()); page.NotDone(); page.Next() {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get network security groups for subscription")
|
||||
}
|
||||
networkSecurityGroups = append(networkSecurityGroups, page.Values()...)
|
||||
}
|
||||
return &networkSecurityGroups, nil
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
func (m *AzNSGModule) AzNSGLinksCommand() error {
|
||||
|
||||
m.getNSGData = m.getNSGLinksData
|
||||
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
m.Log.Infof([]string{"links"}, "Enumerating Network Security Group links for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runNSGLinksCommandForSingleSubcription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
var err error
|
||||
|
||||
m.Log.Infof([]string{"links"}, "Enumerating Network Security Groups links for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
tableHeader := []string{"Network Security Group", "Link Type", "Linked Name", "Link Target"}
|
||||
var tableBody [][]string
|
||||
networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID)
|
||||
nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID)
|
||||
//subnetsClient := internal.GetSubnetsClient(subscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, networkSecurityGroup := range *networkSecurityGroups {
|
||||
tableBody = nil
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
var resource azure.Resource
|
||||
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces")
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{"links"}, "Failed to enumerate links for NSG %s", *networkSecurityGroup.Name)
|
||||
continue
|
||||
} else {
|
||||
m.Log.Infof([]string{"links"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name)
|
||||
}
|
||||
if networkSecurityGroup.Subnets != nil {
|
||||
for _, subnet := range *networkSecurityGroup.Subnets {
|
||||
var addressPrefixes []string
|
||||
if subnet.AddressPrefixes != nil {
|
||||
for _, prefix := range *subnet.AddressPrefixes {
|
||||
addressPrefixes = append(addressPrefixes, prefix)
|
||||
}
|
||||
}
|
||||
if subnet.AddressPrefix != nil {
|
||||
addressPrefixes = append(addressPrefixes, *subnet.AddressPrefix)
|
||||
}
|
||||
tableBody = append(tableBody,
|
||||
[]string{
|
||||
*networkSecurityGroup.Name,
|
||||
"Subnet",
|
||||
ptr.ToString(subnet.Name),
|
||||
strings.Join(addressPrefixes[:], "\n"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if networkSecurityGroup.NetworkInterfaces != nil {
|
||||
for _, networkInterface := range *networkSecurityGroup.NetworkInterfaces {
|
||||
tableBody = append(tableBody,
|
||||
[]string{
|
||||
*networkSecurityGroup.Name,
|
||||
"NIC",
|
||||
ptr.ToString(networkInterface.Name),
|
||||
strings.Join(internal.GetIPAddressesFromInterface(&networkInterface)[:], "\n"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// set up table vars
|
||||
o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: tableHeader,
|
||||
Body: tableBody,
|
||||
Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_LINKS_MODULE_NAME, *networkSecurityGroup.Name)})
|
||||
|
||||
if tableBody != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func stringAndArrayToString(value *string, values *[]string, separator string) string {
|
||||
var final string
|
||||
if value != nil {
|
||||
final = *value
|
||||
} else {
|
||||
final = ""
|
||||
}
|
||||
if len(*values) > 0 {
|
||||
if len(final) > 0 {
|
||||
final = fmt.Sprintf("%s%s%s", final, separator, strings.Join((*values)[:], separator))
|
||||
} else {
|
||||
final = strings.Join((*values)[:], separator)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
|
||||
func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string {
|
||||
var final string
|
||||
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes, "\n")
|
||||
if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil {
|
||||
for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups {
|
||||
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
|
||||
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string {
|
||||
var final string
|
||||
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, "\n")
|
||||
if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil {
|
||||
for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups {
|
||||
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
|
||||
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"context"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
)
|
||||
|
||||
|
||||
func (m *AzNSGModule) AzNSGRulesCommand() error {
|
||||
|
||||
m.getNSGData = m.getNSGRulesData
|
||||
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
m.Log.Infof([]string{"rules"}, "Enumerating Network Security Group rules for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
// cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runNSGRulesCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runNSGRulesCommandForSingleSubcription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) runNSGRulesCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
var err error
|
||||
|
||||
m.Log.Infof([]string{"rules"}, "Enumerating Network Security Groups rules for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
tableHeader := []string{"Name", "Rule Type", "Protocol", "Source", "Destination", "Destination port", "Action", "Description"}
|
||||
var tableBody [][]string
|
||||
networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID)
|
||||
nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID)
|
||||
//subnetsClient := internal.GetSubnetsClient(subscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, networkSecurityGroup := range *networkSecurityGroups {
|
||||
tableBody = nil
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_NSG_RULES_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
var resource azure.Resource
|
||||
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "")
|
||||
if err != nil {
|
||||
m.Log.Warnf([]string{"rules"}, "Failed to enumerate rules for NSG %s", *networkSecurityGroup.Name)
|
||||
continue
|
||||
} else {
|
||||
m.Log.Infof([]string{"rules"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name)
|
||||
}
|
||||
for _, securityRule := range *networkSecurityGroup.SecurityRules {
|
||||
tableBody = append(tableBody,
|
||||
[]string{
|
||||
*securityRule.Name,
|
||||
fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Direction),
|
||||
fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Protocol),
|
||||
getSourceFromSecurityGroupRule(&securityRule),
|
||||
getDestinationFromSecurityGroupRule(&securityRule),
|
||||
stringAndArrayToString(securityRule.SecurityRulePropertiesFormat.DestinationPortRange,
|
||||
securityRule.SecurityRulePropertiesFormat.DestinationPortRanges, "\n"),
|
||||
m.colorRule(fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access)),
|
||||
ptr.ToString(securityRule.Description),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// set up table vars
|
||||
o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: tableHeader,
|
||||
Body: tableBody,
|
||||
Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_RULES_MODULE_NAME, *networkSecurityGroup.Name)})
|
||||
|
||||
if tableBody != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AzNSGModule) colorRule(action string) string {
|
||||
if action == "Allow" {
|
||||
return m.Log.Green(action)
|
||||
} else if action == "Deny" {
|
||||
return m.Log.Red(action)
|
||||
} else {
|
||||
return action
|
||||
}
|
||||
}
|
||||
+74
-69
@@ -16,82 +16,80 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzRBACModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
func (m *AzRBACModule) AzRBACCommand() error {
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_RBAC_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
// initiate command specific client
|
||||
var c CloudFoxRBACclient
|
||||
c.RBACModule = m
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
|
||||
var AzSubscriptionInfo SubsriptionInfo
|
||||
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
var err error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrefixIdentifier = *AzTenant.DefaultDomain
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level")
|
||||
|
||||
m.Log.Infof(nil, "Enumerating RBAC permissions for tenant %s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)
|
||||
header, body, err = m.getRBACperTenant(ptr.ToString(AzTenant.TenantID), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
|
||||
var err error
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
header, body, err = getRBACperTenant(ptr.ToString(tenantInfo.ID), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
|
||||
} else if AzTenantID == "" && AzSubscription != "" {
|
||||
// cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
tenantInfo := populateTenant(tenantID)
|
||||
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID, c)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
// cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
for _, AzSubscription := range m.AzClient.AzSubscriptions {
|
||||
tenantInfo := populateTenant(*AzSubscription.TenantID)
|
||||
o.PrefixIdentifier = ptr.ToString(AzSubscription.DisplayName)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName)
|
||||
|
||||
m.Log.Infof(nil, "Enumerating RBAC permissions for subscription %s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID)
|
||||
header, body = m.getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
//internal.OutputSelector(AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix)
|
||||
//internal.OutputSelector(m.AzClient.AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix)
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) {
|
||||
func (m *AzRBACModule) getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) {
|
||||
var selectedSubs, resultsHeader []string
|
||||
var resultsBody, b [][]string
|
||||
for _, s := range GetSubscriptions() {
|
||||
@@ -110,7 +108,7 @@ func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]st
|
||||
return resultsHeader, resultsBody, nil
|
||||
}
|
||||
|
||||
func getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) {
|
||||
func (m *AzRBACModule) getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) {
|
||||
var resultsHeader []string
|
||||
var resultsBody [][]string
|
||||
for _, s := range GetSubscriptions() {
|
||||
@@ -126,6 +124,7 @@ type CloudFoxRBACclient struct {
|
||||
roleAssignments []authorization.RoleAssignment
|
||||
roleDefinitions []authorization.RoleDefinition
|
||||
AADUsers []graphrbac.User
|
||||
RBACModule *AzRBACModule
|
||||
}
|
||||
|
||||
func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []string) error {
|
||||
@@ -134,19 +133,19 @@ func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []strin
|
||||
c.roleAssignments = nil
|
||||
c.roleDefinitions = nil
|
||||
|
||||
c.AADUsers, err = getAzureADUsers(tenantID)
|
||||
c.AADUsers, err = c.RBACModule.getAzureADUsers(tenantID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] failed to get users for tenant %s: %s", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), tenantID, err)
|
||||
}
|
||||
|
||||
for _, subID := range subscriptionIDs {
|
||||
rd, err := getRoleDefinitions(subID)
|
||||
rd, err := c.RBACModule.getRoleDefinitions(subID)
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] failed to get role definitions for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
|
||||
}
|
||||
c.roleDefinitions = append(c.roleDefinitions, rd...)
|
||||
|
||||
ra, err := getRoleAssignments(subID)
|
||||
ra, err := c.RBACModule.getRoleAssignments(subID)
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] failed to get role assignments for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
|
||||
}
|
||||
@@ -171,7 +170,7 @@ func (c *CloudFoxRBACclient) GetRelevantRBACData(tenantID, subscriptionID string
|
||||
}
|
||||
// Sort the results by userDisplayName using slice.Sort
|
||||
sortedResults := results
|
||||
sort.Slice(sortedResults, func(i, j int) bool {
|
||||
sort.Slice(sortedResults, func (i, j int) bool {
|
||||
return sortedResults[i].userDisplayName < sortedResults[j].userDisplayName
|
||||
})
|
||||
|
||||
@@ -216,9 +215,11 @@ type RoleAssignmentRelevantData struct {
|
||||
roleName string
|
||||
}
|
||||
|
||||
var getAzureADUsers = getAzureADUsersOriginal
|
||||
func (m *AzRBACModule) getAzureADUsers(tenantID string) ([]graphrbac.User, error) {
|
||||
return m.getAzureADUsersOriginal(tenantID)
|
||||
}
|
||||
|
||||
func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
|
||||
func (m *AzRBACModule) getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
|
||||
var users []graphrbac.User
|
||||
client := internal.GetAADUsersClient(tenantID)
|
||||
for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() {
|
||||
@@ -234,7 +235,7 @@ func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
|
||||
func (m *AzRBACModule) mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
|
||||
var users AzureADUsersTestFile
|
||||
|
||||
file, err := os.ReadFile(globals.AAD_USERS_TEST_FILE)
|
||||
@@ -248,10 +249,10 @@ func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
|
||||
return users.AzureADUsers, nil
|
||||
}
|
||||
|
||||
func generateAzureADUsersTestFIle(tenantID string) {
|
||||
func (m *AzRBACModule) generateAzureADUsersTestFIle(tenantID string) {
|
||||
// The READ-ONLY ObjectID attribute needs to be included manually in the test file
|
||||
// ObjectID *string `json:"objectId,omitempty"`
|
||||
users, err := getAzureADUsers(tenantID)
|
||||
users, err := m.getAzureADUsers(tenantID)
|
||||
if err != nil {
|
||||
log.Fatalf("could not enumerate users for tenant %s", tenantID)
|
||||
}
|
||||
@@ -269,9 +270,11 @@ type AzureADUsersTestFile struct {
|
||||
AzureADUsers []graphrbac.User `json:"azureADUsers"`
|
||||
}
|
||||
|
||||
var getRoleDefinitions = getRoleDefinitionsOriginal
|
||||
func (m *AzRBACModule) getRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
|
||||
return m.getRoleDefinitionsOriginal(subscriptionID)
|
||||
}
|
||||
|
||||
func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) {
|
||||
func (m *AzRBACModule) getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) {
|
||||
client := internal.GetRoleDefinitionsClient(subscriptionID)
|
||||
var roleDefinitions []authorization.RoleDefinition
|
||||
for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() {
|
||||
@@ -287,7 +290,7 @@ func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefi
|
||||
return roleDefinitions, nil
|
||||
}
|
||||
|
||||
func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
|
||||
func (m *AzRBACModule) mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
|
||||
var roleDefinitions RoleDefinitionTestFile
|
||||
file, err := os.ReadFile(globals.ROLE_DEFINITIONS_TEST_FILE)
|
||||
if err != nil {
|
||||
@@ -300,16 +303,16 @@ func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefini
|
||||
return roleDefinitions.RoleDefinitions, nil
|
||||
}
|
||||
|
||||
func generateRoleDefinitionsTestFile(subscriptionID string) {
|
||||
func (m *AzRBACModule) generateRoleDefinitionsTestFile(subscriptionID string) {
|
||||
// The READ-ONLY ID attribute needs to be included manually in the test file.
|
||||
// This attribute is the unique identifier for the role.
|
||||
// ID *string `json:"id,omitempty"`.
|
||||
|
||||
roleDefinitions, err := getRoleDefinitions(subscriptionID)
|
||||
roleDefinitions, err := m.getRoleDefinitions(subscriptionID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
roleAssignments, err := getRoleAssignments(subscriptionID)
|
||||
roleAssignments, err := m.getRoleAssignments(subscriptionID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -346,9 +349,11 @@ type RoleDefinitionTestFile struct {
|
||||
RoleDefinitions []authorization.RoleDefinition `json:"roleDefinitions"`
|
||||
}
|
||||
|
||||
var getRoleAssignments = getRoleAssignmentsOriginal
|
||||
func (m *AzRBACModule) getRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
|
||||
return m.getRoleAssignmentsOriginal(subscriptionID)
|
||||
}
|
||||
|
||||
func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) {
|
||||
func (m *AzRBACModule) getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) {
|
||||
var roleAssignments []authorization.RoleAssignment
|
||||
client := internal.GetRoleAssignmentsClient(subscriptionID)
|
||||
for page, err := client.List(context.TODO(), ""); page.NotDone(); page.Next() {
|
||||
@@ -363,7 +368,7 @@ func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssi
|
||||
return roleAssignments, nil
|
||||
}
|
||||
|
||||
func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
|
||||
func (m *AzRBACModule) mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
|
||||
var allRoleAssignments, roleAssignmentsResults []authorization.RoleAssignment
|
||||
file, err := os.ReadFile(globals.ROLE_ASSIGNMENTS_TEST_FILE)
|
||||
if err != nil {
|
||||
@@ -382,8 +387,8 @@ func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssign
|
||||
return roleAssignmentsResults, nil
|
||||
}
|
||||
|
||||
func generateRoleAssignmentsTestFile(subscriptionID string) {
|
||||
ra, err := getRoleAssignments(subscriptionID)
|
||||
func (m *AzRBACModule) generateRoleAssignmentsTestFile(subscriptionID string) {
|
||||
ra, err := m.getRoleAssignments(subscriptionID)
|
||||
if err != nil {
|
||||
log.Fatalf("could not generate role assignments for subscription %s", subscriptionID)
|
||||
}
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ func GetDefaultDomainFromTenantID(tenantID string) (string, error) {
|
||||
|
||||
func populateTenant(tenantID string) TenantInfo {
|
||||
|
||||
for _, t := range getTenants() {
|
||||
for _, t := range GetTenants() {
|
||||
if ptr.ToString(t.TenantID) == tenantID || ptr.ToString(t.DefaultDomain) == tenantID {
|
||||
var subscriptions []SubsriptionInfo
|
||||
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(t.ID)) {
|
||||
|
||||
+95
-85
@@ -9,98 +9,99 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
// Color functions
|
||||
var cyan = color.New(color.FgCyan).SprintFunc()
|
||||
|
||||
func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzStorageModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
func (m *AzStorageModule) AzStorageCommand() error {
|
||||
|
||||
var publicBlobURLs []string
|
||||
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure storage --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
if AzMergedTable {
|
||||
if m.AzClient.AzMergedTable {
|
||||
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
}
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
var err error
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
m.Log.Infof([]string{}, "Enumerating storage accounts for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
|
||||
|
||||
header, body, publicBlobURLs, err = getStorageInfoPerTenant(ptr.ToString(tenantInfo.ID))
|
||||
header, body, publicBlobURLs, err = m.getStorageInfoPerTenant(ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)})
|
||||
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
if publicBlobURLs != nil {
|
||||
err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)})
|
||||
|
||||
} else {
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
if publicBlobURLs != nil {
|
||||
err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
|
||||
runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
} else {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runStorageCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if AzTenantID == "" && AzSubscription != "" {
|
||||
//cloudfox azure storage --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
runStorageCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runStorageCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzStorageModule) runStorageCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
var err error
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,19 +110,11 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
|
||||
var body [][]string
|
||||
var publicBlobURLs []string
|
||||
|
||||
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
tenantInfo := populateTenant(tenantID)
|
||||
AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription)
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
o.PrefixIdentifier = *AzSubscription.DisplayName
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
|
||||
|
||||
fmt.Printf(
|
||||
"[%s][%s] Enumerating storage accounts for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
|
||||
color.CyanString(globals.AZ_STORAGE_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
//AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
header, body, publicBlobURLs, err = getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
|
||||
m.Log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(*AzSubscription.TenantID, *AzSubscription.SubscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,7 +129,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
|
||||
|
||||
}
|
||||
if publicBlobURLs != nil {
|
||||
err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
|
||||
err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,7 +138,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
|
||||
|
||||
}
|
||||
|
||||
func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error {
|
||||
func (m *AzStorageModule) writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error {
|
||||
lootDirectory := filepath.Join(outputDirectory, "loot")
|
||||
lootFilePath := filepath.Join(lootDirectory, "public-blob-urls.txt")
|
||||
|
||||
@@ -171,14 +164,14 @@ func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) {
|
||||
func (m *AzStorageModule) getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) {
|
||||
var err error
|
||||
var header []string
|
||||
var body, b [][]string
|
||||
var publicBlobURLs []string
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
|
||||
header, b, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
|
||||
header, b, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
} else {
|
||||
@@ -188,7 +181,7 @@ func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string,
|
||||
return header, body, publicBlobURLs, nil
|
||||
}
|
||||
|
||||
func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) {
|
||||
func (m *AzStorageModule) getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) {
|
||||
var err error
|
||||
var header []string
|
||||
var body [][]string
|
||||
@@ -196,7 +189,7 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin
|
||||
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(s.SubscriptionID) == AzSubscriptionID {
|
||||
header, body, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
|
||||
header, body, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -205,20 +198,34 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin
|
||||
return header, body, publicBlobURLs, nil
|
||||
}
|
||||
|
||||
func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) {
|
||||
func (m *AzStorageModule) getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) {
|
||||
tableHeader := []string{"Subscription Name", "Storage Account Name", "Container Name", "Access Status"}
|
||||
var tableBody [][]string
|
||||
var publicBlobURLs []string
|
||||
storageAccounts, err := getStorageAccounts(subscriptionID)
|
||||
var urls []string
|
||||
var containers map[string]string
|
||||
storageAccounts, err := m.getStorageAccounts(subscriptionID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for _, sa := range storageAccounts {
|
||||
blobClient, err := internal.GetStorageAccountBlobClient(tenantID, ptr.ToString(sa.Name))
|
||||
// if resource group were submitted, do filtering
|
||||
if len(m.AzClient.AzRGs) > 0 {
|
||||
fmt.Println(*sa.ID)
|
||||
for _, AzRG := range m.AzClient.AzRGs {
|
||||
metaResource, _ := azure.ParseResourceID(*sa.ID)
|
||||
if metaResource.ResourceGroup == *AzRG.Name {
|
||||
goto ADD_RESOURCE
|
||||
}
|
||||
}
|
||||
goto SKIP_RESOURCE
|
||||
}
|
||||
ADD_RESOURCE:
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
containers, err := getStorageAccountContainers(blobClient)
|
||||
containers, err = m.getStorageAccountContainers(blobClient)
|
||||
if err != nil {
|
||||
// rather than return an error, we'll just add a row to the table highlighting the storage account name and that we couldn't get the containers
|
||||
|
||||
@@ -240,20 +247,23 @@ func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [
|
||||
containerName,
|
||||
accessType})
|
||||
}
|
||||
urls, err := getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers)
|
||||
urls, err = m.getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers)
|
||||
if err == nil {
|
||||
continue
|
||||
//return nil, nil, nil, err
|
||||
}
|
||||
publicBlobURLs = append(publicBlobURLs, urls...)
|
||||
SKIP_RESOURCE:
|
||||
|
||||
}
|
||||
return tableHeader, tableBody, publicBlobURLs, nil
|
||||
}
|
||||
|
||||
var getStorageAccounts = getStorageAccountsOriginal
|
||||
func (m *AzStorageModule) getStorageAccounts(subscriptionID string) ([]storage.Account, error) {
|
||||
return m.getStorageAccountsOriginal(subscriptionID)
|
||||
}
|
||||
|
||||
func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) {
|
||||
func (m *AzStorageModule) getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) {
|
||||
storageClient := internal.GetStorageClient(subscriptionID)
|
||||
var storageAccounts []storage.Account
|
||||
for page, err := storageClient.List(context.TODO()); page.NotDone(); page.Next() {
|
||||
@@ -265,7 +275,7 @@ func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error
|
||||
return storageAccounts, nil
|
||||
}
|
||||
|
||||
func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) {
|
||||
func (m *AzStorageModule) mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) {
|
||||
testFile, err := os.ReadFile(globals.STORAGE_ACCOUNTS_TEST_FILE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open storage accounts test file %s", globals.STORAGE_ACCOUNTS_TEST_FILE)
|
||||
@@ -284,7 +294,7 @@ func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error)
|
||||
return storageAccountsResults, nil
|
||||
}
|
||||
|
||||
func getStorageAccountContainers(client *azblob.Client) (map[string]string, error) {
|
||||
func (m *AzStorageModule) getStorageAccountContainers(client *azblob.Client) (map[string]string, error) {
|
||||
containers := make(map[string]string)
|
||||
pager := client.NewListContainersPager(&azblob.ListContainersOptions{
|
||||
Include: azblob.ListContainersInclude{Metadata: true, Deleted: true},
|
||||
@@ -305,11 +315,11 @@ func getStorageAccountContainers(client *azblob.Client) (map[string]string, erro
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) {
|
||||
func (m *AzStorageModule) getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) {
|
||||
var publicBlobURLs []string
|
||||
for containerName, accessType := range containers {
|
||||
if accessType == "public" {
|
||||
url, err := getPublicBlobURLsForContainer(client, storageAccountName, containerName)
|
||||
url, err := m.getPublicBlobURLsForContainer(client, storageAccountName, containerName)
|
||||
if err != nil {
|
||||
//return nil, err
|
||||
continue
|
||||
@@ -320,19 +330,19 @@ func getPublicBlobURLs(client *azblob.Client, storageAccountName string, contain
|
||||
return publicBlobURLs, nil
|
||||
}
|
||||
|
||||
func getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) {
|
||||
blobNames, err := getAllBlobsForContainer(client, containerName)
|
||||
func (m *AzStorageModule) getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) {
|
||||
blobNames, err := m.getAllBlobsForContainer(client, containerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicBlobURLs, err := validatePublicBlobURLs(storageAccountName, containerName, blobNames)
|
||||
publicBlobURLs, err := m.validatePublicBlobURLs(storageAccountName, containerName, blobNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return publicBlobURLs, nil
|
||||
}
|
||||
|
||||
func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) {
|
||||
func (m *AzStorageModule) getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) {
|
||||
var blobNames []string
|
||||
|
||||
pager := blobClient.NewListBlobsFlatPager(containerName, &azblob.ListBlobsFlatOptions{
|
||||
@@ -352,7 +362,7 @@ func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([
|
||||
return blobNames, nil
|
||||
}
|
||||
|
||||
func validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) {
|
||||
func (m *AzStorageModule) validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) {
|
||||
var publicBlobURLs []string
|
||||
|
||||
if blobNames == nil {
|
||||
|
||||
@@ -69,9 +69,9 @@ func TestAzStorageCommand(t *testing.T) {
|
||||
}
|
||||
internal.MockFileSystem(true)
|
||||
// Mocked functions to simulate Azure calls and responses
|
||||
getTenants = mockedGetTenants
|
||||
GetTenants = mockedGetTenants
|
||||
GetSubscriptions = mockedGetSubscriptions
|
||||
getResourceGroups = mockedGetResourceGroups
|
||||
GetResourceGroups = mockedGetResourceGroups
|
||||
getStorageAccounts = mockedGetStorageAccounts
|
||||
|
||||
for _, s := range subtests {
|
||||
|
||||
+130
-107
@@ -16,106 +16,108 @@ import (
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzVMsModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
// cloudfox azure vms --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
|
||||
if AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
var userData string
|
||||
func (m *AzVMsModule) AzVMsCommand() error {
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_VMS_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
}
|
||||
fmt.Printf("[%s][%s] Enumerating VMs for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
|
||||
// populate the table data
|
||||
header, body, userData = getVMsPerTenantID(ptr.ToString(tenantInfo.ID))
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)})
|
||||
|
||||
if body != nil {
|
||||
if userData != "" {
|
||||
o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "loot")
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles,
|
||||
internal.LootFile{
|
||||
Contents: userData,
|
||||
Name: "virtualmachines-user-data"})
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
fmt.Println()
|
||||
} else {
|
||||
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
|
||||
runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
}
|
||||
}
|
||||
|
||||
} else if AzTenantID == "" && AzSubscription != "" {
|
||||
// cloudfox azure vms --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
runVMsCommandForSingleSubscription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_VMS_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
if m.AzClient.AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
var userData string
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
m.Log.Infof(nil, "Enumerating VMs for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
|
||||
|
||||
//populate the table data
|
||||
header, body, userData = m.getVMsPerTenantID(ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)})
|
||||
|
||||
if body != nil {
|
||||
if userData != "" {
|
||||
o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "loot")
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles,
|
||||
internal.LootFile{
|
||||
Contents: userData,
|
||||
Name: "virtualmachines-user-data"})
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
fmt.Println()
|
||||
} else {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runVMsCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runVMsCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzVMsModule) runVMsCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
var userData string
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_VMS_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
var AzSubscriptionInfo SubsriptionInfo
|
||||
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
tenantInfo := populateTenant(tenantID)
|
||||
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
o.PrefixIdentifier = *AzSubscription.DisplayName
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating VMs for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
m.Log.Infof(nil, "Enumerating VMs in subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
|
||||
// populate the table data
|
||||
header, body, userData = getVMsPerSubscriptionID(AzSubscriptionInfo.ID)
|
||||
header, body, userData = m.getVMsPerSubscriptionID(tenantSlug, AzSubscription)
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
@@ -125,7 +127,7 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory
|
||||
|
||||
if body != nil {
|
||||
if userData != "" {
|
||||
o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name, "loot")
|
||||
o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, "loot")
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles,
|
||||
internal.LootFile{
|
||||
Contents: userData,
|
||||
@@ -143,59 +145,65 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) {
|
||||
func (m *AzVMsModule) getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) {
|
||||
var resultsHeader []string
|
||||
var resultsBody, b [][]string
|
||||
var userDataCombined, userData string
|
||||
var err error
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
|
||||
for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg)
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
|
||||
m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
|
||||
} else {
|
||||
resultsBody = append(resultsBody, b...)
|
||||
userDataCombined += userData
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return resultsHeader, resultsBody, userDataCombined
|
||||
}
|
||||
|
||||
func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, string) {
|
||||
func (m *AzVMsModule) getVMsPerSubscriptionID(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, string) {
|
||||
var resultsHeader []string
|
||||
var resultsBody, b [][]string
|
||||
var userDataCombined, userData string
|
||||
var err error
|
||||
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(s.SubscriptionID) == AzSubscriptionID {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
|
||||
} else {
|
||||
resultsBody = append(resultsBody, b...)
|
||||
userDataCombined += userData
|
||||
}
|
||||
}
|
||||
for _, rg := range GetResourceGroups(ptr.ToString(AzSubscription.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = m.getComputeRelevantData(*AzSubscription, rg)
|
||||
if err != nil {
|
||||
m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(AzSubscription.SubscriptionID))
|
||||
} else {
|
||||
resultsBody = append(resultsBody, b...)
|
||||
userDataCombined += userData
|
||||
}
|
||||
}
|
||||
return resultsHeader, resultsBody, userDataCombined
|
||||
}
|
||||
|
||||
func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) {
|
||||
func (m *AzVMsModule) getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) {
|
||||
header := []string{"Subscription Name", "VM Name", "VM Location", "Private IPs", "Public IPs", "Admin Username", "Resource Group Name"}
|
||||
var body [][]string
|
||||
var userDataString string
|
||||
|
||||
// User has requested specific resource groups, filtering
|
||||
if len(m.AzClient.AzRGs) > 0 {
|
||||
for _, AzRG := range m.AzClient.AzRGs {
|
||||
if *rg.Name == *AzRG.Name {
|
||||
goto ADD_RESOURCE
|
||||
}
|
||||
}
|
||||
return header, body, userDataString, nil
|
||||
}
|
||||
ADD_RESOURCE:
|
||||
|
||||
subscriptionID := ptr.ToString(sub.SubscriptionID)
|
||||
subscriptionName := ptr.ToString(sub.DisplayName)
|
||||
resourceGroupName := ptr.ToString(rg.Name)
|
||||
|
||||
vms, err := getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName)
|
||||
vms, err := m.getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("error fetching vms for resource group %s: %s", resourceGroupName, err)
|
||||
}
|
||||
@@ -250,9 +258,12 @@ func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group)
|
||||
return header, body, userDataString, nil
|
||||
}
|
||||
|
||||
var getComputeVMsPerResourceGroup = getComputeVMsPerResourceGroupOriginal
|
||||
|
||||
func getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
|
||||
func (m *AzVMsModule) getComputeVMsPerResourceGroup(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
|
||||
return m.getComputeVMsPerResourceGroupOriginal(subscriptionID, resourceGroup)
|
||||
}
|
||||
|
||||
func (m *AzVMsModule) getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
|
||||
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
|
||||
var vms []compute.VirtualMachine
|
||||
|
||||
@@ -277,8 +288,16 @@ func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string
|
||||
}
|
||||
return vm, nil
|
||||
}
|
||||
func getComputeVmInstanceView(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) {
|
||||
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
|
||||
vm, err := computeClient.Get(context.Background(), resourceGroup, vmName, compute.InstanceViewTypesInstanceView)
|
||||
if err != nil {
|
||||
return compute.VirtualMachine{}, fmt.Errorf("could not get vm %s. %s", vmName, err)
|
||||
}
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
func mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) {
|
||||
func (m *AzVMsModule) mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) {
|
||||
testFile, err := os.ReadFile(globals.VMS_TEST_FILE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read file %s", globals.VMS_TEST_FILE)
|
||||
@@ -306,7 +325,7 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
|
||||
|
||||
if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil {
|
||||
for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces {
|
||||
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference)
|
||||
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference, "")
|
||||
if err != nil {
|
||||
return []string{err.Error()}, []string{err.Error()}
|
||||
}
|
||||
@@ -330,13 +349,15 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
|
||||
return privateIPs, publicIPs
|
||||
}
|
||||
|
||||
var getNICdetails = getNICdetailsOriginal
|
||||
func getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) {
|
||||
return getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference, expand)
|
||||
}
|
||||
|
||||
func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
|
||||
func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) {
|
||||
client := internal.GetNICClient(subscriptionID)
|
||||
NICName := strings.Split(ptr.ToString(nicReference.ID), "/")[len(strings.Split(ptr.ToString(nicReference.ID), "/"))-1]
|
||||
|
||||
nic, err := client.Get(context.TODO(), resourceGroup, NICName, "")
|
||||
nic, err := client.Get(context.TODO(), resourceGroup, NICName, expand)
|
||||
if err != nil {
|
||||
return network.Interface{}, fmt.Errorf("NICnotFound_%s", NICName)
|
||||
}
|
||||
@@ -344,7 +365,7 @@ func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicRefer
|
||||
return nic, nil
|
||||
}
|
||||
|
||||
func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
|
||||
func (m *AzVMsModule) mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
|
||||
testFile, err := os.ReadFile(globals.NICS_TEST_FILE)
|
||||
if err != nil {
|
||||
return network.Interface{}, fmt.Errorf("NICnotFound_%s", globals.NICS_TEST_FILE)
|
||||
@@ -364,7 +385,9 @@ func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference comp
|
||||
return network.Interface{}, fmt.Errorf("NICnotFound_%s", ptr.ToString(nicReference.ID))
|
||||
}
|
||||
|
||||
var getPublicIP = getPublicIPOriginal
|
||||
func getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
return getPublicIPOriginal(subscriptionID, resourceGroup, ip)
|
||||
}
|
||||
|
||||
func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
client := internal.GetPublicIPClient(subscriptionID)
|
||||
@@ -380,7 +403,7 @@ func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network
|
||||
return publicIPExpanded.PublicIPAddressPropertiesFormat.IPAddress, nil
|
||||
}
|
||||
|
||||
func mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
func (m *AzVMsModule) mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
f, err := os.ReadFile(globals.PUBLIC_IPS_TEST_FILE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("IPNotFound_%s", globals.PUBLIC_IPS_TEST_FILE)
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ func TestAzVMsCommand(t *testing.T) {
|
||||
|
||||
// Mocked functions to simulate Azure calls and responses
|
||||
GetSubscriptions = mockedGetSubscriptions
|
||||
getResourceGroups = mockedGetResourceGroups
|
||||
GetResourceGroups = mockedGetResourceGroups
|
||||
getComputeVMsPerResourceGroup = mockedGetComputeVMsPerResourceGroup
|
||||
getNICdetails = mockedGetNICdetails
|
||||
getPublicIP = mockedGetPublicIP
|
||||
|
||||
+19
-15
@@ -15,27 +15,31 @@ import (
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerbosity int, AzWhoamiListRGsAlso bool) error {
|
||||
type AzWhoamiModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
func (m *AzWhoamiModule) AzWhoamiCommand(AzWhoamiListRGsAlso bool) error {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_WHOAMI_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating Azure CLI sessions...\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", version)), color.CyanString(globals.AZ_WHOAMI_MODULE_NAME))
|
||||
|
||||
m.Log.Info(nil, "Enumerating Azure CLI sessions...")
|
||||
var header []string
|
||||
var body [][]string
|
||||
o.PrefixIdentifier = "N/A"
|
||||
if !AzWhoamiListRGsAlso {
|
||||
// cloudfox azure whoami
|
||||
header, body = getWhoamiRelevantDataSubsOnly()
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
|
||||
// append timestamp to filename (time from epoch)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
@@ -46,7 +50,7 @@ func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerb
|
||||
} else {
|
||||
// cloudfox azure whoami --list-rgs
|
||||
header, body = getWhoamiRelevantDataPerRG()
|
||||
o.Table.DirectoryName = filepath.Join(ptr.ToString(internal.GetLogDirPath()), globals.AZ_DIR_BASE, "whoami-data")
|
||||
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
@@ -64,10 +68,10 @@ func getWhoamiRelevantDataPerRG() ([]string, [][]string) {
|
||||
tableHead := []string{"Tenant ID", "Tentant Primary Domain", "Subscription ID", "Subscription Name", "RG Name", "Region"}
|
||||
var tableBody [][]string
|
||||
|
||||
for _, t := range getTenants() {
|
||||
for _, t := range GetTenants() {
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
tableBody = append(
|
||||
tableBody,
|
||||
[]string{
|
||||
@@ -90,7 +94,7 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) {
|
||||
tableHead := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name"}
|
||||
var tableBody [][]string
|
||||
|
||||
for _, t := range getTenants() {
|
||||
for _, t := range GetTenants() {
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
|
||||
tableBody = append(
|
||||
@@ -108,9 +112,9 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) {
|
||||
return tableHead, tableBody
|
||||
}
|
||||
|
||||
var getTenants = getTenantsOriginal
|
||||
var GetTenants = GetTenantsOriginal
|
||||
|
||||
func getTenantsOriginal() []subscriptions.TenantIDDescription {
|
||||
func GetTenantsOriginal() []subscriptions.TenantIDDescription {
|
||||
tenantsClient := internal.GetTenantsClient()
|
||||
var results []subscriptions.TenantIDDescription
|
||||
for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
|
||||
@@ -163,9 +167,9 @@ func mockedGetSubscriptions() []subscriptions.Subscription {
|
||||
return results
|
||||
}
|
||||
|
||||
var getResourceGroups = getResourceGroupsOriginal
|
||||
var GetResourceGroups = GetResourceGroupsOriginal
|
||||
|
||||
func getResourceGroupsOriginal(subscriptionID string) []resources.Group {
|
||||
func GetResourceGroupsOriginal(subscriptionID string) []resources.Group {
|
||||
var results []resources.Group
|
||||
rgClient := internal.GetResourceGroupsClient(subscriptionID)
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ func TestAzWhoamiCommand(t *testing.T) {
|
||||
fmt.Println("[test case] Azure Whoami Command")
|
||||
|
||||
// Mocked functions to simulate Azure calls and responses
|
||||
getTenants = mockedGetTenants
|
||||
GetTenants = mockedGetTenants
|
||||
GetSubscriptions = mockedGetSubscriptions
|
||||
getResourceGroups = mockedGetResourceGroups
|
||||
GetResourceGroups = mockedGetResourceGroups
|
||||
|
||||
// Test case parameters
|
||||
internal.MockFileSystem(true)
|
||||
|
||||
+5
-1
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/codecommit"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codedeploy"
|
||||
"github.com/aws/aws-sdk-go-v2/service/datapipeline"
|
||||
"github.com/aws/aws-sdk-go-v2/service/docdb"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||
@@ -155,7 +156,7 @@ var (
|
||||
DatabasesCommand = &cobra.Command{
|
||||
Use: "databases",
|
||||
Aliases: []string{"db", "rds", "redshift", "dbs"},
|
||||
Short: "Enumerate databases. Get a loot file with connection strings.",
|
||||
Short: "Enumerate RDS databases. Get a loot file with connection strings.",
|
||||
Long: "\nUse case examples:\n" +
|
||||
os.Args[0] + " aws databases --profile readonly_profile",
|
||||
PreRun: awsPreRun,
|
||||
@@ -739,6 +740,7 @@ func runDatabasesCommand(cmd *cobra.Command, args []string) {
|
||||
RDSClient: rds.NewFromConfig(AWSConfig),
|
||||
RedshiftClient: redshift.NewFromConfig(AWSConfig),
|
||||
DynamoDBClient: dynamodb.NewFromConfig(AWSConfig),
|
||||
DocDBClient: docdb.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
AWSProfile: profile,
|
||||
@@ -1394,6 +1396,7 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
codeCommitClient := codecommit.NewFromConfig(AWSConfig)
|
||||
codeDeployClient := codedeploy.NewFromConfig(AWSConfig)
|
||||
dataPipelineClient := datapipeline.NewFromConfig(AWSConfig)
|
||||
docdbClient := docdb.NewFromConfig(AWSConfig)
|
||||
dynamodbClient := dynamodb.NewFromConfig(AWSConfig)
|
||||
ec2Client := ec2.NewFromConfig(AWSConfig)
|
||||
ecrClient := ecr.NewFromConfig(AWSConfig)
|
||||
@@ -1602,6 +1605,7 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
RDSClient: rdsClient,
|
||||
RedshiftClient: redshiftClient,
|
||||
DynamoDBClient: dynamodbClient,
|
||||
DocDBClient: docdbClient,
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
|
||||
+203
-45
@@ -1,27 +1,46 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/BishopFox/cloudfox/azure"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
az "github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
|
||||
)
|
||||
|
||||
var (
|
||||
AzTenantID string
|
||||
AzSubscription string
|
||||
AzRGName string
|
||||
AzOutputFormat string
|
||||
AzOutputDirectory string
|
||||
AzVerbosity int
|
||||
AzWrapTable bool
|
||||
AzMergedTable bool
|
||||
// output options
|
||||
AzOutputFormat string
|
||||
AzOutputDirectory string
|
||||
AzVerbosity int
|
||||
AzWrapTable bool
|
||||
AzMergedTable bool
|
||||
|
||||
// resource selectors
|
||||
AzTenantRefs []string
|
||||
AzSubscriptionRefs []string
|
||||
AzRGRefs []string
|
||||
AzResourceRefs []string
|
||||
|
||||
|
||||
// Shared resources for all modules
|
||||
AzClient *internal.AzureClient
|
||||
AzTenants []*subscriptions.TenantIDDescription
|
||||
AzSubscriptions []*subscriptions.Subscription
|
||||
AzRGs []*resources.Group
|
||||
AzResources []*az.Resource
|
||||
|
||||
// command specific variables
|
||||
// netscan ipv4
|
||||
AzSourceIPv4 string
|
||||
|
||||
AzCommands = &cobra.Command{
|
||||
Use: "azure",
|
||||
Aliases: []string{"az"},
|
||||
Long: `See "Available Commands" for Azure Modules below`,
|
||||
Short: "See \"Available Commands\" for Azure Modules below",
|
||||
PersistentPreRun: azurePreRun,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
@@ -34,12 +53,8 @@ var (
|
||||
Long: `
|
||||
Display Available Azure CLI Sessions:
|
||||
./cloudfox az whoami`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Run: runAzWhoamiCommand,
|
||||
PersistentPreRun: func (cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
AzInventoryCommand = &cobra.Command{
|
||||
Use: "inventory",
|
||||
@@ -52,12 +67,7 @@ Enumerate inventory for a specific tenant:
|
||||
Enumerate inventory for a specific subscription:
|
||||
./cloudfox az inventory --subscription SUBSCRIPTION_ID
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Run: runAzInventoryCommand,
|
||||
}
|
||||
AzRBACCommand = &cobra.Command{
|
||||
Use: "rbac",
|
||||
@@ -70,13 +80,7 @@ Enumerate role assignments for a specific tenant:
|
||||
Enumerate role assignments for a specific subscription:
|
||||
./cloudfox az rbac --subscription SUBSCRIPTION_ID
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Run: runAzRBACCommand,
|
||||
}
|
||||
AzVMsCommand = &cobra.Command{
|
||||
Use: "vms",
|
||||
@@ -88,12 +92,7 @@ Enumerate VMs for a specific tenant:
|
||||
|
||||
Enumerate VMs for a specific subscription:
|
||||
./cloudfox az vms --subscription SUBSCRIPTION_ID`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Run: runAzVMsCommand,
|
||||
}
|
||||
AzStorageCommand = &cobra.Command{
|
||||
Use: "storage",
|
||||
@@ -106,26 +105,182 @@ Enumerate storage accounts for a specific tenant:
|
||||
Enumerate storage accounts for a specific subscription:
|
||||
./cloudfox az storage --subscription SUBSCRIPTION_ID
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Run: runAzStorageCommand,
|
||||
}
|
||||
AzNSGRulesCommand = &cobra.Command{
|
||||
Use: "nsg-rules",
|
||||
Aliases: []string{},
|
||||
Short: "Enumerates azure Network Security Group rules",
|
||||
Long: `
|
||||
Enumerate Network Security Groups rules for a specific tenant:
|
||||
./cloudfox az nsg-rukes --tenant TENANT_ID
|
||||
|
||||
Enumerate Network Security Groups rules for a specific subscription:
|
||||
./cloudfox az nsg-rules --subscription SUBSCRIPTION_ID
|
||||
|
||||
Enumerate rules for a specific Network Security Group:
|
||||
./cloudfox az nsg-rules --nsg NSG_ID
|
||||
`,
|
||||
Run: runAzNSGRulesCommand,
|
||||
}
|
||||
AzNSGLinksCommand = &cobra.Command{
|
||||
Use: "nsg-links",
|
||||
Aliases: []string{},
|
||||
Short: "Enumerates azure Network Security Groups links",
|
||||
Long: `
|
||||
Enumerate Network Security Groups links for a specific tenant:
|
||||
./cloudfox az nsg-links --tenant TENANT_ID
|
||||
|
||||
Enumerate Network Security Groups links for a specific subscription:
|
||||
./cloudfox az nsg-links --subscription SUBSCRIPTION_ID
|
||||
|
||||
Enumerate links for a specific Network Security Group:
|
||||
./cloudfox az nsg-links --nsg NSG_ID
|
||||
`,
|
||||
Run: runAzNSGLinksCommand,
|
||||
}
|
||||
AzNetScanCommand = &cobra.Command{
|
||||
Use: "netscan",
|
||||
Aliases: []string{},
|
||||
Short: "Enumerates internal network targets",
|
||||
Long: `
|
||||
Enumerate targets with a specific resource as origin:
|
||||
./cloudfox az nsg-links --resource-id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
|
||||
Enumerate targets with a specific IP address as origin
|
||||
./cloudfox az nsg-links --ipv4 A.B.C.D
|
||||
`,
|
||||
Run: runAzNetScanCommand,
|
||||
}
|
||||
)
|
||||
|
||||
func runAzWhoamiCommand (cmd *cobra.Command, args []string) {
|
||||
AzClient = new(internal.AzureClient)
|
||||
AzClient.Log = internal.NewLogger("azure")
|
||||
AzClient.Log.Announce(nil, "Analyzing local Azure credentials")
|
||||
AzClient.Version = cmd.Root().Version
|
||||
AzClient.AzWrapTable = AzWrapTable
|
||||
AzClient.AzMergedTable = AzMergedTable
|
||||
AzClient.AzVerbosity = AzVerbosity
|
||||
AzClient.AzOutputFormat = AzOutputFormat
|
||||
AzClient.AzOutputDirectory = AzOutputDirectory
|
||||
|
||||
m := azure.AzWhoamiModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("whoami"),
|
||||
}
|
||||
err := m.AzWhoamiCommand(AzWhoamiListRGsAlso)
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzInventoryCommand (cmd *cobra.Command, args []string) {
|
||||
m := azure.AzInventoryModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("inventory"),
|
||||
}
|
||||
err := m.AzInventoryCommand()
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzRBACCommand (cmd *cobra.Command, args []string) {
|
||||
m := azure.AzRBACModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("rbac"),
|
||||
}
|
||||
err := m.AzRBACCommand()
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzVMsCommand (cmd *cobra.Command, args []string) {
|
||||
m := azure.AzVMsModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("vms"),
|
||||
}
|
||||
err := m.AzVMsCommand()
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzStorageCommand (cmd *cobra.Command, args []string) {
|
||||
m := azure.AzStorageModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("storage"),
|
||||
}
|
||||
err := m.AzStorageCommand()
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzNSGRulesCommand(cmd *cobra.Command, args []string) {
|
||||
m := azure.AzNSGModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("nsg"),
|
||||
}
|
||||
err := m.AzNSGCommand("rules")
|
||||
if err != nil {
|
||||
m.Log.Fatal([]string{"rules"}, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzNSGLinksCommand(cmd *cobra.Command, args []string) {
|
||||
m := azure.AzNSGModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("nsg"),
|
||||
}
|
||||
err := m.AzNSGCommand("links")
|
||||
if err != nil {
|
||||
m.Log.Fatal([]string{"links"}, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzNetScanCommand(cmd *cobra.Command, args []string) {
|
||||
m := azure.AzNetScanModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("netscan"),
|
||||
}
|
||||
|
||||
err := m.AzNetScanCommand(AzSourceIPv4)
|
||||
if err != nil {
|
||||
m.Log.Fatal(nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func azurePreRun(cmd *cobra.Command, args []string) {
|
||||
AzClient = internal.NewAzureClient(AzVerbosity, AzWrapTable, AzMergedTable, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd, AzOutputFormat, AzOutputDirectory)
|
||||
nTenants := len(AzClient.AzTenants)
|
||||
nSubscriptions := len(AzClient.AzSubscriptions)
|
||||
nRGs := len(AzClient.AzRGs)
|
||||
nResources := len(AzClient.AzResources)
|
||||
nTotal := nTenants + nSubscriptions + nRGs + nResources
|
||||
if nTotal == 0 {
|
||||
AzClient.Log.Fatal(nil, "No valid target supplied, stopping")
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
AzWhoamiCommand.Flags().BoolVarP(&AzWhoamiListRGsAlso, "list-rgs", "l", false, "Drill down to the resource group level")
|
||||
|
||||
AzNetScanCommand.Flags().StringVar(&AzSourceIPv4, "ipv4", "", "The Source IP address to run the analysis from")
|
||||
|
||||
// Global flags
|
||||
AzCommands.PersistentFlags().StringVarP(&AzOutputFormat, "output", "o", "all", "[\"table\" | \"csv\" | \"all\" ]")
|
||||
AzCommands.PersistentFlags().StringVar(&AzOutputDirectory, "outdir", defaultOutputDir, "Output Directory ")
|
||||
AzCommands.PersistentFlags().IntVarP(&AzVerbosity, "verbosity", "v", 2, "1 = Print control messages only\n2 = Print control messages, module output\n3 = Print control messages, module output, and loot file output\n")
|
||||
AzCommands.PersistentFlags().StringVarP(&AzTenantID, "tenant", "t", "", "Tenant name")
|
||||
AzCommands.PersistentFlags().StringVarP(&AzSubscription, "subscription", "s", "", "Subscription ID or Name")
|
||||
AzCommands.PersistentFlags().StringVarP(&AzRGName, "resource-group", "g", "", "Resource Group name")
|
||||
|
||||
AzCommands.PersistentFlags().StringSliceVarP(&AzTenantRefs, "tenant", "t", []string{}, "Tenant ID or name, repeatable")
|
||||
AzCommands.PersistentFlags().StringSliceVarP(&AzSubscriptionRefs, "subscription", "s", []string{}, "Subscription ID or name, repeatable")
|
||||
AzCommands.PersistentFlags().StringSliceVarP(&AzRGRefs, "resource-group", "r", []string{}, "Resource Group name or ID, repeatable")
|
||||
AzCommands.PersistentFlags().StringSliceVarP(&AzResourceRefs, "resource-id", "i", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}), repeatable")
|
||||
|
||||
AzCommands.PersistentFlags().BoolVarP(&AzWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)")
|
||||
AzCommands.PersistentFlags().BoolVarP(&AzMergedTable, "merged-table", "m", false, "Writes a single table for all subscriptions in the tenant. Default writes a table per subscription.")
|
||||
|
||||
@@ -134,6 +289,9 @@ func init() {
|
||||
AzRBACCommand,
|
||||
AzVMsCommand,
|
||||
AzStorageCommand,
|
||||
AzInventoryCommand)
|
||||
AzNSGRulesCommand,
|
||||
AzNSGLinksCommand,
|
||||
AzInventoryCommand,
|
||||
AzNetScanCommand)
|
||||
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/gcp/commands"
|
||||
oauthservice "github.com/BishopFox/cloudfox/gcp/services/oauthService"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// GCP resources filtering options
|
||||
GCPOrganization string
|
||||
GCPProjectID string
|
||||
GCPProjectIDsFilePath string
|
||||
GCPProjectIDs []string
|
||||
|
||||
// Output formatting options
|
||||
GCPOutputFormat string
|
||||
GCPOutputDirectory string
|
||||
GCPVerbosity int
|
||||
GCPWrapTable bool
|
||||
|
||||
// misc options
|
||||
// GCPIgnoreCache bool
|
||||
|
||||
// logger
|
||||
GCPLogger = internal.NewLogger()
|
||||
|
||||
// GCP root command
|
||||
GCPCommands = &cobra.Command{
|
||||
Use: "gcp",
|
||||
Aliases: []string{"gcloud"},
|
||||
Long: `See "Available Commands" for GCP Modules below`,
|
||||
Short: "See \"Available Commands\" for GCP Modules below",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
if GCPProjectID != "" {
|
||||
GCPProjectIDs = append(GCPProjectIDs, GCPProjectID)
|
||||
} else if GCPProjectIDsFilePath != "" {
|
||||
GCPProjectIDs = internal.LoadFileLinesIntoArray(GCPProjectIDsFilePath)
|
||||
} else {
|
||||
GCPLogger.InfoM("project or project-list flags not given, commands requiring a project ID will fail", "gcp")
|
||||
}
|
||||
// Create a context with this value to share it with subcommands at runtime
|
||||
ctx := context.WithValue(context.Background(), "projectIDs", GCPProjectIDs)
|
||||
|
||||
// Set the context for this command which all subcommands can access via [SUBCMD].Parent().Context()
|
||||
// cmd.SetContext(ctx)
|
||||
os := oauthservice.NewOAuthService()
|
||||
principal, err := os.WhoAmI()
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("could not determine default user credential with error %s. \n\nPlease use default application default credentials: https://cloud.google.com/docs/authentication/application-default-credentials", err.Error()), "gcp")
|
||||
}
|
||||
ctx = context.WithValue(ctx, "account", principal.Email)
|
||||
cmd.SetContext(ctx)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// New RunAllGCPCommands function to execute all child commands
|
||||
var GCPAllChecksCommand = &cobra.Command{
|
||||
Use: "all-checks",
|
||||
Short: "Runs all available GCP commands",
|
||||
Long: `Executes all available GCP commands to collect and display information from all supported GCP services.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
for _, childCmd := range GCPCommands.Commands() {
|
||||
if childCmd == cmd { // Skip the run-all command itself to avoid infinite recursion
|
||||
continue
|
||||
}
|
||||
|
||||
GCPLogger.InfoM(fmt.Sprintf("Running command: %s", childCmd.Use), "all-checks")
|
||||
childCmd.Run(cmd, args)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Globals flags for the GCP commands
|
||||
|
||||
// Allow selection of non-default account to be used when accessing gcloud API
|
||||
// TODO
|
||||
|
||||
// Resource filtering options
|
||||
// GCPCommands.PersistentFlags().StringVarP(&GCPOrganization, "organization", "o", "", "Organization name or number, repetable")
|
||||
GCPCommands.PersistentFlags().StringVarP(&GCPProjectID, "project", "p", "", "GCP project ID")
|
||||
GCPCommands.PersistentFlags().StringVarP(&GCPProjectIDsFilePath, "project-list", "l", "", "Path to a file containing a list of project IDs separated by newlines")
|
||||
// GCPCommands.PersistentFlags().BoolVarP(&GCPAllProjects, "all-projects", "a", false, "Use all project IDs available to activated gloud account or given gcloud account")
|
||||
// GCPCommands.PersistentFlags().BoolVarP(&GCPConfirm, "yes", "y", false, "Non-interactive mode (like apt/yum)")
|
||||
// GCPCommands.PersistentFlags().StringVarP(&GCPOutputFormat, "output", "", "brief", "[\"brief\" | \"wide\" ]")
|
||||
GCPCommands.PersistentFlags().IntVarP(&Verbosity, "verbosity", "v", 1, "1 = Print control messages only\n2 = Print control messages, module output\n3 = Print control messages, module output, and loot file output\n")
|
||||
// defaultOutputDir is defined in cli.aws
|
||||
GCPCommands.PersistentFlags().StringVar(&GCPOutputDirectory, "outdir", defaultOutputDir, "Output Directory ")
|
||||
// GCPCommands.PersistentFlags().IntVarP(&Goroutines, "max-goroutines", "g", 30, "Maximum number of concurrent goroutines")
|
||||
GCPCommands.PersistentFlags().BoolVarP(&GCPWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)")
|
||||
|
||||
// Available commands
|
||||
GCPCommands.AddCommand(
|
||||
commands.GCPBucketsCommand,
|
||||
commands.GCPArtifactRegistryCommand,
|
||||
commands.GCPBigQueryCommand,
|
||||
commands.GCPSecretsCommand,
|
||||
commands.GCPIAMCommand,
|
||||
commands.GCPInstancesCommand,
|
||||
commands.GCPWhoAmICommand,
|
||||
GCPAllChecksCommand,
|
||||
)
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
artifactregistry "cloud.google.com/go/artifactregistry/apiv1"
|
||||
ArtifactRegistryService "github.com/BishopFox/cloudfox/gcp/services/artifactRegistryService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPArtifactRegistryCommand = &cobra.Command{
|
||||
Use: globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME,
|
||||
Aliases: []string{},
|
||||
Short: "Display GCP artifact registry information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display available artifact registry resource information:
|
||||
cloudfox gcp artfact-registry`,
|
||||
Run: runGCPArtifactRegistryCommand,
|
||||
}
|
||||
|
||||
// Code needed to output fields from buckets results using generic HandleOutput function
|
||||
|
||||
// Results struct that implements the internal.OutputInterface
|
||||
type GCPArtifactRegistryResults struct {
|
||||
ArtifactData []ArtifactRegistryService.ArtifactInfo
|
||||
RepositoryData []ArtifactRegistryService.RepositoryInfo
|
||||
}
|
||||
|
||||
// Decide what format the name, header and body of the CSV & JSON files will be
|
||||
func (g GCPArtifactRegistryResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
repoHeader := []string{
|
||||
"Name",
|
||||
"Format",
|
||||
"Description",
|
||||
"Size",
|
||||
"Location",
|
||||
"ProjectID",
|
||||
}
|
||||
|
||||
var repoBody [][]string
|
||||
|
||||
for _, value := range g.RepositoryData {
|
||||
repoBody = append(
|
||||
repoBody,
|
||||
[]string{
|
||||
value.Name,
|
||||
value.Format,
|
||||
value.Description,
|
||||
value.SizeBytes,
|
||||
value.Location,
|
||||
value.ProjectID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
repoTableFile := internal.TableFile{
|
||||
Header: repoHeader,
|
||||
Body: repoBody,
|
||||
Name: fmt.Sprintf("%s-repos", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME),
|
||||
}
|
||||
|
||||
tableFiles = append(tableFiles, repoTableFile)
|
||||
|
||||
artifactHeader := []string{
|
||||
"Name",
|
||||
"Format",
|
||||
"Version",
|
||||
"Location",
|
||||
"Repository",
|
||||
"Size",
|
||||
"Updated",
|
||||
"ProjectID",
|
||||
}
|
||||
|
||||
var artifactBody [][]string
|
||||
|
||||
for _, value := range g.ArtifactData {
|
||||
artifactBody = append(
|
||||
artifactBody,
|
||||
[]string{
|
||||
value.Name,
|
||||
value.Format,
|
||||
value.Version,
|
||||
value.Location,
|
||||
value.Repository,
|
||||
value.SizeBytes,
|
||||
value.Updated,
|
||||
value.ProjectID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
artifactTableFile := internal.TableFile{
|
||||
Header: artifactHeader,
|
||||
Body: artifactBody,
|
||||
Name: fmt.Sprintf("%s-artifacts", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME),
|
||||
}
|
||||
|
||||
tableFiles = append(tableFiles, artifactTableFile)
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
// Decide what is loot based on resource information
|
||||
func (g GCPArtifactRegistryResults) LootFiles() []internal.LootFile {
|
||||
// TODO consider a loot file of the URLs to the all docker image artifacts. Maybe sample commands to pull the images
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
// Houses high-level logic that retrieves resources and writes to output
|
||||
func runGCPArtifactRegistryCommand(cmd *cobra.Command, args []string) {
|
||||
// Retrieve projectIDs from parent (gcp command) ctx
|
||||
var projectIDs []string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
}
|
||||
|
||||
client, err := artifactregistry.NewClient(ctx)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("failed to create secret manager client: %v", err), globals.GCP_SECRETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Get the artifact repositories and artifacts using the projectIDs and ArtifactRegistryService
|
||||
ars := ArtifactRegistryService.New(client)
|
||||
var artifactResults []ArtifactRegistryService.ArtifactInfo
|
||||
var repoRestuls []ArtifactRegistryService.RepositoryInfo
|
||||
|
||||
// Set output params leveraging parent (gcp) pflag values
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving all artifact repositories and supported artifacts in all locations from project: %s", projectID), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
result, err := ars.RepositoriesAndArtifacts(projectID)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
artifactResults = append(artifactResults, result.Artifacts...)
|
||||
repoRestuls = append(repoRestuls, result.Repositories...)
|
||||
logger.InfoM(fmt.Sprintf("Done retrieving artifact repository resource data from project: %s", projectID), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
cloudfoxOutput := GCPArtifactRegistryResults{ArtifactData: artifactResults, RepositoryData: repoRestuls}
|
||||
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM(fmt.Sprintf("Done writing output for project %s", projectID), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
BigQueryService "github.com/BishopFox/cloudfox/gcp/services/bigqueryService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPBigQueryCommand = &cobra.Command{
|
||||
Use: "bigquery",
|
||||
Aliases: []string{},
|
||||
Short: "Display Bigauery datasets and tables information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display available Bigauery datasets and tables resource information:
|
||||
cloudfox gcp bigquery`,
|
||||
Run: runGCPBigQueryCommand,
|
||||
}
|
||||
|
||||
// GCPBigQueryResults struct that implements the internal.OutputInterface
|
||||
type GCPBigQueryResults struct {
|
||||
DatasetsData []BigQueryService.BigqueryDataset
|
||||
TablesData []BigQueryService.BigqueryTable
|
||||
}
|
||||
|
||||
// Define the format for CSV & JSON output
|
||||
func (g GCPBigQueryResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
// For Datasets
|
||||
datasetHeader := []string{"Name", "DatasetID", "Description", "CreationTime", "LastModifiedTime", "Location", "ProjectID"}
|
||||
var datasetBody [][]string
|
||||
for _, dataset := range g.DatasetsData {
|
||||
datasetBody = append(datasetBody, []string{
|
||||
dataset.Name,
|
||||
dataset.DatasetID,
|
||||
dataset.Description,
|
||||
dataset.CreationTime.Format(time.RFC3339),
|
||||
dataset.LastModifiedTime.Format(time.RFC3339),
|
||||
dataset.Location,
|
||||
dataset.ProjectID,
|
||||
})
|
||||
}
|
||||
datasetTableFile := internal.TableFile{
|
||||
Header: datasetHeader,
|
||||
Body: datasetBody,
|
||||
Name: "bigquery-datasets",
|
||||
}
|
||||
tableFiles = append(tableFiles, datasetTableFile)
|
||||
|
||||
// For Tables
|
||||
tableHeader := []string{"TableID", "DatasetID", "Description", "CreationTime", "LastModifiedTime", "NumBytes", "Location", "ProjectID"}
|
||||
var tableBody [][]string
|
||||
for _, table := range g.TablesData {
|
||||
tableBody = append(tableBody, []string{
|
||||
table.TableID,
|
||||
table.DatasetID,
|
||||
table.Description,
|
||||
table.CreationTime.Format(time.RFC3339),
|
||||
table.LastModifiedTime.Format(time.RFC3339),
|
||||
fmt.Sprintf("%d", table.NumBytes),
|
||||
table.Location,
|
||||
table.ProjectID,
|
||||
})
|
||||
}
|
||||
tableTableFile := internal.TableFile{
|
||||
Header: tableHeader,
|
||||
Body: tableBody,
|
||||
Name: "bigquery-tables",
|
||||
}
|
||||
tableFiles = append(tableFiles, tableTableFile)
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
func (g GCPBigQueryResults) LootFiles() []internal.LootFile {
|
||||
// Implement if there's specific data considered as loot
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
func runGCPBigQueryCommand(cmd *cobra.Command, args []string) {
|
||||
var projectIDs []string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
}
|
||||
|
||||
bqService := BigQueryService.New()
|
||||
var datasetsResults []BigQueryService.BigqueryDataset
|
||||
var tablesResults []BigQueryService.BigqueryTable
|
||||
|
||||
// Set output params leveraging parent (gcp) pflag values
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving BigQuery datasets and tables from project: %s", projectID), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
result, err := bqService.BigqueryDatasetsAndTables(projectID)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
datasetsResults = append(datasetsResults, result.Datasets...)
|
||||
tablesResults = append(tablesResults, result.Tables...)
|
||||
cloudfoxOutput := GCPBigQueryResults{DatasetsData: datasetsResults, TablesData: tablesResults}
|
||||
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_BIGQUERY_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM(fmt.Sprintf("Done writing output for project %s", projectID), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
CloudStorageService "github.com/BishopFox/cloudfox/gcp/services/cloudStorageService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPBucketsCommand = &cobra.Command{
|
||||
Use: globals.GCP_BUCKETS_MODULE_NAME,
|
||||
Aliases: []string{},
|
||||
Short: "Display GCP buckets information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display available bucket information:
|
||||
cloudfox gcp buckets`,
|
||||
Run: runGCPBucketsCommand,
|
||||
}
|
||||
|
||||
// Code needed to output fields from buckets results using generic HandleOutput function
|
||||
|
||||
// Results struct that implements the internal.OutputInterface
|
||||
type GCPBucketsResults struct {
|
||||
Data []CloudStorageService.BucketInfo
|
||||
}
|
||||
|
||||
// Decide what format the name, header and body of the CSV & JSON files will be
|
||||
func (g GCPBucketsResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
header := []string{
|
||||
"Name",
|
||||
"Location",
|
||||
"ProjectID",
|
||||
}
|
||||
|
||||
var body [][]string
|
||||
|
||||
for _, value := range g.Data {
|
||||
body = append(
|
||||
body,
|
||||
[]string{
|
||||
value.Name,
|
||||
value.Location,
|
||||
value.ProjectID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
tableFile := internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: globals.GCP_BUCKETS_MODULE_NAME,
|
||||
}
|
||||
tableFiles = append(tableFiles, tableFile)
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
// Decide what is loot based on resource information
|
||||
func (g GCPBucketsResults) LootFiles() []internal.LootFile {
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
// Houses high-level logic that retrieves resources and writes to output
|
||||
func runGCPBucketsCommand(cmd *cobra.Command, args []string) {
|
||||
// Retrieve projectIDs from parent (gcp command) ctx
|
||||
var projectIDs []string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_BUCKETS_MODULE_NAME)
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_BUCKETS_MODULE_NAME)
|
||||
}
|
||||
|
||||
// Get the bucket info using the projectIDs and CloudStorageService
|
||||
cs := CloudStorageService.New()
|
||||
var results []CloudStorageService.BucketInfo
|
||||
|
||||
// Set output params leveraging parent (gcp) pflag values
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving all buckets from project: %s", projectID), globals.GCP_BUCKETS_MODULE_NAME)
|
||||
result, err := cs.Buckets(projectID)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_BUCKETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
results = append(results, result...)
|
||||
logger.InfoM(fmt.Sprintf("Done retrieving all buckets from project: %s", projectID), globals.GCP_BUCKETS_MODULE_NAME)
|
||||
cloudfoxOutput := GCPBucketsResults{Data: results}
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_BUCKETS_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_BUCKETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM(fmt.Sprintf("Done writing output for project %s", projectID), globals.GCP_BUCKETS_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPIAMCommand = &cobra.Command{
|
||||
Use: globals.GCP_IAM_MODULE_NAME,
|
||||
Aliases: []string{},
|
||||
Short: "Display GCP IAM information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display IAM principals and their roles information within GCP resources:
|
||||
cloudfox gcp iam`,
|
||||
Run: runGCPIAMCommand,
|
||||
}
|
||||
|
||||
// Results struct for IAM command that implements the internal.OutputInterface
|
||||
type GCPIAMResults struct {
|
||||
Data []IAMService.PrincipalWithRoles
|
||||
}
|
||||
|
||||
// TableFiles formats the data for table output, CSV & JSON files
|
||||
func (g GCPIAMResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
header := []string{
|
||||
"Name",
|
||||
"Principal Type",
|
||||
"Role",
|
||||
"PolicyResourceType",
|
||||
"PolicyResourceID",
|
||||
}
|
||||
|
||||
var body [][]string
|
||||
|
||||
for _, principal := range g.Data {
|
||||
for _, binding := range principal.PolicyBindings {
|
||||
body = append(body, []string{
|
||||
principal.Name,
|
||||
principal.Type,
|
||||
binding.Role,
|
||||
principal.ResourceType,
|
||||
principal.ResourceID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tableFile := internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: globals.GCP_IAM_MODULE_NAME,
|
||||
}
|
||||
tableFiles = append(tableFiles, tableFile)
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
// LootFiles can be implemented if needed
|
||||
func (g GCPIAMResults) LootFiles() []internal.LootFile {
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
// Houses high-level logic that retrieves IAM information and writes to output
|
||||
func runGCPIAMCommand(cmd *cobra.Command, args []string) {
|
||||
// Retrieve projectIDs and resource type from parent (gcp command) ctx
|
||||
var projectIDs []string
|
||||
var resourceType string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_IAM_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_IAM_MODULE_NAME)
|
||||
}
|
||||
|
||||
// TODO fix once folders or organizations are supported as input for project root
|
||||
resourceType = "project"
|
||||
|
||||
// Initialize IAMService and fetch principals with roles for the given projectIDs and resource type
|
||||
iamService := IAMService.New()
|
||||
var results []IAMService.PrincipalWithRoles
|
||||
|
||||
// Set output params leveraging parent (gcp) pflag values
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving IAM information for resource: %s of type %s", projectID, resourceType), globals.GCP_IAM_MODULE_NAME)
|
||||
principals, err := iamService.PrincipalsWithRoles(projectID, resourceType)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_IAM_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
results = append(results, principals...)
|
||||
logger.InfoM(fmt.Sprintf("Done retrieving IAM information for resource: %s of type %s", projectID, resourceType), globals.GCP_IAM_MODULE_NAME)
|
||||
cloudfoxOutput := GCPIAMResults{Data: results}
|
||||
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_IAM_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_IAM_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM(fmt.Sprintf("Done writing output for project %s", projectID), globals.GCP_IAM_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ComputeEngineService "github.com/BishopFox/cloudfox/gcp/services/computeEngineService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPInstancesCommand = &cobra.Command{
|
||||
Use: globals.GCP_INSTANCES_MODULE_NAME, // This should be defined in the globals package
|
||||
Aliases: []string{},
|
||||
Short: "Display GCP Compute Engine instances information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display available Compute Engine instances information:
|
||||
cloudfox gcp instances`,
|
||||
Run: runGCPInstancesCommand,
|
||||
}
|
||||
|
||||
// GCPInstancesResults implements internal.OutputInterface for Compute Engine instances
|
||||
type GCPInstancesResults struct {
|
||||
Data []ComputeEngineService.ComputeEngineInfo
|
||||
}
|
||||
|
||||
func (g GCPInstancesResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
header := []string{
|
||||
"Name",
|
||||
"ID",
|
||||
"State",
|
||||
"ExternalIP",
|
||||
"InternalIP",
|
||||
"ServiceAccount", // Adding ServiceAccount to the header
|
||||
"Zone",
|
||||
"ProjectID",
|
||||
}
|
||||
|
||||
var body [][]string
|
||||
for _, instance := range g.Data {
|
||||
// Initialize an empty string to aggregate service account emails
|
||||
var serviceAccountEmails string
|
||||
for _, serviceAccount := range instance.ServiceAccounts {
|
||||
// Assuming each instance can have multiple service accounts, concatenate their emails
|
||||
if serviceAccountEmails != "" {
|
||||
serviceAccountEmails += "; " // Use semicolon as a delimiter for multiple emails
|
||||
}
|
||||
serviceAccountEmails += serviceAccount.Email
|
||||
}
|
||||
|
||||
body = append(body, []string{
|
||||
instance.Name,
|
||||
instance.ID,
|
||||
instance.State,
|
||||
instance.ExternalIP,
|
||||
instance.InternalIP,
|
||||
serviceAccountEmails, // Add the aggregated service account emails to the output
|
||||
instance.Zone,
|
||||
instance.ProjectID,
|
||||
})
|
||||
}
|
||||
|
||||
tableFiles = append(tableFiles, internal.TableFile{
|
||||
Name: globals.GCP_INSTANCES_MODULE_NAME,
|
||||
Header: header,
|
||||
Body: body,
|
||||
})
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
func (g GCPInstancesResults) LootFiles() []internal.LootFile {
|
||||
// Define any loot files if applicable
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
func runGCPInstancesCommand(cmd *cobra.Command, args []string) {
|
||||
var projectIDs []string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_INSTANCES_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_IAM_MODULE_NAME)
|
||||
}
|
||||
|
||||
ces := ComputeEngineService.New()
|
||||
var results []ComputeEngineService.ComputeEngineInfo
|
||||
|
||||
// Set output params leveraging parent (gcp) pflag values
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving all instances from project: %s", projectID), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
result, err := ces.Instances(projectID)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
results = append(results, result...)
|
||||
logger.InfoM(fmt.Sprintf("Done retrieving all instances from project: %s", projectID), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
cloudfoxOutput := GCPInstancesResults{Data: results}
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_INSTANCES_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM("Done writing output", globals.GCP_INSTANCES_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
||||
SecretsService "github.com/BishopFox/cloudfox/gcp/services/secretsService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPSecretsCommand = &cobra.Command{
|
||||
Use: globals.GCP_SECRETS_MODULE_NAME,
|
||||
Aliases: []string{},
|
||||
Short: "Display GCP secrets information",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Long: `
|
||||
Display available secrets information:
|
||||
cloudfox gcp secrets`,
|
||||
Run: runGCPSecretsCommand,
|
||||
}
|
||||
|
||||
// GCPSecretsResults struct that implements the internal.OutputInterface
|
||||
type GCPSecretsResults struct {
|
||||
Data []SecretsService.SecretInfo
|
||||
}
|
||||
|
||||
func (g GCPSecretsResults) TableFiles() []internal.TableFile {
|
||||
var tableFiles []internal.TableFile
|
||||
|
||||
header := []string{
|
||||
"Name",
|
||||
"CreationTime",
|
||||
"Labels",
|
||||
"Rotation",
|
||||
"ProjectID",
|
||||
// Add more fields as necessary
|
||||
}
|
||||
|
||||
var body [][]string
|
||||
for _, value := range g.Data {
|
||||
body = append(body, []string{
|
||||
value.Name,
|
||||
value.CreationTime,
|
||||
fmt.Sprintf("%v", value.Labels),
|
||||
value.Rotation,
|
||||
value.ProjectID,
|
||||
})
|
||||
}
|
||||
|
||||
tableFile := internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: globals.GCP_SECRETS_MODULE_NAME,
|
||||
}
|
||||
tableFiles = append(tableFiles, tableFile)
|
||||
|
||||
return tableFiles
|
||||
}
|
||||
|
||||
func (g GCPSecretsResults) LootFiles() []internal.LootFile {
|
||||
// Define any specific data considered as loot
|
||||
return []internal.LootFile{}
|
||||
}
|
||||
|
||||
func runGCPSecretsCommand(cmd *cobra.Command, args []string) {
|
||||
var projectIDs []string
|
||||
var account string
|
||||
parentCmd := cmd.Parent()
|
||||
ctx := cmd.Context()
|
||||
logger := internal.NewLogger()
|
||||
if value, ok := ctx.Value("projectIDs").([]string); ok && len(value) > 0 {
|
||||
projectIDs = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve projectIDs from flag value or value is empty", globals.GCP_SECRETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := ctx.Value("account").(string); ok {
|
||||
account = value
|
||||
} else {
|
||||
logger.ErrorM("Could not retrieve account email from command", globals.GCP_IAM_MODULE_NAME)
|
||||
}
|
||||
|
||||
client, err := secretmanager.NewClient(ctx)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("failed to create secret manager client: %v", err), globals.GCP_SECRETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ss := SecretsService.New(client)
|
||||
var results []SecretsService.SecretInfo
|
||||
|
||||
// Set output params from parentCmd
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDirectory, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
|
||||
for _, projectID := range projectIDs {
|
||||
logger.InfoM(fmt.Sprintf("Retrieving all secrets from project: %s", projectID), globals.GCP_SECRETS_MODULE_NAME)
|
||||
result, err := ss.Secrets(projectID)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_SECRETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
results = append(results, result...)
|
||||
logger.InfoM(fmt.Sprintf("Done retrieving all secrets from project: %s", projectID), globals.GCP_SECRETS_MODULE_NAME)
|
||||
cloudfoxOutput := GCPSecretsResults{Data: results}
|
||||
err = internal.HandleOutput("gcp", format, outputDirectory, verbosity, wrap, globals.GCP_SECRETS_MODULE_NAME, account, projectID, cloudfoxOutput)
|
||||
if err != nil {
|
||||
logger.ErrorM(err.Error(), globals.GCP_SECRETS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
logger.InfoM(fmt.Sprintf("Done writing output for project %s", projectID), globals.GCP_SECRETS_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
OAuthService "github.com/BishopFox/cloudfox/gcp/services/oauthService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GCPWhoAmICommand = &cobra.Command{
|
||||
Use: globals.GCP_WHOAMI_MODULE_NAME,
|
||||
Short: "Display the email address of the GCP authenticated user",
|
||||
Args: cobra.NoArgs,
|
||||
Run: runGCPWhoAmICommand,
|
||||
}
|
||||
|
||||
func runGCPWhoAmICommand(cmd *cobra.Command, args []string) {
|
||||
logger := internal.NewLogger()
|
||||
|
||||
// Initialize the OAuthService
|
||||
oauthService := OAuthService.NewOAuthService()
|
||||
|
||||
// Call the WhoAmI function
|
||||
principal, err := oauthService.WhoAmI()
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error retrieving token info: %v", err), globals.GCP_WHOAMI_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoM(fmt.Sprintf("authenticated user email: %s", principal.Email), globals.GCP_WHOAMI_MODULE_NAME)
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package artifactregistryservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
artifactregistry "cloud.google.com/go/artifactregistry/apiv1"
|
||||
artifactregistrypb "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb"
|
||||
"github.com/BishopFox/cloudfox/gcp/services/models"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
locationpb "google.golang.org/genproto/googleapis/cloud/location"
|
||||
)
|
||||
|
||||
// New creates a new instance of the ArtifactRegistryService using an artifactregistry.Client
|
||||
func New(client *artifactregistry.Client) ArtifactRegistryService {
|
||||
ars := ArtifactRegistryService{
|
||||
Client: &ArtifactRegistryClientWrapper{
|
||||
Closer: client.Close,
|
||||
RepositoryLister: func(ctx context.Context, req *artifactregistrypb.ListRepositoriesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.Repository] {
|
||||
return client.ListRepositories(ctx, req, opts...)
|
||||
},
|
||||
LocationLister: func(ctx context.Context, req *locationpb.ListLocationsRequest, opts ...gax.CallOption) models.GenericIterator[locationpb.Location] {
|
||||
return client.ListLocations(ctx, req, opts...)
|
||||
},
|
||||
RepositoryGetter: func(ctx context.Context, req *artifactregistrypb.GetRepositoryRequest, opts ...gax.CallOption) (*artifactregistrypb.Repository, error) {
|
||||
return client.GetRepository(ctx, req, opts...)
|
||||
},
|
||||
DockerImageLister: func(ctx context.Context, req *artifactregistrypb.ListDockerImagesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.DockerImage] {
|
||||
return client.ListDockerImages(ctx, req, opts...)
|
||||
},
|
||||
},
|
||||
}
|
||||
return ars
|
||||
}
|
||||
|
||||
var logger internal.Logger
|
||||
|
||||
// RepositoriesAndArtifacts retrieves both repositories and their artifacts for a given projectID.
|
||||
func (ars *ArtifactRegistryService) RepositoriesAndArtifacts(projectID string) (CombinedRepoArtifactInfo, error) {
|
||||
var combinedInfo CombinedRepoArtifactInfo
|
||||
|
||||
// Retrieve repositories.
|
||||
repos, err := ars.Repositories(projectID)
|
||||
if err != nil {
|
||||
return combinedInfo, fmt.Errorf("failed to retrieve repositories: %v", err)
|
||||
}
|
||||
combinedInfo.Repositories = repos
|
||||
|
||||
// Iterate over repositories to fetch artifacts.
|
||||
for _, repo := range repos {
|
||||
// Extract location and repository name from the repository Name.
|
||||
parts := strings.Split(repo.Name, "/")
|
||||
if len(parts) < 6 {
|
||||
logger.InfoM("Unexpected repository name format, skipping artifacts retrieval", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
continue
|
||||
}
|
||||
location := parts[3]
|
||||
repositoryName := parts[5]
|
||||
|
||||
// Fetch artifacts for the current repository.
|
||||
artifacts, err := ars.Artifacts(projectID, location, repositoryName)
|
||||
if err != nil {
|
||||
logger.InfoM(fmt.Sprintf("Failed to retrieve artifacts for repository %s: %v", repositoryName, err), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
continue // Optionally continue to the next repository or handle error differently.
|
||||
}
|
||||
combinedInfo.Artifacts = append(combinedInfo.Artifacts, artifacts...)
|
||||
}
|
||||
|
||||
return combinedInfo, nil
|
||||
}
|
||||
|
||||
// RepositoriesAndArtifacts returns a list of repositories and artifacts given projectID.
|
||||
// TODO consider putting location as input, so that we can put Locations iteration before it & potentially filter by location at top level of GCP module
|
||||
func (ars *ArtifactRegistryService) Repositories(projectID string) ([]RepositoryInfo, error) {
|
||||
// Get the list of available locations for the given project.
|
||||
locations, err := ars.projectLocations(projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repositories []RepositoryInfo
|
||||
ctx := context.Background()
|
||||
// Construct the request for listing repositories.
|
||||
for _, location := range locations {
|
||||
req := &artifactregistrypb.ListRepositoriesRequest{
|
||||
Parent: fmt.Sprintf("projects/%s/locations/%s", projectID, location),
|
||||
}
|
||||
|
||||
it := ars.Client.ListRepositories(ctx, req)
|
||||
for {
|
||||
repo, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repositories = append(repositories, RepositoryInfo{
|
||||
Name: repo.Name,
|
||||
Format: repo.Format.String(),
|
||||
Description: repo.Description,
|
||||
SizeBytes: fmt.Sprintf("%d", repo.SizeBytes),
|
||||
ProjectID: projectID,
|
||||
Location: location,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
// Artifacts fetches the artifacts for a given repository, handling different formats.
|
||||
func (ars *ArtifactRegistryService) Artifacts(projectID string, location string, repositoryName string) ([]ArtifactInfo, error) {
|
||||
ctx := context.Background()
|
||||
// client, err := artifactregistry.NewClient(ctx)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to create artifact registry client: %v", err)
|
||||
// }
|
||||
// defer client.Close()
|
||||
|
||||
repoFullName := fmt.Sprintf("projects/%s/locations/%s/repositories/%s", projectID, location, repositoryName)
|
||||
|
||||
// Fetch repository details to determine its format
|
||||
repo, err := ars.Client.GetRepository(ctx, &artifactregistrypb.GetRepositoryRequest{Name: repoFullName})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get repository details: %v", err)
|
||||
}
|
||||
|
||||
// Handle different repository formats
|
||||
switch repo.Format {
|
||||
case artifactregistrypb.Repository_DOCKER:
|
||||
return ars.DockerImages(repoFullName)
|
||||
// Placeholder for other formats
|
||||
// case artifactregistrypb.Repository_MAVEN:
|
||||
// return listMavenArtifacts(ctx, client, repoFullName)
|
||||
// case artifactregistrypb.Repository_NPM:
|
||||
// return listNpmPackages(ctx, client, repoFullName)
|
||||
default:
|
||||
logger.InfoM("Repository format is unsupported, so no artifacts were retrieved.", globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseDockerImageName parses the Docker image name string and returns the extracted details.
|
||||
func parseDockerImageName(imageName string) DockerImageDetails {
|
||||
// Split the image name by '/'
|
||||
parts := strings.Split(imageName, "/")
|
||||
|
||||
// Extract details based on the known structure of the image name.
|
||||
// Assuming the format is always consistent as described.
|
||||
projectID := parts[1]
|
||||
location := parts[3]
|
||||
repository := parts[5]
|
||||
// The image name and digest are after the last '/', separated by '@'
|
||||
imageAndDigest := strings.Split(parts[7], "@")
|
||||
imageName = imageAndDigest[0]
|
||||
digest := imageAndDigest[1]
|
||||
|
||||
return DockerImageDetails{
|
||||
ProjectID: projectID,
|
||||
Location: location,
|
||||
Repository: repository,
|
||||
ImageName: imageName,
|
||||
Digest: digest,
|
||||
}
|
||||
}
|
||||
|
||||
// listDockerImages specifically lists Docker images within a given repository.
|
||||
func (ars *ArtifactRegistryService) DockerImages(repositoryName string) ([]ArtifactInfo, error) {
|
||||
ctx := context.Background()
|
||||
var artifacts []ArtifactInfo
|
||||
|
||||
req := &artifactregistrypb.ListDockerImagesRequest{
|
||||
Parent: repositoryName,
|
||||
}
|
||||
|
||||
it := ars.Client.ListDockerImages(ctx, req)
|
||||
for {
|
||||
image, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse image name to extract detailed information.
|
||||
details := parseDockerImageName(image.Name)
|
||||
|
||||
// Populate the ArtifactInfo structure with Docker image details.
|
||||
artifacts = append(artifacts, ArtifactInfo{
|
||||
Name: details.ImageName,
|
||||
Format: "DOCKER",
|
||||
Location: details.Location,
|
||||
Repository: details.Repository,
|
||||
SizeBytes: fmt.Sprintf("%d", image.ImageSizeBytes),
|
||||
Updated: image.UpdateTime.AsTime().String(),
|
||||
Digest: details.Digest,
|
||||
ProjectID: details.ProjectID,
|
||||
})
|
||||
}
|
||||
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// listProjectLocations returns a list of available locations for the given project ID.
|
||||
func (ars *ArtifactRegistryService) projectLocations(projectID string) ([]string, error) {
|
||||
// Define the request to list locations.
|
||||
req := &locationpb.ListLocationsRequest{
|
||||
Name: fmt.Sprintf("projects/%s", projectID),
|
||||
}
|
||||
|
||||
// Initialize an empty slice to hold the locations.
|
||||
var locations []string
|
||||
|
||||
// Call the ListLocations method and handle pagination.
|
||||
ctx := context.Background()
|
||||
it := ars.Client.ListLocations(ctx, req)
|
||||
for {
|
||||
loc, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list locations: %w", err)
|
||||
}
|
||||
locations = append(locations, loc.LocationId)
|
||||
}
|
||||
|
||||
return locations, nil
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package artifactregistryservice_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
artifactRegistryService "github.com/BishopFox/cloudfox/gcp/services/artifactRegistryService"
|
||||
"github.com/BishopFox/cloudfox/gcp/services/models"
|
||||
|
||||
artifactregistrypb "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
locationpb "google.golang.org/genproto/googleapis/cloud/location"
|
||||
)
|
||||
|
||||
// Mock structure for ArtifactRegistryClientWrapper
|
||||
type mockArtifactRegistryClient struct {
|
||||
repositories []*artifactregistrypb.Repository
|
||||
locations []*locationpb.Location
|
||||
dockerImages []*artifactregistrypb.DockerImage
|
||||
}
|
||||
|
||||
func (m *mockArtifactRegistryClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mock iterator for repositories
|
||||
type mockRepoIterator struct {
|
||||
repositories []*artifactregistrypb.Repository
|
||||
index int
|
||||
}
|
||||
|
||||
func (it *mockRepoIterator) Next() (*artifactregistrypb.Repository, error) {
|
||||
if it.index < len(it.repositories) {
|
||||
repo := it.repositories[it.index]
|
||||
it.index++
|
||||
return repo, nil
|
||||
}
|
||||
return nil, iterator.Done
|
||||
}
|
||||
|
||||
// Mock iterator for locations
|
||||
type mockLocationIterator struct {
|
||||
locations []*locationpb.Location
|
||||
index int
|
||||
}
|
||||
|
||||
func (it *mockLocationIterator) Next() (*locationpb.Location, error) {
|
||||
if it.index < len(it.locations) {
|
||||
loc := it.locations[it.index]
|
||||
it.index++
|
||||
return loc, nil
|
||||
}
|
||||
return nil, iterator.Done
|
||||
}
|
||||
|
||||
// Mock iterator for Docker images
|
||||
type mockDockerImageIterator struct {
|
||||
dockerImages []*artifactregistrypb.DockerImage
|
||||
index int
|
||||
}
|
||||
|
||||
func (it *mockDockerImageIterator) Next() (*artifactregistrypb.DockerImage, error) {
|
||||
if it.index < len(it.dockerImages) {
|
||||
img := it.dockerImages[it.index]
|
||||
it.index++
|
||||
return img, nil
|
||||
}
|
||||
return nil, iterator.Done
|
||||
}
|
||||
|
||||
// Mock methods
|
||||
func (m *mockArtifactRegistryClient) ListRepositories(ctx context.Context, req *artifactregistrypb.ListRepositoriesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.Repository] {
|
||||
return &mockRepoIterator{repositories: m.repositories}
|
||||
}
|
||||
|
||||
func (m *mockArtifactRegistryClient) ListLocations(ctx context.Context, req *locationpb.ListLocationsRequest, opts ...gax.CallOption) models.GenericIterator[locationpb.Location] {
|
||||
return &mockLocationIterator{locations: m.locations}
|
||||
}
|
||||
|
||||
func (m *mockArtifactRegistryClient) GetRepository(ctx context.Context, req *artifactregistrypb.GetRepositoryRequest, opts ...gax.CallOption) (*artifactregistrypb.Repository, error) {
|
||||
for _, repo := range m.repositories {
|
||||
if repo.Name == req.Name {
|
||||
return repo, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("repository not found")
|
||||
}
|
||||
|
||||
func (m *mockArtifactRegistryClient) ListDockerImages(ctx context.Context, req *artifactregistrypb.ListDockerImagesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.DockerImage] {
|
||||
return &mockDockerImageIterator{dockerImages: m.dockerImages}
|
||||
}
|
||||
|
||||
var mockClient = mockArtifactRegistryClient{}
|
||||
|
||||
var ars = artifactRegistryService.ArtifactRegistryService{
|
||||
Client: &artifactRegistryService.ArtifactRegistryClientWrapper{
|
||||
Closer: mockClient.Close,
|
||||
RepositoryLister: func(ctx context.Context, req *artifactregistrypb.ListRepositoriesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.Repository] {
|
||||
return mockClient.ListRepositories(ctx, req, opts...)
|
||||
},
|
||||
LocationLister: func(ctx context.Context, req *locationpb.ListLocationsRequest, opts ...gax.CallOption) models.GenericIterator[locationpb.Location] {
|
||||
return mockClient.ListLocations(ctx, req, opts...)
|
||||
},
|
||||
RepositoryGetter: func(ctx context.Context, req *artifactregistrypb.GetRepositoryRequest, opts ...gax.CallOption) (*artifactregistrypb.Repository, error) {
|
||||
return mockClient.GetRepository(ctx, req, opts...)
|
||||
},
|
||||
DockerImageLister: func(ctx context.Context, req *artifactregistrypb.ListDockerImagesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.DockerImage] {
|
||||
return mockClient.ListDockerImages(ctx, req, opts...)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Tests go here
|
||||
func TestArtifacts(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
projectID string
|
||||
location string
|
||||
repositoryName string
|
||||
setupMockClient func(client *mockArtifactRegistryClient)
|
||||
expectedArtifacts []artifactRegistryService.ArtifactInfo
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid artifacts retrieval",
|
||||
projectID: "project1",
|
||||
location: "us-central1",
|
||||
repositoryName: "repo1",
|
||||
setupMockClient: func(client *mockArtifactRegistryClient) {
|
||||
client.dockerImages = []*artifactregistrypb.DockerImage{
|
||||
{
|
||||
Name: "projects/project1/locations/us-central1/repositories/repo1/dockerImages/image1@sha256:e9954c1fc875017be1c3e36eca16be2d9e9bccc4bf072163515467d6a823c7cf",
|
||||
Uri: "us-central1-docker.pkg.dev/project1/repo1/image1@sha256:e9954c1fc875017be1c3e36eca16be2d9e9bccc4bf072163515467d6a823c7cf",
|
||||
ImageSizeBytes: 1024,
|
||||
},
|
||||
}
|
||||
client.locations = []*locationpb.Location{
|
||||
{
|
||||
LocationId: "us-central1",
|
||||
},
|
||||
}
|
||||
client.repositories = []*artifactregistrypb.Repository{{
|
||||
Name: "projects/project1/locations/us-central1/repositories/repo1",
|
||||
Format: artifactregistrypb.Repository_DOCKER,
|
||||
},
|
||||
}
|
||||
},
|
||||
expectedArtifacts: []artifactRegistryService.ArtifactInfo{
|
||||
{
|
||||
Name: "image1",
|
||||
Format: "DOCKER",
|
||||
Location: "us-central1",
|
||||
Repository: "repo1",
|
||||
SizeBytes: "1024",
|
||||
ProjectID: "project1",
|
||||
Digest: "sha256:e9954c1fc875017be1c3e36eca16be2d9e9bccc4bf072163515467d6a823c7cf",
|
||||
Updated: "1970-01-01 00:00:00 +0000 UTC",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
// Add more test cases here
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset mockClient state
|
||||
mockClient.repositories = nil
|
||||
mockClient.locations = nil
|
||||
mockClient.dockerImages = nil
|
||||
|
||||
// Setup mock client for the current test case
|
||||
tc.setupMockClient(&mockClient)
|
||||
|
||||
artifacts, err := ars.Artifacts(tc.projectID, tc.location, tc.repositoryName)
|
||||
if (err != nil) != tc.expectError {
|
||||
t.Errorf("Artifacts() error = %v, expectError %v", err, tc.expectError)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(artifacts, tc.expectedArtifacts) {
|
||||
t.Errorf("Artifacts() = %v, expected %v", artifacts, tc.expectedArtifacts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositories(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
projectID string
|
||||
setupMockClient func(client *mockArtifactRegistryClient)
|
||||
expectedRepositories []artifactRegistryService.RepositoryInfo
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid repositories retrieval",
|
||||
projectID: "project1",
|
||||
setupMockClient: func(client *mockArtifactRegistryClient) {
|
||||
client.repositories = []*artifactregistrypb.Repository{
|
||||
{
|
||||
Name: "projects/project1/locations/us-central1/repositories/repo1",
|
||||
Format: artifactregistrypb.Repository_DOCKER,
|
||||
Description: "Test repository",
|
||||
},
|
||||
}
|
||||
client.locations = []*locationpb.Location{
|
||||
{
|
||||
LocationId: "us-central1",
|
||||
},
|
||||
}
|
||||
},
|
||||
expectedRepositories: []artifactRegistryService.RepositoryInfo{
|
||||
{
|
||||
Name: "projects/project1/locations/us-central1/repositories/repo1",
|
||||
Format: "DOCKER",
|
||||
Description: "Test repository",
|
||||
SizeBytes: "0",
|
||||
ProjectID: "project1",
|
||||
Location: "us-central1",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
// Add more test cases here
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset mockClient state
|
||||
mockClient.repositories = nil
|
||||
mockClient.locations = nil
|
||||
mockClient.dockerImages = nil
|
||||
|
||||
// Setup mock client for the current test case
|
||||
tc.setupMockClient(&mockClient)
|
||||
|
||||
repositories, err := ars.Repositories(tc.projectID)
|
||||
if (err != nil) != tc.expectError {
|
||||
t.Errorf("Repositories() error = %v, expectError %v", err, tc.expectError)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(repositories, tc.expectedRepositories) {
|
||||
t.Errorf("Repositories() = %v, expected %v", repositories, tc.expectedRepositories)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package artifactregistryservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
artifactregistrypb "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb"
|
||||
"github.com/BishopFox/cloudfox/gcp/services/models"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
locationpb "google.golang.org/genproto/googleapis/cloud/location"
|
||||
)
|
||||
|
||||
// CombinedRepoArtifactInfo holds the combined information of repositories and their respective artifacts.
|
||||
type CombinedRepoArtifactInfo struct {
|
||||
Repositories []RepositoryInfo `json:"repositories"`
|
||||
Artifacts []ArtifactInfo `json:"artifacts"`
|
||||
}
|
||||
|
||||
// ArtifactInfo represents the basic information of an artifact within a registry.
|
||||
type ArtifactInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
Version string `json:"version"`
|
||||
Location string `json:"location"`
|
||||
Repository string `json:"repository"`
|
||||
SizeBytes string `json:"virtualSize"`
|
||||
Updated string `json:"updated"`
|
||||
Digest string `json:"digest"`
|
||||
ProjectID string `json:"projectID"`
|
||||
}
|
||||
|
||||
// RepositoryInfo holds information about a repository and its artifacts.
|
||||
type RepositoryInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
Description string `json:"description"`
|
||||
SizeBytes string `json:"sizeBytes"`
|
||||
ProjectID string `json:"projectID"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
// DockerImageDetails holds the extracted parts from a Docker image name.
|
||||
type DockerImageDetails struct {
|
||||
ProjectID string `json:"projectID"`
|
||||
Location string `json:"location"`
|
||||
Repository string `json:"repository"`
|
||||
ImageName string `json:"name"`
|
||||
Digest string `json:"digest"`
|
||||
}
|
||||
|
||||
// Function calls to the API are wrapped to facilitate mocking
|
||||
type ArtifactRegistryClientWrapper struct {
|
||||
Closer func() error
|
||||
RepositoryLister func(ctx context.Context, req *artifactregistrypb.ListRepositoriesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.Repository]
|
||||
LocationLister func(ctx context.Context, req *locationpb.ListLocationsRequest, opts ...gax.CallOption) models.GenericIterator[locationpb.Location]
|
||||
RepositoryGetter func(ctx context.Context, req *artifactregistrypb.GetRepositoryRequest, opts ...gax.CallOption) (*artifactregistrypb.Repository, error)
|
||||
DockerImageLister func(ctx context.Context, req *artifactregistrypb.ListDockerImagesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.DockerImage]
|
||||
}
|
||||
|
||||
func (w *ArtifactRegistryClientWrapper) ListRepositories(ctx context.Context, req *artifactregistrypb.ListRepositoriesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.Repository] {
|
||||
return w.RepositoryLister(ctx, req, opts...)
|
||||
}
|
||||
|
||||
func (w *ArtifactRegistryClientWrapper) ListLocations(ctx context.Context, req *locationpb.ListLocationsRequest, opts ...gax.CallOption) models.GenericIterator[locationpb.Location] {
|
||||
return w.LocationLister(ctx, req, opts...)
|
||||
}
|
||||
|
||||
func (w *ArtifactRegistryClientWrapper) GetRepository(ctx context.Context, req *artifactregistrypb.GetRepositoryRequest, opts ...gax.CallOption) (*artifactregistrypb.Repository, error) {
|
||||
return w.RepositoryGetter(ctx, req, opts...)
|
||||
}
|
||||
|
||||
func (w *ArtifactRegistryClientWrapper) ListDockerImages(ctx context.Context, req *artifactregistrypb.ListDockerImagesRequest, opts ...gax.CallOption) models.GenericIterator[artifactregistrypb.DockerImage] {
|
||||
return w.DockerImageLister(ctx, req, opts...)
|
||||
}
|
||||
|
||||
// ArtifactRegistryService provides methods to interact with Artifact Registry resources.
|
||||
type ArtifactRegistryService struct {
|
||||
Client *ArtifactRegistryClientWrapper
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package bigqueryservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/bigquery"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
// BigqueryDataset represents a dataset in BigQuery
|
||||
type BigqueryDataset struct {
|
||||
DatasetID string
|
||||
Location string
|
||||
CreationTime time.Time
|
||||
LastModifiedTime time.Time
|
||||
Description string
|
||||
Name string
|
||||
ProjectID string
|
||||
}
|
||||
|
||||
// BigqueryTable represents a table in BigQuery
|
||||
type BigqueryTable struct {
|
||||
TableID string
|
||||
DatasetID string
|
||||
Location string
|
||||
CreationTime time.Time
|
||||
LastModifiedTime time.Time
|
||||
NumBytes int64
|
||||
Description string
|
||||
ProjectID string
|
||||
}
|
||||
|
||||
// CombinedBigqueryData represents both datasets and tables within a project
|
||||
type CombinedBigqueryData struct {
|
||||
Datasets []BigqueryDataset
|
||||
Tables []BigqueryTable
|
||||
}
|
||||
|
||||
type BigQueryService struct {
|
||||
// Placeholder for any required services or configuration
|
||||
}
|
||||
|
||||
// New creates a new instance of BigQueryService
|
||||
func New() *BigQueryService {
|
||||
return &BigQueryService{}
|
||||
}
|
||||
|
||||
// gcloud alpha bq datasets list
|
||||
// gcloud alpha bq datasets describe terragoat_dev_dataset
|
||||
// gcloud alpha bq tables list --dataset terragoat_dev_dataset
|
||||
// gcloud alpha bq tables describe bar --dataset terragoat_dev_dataset
|
||||
|
||||
// BigqueryDatasetsAndTables retrieves all datasets and their tables for a given projectID
|
||||
func (bq *BigQueryService) BigqueryDatasetsAndTables(projectID string) (CombinedBigqueryData, error) {
|
||||
// Initialize the combined struct to hold datasets and tables
|
||||
combinedData := CombinedBigqueryData{}
|
||||
|
||||
// Retrieve all datasets
|
||||
datasets, err := bq.BigqueryDatasets(projectID)
|
||||
if err != nil {
|
||||
return combinedData, err
|
||||
}
|
||||
combinedData.Datasets = datasets
|
||||
|
||||
// Iterate over each dataset to retrieve its tables
|
||||
for _, dataset := range datasets {
|
||||
tables, err := bq.BigqueryTables(projectID, dataset.DatasetID)
|
||||
if err != nil {
|
||||
return combinedData, err
|
||||
}
|
||||
combinedData.Tables = append(combinedData.Tables, tables...)
|
||||
}
|
||||
|
||||
return combinedData, nil
|
||||
}
|
||||
|
||||
// BigqueryDatasets retrieves datasets from the given projectID across all locations
|
||||
func (bq *BigQueryService) BigqueryDatasets(projectID string) ([]BigqueryDataset, error) {
|
||||
ctx := context.Background()
|
||||
client, err := bigquery.NewClient(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var datasets []BigqueryDataset
|
||||
it := client.Datasets(ctx)
|
||||
for {
|
||||
ds, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta, err := ds.Metadata(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
datasets = append(datasets, BigqueryDataset{
|
||||
DatasetID: ds.DatasetID,
|
||||
Location: meta.Location,
|
||||
CreationTime: meta.CreationTime,
|
||||
LastModifiedTime: meta.LastModifiedTime,
|
||||
Description: meta.Description,
|
||||
Name: meta.Name,
|
||||
ProjectID: projectID,
|
||||
})
|
||||
}
|
||||
return datasets, nil
|
||||
}
|
||||
|
||||
// BigqueryTables retrieves tables from the given projectID and dataset across all locations
|
||||
func (bq *BigQueryService) BigqueryTables(projectID string, datasetID string) ([]BigqueryTable, error) {
|
||||
ctx := context.Background()
|
||||
client, err := bigquery.NewClient(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var tables []BigqueryTable
|
||||
ds := client.Dataset(datasetID)
|
||||
it := ds.Tables(ctx)
|
||||
for {
|
||||
table, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta, err := table.Metadata(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tables = append(tables, BigqueryTable{
|
||||
TableID: table.TableID,
|
||||
DatasetID: datasetID,
|
||||
Location: meta.Location,
|
||||
CreationTime: meta.CreationTime,
|
||||
LastModifiedTime: meta.LastModifiedTime,
|
||||
NumBytes: meta.NumBytes,
|
||||
Description: meta.Description,
|
||||
ProjectID: projectID,
|
||||
})
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package bigqueryservice_test
|
||||
@@ -1,117 +0,0 @@
|
||||
package cloudstorageservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
type CloudStorageService struct {
|
||||
// DataStoreService datastoreservice.DataStoreService
|
||||
}
|
||||
|
||||
func New() *CloudStorageService {
|
||||
return &CloudStorageService{}
|
||||
}
|
||||
|
||||
// type ObjectInfo struct {
|
||||
// ObjectName string `json:"objecttName"`
|
||||
// ObjectSizeBytes float64 `json:"objectSizeBytes"`
|
||||
// IsPublic bool `json:"isPublic"`
|
||||
// }
|
||||
|
||||
type BucketInfo struct {
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
ProjectID string `json:"projectID"`
|
||||
}
|
||||
|
||||
func (cs *CloudStorageService) Buckets(projectID string) ([]BucketInfo, error) {
|
||||
ctx := context.Background()
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create client: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var buckets []BucketInfo
|
||||
bucketIterator := client.Buckets(ctx, projectID)
|
||||
for {
|
||||
battrs, err := bucketIterator.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bucket := BucketInfo{Name: battrs.Name, Location: battrs.Location, ProjectID: projectID}
|
||||
buckets = append(buckets, bucket)
|
||||
}
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
// func (cs *CloudStorageService) BucketsWithMetaData(projectID string) (map[string][]BucketInfo, error) {
|
||||
// buckets, _ := cs.Buckets(projectID)
|
||||
// bucketInfos := make(map[string][]BucketInfo)
|
||||
// ctx := context.Background()
|
||||
// client, err := storage.NewClient(ctx)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("Failed to create client: %v", err)
|
||||
// }
|
||||
// for {
|
||||
// bucketAttrs, err := buckets.Next()
|
||||
// if err == iterator.Done {
|
||||
// break
|
||||
// }
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to list buckets: %v", err)
|
||||
// }
|
||||
|
||||
// bucketName := bucketAttrs.Name
|
||||
// log.Printf("Working on bucket %s", bucketName)
|
||||
|
||||
// // List all objects in the bucket and calculate total size
|
||||
// totalSize := int64(0)
|
||||
// var objects []ObjectInfo
|
||||
// it := client.Bucket(bucketName).Objects(ctx, nil)
|
||||
// for {
|
||||
// objectAttrs, err := it.Next()
|
||||
// if err == iterator.Done {
|
||||
// break
|
||||
// }
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to list objects in bucket %s: %v", bucketName, err)
|
||||
// }
|
||||
|
||||
// // Get size
|
||||
// objectSize := objectAttrs.Size
|
||||
// totalSize += objectSize
|
||||
|
||||
// // Check if public
|
||||
// isPublic := false
|
||||
// for _, rule := range objectAttrs.ACL {
|
||||
// if rule.Entity == storage.AllUsers {
|
||||
// isPublic = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// objects = append(objects, ObjectInfo{ObjectName: objectAttrs.Name, ObjectSizeBytes: float64(objectSize), IsPublic: isPublic})
|
||||
|
||||
// if totalSize > 3221225472 { // 3 GiB in bytes
|
||||
// log.Printf("%s bucket is over 3 GiB. Skipping remaining objects in this bucket...", bucketName)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// bucketSizeMB := float64(totalSize) / 1024 / 1024
|
||||
// bucketInfos[projectID] = append(bucketInfos[projectID], BucketInfo{BucketName: bucketName, BucketSizeMB: bucketSizeMB, Objects: objects})
|
||||
// }
|
||||
// log.Printf("Sorting resulting list of buckets in descending order %s", projectID)
|
||||
// sort.Slice(bucketInfos[projectID], func(i, j int) bool {
|
||||
// return bucketInfos[projectID][i].BucketSizeMB > bucketInfos[projectID][j].BucketSizeMB
|
||||
// })
|
||||
|
||||
// return bucketInfos, nil
|
||||
// }
|
||||
@@ -1 +0,0 @@
|
||||
package cloudstorageservice_test
|
||||
@@ -1,99 +0,0 @@
|
||||
package computeengineservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
type ComputeEngineService struct {
|
||||
// DataStoreService datastoreservice.DataStoreService
|
||||
}
|
||||
|
||||
func New() *ComputeEngineService {
|
||||
return &ComputeEngineService{}
|
||||
}
|
||||
|
||||
type ComputeEngineInfo struct {
|
||||
Name string
|
||||
ID string
|
||||
Zone string
|
||||
State string
|
||||
ExternalIP string
|
||||
InternalIP string
|
||||
ServiceAccounts []*compute.ServiceAccount // Assuming role is derived from service accounts
|
||||
NetworkInterfaces []*compute.NetworkInterface
|
||||
Tags *compute.Tags
|
||||
ProjectID string
|
||||
}
|
||||
|
||||
// Retrieves instances from all regions and zones for a project without using concurrency.
|
||||
func (ces *ComputeEngineService) Instances(projectID string) ([]ComputeEngineInfo, error) {
|
||||
ctx := context.Background()
|
||||
computeService, err := compute.NewService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
regions, err := computeService.Regions.List(projectID).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instanceInfos []ComputeEngineInfo
|
||||
for _, region := range regions.Items {
|
||||
for _, zoneURL := range region.Zones {
|
||||
zone := getZoneNameFromURL(zoneURL)
|
||||
instanceList, err := computeService.Instances.List(projectID, zone).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving instances from zone %s: %v", zone, err)
|
||||
}
|
||||
for _, instance := range instanceList.Items {
|
||||
info := ComputeEngineInfo{
|
||||
Name: instance.Name,
|
||||
ID: fmt.Sprintf("%v", instance.Id),
|
||||
Zone: zoneURL,
|
||||
State: instance.Status,
|
||||
ExternalIP: getExternalIP(instance),
|
||||
InternalIP: getInternalIP(instance),
|
||||
ServiceAccounts: instance.ServiceAccounts,
|
||||
NetworkInterfaces: instance.NetworkInterfaces,
|
||||
Tags: instance.Tags,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
return instanceInfos, nil
|
||||
}
|
||||
|
||||
// Returns the zone from a GCP URL string with the zone in it
|
||||
func getZoneNameFromURL(zoneURL string) string {
|
||||
splits := strings.Split(zoneURL, "/")
|
||||
return splits[len(splits)-1]
|
||||
}
|
||||
|
||||
// getExternalIP extracts the external IP address from an instance if available.
|
||||
func getExternalIP(instance *compute.Instance) string {
|
||||
for _, iface := range instance.NetworkInterfaces {
|
||||
for _, accessConfig := range iface.AccessConfigs {
|
||||
if accessConfig.NatIP != "" {
|
||||
return accessConfig.NatIP
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getInternalIP extracts the internal IP address from an instance.
|
||||
func getInternalIP(instance *compute.Instance) string {
|
||||
if len(instance.NetworkInterfaces) > 0 {
|
||||
return instance.NetworkInterfaces[0].NetworkIP
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TODO consider just getting the emails of the service account and returning a []string
|
||||
@@ -1 +0,0 @@
|
||||
package computeengineservice
|
||||
@@ -1,93 +0,0 @@
|
||||
package iamservice
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "strings"
|
||||
// "time"
|
||||
// "github.com/fatih/color"
|
||||
// "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
// "github.com/BishopFox/cloudfox/internal"
|
||||
// "github.com/BishopFox/cloudfox/globals"
|
||||
// )
|
||||
|
||||
// type AccessTokensModule struct {
|
||||
// // Filtering data
|
||||
// Organizations []string
|
||||
// Folders []string
|
||||
// Projects []string
|
||||
// }
|
||||
|
||||
// // Be incorportated in iam.go
|
||||
// func (m *AccessTokensModule) PrintAccessTokens(outputFormat string, outputDirectory string, verbosity int) error {
|
||||
// GCPLogger.InfoM(fmt.Sprintf("Enumerating GCP local access tokens (%s, %s)...", color.CyanString("default user token"), color.RedString("application-default token")), globals.GCP_ACCESSTOKENS_MODULE_NAME)
|
||||
// tokens := gcp.ReadRefreshTokens()
|
||||
// accessTokens := gcp.ReadAccessTokens()
|
||||
// applicationdefaulthash := gcp.GetDefaultApplicationHash()
|
||||
// activeAccount := gcp.GetActiveAccount()
|
||||
|
||||
// var tableHead = []string{"Account", "Credential Type", "Validity", "Refresh Token", "Refresh Token needs password"}
|
||||
// var tableBody [][]string
|
||||
// for _, accessToken := range accessTokens {
|
||||
// // prepare token's associated ID (email or hash)
|
||||
// var emailinfo string
|
||||
// if (accessToken.AccountID == activeAccount) {
|
||||
// emailinfo = color.BlueString(accessToken.AccountID)
|
||||
// } else {
|
||||
// emailinfo = accessToken.AccountID
|
||||
// }
|
||||
|
||||
// // format expiration time
|
||||
// Exp, _ := time.Parse(time.RFC3339, accessToken.TokenExpiry)
|
||||
// var timeinfo string
|
||||
// if Exp.After(time.Now()) {
|
||||
// timeinfo = Exp.Format(time.RFC1123)
|
||||
// } else {
|
||||
// timeinfo = "EXPIRED"
|
||||
// }
|
||||
|
||||
// // display token type, user or application credential
|
||||
// var tokentype string
|
||||
// if (strings.Contains(accessToken.AccountID, "@")) {
|
||||
// tokentype = "User"
|
||||
// } else {
|
||||
// tokentype = "Application"
|
||||
// }
|
||||
// // is there an associated refres htoken ?
|
||||
// var refreshtokeninfo string
|
||||
// refreshtokeninfo = "No"
|
||||
// for _, refreshtoken := range tokens {
|
||||
// if (accessToken.AccountID == refreshtoken.Email) {
|
||||
// refreshtokeninfo = "Yes"
|
||||
// } else if (accessToken.AccountID == applicationdefaulthash) {
|
||||
// refreshtokeninfo = "Yes"
|
||||
// emailinfo = color.RedString(emailinfo)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // is there a proof of reauthentication to use the refresh
|
||||
// // token ? If not, password is needed
|
||||
// var raptinfo string
|
||||
// if refreshtokeninfo == "No" {
|
||||
// raptinfo = "-"
|
||||
// } else if accessToken.RaptToken.Valid {
|
||||
// raptinfo = "No"
|
||||
// } else {
|
||||
// if (tokentype == "Application") {
|
||||
// raptinfo = "TBD"
|
||||
// } else {
|
||||
// raptinfo = "Yes"
|
||||
// }
|
||||
// }
|
||||
// tableBody = append(
|
||||
// tableBody,
|
||||
// []string{
|
||||
// emailinfo,
|
||||
// tokentype,
|
||||
// timeinfo,
|
||||
// refreshtokeninfo,
|
||||
// raptinfo,
|
||||
// })
|
||||
// }
|
||||
// internal.PrintTableToScreen(tableHead, tableBody, false)
|
||||
// return nil
|
||||
// }
|
||||
@@ -1,189 +0,0 @@
|
||||
package iamservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
iampb "cloud.google.com/go/iam/apiv1/iampb"
|
||||
resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
|
||||
resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
)
|
||||
|
||||
type IAMService struct {
|
||||
// DataStoreService datastoreservice.DataStoreService
|
||||
}
|
||||
|
||||
func New() *IAMService {
|
||||
return &IAMService{}
|
||||
}
|
||||
|
||||
// AncestryResource represents a single resource in the project's ancestry.
|
||||
type AncestryResource struct {
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// PolicyBindings represents IAM policy bindings.
|
||||
type PolicyBinding struct {
|
||||
Role string `json:"role"`
|
||||
Members []string `json:"members"`
|
||||
ResourceID string `json:"resourceID"`
|
||||
ResourceType string
|
||||
PolicyName string `json:"policyBindings"`
|
||||
Condition string
|
||||
}
|
||||
|
||||
type PrincipalWithRoles struct {
|
||||
Name string
|
||||
Type string
|
||||
PolicyBindings []PolicyBinding
|
||||
ResourceID string
|
||||
ResourceType string
|
||||
}
|
||||
|
||||
var logger internal.Logger
|
||||
|
||||
func projectAncestry(projectID string) ([]AncestryResource, error) {
|
||||
ctx := context.Background()
|
||||
projectsClient, err := resourcemanager.NewProjectsClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create projects client: %v", err)
|
||||
}
|
||||
defer projectsClient.Close()
|
||||
|
||||
foldersClient, err := resourcemanager.NewFoldersClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create folders client: %v", err)
|
||||
}
|
||||
defer foldersClient.Close()
|
||||
|
||||
resourceID := "projects/" + projectID
|
||||
var ancestry []AncestryResource
|
||||
|
||||
for {
|
||||
if strings.HasPrefix(resourceID, "organizations/") {
|
||||
ancestry = append(ancestry, AncestryResource{Type: "organization", Id: strings.TrimPrefix(resourceID, "organizations/")})
|
||||
break
|
||||
} else if strings.HasPrefix(resourceID, "folders/") {
|
||||
resp, err := foldersClient.GetFolder(ctx, &resourcemanagerpb.GetFolderRequest{Name: resourceID})
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("failed to access folder %s, %v", resourceID, err), globals.GCP_IAM_MODULE_NAME)
|
||||
break // Stop processing further if a folder is inaccessible
|
||||
}
|
||||
ancestry = append(ancestry, AncestryResource{Type: "folder", Id: strings.TrimPrefix(resp.Name, "folders/")})
|
||||
resourceID = resp.Parent
|
||||
} else if strings.HasPrefix(resourceID, "projects/") {
|
||||
resp, err := projectsClient.GetProject(ctx, &resourcemanagerpb.GetProjectRequest{Name: resourceID})
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("failed to access project %s, %v", resourceID, err), globals.GCP_IAM_MODULE_NAME)
|
||||
return nil, fmt.Errorf("failed to get project: %v", err)
|
||||
}
|
||||
ancestry = append(ancestry, AncestryResource{Type: "project", Id: strings.TrimPrefix(resp.Name, "projects/")})
|
||||
resourceID = resp.Parent
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown resource type for: %s", resourceID)
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the slice as we've built it from child to ancestor
|
||||
for i, j := 0, len(ancestry)-1; i < j; i, j = i+1, j-1 {
|
||||
ancestry[i], ancestry[j] = ancestry[j], ancestry[i]
|
||||
}
|
||||
|
||||
return ancestry, nil
|
||||
}
|
||||
|
||||
// Policies fetches IAM policy for a given resource and all policies in resource ancestry
|
||||
func (s *IAMService) Policies(resourceID string, resourceType string) ([]PolicyBinding, error) {
|
||||
ctx := context.Background()
|
||||
client, err := resourcemanager.NewProjectsClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resourcemanager.NewProjectsClient: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var resourceName string
|
||||
switch resourceType {
|
||||
case "project":
|
||||
resourceName = "projects/" + resourceID
|
||||
case "folder":
|
||||
resourceName = "folders/" + resourceID
|
||||
case "organization":
|
||||
resourceName = "organizations/" + resourceID
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported resource type: %s", resourceType)
|
||||
}
|
||||
|
||||
req := &iampb.GetIamPolicyRequest{
|
||||
Resource: resourceName,
|
||||
}
|
||||
|
||||
// Fetch the IAM policy for the resource
|
||||
policy, err := client.GetIamPolicy(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client.GetIamPolicy: %v", err)
|
||||
}
|
||||
|
||||
// Assemble the policy bindings
|
||||
var policyBindings []PolicyBinding
|
||||
for _, binding := range policy.Bindings {
|
||||
policyBinding := PolicyBinding{
|
||||
Role: binding.Role,
|
||||
Members: binding.Members,
|
||||
ResourceID: resourceID,
|
||||
ResourceType: resourceType,
|
||||
Condition: binding.Condition.String(),
|
||||
PolicyName: resourceName + "_policyBindings",
|
||||
}
|
||||
policyBindings = append(policyBindings, policyBinding)
|
||||
}
|
||||
|
||||
return policyBindings, nil
|
||||
}
|
||||
|
||||
func determinePrincipalType(member string) string {
|
||||
if strings.HasPrefix(member, "user:") {
|
||||
return "User"
|
||||
} else if strings.HasPrefix(member, "serviceAccount:") {
|
||||
return "ServiceAccount"
|
||||
} else if strings.HasPrefix(member, "group:") {
|
||||
return "Group"
|
||||
} else {
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IAMService) PrincipalsWithRoles(resourceID string, resourceType string) ([]PrincipalWithRoles, error) {
|
||||
policyBindings, err := s.Policies(resourceID, resourceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
principalMap := make(map[string]*PrincipalWithRoles)
|
||||
for _, pb := range policyBindings {
|
||||
for _, member := range pb.Members {
|
||||
principalType := determinePrincipalType(member) // Implement this function based on member prefix
|
||||
if principal, ok := principalMap[member]; ok {
|
||||
principal.PolicyBindings = append(principal.PolicyBindings, pb)
|
||||
} else {
|
||||
principalMap[member] = &PrincipalWithRoles{
|
||||
Name: member,
|
||||
Type: principalType,
|
||||
PolicyBindings: []PolicyBinding{pb},
|
||||
ResourceID: resourceID,
|
||||
ResourceType: resourceType,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var principals []PrincipalWithRoles
|
||||
for _, principal := range principalMap {
|
||||
principals = append(principals, *principal)
|
||||
}
|
||||
|
||||
return principals, nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package iamservice_test
|
||||
@@ -1,5 +0,0 @@
|
||||
package models
|
||||
|
||||
type GenericIterator[T any] interface {
|
||||
Next() (*T, error)
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
package networkservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
ComputeEngineService "github.com/BishopFox/cloudfox/gcp/services/computeEngineService"
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// VPC / network / subnets
|
||||
// gcloud compute networks list
|
||||
// gcloud compute networks get-effective-firewalls terragoat-dev-network
|
||||
// gcloud compute networks subnets list
|
||||
// FirewallRule structure for JSON output
|
||||
|
||||
// firewall-rules
|
||||
// gcloud compute firewall-rules list - `terragoat-dev-firewall`
|
||||
|
||||
// forwarding rules (load balancers)
|
||||
// gcloud compute forwarding-rules list
|
||||
|
||||
// Router & NAT gateways
|
||||
// gcloud compute routers list
|
||||
// gcloud compute routers nats list --router
|
||||
|
||||
// Global network firewall policy
|
||||
// gcloud compute network-firewall-policies
|
||||
// gcloud compute network-firewall-policies rules list
|
||||
// gcloud compute network-firewall-policies associations list
|
||||
|
||||
type FirewallRule struct {
|
||||
FWName string `json:"fw_name"`
|
||||
Description string `json:"description"`
|
||||
Direction string `json:"direction"`
|
||||
Allowed map[string][]string `json:"allowed"`
|
||||
Denied map[string][]string `json:"denied"`
|
||||
SourceRanges []string `json:"source_ranges"`
|
||||
TargetTag string `json:"target_tag"`
|
||||
AllowedTraffic Traffic `json:"allowed_traffic"`
|
||||
ExposedEndpoints []Endpoint `json:"exposed_endpoints"`
|
||||
DestinationRanges []string `json:"destinatioRanges"`
|
||||
TargetTags []string `json:"targetTags"`
|
||||
}
|
||||
|
||||
type Traffic struct {
|
||||
TCP []string `json:"tcp"`
|
||||
UDP []string `json:"udp"`
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
IP string `json:"ip"`
|
||||
Ports []string `json:"ports"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
type NetwworkService struct {
|
||||
// DataStoreService datastoreservice.DataStoreService
|
||||
}
|
||||
|
||||
func New() *NetwworkService {
|
||||
return &NetwworkService{}
|
||||
}
|
||||
|
||||
// Returns firewall rules for a project.
|
||||
func (ns *NetwworkService) FirewallRules(projectID string) ([]*compute.Firewall, error) {
|
||||
ctx := context.Background()
|
||||
computeService, err := compute.NewService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
firewallList, err := computeService.Firewalls.List(projectID).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return firewallList.Items, nil
|
||||
}
|
||||
|
||||
// Returns firewall rules with additional logic to tag rules that expose resources to the public
|
||||
func (ns *NetwworkService) FirewallRulesWithPublicExposure(projectID string) ([]FirewallRule, error) {
|
||||
var results []FirewallRule
|
||||
|
||||
log.Printf("Retrieving firewall rules for project %s", projectID)
|
||||
firewalls, err := ns.FirewallRules(projectID)
|
||||
log.Print("Done")
|
||||
if err != nil {
|
||||
log.Printf("Error fetching firewall rules for project %s: %v", projectID, err)
|
||||
return nil, err
|
||||
}
|
||||
log.Print("Parsing firewall rules")
|
||||
for _, fw := range firewalls {
|
||||
parsedRule, err := parseFirewallRule(fw, projectID)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing firewall rule for project %s: %v", projectID, err)
|
||||
continue
|
||||
}
|
||||
results = append(results, parsedRule)
|
||||
}
|
||||
log.Print("Done")
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Returns a list of IPs that are mapped to a given tag. Uses a list of instances
|
||||
func getIPAddressesForTargetTag(instances []ComputeEngineService.ComputeEngineInfo, tag string) ([]string, error) {
|
||||
var ips []string
|
||||
for _, instance := range instances {
|
||||
if contains(instance.Tags.Items, tag) {
|
||||
ips = append(ips, instance.NetworkInterfaces[0].NetworkIP)
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// Returns the zone from a GCP URL string with the zone in it
|
||||
func getZoneNameFromURL(zoneURL string) string {
|
||||
splits := strings.Split(zoneURL, "/")
|
||||
return splits[len(splits)-1]
|
||||
}
|
||||
|
||||
// Returns true of a string is in the given list of strings. Else false
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, a := range slice {
|
||||
if a == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if a string in compute.Firewall.SourceRanges is "0.0.0.0/0"
|
||||
func isExposedToInternet(fw *compute.Firewall) bool {
|
||||
for _, rangeVal := range fw.SourceRanges {
|
||||
if rangeVal == "0.0.0.0/0" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if the IP is an internal ipv4 or ipv6 IP
|
||||
func IsInternalIP(cidr string) bool {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
// Try parsing as just an IP (not CIDR)
|
||||
ip = net.ParseIP(cidr)
|
||||
if ip == nil {
|
||||
log.Printf("Error parsing input %s\n", cidr)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check for IPv4 private ranges
|
||||
if ip.To4() != nil {
|
||||
for _, privateCIDR := range []string{
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(privateCIDR)
|
||||
if block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for IPv6 ULA range fc00::/7
|
||||
// This checks if the first byte of the address is in the range 0xfc to 0xfd
|
||||
if ip[0] == 0xfc || ip[0] == 0xfd {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getPublicEndpoints fetches the endpoints exposed to the internet
|
||||
// and, if a TargetTag is provided, gets the IP associated with it.
|
||||
func getPublicEndpoints(fw *compute.Firewall, projectID string) ([]Endpoint, error) {
|
||||
var exposedEndpoints []Endpoint
|
||||
ces := ComputeEngineService.New()
|
||||
if isExposedToInternet(fw) {
|
||||
for _, allowed := range fw.Allowed {
|
||||
// Handle destination ranges
|
||||
for _, destRange := range fw.DestinationRanges {
|
||||
if !IsInternalIP(destRange) {
|
||||
exposed := Endpoint{
|
||||
IP: destRange,
|
||||
Ports: allowed.Ports,
|
||||
Protocol: allowed.IPProtocol,
|
||||
}
|
||||
exposedEndpoints = append(exposedEndpoints, exposed)
|
||||
}
|
||||
}
|
||||
|
||||
// If TargetTag is provided, fetch the associated IP addresses.
|
||||
if len(fw.TargetTags) > 0 {
|
||||
log.Printf("Target tags found, getting their IP...")
|
||||
instances, err := ces.Instances(projectID)
|
||||
if err != nil {
|
||||
log.Printf("Error retrieving instances: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, tag := range fw.TargetTags {
|
||||
ips, err := getIPAddressesForTargetTag(instances, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if !IsInternalIP(ip) {
|
||||
exposed := Endpoint{
|
||||
IP: ip,
|
||||
Ports: allowed.Ports,
|
||||
Protocol: allowed.IPProtocol,
|
||||
}
|
||||
exposedEndpoints = append(exposedEndpoints, exposed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return exposedEndpoints, nil
|
||||
}
|
||||
|
||||
// The original parseFirewallRule function
|
||||
func parseFirewallRule(fw *compute.Firewall, projectID string) (FirewallRule, error) {
|
||||
var exposedEndpoints []Endpoint
|
||||
trafficAllowed := make(map[string][]string)
|
||||
trafficDenied := make(map[string][]string)
|
||||
|
||||
for _, allowed := range fw.Allowed {
|
||||
trafficAllowed[allowed.IPProtocol] = append(trafficAllowed[allowed.IPProtocol], allowed.Ports...)
|
||||
}
|
||||
|
||||
exposedEndpoints, err := getPublicEndpoints(fw, projectID)
|
||||
if err != nil {
|
||||
return FirewallRule{}, err
|
||||
}
|
||||
|
||||
for _, denied := range fw.Denied {
|
||||
trafficDenied[denied.IPProtocol] = append(trafficDenied[denied.IPProtocol], denied.Ports...)
|
||||
}
|
||||
|
||||
return FirewallRule{
|
||||
FWName: fw.Name,
|
||||
Description: fw.Description,
|
||||
Direction: fw.Direction,
|
||||
Allowed: trafficAllowed,
|
||||
Denied: trafficDenied,
|
||||
SourceRanges: fw.SourceRanges,
|
||||
TargetTag: strings.Join(fw.TargetTags, ","),
|
||||
ExposedEndpoints: exposedEndpoints,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO
|
||||
// func (ns *NetworkService) ForwardingRules() {}
|
||||
@@ -1,35 +0,0 @@
|
||||
package networkservice_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
networkservice "github.com/BishopFox/cloudfox/gcp/services/networkService"
|
||||
)
|
||||
|
||||
func TestIsInternalIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect bool
|
||||
}{
|
||||
// IPv4 tests
|
||||
{"10.1.2.3", true}, // falls within 10.0.0.0/8
|
||||
{"172.17.0.1", true}, // falls within 172.16.0.0/12
|
||||
{"192.168.10.1", true}, // falls within 192.168.0.0/16
|
||||
{"11.1.2.3", false}, // public IP
|
||||
{"172.32.0.1", false}, // public IP
|
||||
{"192.169.0.1", false}, // public IP
|
||||
{"8.8.8.8", false}, // public IP
|
||||
// IPv6 tests
|
||||
{"fc00::1", true}, // falls within fc00::/7
|
||||
{"fd00::1", true}, // falls within fd00::/8
|
||||
{"fe80::1", false}, // link-local address, not ULA
|
||||
{"2001:0db8:85a3::8a2e", false}, // public IP
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := networkservice.IsInternalIP(test.input)
|
||||
if result != test.expect {
|
||||
t.Errorf("IsInternalIP(%s) = %v; want %v", test.input, result, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package oauthservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
// Principal struct modified to map JSON response fields.
|
||||
type Principal struct {
|
||||
Email string `json:"email"`
|
||||
Scope []string
|
||||
}
|
||||
|
||||
// Temporary struct to match the JSON structure.
|
||||
type tokenInfoResponse struct {
|
||||
Email string `json:"email"`
|
||||
Scope string `json:"scope"` // Keep as string to match JSON.
|
||||
}
|
||||
|
||||
// OAuthService struct definition (unchanged)
|
||||
type OAuthService struct {
|
||||
// This struct can be expanded in the future if necessary.
|
||||
}
|
||||
|
||||
// NewOAuthService creates a new instance of OAuthService.
|
||||
func NewOAuthService() *OAuthService {
|
||||
return &OAuthService{}
|
||||
}
|
||||
|
||||
// WhoAmI function returns the Principal struct showing the email and scope of the currently authenticated user by generating an oauth token
|
||||
func (s *OAuthService) WhoAmI() (*Principal, error) {
|
||||
ctx := context.Background()
|
||||
ts, err := google.DefaultTokenSource(ctx, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain default token source: %v", err)
|
||||
}
|
||||
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token from token source: %v", err)
|
||||
}
|
||||
|
||||
tokenInfo, err := queryTokenInfo(token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("failed to retrieve metada of the token with error: %s", err.Error()))
|
||||
}
|
||||
// Split the scope string into a slice of strings.
|
||||
scopes := strings.Split(tokenInfo.Scope, " ")
|
||||
|
||||
return &Principal{
|
||||
Email: tokenInfo.Email,
|
||||
Scope: scopes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// queryTokenInfo function modified to return (*Principal, error).
|
||||
func queryTokenInfo(accessToken string) (*tokenInfoResponse, error) {
|
||||
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s", accessToken)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request to tokeninfo endpoint: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading tokeninfo response body: %v", err)
|
||||
}
|
||||
|
||||
var response tokenInfoResponse
|
||||
// Unmarshal the JSON response body into the Principal struct.
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling tokeninfo response body: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package secretservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
||||
secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
// Wrappers and abstracting types to facilitate mocking the client responses
|
||||
type Iterator interface {
|
||||
Next() (*secretmanagerpb.Secret, error)
|
||||
}
|
||||
|
||||
type SecretsManagerClientWrapper struct {
|
||||
Closer func() error
|
||||
SecretLister func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator
|
||||
}
|
||||
|
||||
func (w *SecretsManagerClientWrapper) Close() error {
|
||||
return w.Closer()
|
||||
}
|
||||
|
||||
func (w *SecretsManagerClientWrapper) ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator {
|
||||
return w.SecretLister(ctx, req, opts...)
|
||||
|
||||
}
|
||||
|
||||
type SecretsService struct {
|
||||
Client *SecretsManagerClientWrapper
|
||||
}
|
||||
|
||||
// New function to facilitate using the ss client
|
||||
func New(client *secretmanager.Client) SecretsService {
|
||||
ss := SecretsService{
|
||||
Client: &SecretsManagerClientWrapper{
|
||||
Closer: client.Close,
|
||||
SecretLister: func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator {
|
||||
return client.ListSecrets(ctx, req, opts...)
|
||||
},
|
||||
},
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Name string `json:"name"`
|
||||
ProjectID string `json:"projectID"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Rotation string `json:"rotation,omitempty"`
|
||||
}
|
||||
|
||||
func (ss *SecretsService) Secrets(projectID string) ([]SecretInfo, error) {
|
||||
var secrets []SecretInfo
|
||||
req := &secretmanagerpb.ListSecretsRequest{
|
||||
Parent: fmt.Sprintf("projects/%s", projectID),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
it := ss.Client.ListSecrets(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next() //Here it errors out
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list secrets: %v", err)
|
||||
}
|
||||
|
||||
secrets = append(secrets, SecretInfo{
|
||||
Name: resp.Name,
|
||||
ProjectID: projectID,
|
||||
CreationTime: resp.CreateTime.AsTime().String(),
|
||||
Labels: resp.Labels,
|
||||
Rotation: resp.Rotation.String(),
|
||||
})
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package secretservice_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
|
||||
secretservice "github.com/BishopFox/cloudfox/gcp/services/secretsService"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type mockSecretManagerClient struct {
|
||||
secrets []*secretmanagerpb.Secret
|
||||
}
|
||||
|
||||
func (m *mockSecretManagerClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SecretIterator simulates the iterator over secrets.
|
||||
type SecretIterator struct {
|
||||
secrets []*secretmanagerpb.Secret // Current secrets batch
|
||||
nextIndex int // Index of the next secret to return
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*secretmanagerpb.Secret, nextPageToken string, err error)
|
||||
currentPageToken string
|
||||
}
|
||||
|
||||
func (it *SecretIterator) Next() (*secretmanagerpb.Secret, error) {
|
||||
// If we have reached the end of the current secrets batch, attempt to fetch the next batch
|
||||
if it.nextIndex >= len(it.secrets) {
|
||||
var err error
|
||||
it.secrets, it.currentPageToken, err = it.InternalFetch(10, it.currentPageToken) // Assuming a fixed page size for simplicity
|
||||
if err != nil {
|
||||
return nil, err // Propagate errors from fetch
|
||||
}
|
||||
it.nextIndex = 0 // Reset the index for the new batch
|
||||
|
||||
// If the fetched batch is empty, we have reached the end of the iteration
|
||||
if len(it.secrets) == 0 {
|
||||
return nil, iterator.Done
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the next secret and increment the index
|
||||
secret := it.secrets[it.nextIndex]
|
||||
it.nextIndex++
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// ListSecrets simulates the ListSecrets method of the SecretManagerClient interface.
|
||||
func (m *mockSecretManagerClient) ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) secretservice.Iterator {
|
||||
it := &SecretIterator{}
|
||||
|
||||
// Copy secrets to avoid modifying the original slice
|
||||
allSecrets := make([]*secretmanagerpb.Secret, len(m.secrets))
|
||||
copy(allSecrets, m.secrets)
|
||||
|
||||
// Simulate internal fetch by setting InternalFetch to a function that returns the next set of secrets
|
||||
nextIndex := 0
|
||||
it.InternalFetch = func(pageSize int, pageToken string) (results []*secretmanagerpb.Secret, nextPageToken string, err error) {
|
||||
// Check if we have reached the end of the secrets slice
|
||||
if nextIndex >= len(allSecrets) {
|
||||
return nil, "", iterator.Done
|
||||
}
|
||||
|
||||
// Determine the slice of secrets to return
|
||||
endIndex := nextIndex + pageSize
|
||||
if endIndex > len(allSecrets) {
|
||||
endIndex = len(allSecrets)
|
||||
}
|
||||
results = allSecrets[nextIndex:endIndex]
|
||||
nextIndex = endIndex
|
||||
|
||||
// Simulate the nextPageToken logic (omitted for brevity, always return empty string in this mock)
|
||||
nextPageToken = ""
|
||||
return results, nextPageToken, nil
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
mockClient := &mockSecretManagerClient{}
|
||||
ss := secretservice.SecretsService{
|
||||
Client: &secretservice.SecretsManagerClientWrapper{
|
||||
Closer: mockClient.Close,
|
||||
SecretLister: func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) secretservice.Iterator {
|
||||
return mockClient.ListSecrets(ctx, req, opts...)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
projectID string
|
||||
secrets []*secretmanagerpb.Secret
|
||||
want []secretservice.SecretInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Retrieve secrets successfully",
|
||||
projectID: "my-project",
|
||||
secrets: []*secretmanagerpb.Secret{
|
||||
{
|
||||
Name: "projects/my-project/secrets/secret1",
|
||||
Labels: map[string]string{"env": "test"},
|
||||
CreateTime: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
want: []secretservice.SecretInfo{
|
||||
{
|
||||
Name: "projects/my-project/secrets/secret1",
|
||||
ProjectID: "my-project",
|
||||
CreationTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).String(),
|
||||
Labels: map[string]string{"env": "test"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "No secrets found",
|
||||
projectID: "empty-project",
|
||||
secrets: []*secretmanagerpb.Secret{},
|
||||
want: []secretservice.SecretInfo{},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient.secrets = tt.secrets
|
||||
got, err := ss.Secrets(tt.projectID) // Adapt this call to match the actual method signature
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("[%s] Secrets() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("[%s] Secrets() got length = %v, want length %v", tt.name, len(got), len(tt.want))
|
||||
return
|
||||
}
|
||||
|
||||
for i, g := range got {
|
||||
w := tt.want[i]
|
||||
|
||||
// Compare Name, Labels, and CreationTime as string
|
||||
if g.Name != w.Name || !reflect.DeepEqual(g.Labels, w.Labels) || g.CreationTime != w.CreationTime {
|
||||
t.Errorf("Secrets() got = %v, want %v", g, w)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1.13.0-prerelease
|
||||
@@ -21,6 +21,8 @@ const AZ_INVENTORY_MODULE_NAME = "inventory"
|
||||
const AZ_VMS_MODULE_NAME = "vms"
|
||||
const AZ_RBAC_MODULE_NAME = "rbac"
|
||||
const AZ_STORAGE_MODULE_NAME = "storage"
|
||||
const AZ_NSG_LINKS_MODULE_NAME = "nsg-links"
|
||||
const AZ_NSG_RULES_MODULE_NAME = "nsg-rules"
|
||||
|
||||
// Microsoft endpoints
|
||||
const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package globals
|
||||
|
||||
// Module names
|
||||
// const GCP_WHOAMI_MODULE_NAME = "whoami"
|
||||
const GCP_ARTIFACT_RESGISTRY_MODULE_NAME string = "artifact-registry"
|
||||
const GCP_BIGQUERY_MODULE_NAME string = "bigquery"
|
||||
const GCP_BUCKETS_MODULE_NAME string = "buckets"
|
||||
const GCP_INSTANCES_MODULE_NAME string = "instances"
|
||||
const GCP_IAM_MODULE_NAME string = "iam"
|
||||
const GCP_SECRETS_MODULE_NAME string = "secrets"
|
||||
const GCP_WHOAMI_MODULE_NAME string = "whoami"
|
||||
|
||||
// const GCP_INVENTORY_MODULE_NAME string = "inventory"
|
||||
// const GCP_GCLOUD_REFRESH_TOKENS_DB_PATH = ".config/gcloud/credentials.db"
|
||||
// const GCP_GCLOUD_ACCESS_TOKENS_DB_PATH = ".config/gcloud/access_tokens.db"
|
||||
// const GCP_GCLOUD_DEFAULT_CONFIG_PATH = ".config/gcloud/configurations/config_default"
|
||||
// const GCP_GCLOUD_APPLICATION_DEFAULT_PATH = ".config/gcloud/application_default_credentials.json"
|
||||
+8
-1
@@ -1,7 +1,14 @@
|
||||
package globals
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const CLOUDFOX_USER_AGENT = "cloudfox"
|
||||
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
|
||||
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
|
||||
const LOOT_DIRECTORY_NAME = "loot"
|
||||
const CLOUDFOX_VERSION = "1.13.5"
|
||||
var CLOUDFOX_VERSION string = strings.TrimSpace(version)
|
||||
//go:embed VERSION
|
||||
var version string
|
||||
@@ -3,12 +3,6 @@ module github.com/BishopFox/cloudfox
|
||||
go 1.21.2
|
||||
|
||||
require (
|
||||
cloud.google.com/go/artifactregistry v1.14.6
|
||||
cloud.google.com/go/bigquery v1.57.1
|
||||
cloud.google.com/go/iam v1.1.5
|
||||
cloud.google.com/go/resourcemanager v1.9.4
|
||||
cloud.google.com/go/secretmanager v1.11.4
|
||||
cloud.google.com/go/storage v1.35.1
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
|
||||
@@ -16,7 +10,7 @@ require (
|
||||
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.24.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.2
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6
|
||||
@@ -58,10 +52,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6
|
||||
github.com/aws/smithy-go v1.19.0
|
||||
github.com/bishopfox/awsservicemap v1.0.3
|
||||
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/googleapis/gax-go/v2 v2.12.0
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
@@ -69,45 +61,11 @@ require (
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
google.golang.org/api v0.152.0
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17
|
||||
google.golang.org/protobuf v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/apache/arrow/go/v12 v12.0.0 // indirect
|
||||
github.com/apache/thrift v0.16.0 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/flatbuffers v2.0.8+incompatible // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/klauspost/asmfmt v1.3.2 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -127,8 +85,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.13
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3
|
||||
@@ -142,7 +100,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
|
||||
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
|
||||
cloud.google.com/go/artifactregistry v1.14.6 h1:/hQaadYytMdA5zBh+RciIrXZQBWK4vN7EUsrQHG+/t8=
|
||||
cloud.google.com/go/artifactregistry v1.14.6/go.mod h1:np9LSFotNWHcjnOgh8UVK0RFPCTUGbO0ve3384xyHfE=
|
||||
cloud.google.com/go/bigquery v1.57.1 h1:FiULdbbzUxWD0Y4ZGPSVCDLvqRSyCIO6zKV7E2nf5uA=
|
||||
cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datacatalog v1.18.3 h1:zmdxP6nOjN5Qb1rtu9h4kbEVwerQ6Oshf+t747QJUew=
|
||||
cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
|
||||
cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
|
||||
cloud.google.com/go/resourcemanager v1.9.4 h1:JwZ7Ggle54XQ/FVYSBrMLOQIKoIT/uer8mmNvNLK51k=
|
||||
cloud.google.com/go/resourcemanager v1.9.4/go.mod h1:N1dhP9RFvo3lUfwtfLWVxfUWq8+KUQ+XLlHLH3BoFJ0=
|
||||
cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k=
|
||||
cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w=
|
||||
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
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.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
|
||||
@@ -30,13 +7,10 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
|
||||
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/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1 h1:AMf7YbZOZIW5b66cXNHMWWT/zkjhz5+a+k/3x40EO7E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1/go.mod h1:uwfk06ZBcvL/g4VHNjurPfVln9NMbsk2XIZxJ+hu81k=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
@@ -68,21 +42,12 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc=
|
||||
github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg=
|
||||
github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY=
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
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-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.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.2 h1:+RWLEIWQIGgrz2pBPAUoGgNGs1TOyF4Hml7hCnYj2jc=
|
||||
@@ -91,10 +56,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.16.13 h1:WLABQ4Cp4vXtXfOWOS3MEZKr6AA
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.13/go.mod h1:Qg6x82FXwW0sJHzYruxGiuApNo31UEtJvXVSZAXeWiw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
|
||||
@@ -163,8 +128,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucB
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10 h1:h8uweImUHGgyNKrxIUwpPs6XiH0a6DJ17hSJvFLgPAo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.10/go.mod h1:LZKVtMBiZfdvUWgwg61Qo6kyAmE5rn9Dw36AqnycvG8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA=
|
||||
@@ -215,9 +180,6 @@ github.com/bishopfox/awsservicemap v1.0.3 h1:0T+mJLwG+vQV9+o3dzwzxhWJWE40VpoCLWt
|
||||
github.com/bishopfox/awsservicemap v1.0.3/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
|
||||
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb h1:ot96tC/kdm0GKV1kl+aXJorqJbyx92R9bjRQvbBmLKU=
|
||||
github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb/go.mod h1:2OnSqu4B86+2xGSIE5D4z3Rze9yJ/LNNjNXHhwMR+vY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -225,72 +187,25 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
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.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
github.com/dominikbraun/graph v0.23.0/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
|
||||
github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho=
|
||||
github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
|
||||
github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=
|
||||
github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
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=
|
||||
@@ -299,17 +214,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
|
||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
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=
|
||||
@@ -321,10 +226,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||
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.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
@@ -334,18 +235,13 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
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/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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/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.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@@ -362,26 +258,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/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=
|
||||
@@ -389,43 +275,17 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -444,7 +304,6 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/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=
|
||||
@@ -452,67 +311,15 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
|
||||
gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
|
||||
google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
|
||||
google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1,119 +0,0 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
)
|
||||
|
||||
type TrustPolicyDocument struct {
|
||||
Version string `json:"Version"`
|
||||
Statement []RoleTrustStatementEntry `json:"Statement"`
|
||||
}
|
||||
|
||||
type RoleTrustStatementEntry struct {
|
||||
Sid string `json:"Sid"`
|
||||
Effect string `json:"Effect"`
|
||||
Principal struct {
|
||||
AWS ListOfPrincipals `json:"AWS"`
|
||||
Service ListOfPrincipals `json:"Service"`
|
||||
Federated ListOfPrincipals `json:"Federated"`
|
||||
} `json:"Principal"`
|
||||
Action string `json:"Action"`
|
||||
Condition struct {
|
||||
StringEquals struct {
|
||||
StsExternalID string `json:"sts:ExternalId"`
|
||||
SAMLAud string `json:"SAML:aud"`
|
||||
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
|
||||
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
|
||||
OidcEksSub ListOfPrincipals `json:"OidcEksSub"`
|
||||
OidcEksAud string `json:"OidcEksAud"`
|
||||
CognitoAud string `json:"cognito-identity.amazonaws.com:aud"`
|
||||
TerraformAud string `json:"app.terraform.io:aud"` // Terraform Cloud specific
|
||||
TerraformSub ListOfPrincipals `json:"app.terraform.io:sub"` // Terraform Cloud specific
|
||||
GCPAud string `json:"accounts.google.com:aud"`
|
||||
GCPSub ListOfPrincipals `json:"accounts.google.com:sub"`
|
||||
AzureADIss ListOfPrincipals `json:"http://sts.windows.net/tenant-id/iss"` // Azure AD specific
|
||||
AzureADSub ListOfPrincipals `json:"sub"` // Common among OIDC providers
|
||||
PingClientId string `json:"pingidentity.com:client_id"`
|
||||
GoogleWorkspaceAud string `json:"workspace.google.com:aud"`
|
||||
GoogleWorkspaceSub ListOfPrincipals `json:"workspace.google.com:sub"`
|
||||
CircleCIAud ListOfPrincipals `json:"CircleCIAud"`
|
||||
CircleCISub ListOfPrincipals `json:"CircleCISub"`
|
||||
// Add other provider-specific claims here
|
||||
} `json:"StringEquals"`
|
||||
StringLike struct {
|
||||
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
|
||||
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
|
||||
OidcEksSub ListOfPrincipals `json:"OidcEksSub"`
|
||||
OidcEksAud string `json:"OidcEksAud"`
|
||||
TerraformAud string `json:"app.terraform.io:aud"` // Terraform Cloud specific
|
||||
TerraformSub ListOfPrincipals `json:"app.terraform.io:sub"` // Terraform Cloud specific
|
||||
GCPAud string `json:"accounts.google.com:aud"`
|
||||
GCPSub ListOfPrincipals `json:"accounts.google.com:sub"`
|
||||
AzureADIss ListOfPrincipals `json:"http://sts.windows.net/tenant-id/iss"` // Azure AD specific
|
||||
AzureADSub ListOfPrincipals `json:"sub"` // Common among OIDC providers
|
||||
PingClientId string `json:"pingidentity.com:client_id"`
|
||||
GoogleWorkspaceAud string `json:"workspace.google.com:aud"`
|
||||
GoogleWorkspaceSub ListOfPrincipals `json:"workspace.google.com:sub"`
|
||||
CircleCIAud ListOfPrincipals `json:"CircleCIAud"`
|
||||
CircleCISub ListOfPrincipals `json:"CircleCISub"`
|
||||
// Add patterns for provider-specific claims that support wildcards or partial matches
|
||||
} `json:"StringLike"`
|
||||
ForAnyValueStringLike struct {
|
||||
CognitoAMR string `json:"cognito-identity.amazonaws.com:amr"`
|
||||
//Auth0Amr ListOfPrincipals `json:"Auth0Amr"`
|
||||
// This section can be extended with any-value-like conditions for other providers if they support such conditions
|
||||
} `json:"ForAnyValue:StringLike"`
|
||||
} `json:"Condition"`
|
||||
}
|
||||
|
||||
// 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 ListOfPrincipals []string
|
||||
|
||||
func (r *ListOfPrincipals) 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")
|
||||
}
|
||||
|
||||
func ParseRoleTrustPolicyDocument(role types.Role) (TrustPolicyDocument, error) {
|
||||
document, _ := url.QueryUnescape(aws.ToString(role.AssumeRolePolicyDocument))
|
||||
|
||||
// These next six lines are a hack, needed because the EKS OIDC json field name is dynamic
|
||||
// and therefore can't be used to unmarshall in a predictable way. The hack involves replacing
|
||||
// the random pattern with a predictable one so that we can add the predictable one in the struct
|
||||
// used to unmarshall.
|
||||
pattern := `(\w+)\:`
|
||||
pattern2 := `".[a-zA-Z0-9\-\.]+/id/`
|
||||
//auth0Pattern := `"auth0.com\:`
|
||||
circleCIPattern := `"oidc.circleci.com/org/[a-zA-Z0-9\-]+:`
|
||||
var reEKSSub = regexp.MustCompile(pattern2 + pattern + "sub")
|
||||
var reEKSAud = regexp.MustCompile(pattern2 + pattern + "aud")
|
||||
//var reAuth0Sub = regexp.MustCompile(auth0Pattern + "amr")
|
||||
var reCircleCIAud = regexp.MustCompile(circleCIPattern + "aud")
|
||||
var reCircleCISSub = regexp.MustCompile(circleCIPattern + "sub")
|
||||
|
||||
document = reEKSSub.ReplaceAllString(document, "\"OidcEksSub")
|
||||
document = reEKSAud.ReplaceAllString(document, "\"OidcEksAud")
|
||||
//document = reAuth0Sub.ReplaceAllString(document, "\"Auth0Amr")
|
||||
document = reCircleCIAud.ReplaceAllString(document, "\"CircleCIAud")
|
||||
document = reCircleCISSub.ReplaceAllString(document, "\"CircleCISub")
|
||||
|
||||
var parsedDocumentToJSON TrustPolicyDocument
|
||||
_ = json.Unmarshal([]byte(document), &parsedDocumentToJSON)
|
||||
return parsedDocumentToJSON, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package internal
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"context"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
|
||||
@@ -15,10 +16,146 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type AzureClient struct {
|
||||
AzVerbosity int
|
||||
AzWrapTable bool
|
||||
AzMergedTable bool
|
||||
|
||||
Version string
|
||||
AzOutputFormat string
|
||||
AzOutputDirectory string
|
||||
|
||||
AzTenants []*subscriptions.TenantIDDescription
|
||||
AzSubscriptions []*subscriptions.Subscription
|
||||
AzSubscriptionsAlt map[string][]*subscriptions.Subscription
|
||||
AzRGs []*resources.Group
|
||||
AzResources []*azure.Resource
|
||||
|
||||
Log *Logger
|
||||
}
|
||||
|
||||
func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string){
|
||||
availableSubscriptions := GetSubscriptions()
|
||||
availableTenants := GetTenants()
|
||||
|
||||
// resource identifiers were submitted on the CLI, running modules on them only
|
||||
if len(AzResourceRefs) > 0 {
|
||||
a.Log.Info(nil, "Azure resource identifiers submitted, skipping submitted tenants and subscriptions")
|
||||
// remove any other resource scope filter
|
||||
a.AzTenants = nil
|
||||
a.AzSubscriptions = nil
|
||||
a.AzRGs = nil
|
||||
|
||||
var (
|
||||
err error
|
||||
resource azure.Resource
|
||||
)
|
||||
// check if the submitted resource can be reached with existing credentials
|
||||
// this does not guarantees that the resource will be found since only the prefix of the resource ID
|
||||
// is checked against the available ones
|
||||
for _, azResourceRef := range AzResourceRefs {
|
||||
resource, err = azure.ParseResourceID(azResourceRef)
|
||||
if err != nil {
|
||||
a.Log.Errorf(nil, "Invalid resource identifier : %s", azResourceRef)
|
||||
continue
|
||||
}
|
||||
for _, subscription := range availableSubscriptions {
|
||||
if resource.SubscriptionID == *subscription.SubscriptionID {
|
||||
// add the resource to the final list of targets
|
||||
a.AzResources = append(a.AzResources, &resource)
|
||||
// also add the associated subscription since you cannot query a resource alone
|
||||
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
|
||||
goto FOUND_RESOURCE
|
||||
}
|
||||
}
|
||||
a.Log.Errorf(nil, "No active credentials valid for resource %s, removing from target list", azResourceRef)
|
||||
FOUND_RESOURCE:
|
||||
}
|
||||
} else if len(AzRGRefs) > 0 {
|
||||
// resource groups were submitted on the CLI, running modules on them only
|
||||
a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants")
|
||||
|
||||
a.AzTenants = nil
|
||||
a.AzSubscriptions = nil
|
||||
|
||||
for _, AzRGRef := range AzRGRefs {
|
||||
for _, subscription := range availableSubscriptions {
|
||||
for _, rg := range GetResourceGroups(*subscription.SubscriptionID) {
|
||||
if *rg.ID == AzRGRef {
|
||||
a.AzRGs = append(a.AzRGs, &rg)
|
||||
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
|
||||
goto FOUND_RG
|
||||
} else if *rg.Name == AzRGRef {
|
||||
a.AzRGs = append(a.AzRGs, &rg)
|
||||
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
|
||||
goto FOUND_RG
|
||||
}
|
||||
}
|
||||
}
|
||||
a.Log.Warnf(nil, "Resource Group %s not accessible with active CLI credentials, removing from targets", AzRGRef)
|
||||
FOUND_RG:
|
||||
}
|
||||
} else if len(AzSubscriptionRefs) > 0 {
|
||||
// subscriptions were submitted on the CLI, running modules on them only
|
||||
a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants")
|
||||
// remove any other resource scope filter
|
||||
a.AzTenants = nil
|
||||
a.AzSubscriptionsAlt = make(map[string][]*subscriptions.Subscription, 0)
|
||||
for _, AzSubscriptionRef := range AzSubscriptionRefs {
|
||||
for _, subscription := range availableSubscriptions {
|
||||
if (*subscription.SubscriptionID == AzSubscriptionRef) || (*subscription.DisplayName == AzSubscriptionRef) {
|
||||
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
|
||||
for _, AzTenant := range availableTenants {
|
||||
if *subscription.TenantID == *AzTenant.TenantID {
|
||||
a.AzSubscriptionsAlt[*AzTenant.DefaultDomain] = append(a.AzSubscriptionsAlt[*AzTenant.DefaultDomain], &subscription)
|
||||
break
|
||||
}
|
||||
}
|
||||
goto FOUND_SUB
|
||||
}
|
||||
}
|
||||
a.Log.Warnf(nil, "Subscription %s not accessible with active CLI credentials, removing from targets", AzSubscriptionRef)
|
||||
FOUND_SUB:
|
||||
}
|
||||
} else if len(AzTenantRefs) > 0 {
|
||||
// tenants were submitted on the CLI, running modules on them only
|
||||
for _, AzTenantRef := range AzTenantRefs {
|
||||
for _, tenant := range availableTenants {
|
||||
if *tenant.ID == AzTenantRef {
|
||||
a.AzTenants = append(a.AzTenants, &tenant)
|
||||
goto FOUND_TENANT
|
||||
} else if *tenant.DefaultDomain == AzTenantRef {
|
||||
a.AzTenants = append(a.AzTenants, &tenant)
|
||||
goto FOUND_TENANT
|
||||
}
|
||||
}
|
||||
a.Log.Warnf(nil, "Tenant %s not accessible with active CLI credentials, removing from targets", AzTenantRef)
|
||||
FOUND_TENANT:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command, AzOutputFormat, AzOutputDirectory string) *AzureClient {
|
||||
client := new(AzureClient)
|
||||
client.Log = NewLogger("azure")
|
||||
client.Log.Announce(nil, "Searching existing credentials and validating submitted targets...")
|
||||
client.Version = cmd.Root().Version
|
||||
client.AzWrapTable = AzWrapTable
|
||||
client.AzMergedTable = AzMergedTable
|
||||
client.AzVerbosity = AzVerbosity
|
||||
client.AzOutputFormat = AzOutputFormat
|
||||
client.AzOutputDirectory = AzOutputDirectory
|
||||
client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs)
|
||||
return client
|
||||
}
|
||||
|
||||
|
||||
func getAuthorizer(endpoint string) (autorest.Authorizer, error) {
|
||||
auth, err := auth.NewAuthorizerFromCLIWithResource(endpoint)
|
||||
if err != nil {
|
||||
@@ -126,6 +263,39 @@ func GetNICClient(subscriptionID string) network.InterfacesClient {
|
||||
return client
|
||||
}
|
||||
|
||||
func GetNSGClient(subscriptionID string) network.SecurityGroupsClient {
|
||||
client := network.NewSecurityGroupsClient(subscriptionID)
|
||||
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get nsg client: %s", err)
|
||||
}
|
||||
client.Authorizer = authorizer
|
||||
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
|
||||
return client
|
||||
}
|
||||
|
||||
func GetSubnetsClient(subscriptionID string) network.SubnetsClient {
|
||||
client := network.NewSubnetsClient(subscriptionID)
|
||||
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get subnets client: %s", err)
|
||||
}
|
||||
client.Authorizer = authorizer
|
||||
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
|
||||
return client
|
||||
}
|
||||
|
||||
func GetVNETsClient(subscriptionID string) network.VirtualNetworksClient {
|
||||
client := network.NewVirtualNetworksClient(subscriptionID)
|
||||
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get subnets client: %s", err)
|
||||
}
|
||||
client.Authorizer = authorizer
|
||||
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
|
||||
return client
|
||||
}
|
||||
|
||||
func GetPublicIPClient(subscriptionID string) network.PublicIPAddressesClient {
|
||||
client := network.NewPublicIPAddressesClient(subscriptionID)
|
||||
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
|
||||
@@ -171,3 +341,57 @@ func GetARMresourcesClient(tenantID, subscriptionID string) *armresources.Client
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func GetIPAddressesFromInterface(iface *network.Interface) []string{
|
||||
var ipAddresses []string
|
||||
for _, ipConfiguration := range *iface.InterfacePropertiesFormat.IPConfigurations {
|
||||
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress != nil {
|
||||
ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress)
|
||||
}
|
||||
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress != nil {
|
||||
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat != nil {
|
||||
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress != nil {
|
||||
ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipAddresses
|
||||
}
|
||||
|
||||
func GetSubscriptions() []subscriptions.Subscription {
|
||||
var results []subscriptions.Subscription
|
||||
subsClient := GetSubscriptionsClient()
|
||||
for page, err := subsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
log.Fatal("could not get subscriptions for active session")
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func GetTenants() []subscriptions.TenantIDDescription {
|
||||
tenantsClient := GetTenantsClient()
|
||||
var results []subscriptions.TenantIDDescription
|
||||
for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
log.Fatal("could not get tenants for active session")
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func GetResourceGroups(subscriptionID string) []resources.Group {
|
||||
var results []resources.Group
|
||||
rgClient := GetResourceGroupsClient(subscriptionID)
|
||||
|
||||
for page, err := rgClient.List(context.TODO(), "", nil); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
log.Fatalf("error reading resource groups for subscription %s", subscriptionID)
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
+84
-30
@@ -1,17 +1,17 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/text"
|
||||
"github.com/kyokomi/emoji"
|
||||
"github.com/fatih/color"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -37,51 +37,105 @@ func GetLogDirPath() *string {
|
||||
|
||||
type Logger struct {
|
||||
version string
|
||||
txtLog *logrus.Logger
|
||||
module string
|
||||
txtLog *logrus.Logger
|
||||
Cyan func(...interface{}) string
|
||||
Red func(...interface{}) string
|
||||
Green func(...interface{}) string
|
||||
Yellow func(...interface{}) string
|
||||
}
|
||||
|
||||
func NewLogger() Logger {
|
||||
func NewLogger(module string) *Logger {
|
||||
lootLogger := logrus.New()
|
||||
logDirPath := GetLogDirPath()
|
||||
logFile, err := os.OpenFile(filepath.Join(*logDirPath, "cloudfox.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic("Could not initiate logger")
|
||||
}
|
||||
lootLogger.Out = logFile
|
||||
lootLogger.SetLevel(logrus.InfoLevel)
|
||||
var logger = Logger{
|
||||
version: globals.CLOUDFOX_VERSION,
|
||||
txtLog: TxtLog,
|
||||
txtLog: lootLogger,
|
||||
module: module,
|
||||
Cyan: color.New(color.FgCyan).SprintFunc(),
|
||||
Red: color.New(color.FgRed).SprintFunc(),
|
||||
Green:color.New(color.FgGreen).SprintFunc(),
|
||||
Yellow: color.New(color.FgYellow).SprintFunc(),
|
||||
}
|
||||
return logger
|
||||
return &logger
|
||||
}
|
||||
|
||||
func (l *Logger) Info(text string) {
|
||||
l.InfoM(text, "config")
|
||||
|
||||
func (l *Logger) Print(categories []string, color func(...interface{}) string, text string) {
|
||||
formatString := text
|
||||
formatArgs := []any{}
|
||||
l.Printf(categories, color, formatString, formatArgs...)
|
||||
}
|
||||
|
||||
func (l *Logger) InfoM(text string, module string) {
|
||||
var cyan = color.New(color.FgCyan).SprintFunc()
|
||||
fmt.Printf("[%s][%s] %s\n", cyan(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), cyan(module), text)
|
||||
func (l *Logger) Printf(categories []string, color func(...interface{}) string, format string, params ...any) {
|
||||
formatString := ""
|
||||
formatArgs := []any{}
|
||||
blocks := []string{}
|
||||
blocks = append(blocks, categories...)
|
||||
for _, block := range blocks {
|
||||
formatString += "[%s]"
|
||||
formatArgs = append(formatArgs, color(block))
|
||||
}
|
||||
for _, param := range params {
|
||||
formatArgs = append(formatArgs, color(param))
|
||||
}
|
||||
formatString += " " + format + "\n"
|
||||
|
||||
fmt.Printf(formatString, formatArgs...)
|
||||
}
|
||||
|
||||
func (l *Logger) Success(text string) {
|
||||
l.SuccessM(text, "config")
|
||||
}
|
||||
func (l *Logger) SuccessM(text string, module string) {
|
||||
var green = color.New(color.FgGreen).SprintFunc()
|
||||
fmt.Printf("[%s][%s] %s\n", green(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), green(module), text)
|
||||
func (l *Logger) Announce(categories []string, text string) {
|
||||
l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, text)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(text string) {
|
||||
l.ErrorM(text, "config")
|
||||
func (l *Logger) Announcef(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, format, params...)
|
||||
}
|
||||
|
||||
func (l *Logger) ErrorM(text string, module string) {
|
||||
var red = color.New(color.FgRed).SprintFunc()
|
||||
fmt.Printf("[%s][%s] %s\n", red(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), red(module), text)
|
||||
l.txtLog.Printf("[%s] %s", module, text)
|
||||
func (l *Logger) Info(categories []string, text string) {
|
||||
l.Print(append([]string{l.module}, categories...), l.Cyan, text)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(text string) {
|
||||
l.FatalM(text, "config")
|
||||
func (l *Logger) Infof(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{l.module}, categories...), l.Cyan, format, params...)
|
||||
}
|
||||
|
||||
func (l *Logger) FatalM(text string, module string) {
|
||||
var red = color.New(color.FgRed).SprintFunc()
|
||||
l.txtLog.Printf("[%s] %s", module, text)
|
||||
fmt.Printf("[%s][%s] %s\n", red(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), red(module), text)
|
||||
os.Exit(1)
|
||||
func (l *Logger) Success(categories []string, text string) {
|
||||
l.Print(append([]string{l.module}, categories...), l.Green, text)
|
||||
}
|
||||
|
||||
func (l *Logger) Successf(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{l.module}, categories...), l.Green, format, params...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(categories []string, text string) {
|
||||
l.Print(append([]string{l.module}, categories...), l.Yellow, text)
|
||||
}
|
||||
|
||||
func (l *Logger) Warnf(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{l.module}, categories...), l.Yellow, format, params...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(categories []string, text string) {
|
||||
l.Print(append([]string{l.module}, categories...), l.Red, text)
|
||||
}
|
||||
|
||||
func (l *Logger) Errorf(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{l.module}, categories...), l.Red, format, params...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(categories []string, text string) {
|
||||
l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, text)
|
||||
panic(nil)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalf(categories []string, format string, params ...any) {
|
||||
l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, format, params...)
|
||||
panic(nil)
|
||||
}
|
||||
+2
-47
@@ -7,7 +7,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -60,49 +59,6 @@ type LootFile struct {
|
||||
Contents string
|
||||
}
|
||||
|
||||
// TODO support datastructures that enable brief or wide format
|
||||
type CloudfoxOutput interface {
|
||||
TableFiles() []TableFile
|
||||
LootFiles() []LootFile
|
||||
}
|
||||
|
||||
// HandleOutput dynamically handles the output based on the provided arguments.
|
||||
// TODO support brief of wide
|
||||
func HandleOutput(
|
||||
cloudProvider string,
|
||||
format string,
|
||||
outputDirectory string,
|
||||
verbosity int,
|
||||
wrap bool,
|
||||
baseCloudfoxModule string,
|
||||
principal string,
|
||||
resultsIdentifier string,
|
||||
dataToOutput CloudfoxOutput,
|
||||
) error {
|
||||
// Update OutputClient fields based on arguments
|
||||
outDirectoryPath := filepath.Join(outputDirectory, "cloudfox-output", cloudProvider, fmt.Sprintf("%s-%s", principal, resultsIdentifier), baseCloudfoxModule)
|
||||
tables := dataToOutput.TableFiles()
|
||||
lootFiles := dataToOutput.LootFiles()
|
||||
|
||||
outputClient := OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: baseCloudfoxModule,
|
||||
Table: TableClient{
|
||||
Wrap: wrap,
|
||||
DirectoryName: outDirectoryPath,
|
||||
TableFiles: tables,
|
||||
},
|
||||
Loot: LootClient{
|
||||
DirectoryName: outDirectoryPath,
|
||||
LootFiles: lootFiles,
|
||||
},
|
||||
}
|
||||
|
||||
// Handle output based on the verbosity level
|
||||
outputClient.WriteFullOutput(tables, lootFiles)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeColorCodes(input string) string {
|
||||
// Regular expression to match ANSI color codes
|
||||
ansiRegExp := regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
@@ -141,7 +97,7 @@ func removeColorCodesFromNestedSlice(input [][]string) [][]string {
|
||||
}
|
||||
|
||||
func (o *OutputClient) WriteFullOutput(tables []TableFile, lootFiles []LootFile) {
|
||||
logger := NewLogger()
|
||||
|
||||
switch o.Verbosity {
|
||||
case 2:
|
||||
o.Table.printTablesToScreen(tables)
|
||||
@@ -171,8 +127,7 @@ func (o *OutputClient) WriteFullOutput(tables []TableFile, lootFiles []LootFile)
|
||||
}
|
||||
|
||||
for _, path := range outputPaths {
|
||||
logger.InfoM(fmt.Sprintf("Output written to %s", path), o.CallingModule)
|
||||
// fmt.Printf("[%s][%s] Output written to %s\n", cyan(o.CallingModule), cyan(o.PrefixIdentifier), path)
|
||||
fmt.Printf("[%s][%s] Output written to %s\n", cyan(o.CallingModule), cyan(o.PrefixIdentifier), path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,18 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/BishopFox/cloudfox/cli"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: os.Args[0],
|
||||
Version: "1.14.0-prerelease",
|
||||
Version: globals.CLOUDFOX_VERSION,
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
rootCmd.AddCommand(cli.AWSCommands, cli.AzCommands, cli.GCPCommands)
|
||||
rootCmd.AddCommand(cli.AWSCommands, cli.AzCommands)
|
||||
rootCmd.Execute()
|
||||
}
|
||||
Reference in new issue
Block a user