mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 17:05:53 -10:00
Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3fb6eae0 | ||
|
|
8e94b3c46a | ||
|
|
94628aae73 | ||
|
|
23e8878e49 | ||
|
|
dee22b3ebc | ||
|
|
84369247a7 | ||
|
|
334583d3a2 | ||
|
|
b73cd90323 | ||
|
|
ce0310675d | ||
|
|
f9d9ec4bb8 | ||
|
|
125c1ea0cb | ||
|
|
00e63b0e18 | ||
|
|
eaac503bff | ||
|
|
cec0d49602 | ||
|
|
6a21b9665d | ||
|
|
a1903f67f7 | ||
|
|
2f99a7642f | ||
|
|
8d039323bf | ||
|
|
a40f0aa179 | ||
|
|
23409e3b82 | ||
|
|
fd01647a8a | ||
|
|
f415f991c8 | ||
|
|
15e0507ad6 | ||
|
|
80c00f3d97 | ||
|
|
59afbb381e | ||
|
|
ca437d3626 | ||
|
|
1ca5dbfb10 | ||
|
|
f00044670b | ||
|
|
ae1dac6c21 | ||
|
|
237b0735a8 | ||
|
|
18e38bf1ea | ||
|
|
b5908fc720 | ||
|
|
29514a96b5 | ||
|
|
b2c450ee02 | ||
|
|
a65610390b | ||
|
|
997c13d4de | ||
|
|
1d1d198a01 | ||
|
|
6069267758 | ||
|
|
e08967bb31 | ||
|
|
8e99347789 | ||
|
|
ff810ba17d | ||
|
|
23fc346e4f | ||
|
|
89789a05c1 | ||
|
|
5827abb2a2 | ||
|
|
13a03c5de2 | ||
|
|
dd6bfefa9d | ||
|
|
8c4bcc035b | ||
|
|
a38451c1cd | ||
|
|
5df1ca6d75 | ||
|
|
ea2ee3dd2c | ||
|
|
97e2fef388 | ||
|
|
1d53b984ac | ||
|
|
525f262460 | ||
|
|
f13df0708c | ||
|
|
7520999386 | ||
|
|
f1b6eae9f1 | ||
|
|
06f8e36be2 | ||
|
|
cba252d151 | ||
|
|
5c4d0fda89 | ||
|
|
d648e5b347 | ||
|
|
d1af1bbf8f | ||
|
|
2b19d0ac62 | ||
|
|
cef6c9e656 | ||
|
|
6e146a567a | ||
|
|
ae7ed9e19a | ||
|
|
a690ad3185 | ||
|
|
26cb99778c | ||
|
|
98af83933e | ||
|
|
82738dbf63 | ||
|
|
96ab676f49 | ||
|
|
83866f46cc | ||
|
|
7343852a66 | ||
|
|
b1c248bbe6 | ||
|
|
2ce1f0f82f | ||
|
|
5eff1c71ac | ||
|
|
17011f345c |
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
@sethsec-bf carlosvendramini-bf
|
||||
* @bishopfaure @dbravo-bishopfox
|
||||
@@ -13,4 +13,4 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install --user codespell
|
||||
- run: codespell --ignore-words-list="aks" # --skip="*.css,*.js,*.lock,*.po"
|
||||
- run: codespell --ignore-words-list="aks" --skip="*.sum"
|
||||
@@ -15,6 +15,7 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.vscode
|
||||
.idea
|
||||
launch.json
|
||||
|
||||
######################################################
|
||||
|
||||
@@ -14,6 +14,7 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
|
||||
|
||||
## Demos, Examples, Walkthroughs
|
||||
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
|
||||
* [Video - CloudFox + CloudFoxable A Powerful Duo for Mastering the Art of Identifying and Exploiting AWS Attack Paths](https://www.youtube.com/watch?v=RdQiIvCrSzk)
|
||||
* [Video - Penetration Testing with CloudFox](https://www.youtube.com/watch?v=Ljt_JUp5HbM)
|
||||
* [Video - CloudFox Intro Demos](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
|
||||
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
|
||||
@@ -21,6 +22,10 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
|
||||
## Intentionally Vulnerable Playground
|
||||
* [CloudFoxable - A Gamified Cloud Hacking Sandbox](https://cloudfoxable.bishopfox.com/)
|
||||
|
||||
## Want to chat about CloudFox?
|
||||
Join us on the [RedSec discord server](https://discord.gg/redsec)
|
||||
|
||||
|
||||
## Quick Start
|
||||
CloudFox is modular (you can run one command at a time), but there is an `aws all-checks` command that will run the other aws commands for you with sane defaults:
|
||||
|
||||
@@ -43,7 +48,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
|
||||
| Provider| CloudFox Commands |
|
||||
| - | - |
|
||||
| AWS | 30 |
|
||||
| AWS | 33 |
|
||||
| Azure | 4 |
|
||||
| GCP | Support Planned |
|
||||
| Kubernetes | Support Planned |
|
||||
@@ -62,13 +67,20 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
[Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
|
||||
```
|
||||
# git clone https://github.com/BishopFox/cloudfox.git
|
||||
...omitted for brevity...
|
||||
# cd ./cloudfox
|
||||
# Make any changes necessary
|
||||
# go build .
|
||||
# ./cloudfox
|
||||
```
|
||||
|
||||
**Option 5:** Testing a bug fix
|
||||
```
|
||||
git clone [email protected]:BishopFox/cloudfox.git
|
||||
git checkout seth-dev
|
||||
go build .
|
||||
./cloudfox [rest of the command options]
|
||||
```
|
||||
|
||||
# Prerequisites
|
||||
|
||||
|
||||
@@ -97,8 +109,11 @@ Additional policy notes (as of 09/2022):
|
||||
| - | - | - |
|
||||
| AWS | [all-checks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#all-checks) | Run all of the other commands using reasonable defaults. You'll still want to check out the non-default options of each command, but this is a great place to start. |
|
||||
| AWS | [access-keys](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#access-keys) | Lists active access keys for all users. Useful for cross referencing a key you found with which in-scope account it belongs to. |
|
||||
| AWS | [api-gw](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#api-gw) | Lists API gateway endpoints and gives you custom curl commands including API tokens if they are stored in metadata. |
|
||||
| AWS | [buckets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#filesystems) | Lists the buckets in the account and gives you handy commands for inspecting them further. |
|
||||
| AWS | [cloudformation](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#cloudformation) | Lists the cloudformation stacks in the account. Generates loot file with stack details, stack parameters, and stack output - look for secrets. |
|
||||
| AWS | [codebuild](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#codebuild) | Enumerate CodeBuild projects |
|
||||
| AWS | [databases](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#databases) | Enumerate RDS databases. Get a loot file with connection strings. |
|
||||
| AWS | [ecr](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ecr) | List the most recently pushed image URI from all repositories. Use the loot file to pull selected images down with docker/nerdctl for inspection. |
|
||||
| AWS | [ecs-tasks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ecs-tasks) | List all ecs tasks. This returns a list of ecs tasks and associated cluster, task definition, container instance, launch type, and associated IAM principal. |
|
||||
| AWS | [eks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#eks) | List all EKS clusters, see if they expose their endpoint publicly, and check the associated IAM roles attached to reach cluster or node group. Generates a loot file with the `aws eks udpate-kubeconfig` command needed to connect to each cluster. |
|
||||
@@ -111,27 +126,30 @@ Additional policy notes (as of 09/2022):
|
||||
| AWS | [inventory](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#inventory) | Gain a rough understanding of size of the account and preferred regions. |
|
||||
| AWS | [lambda](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#lambda) | Lists the lambda functions in the account, including which one's have admin roles attached. Also gives you handy commands for downloading each function. |
|
||||
| AWS | [network-ports](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#network-ports) | Enumerates AWS services that are potentially exposing a network service. The security groups and the network ACLs are parsed for each resource to determine what ports are potentially exposed. |
|
||||
| AWS | [outbound-assumed-roles](#outbound-assumed-roles) | List the roles that have been assumed by principals in this account. This is an excellent way to find outbound attack paths that lead into other accounts. |
|
||||
| AWS | [orgs](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#orgs) | Enumerate accounts in an organization |
|
||||
| AWS | [outbound-assumed-roles](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#outbound-assumed-roles) | List the roles that have been assumed by principals in this account. This is an excellent way to find outbound attack paths that lead into other accounts. |
|
||||
| AWS | [permissions](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#permissions) | Enumerates IAM permissions associated with all users and roles. Grep this output to figure out what permissions a particular principal has rather than logging into the AWS console and painstakingly expanding each policy attached to the principal you are investigating. |
|
||||
| AWS | [principals](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#principals) | Enumerates IAM users and Roles so you have the data at your fingertips. |
|
||||
| AWS | [pmapper](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#pmapper) | Looks for pmapper data stored on the local filesystem, [in the locations defined here](https://github.com/nccgroup/PMapper/wiki/Frequently-Asked-Questions#where-does-pmapper-store-its-data). If pmapper data has been found (you already ran `pmapper graph create`), then this command will use this data to build a graph in cloudfox memory let you know who can privesc to admin.
|
||||
| AWS | [principals](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#principals) | Enumerates IAM users and Roles so you have the data at your fingertips. |
|
||||
| AWS | [ram](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ram) | List all resources in this account that are shared with other accounts, or resources from other accounts that are shared with this account. Useful for cross-account attack paths. |
|
||||
| AWS | [resource-trusts](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#resource-trusts) | Looks through multiple services that support resource policies and helps you find any overly permissive resource trusts.|
|
||||
| AWS | [role-trusts](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#role-trusts) | Enumerates IAM role trust policies so you can look for overly permissive role trusts or find roles that trust a specific service. |
|
||||
| AWS | [route53](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#route53) | Enumerate all records from all route53 managed zones. Use this for application and service enumeration. |
|
||||
| AWS | [secrets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#secrets) | List secrets from SecretsManager and SSM. Look for interesting secrets in the list and then see who has access to them using use `cloudfox iam-simulator` and/or `pmapper`. |
|
||||
| AWS | [sns](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#sns) | This command enumerates all of the sns topics and gives you the commands to subscribe to a topic or send messages to a topic (if you have the permissions needed). This command only deals with topics, and not the SMS functionality. This command also attempts to summarize topic resource policies if they exist.|
|
||||
| AWS | [sqs](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#sqs) | This command enumerates all of the sqs queues and gives you the commands to receive messages from a queue and send messages to a queue (if you have the permissions needed). This command also attempts to summarize queue resource policies if they exist.|
|
||||
| AWS | [tags](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#tags) | List all resources with tags, and all of the tags. This can be used similar to inventory as another method to identify what types of resources exist in an account. |
|
||||
| AWS | [workloads](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#workloads) | List all of the compute workloads and what role they have. Tells you if any of the roles are admin (bad) and if you have pmapper data locally, it will tell you if any of the roles can privesc to admin (also bad) |
|
||||
|
||||
|
||||
# Azure Commands
|
||||
| Provider | Command Name | Description
|
||||
| - | - | - |
|
||||
| Azure | [whoami](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#whoami) | Displays information on the tenant, subscriptions and resource groups available to your current Azure CLI session. This is useful to provide situation awareness on what tenant and subscription IDs to use with the other sub commands. |
|
||||
| Azure | [instances](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#instances) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
|
||||
| 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 | [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)
|
||||
|
||||
+681
@@ -0,0 +1,681 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
apigatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
|
||||
apigatewayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/bishopfox/awsservicemap"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var CURL_COMMAND string = "curl -X %s %s"
|
||||
|
||||
type ApiGwModule struct {
|
||||
// General configuration data
|
||||
APIGatewayClient sdk.APIGatewayClientInterface
|
||||
APIGatewayv2Client sdk.APIGatewayv2ClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Gateways []ApiGateway
|
||||
CommandCounter internal.CommandCounter
|
||||
Errors []string
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type ApiGateway struct {
|
||||
AWSService string
|
||||
Region string
|
||||
Name string
|
||||
Endpoint string
|
||||
ApiKey string
|
||||
Public string
|
||||
Method string
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) PrintApiGws(outputDirectory string, verbosity int) {
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "api-gw"
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = internal.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating api-gateways for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan ApiGateway)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
//execute regional checks
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
sort.Slice(m.Gateways, func(i, j int) bool {
|
||||
return m.Gateways[i].AWSService < m.Gateways[j].AWSService
|
||||
})
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Method",
|
||||
"Endpoint",
|
||||
"ApiKey",
|
||||
"Public",
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Gateways {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Gateways[i].AWSService,
|
||||
m.Gateways[i].Region,
|
||||
m.Gateways[i].Name,
|
||||
m.Gateways[i].Method,
|
||||
m.Gateways[i].Endpoint,
|
||||
m.Gateways[i].ApiKey,
|
||||
m.Gateways[i].Public,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
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,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
loot := m.writeLoot(filepath, verbosity)
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
|
||||
Name: m.output.CallingModule,
|
||||
Contents: loot,
|
||||
})
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
|
||||
fmt.Printf("[%s][%s] %s API gateways found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No API gateways found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) Receiver(receiver chan ApiGateway, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
case data := <-receiver:
|
||||
m.Gateways = append(m.Gateways, data)
|
||||
case <-receiverDone:
|
||||
receiverDone <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer wg.Done()
|
||||
// check the concurrency semaphore
|
||||
// semaphore <- struct{}{}
|
||||
// defer func() {
|
||||
// <-semaphore
|
||||
// }()
|
||||
|
||||
servicemap := &awsservicemap.AwsServiceMap{
|
||||
JsonFileSource: "DOWNLOAD_FROM_AWS",
|
||||
}
|
||||
res, err := servicemap.IsServiceInRegion("apigateway", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayAPIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayVIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2APIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2VIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
f := filepath.Join(path, "api-gws.txt")
|
||||
|
||||
var out string
|
||||
|
||||
for _, endpoint := range m.Gateways {
|
||||
method := endpoint.Method
|
||||
// Write a GET and POST for ANY
|
||||
if endpoint.Method == "ANY" {
|
||||
line := fmt.Sprintf(CURL_COMMAND, "GET", endpoint.Endpoint)
|
||||
if endpoint.ApiKey != "" {
|
||||
line += fmt.Sprintf(" -H 'X-Api-Key: %s'", endpoint.ApiKey)
|
||||
}
|
||||
|
||||
out += line + "\n"
|
||||
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
line := fmt.Sprintf(CURL_COMMAND, method, endpoint.Endpoint)
|
||||
if endpoint.ApiKey != "" {
|
||||
line += fmt.Sprintf(" -H 'X-Api-Key: %s'", endpoint.ApiKey)
|
||||
}
|
||||
|
||||
if method == "DELETE" || method == "PATCH" || method == "POST" || method == "PUT" {
|
||||
line += " -H 'Content-Type: application/json' -d '{}'"
|
||||
}
|
||||
|
||||
out += line + "\n"
|
||||
}
|
||||
|
||||
// err = os.WriteFile(f, []byte(out), 0644)
|
||||
// if err != nil {
|
||||
// m.modLog.Error(err.Error())
|
||||
// m.CommandCounter.Error++
|
||||
// panic(err.Error())
|
||||
// }
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Send these requests through your favorite interception proxy"))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
|
||||
return out
|
||||
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, api := range Items {
|
||||
m.CommandCounter.Total++
|
||||
for _, endpoint := range m.getEndpointsPerAPIGateway(r, api) {
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
GetDomainNames, err := sdk.CachedApiGatewayGetDomainNames(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
GetBasePathMappings, err := sdk.CachedApiGatewayGetBasePathMappings(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, item.DomainName)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, mapping := range GetBasePathMappings {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
basePath := aws.ToString(mapping.BasePath)
|
||||
if basePath == "(none)" {
|
||||
basePath = "" // Empty string since '/' is already prepended
|
||||
}
|
||||
|
||||
for _, api := range Items {
|
||||
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
|
||||
m.CommandCounter.Total++
|
||||
|
||||
endpoints := m.getEndpointsPerAPIGateway(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
|
||||
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if basePath == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
|
||||
}
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.RestApi) []ApiGateway {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
var gateways []ApiGateway
|
||||
|
||||
//var PaginationControl2 *string
|
||||
awsService := "APIGateway"
|
||||
var public string
|
||||
|
||||
name := aws.ToString(api.Name)
|
||||
id := aws.ToString(api.Id)
|
||||
raw_endpoint := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com", id, r)
|
||||
|
||||
endpointType := *api.EndpointConfiguration
|
||||
//fmt.Println(endpointType)
|
||||
if endpointType.Types[0] == "PRIVATE" {
|
||||
public = "False"
|
||||
} else {
|
||||
public = "True"
|
||||
}
|
||||
|
||||
GetStages, err := sdk.CachedApiGatewayGetStages(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return gateways
|
||||
}
|
||||
|
||||
resources, err := sdk.CachedApiGatewayGetResources(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages.Item {
|
||||
stageName := aws.ToString(stage.StageName)
|
||||
for _, resource := range resources {
|
||||
if len(resource.ResourceMethods) != 0 {
|
||||
for method := range resource.ResourceMethods {
|
||||
|
||||
// Check if API Key is required for endpoint
|
||||
apiKey := ""
|
||||
if m.ApiGatewayApiKeyRequired(r, api.Id, resource.Id, method) {
|
||||
apiKey, err = m.GetApiGatewayApiKey(r, id, stageName)
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
path := aws.ToString(resource.Path)
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
|
||||
|
||||
gateways = append(gateways, ApiGateway{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Method: method,
|
||||
Public: public,
|
||||
ApiKey: apiKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gateways
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
for _, api := range Items {
|
||||
m.CommandCounter.Total++
|
||||
for _, endpoint := range m.getEndpointsPerAPIGatewayv2(r, api) {
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
GetDomainNames, err := sdk.CachedAPIGatewayv2GetDomainNames(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
GetApiMappings, err := sdk.CachedAPIGatewayv2GetApiMappings(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, domain)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, mapping := range GetApiMappings {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
if stage == "$default" {
|
||||
stage = ""
|
||||
}
|
||||
path := aws.ToString(mapping.ApiMappingKey)
|
||||
|
||||
for _, api := range Items {
|
||||
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
|
||||
m.CommandCounter.Total++
|
||||
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
var old string
|
||||
if stage == "" {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
|
||||
} else {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
|
||||
}
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if path == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, path)
|
||||
}
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Types.Api) []ApiGateway {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
|
||||
var gateways []ApiGateway
|
||||
|
||||
awsService := "APIGatewayv2"
|
||||
|
||||
var public string
|
||||
|
||||
name := aws.ToString(api.Name)
|
||||
raw_endpoint := aws.ToString(api.ApiEndpoint)
|
||||
id := aws.ToString(api.ApiId)
|
||||
|
||||
var stages []string
|
||||
GetStages, err := sdk.CachedAPIGatewayv2GetStages(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages {
|
||||
s := aws.ToString(stage.StageName)
|
||||
if s == "$default" {
|
||||
s = ""
|
||||
}
|
||||
stages = append(stages, s)
|
||||
}
|
||||
GetRoutes, err := sdk.CachedAPIGatewayv2GetRoutes(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range stages {
|
||||
for _, route := range GetRoutes {
|
||||
routeKey := route.RouteKey
|
||||
var method string
|
||||
var path string
|
||||
if len(strings.Fields(*routeKey)) == 2 {
|
||||
method = strings.Fields(*routeKey)[0]
|
||||
path = strings.Fields(*routeKey)[1]
|
||||
}
|
||||
var endpoint string
|
||||
if stage == "" {
|
||||
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
|
||||
}
|
||||
public = "True"
|
||||
|
||||
gateways = append(gateways, ApiGateway{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Method: method,
|
||||
Endpoint: endpoint,
|
||||
Public: public,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return gateways
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) ApiGatewayApiKeyRequired(r string, ApiId *string, ResourceId *string, method string) bool {
|
||||
GetMethod, err := sdk.CachedApiGatewayGetMethod(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, aws.ToString(ApiId), aws.ToString(ResourceId), method)
|
||||
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
} else {
|
||||
return aws.ToBool(GetMethod.ApiKeyRequired)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *ApiGwModule) GetApiGatewayApiKey(r string, ApiId string, Stage string) (string, error) {
|
||||
var items []apigatewayTypes.UsagePlan
|
||||
|
||||
GetUsagePlans, err := sdk.CachedApiGatewayGetUsagePlans(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, item := range GetUsagePlans {
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
for _, apiStage := range item.ApiStages {
|
||||
if aws.ToString(apiStage.ApiId) == ApiId && aws.ToString(apiStage.Stage) == Stage {
|
||||
GetUsagePlanKeys, err := sdk.CachedApiGatewayGetUsagePlanKeys(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, aws.ToString(item.Id))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, i := range GetUsagePlanKeys {
|
||||
if aws.ToString(i.Type) == "API_KEY" {
|
||||
return aws.ToString(i.Value), nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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 TestApiGw(t *testing.T) {
|
||||
|
||||
m := ApiGwModule{
|
||||
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,
|
||||
APIGatewayClient: &sdk.MockedAWSAPIGatewayClient{},
|
||||
APIGatewayv2Client: &sdk.MockedAWSAPIGatewayv2Client{},
|
||||
}
|
||||
|
||||
fs := internal.MockFileSystem(true)
|
||||
defer internal.MockFileSystem(false)
|
||||
//tmpDir := "~/.cloudfox/"
|
||||
tmpDir := ptr.ToString(internal.GetLogDirPath())
|
||||
|
||||
m.PrintApiGws(tmpDir, 2)
|
||||
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/api-gw.txt")
|
||||
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
|
||||
}
|
||||
//print the results file to the screen
|
||||
//fmt.Println(string(resultsFile))
|
||||
|
||||
// I want a test that runs the main function and checks the output to see if the following items are in the output: "https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1", "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2"
|
||||
|
||||
expectedResults := []string{
|
||||
"https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1",
|
||||
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2",
|
||||
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage2/route1",
|
||||
"23oieuwefo3rfs",
|
||||
}
|
||||
|
||||
for _, expected := range expectedResults {
|
||||
if !strings.Contains(string(resultsFile), expected) {
|
||||
t.Errorf("Expected %s to be in the results file", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -326,7 +326,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return publicAccessBlock.IgnorePublicAcls
|
||||
return aws.ToBool(publicAccessBlock.IgnorePublicAcls) && aws.ToBool(publicAccessBlock.BlockPublicPolicy) && aws.ToBool(publicAccessBlock.RestrictPublicBuckets)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -63,10 +63,10 @@ func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBu
|
||||
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
|
||||
output := &s3.GetPublicAccessBlockOutput{
|
||||
PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{
|
||||
BlockPublicAcls: true,
|
||||
BlockPublicPolicy: true,
|
||||
IgnorePublicAcls: false,
|
||||
RestrictPublicBuckets: true,
|
||||
BlockPublicAcls: aws.Bool(true),
|
||||
BlockPublicPolicy: aws.Bool(true),
|
||||
IgnorePublicAcls: aws.Bool(false),
|
||||
RestrictPublicBuckets: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
return output, nil
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
|
||||
|
||||
iamSimMod := IamSimulatorModule{
|
||||
IAMClient: iamSimPPClient,
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
Goroutines: Goroutines,
|
||||
IAMClient: iamSimPPClient,
|
||||
Caller: caller,
|
||||
AWSProfileProvided: AWSProfile,
|
||||
Goroutines: Goroutines,
|
||||
}
|
||||
|
||||
return iamSimMod
|
||||
|
||||
+146
-135
@@ -3,7 +3,6 @@ package aws
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -22,7 +21,6 @@ type DatabasesModule struct {
|
||||
RDSClient sdk.RDSClientInterface
|
||||
RedshiftClient sdk.AWSRedShiftClientInterface
|
||||
DynamoDBClient sdk.DynamoDBClientInterface
|
||||
DocDBClient sdk.DocDBClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
@@ -53,6 +51,7 @@ type Database struct {
|
||||
Protocol string
|
||||
Public string
|
||||
Size string
|
||||
Roles string
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int) {
|
||||
@@ -68,13 +67,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, DynamoDB\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] Supported Services: RDS, Redshift, DynamoDB, DocumentDB, Neptune\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
//fire up the task status spinner/updated
|
||||
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
|
||||
|
||||
//create a channel to receive the objects
|
||||
@@ -112,7 +111,8 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
//"Port",
|
||||
"Port",
|
||||
"Roles",
|
||||
//"Protocol",
|
||||
//"Public",
|
||||
|
||||
@@ -139,6 +139,8 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
"Roles",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
@@ -150,6 +152,7 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +169,8 @@ 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)),
|
||||
strconv.Itoa(int(m.Databases[i].Port)),
|
||||
m.Databases[i].Roles,
|
||||
// m.Databases[i].Protocol,
|
||||
// m.Databases[i].Public,
|
||||
},
|
||||
@@ -174,13 +178,17 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
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,
|
||||
Wrap: m.WrapTable,
|
||||
DirectoryName: filepath,
|
||||
},
|
||||
Loot: internal.LootClient{
|
||||
DirectoryName: filepath,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
@@ -190,9 +198,15 @@ func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int)
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
m.writeLoot(o.Table.DirectoryName, verbosity)
|
||||
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)
|
||||
|
||||
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))
|
||||
@@ -217,56 +231,74 @@ 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",
|
||||
}
|
||||
res, err := servicemap.IsServiceInRegion("rds", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getRdsClustersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
res, err = servicemap.IsServiceInRegion("redshift", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getRedshiftDatabasesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
res, err = servicemap.IsServiceInRegion("dynamodb", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getDynamoDBTablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
res, err = servicemap.IsServiceInRegion("docdb", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getDocDBTablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
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)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
check.wg.Add(1)
|
||||
go check.executor(check.region, check.wg, check.semaphore, check.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 {
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
f := filepath.Join(path, "databases-UrlsOnly.txt")
|
||||
|
||||
var out string
|
||||
@@ -275,12 +307,12 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
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()
|
||||
@@ -291,6 +323,8 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
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) {
|
||||
@@ -307,45 +341,58 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
DBClusters, err := sdk.CachedRDSDescribeDBClusters(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
if errors.As(err, &oe) {
|
||||
m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation())))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
var public string
|
||||
for _, instance := range DBInstances {
|
||||
if instance.Endpoint != nil {
|
||||
name := aws.ToString(instance.DBInstanceIdentifier)
|
||||
port := instance.Endpoint.Port
|
||||
endpoint := aws.ToString(instance.Endpoint.Address)
|
||||
engine := aws.ToString(instance.Engine)
|
||||
|
||||
if 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,
|
||||
}
|
||||
for _, cluster := range DBClusters {
|
||||
var public string
|
||||
var service string
|
||||
var roles string
|
||||
if cluster.Endpoint == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
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) {
|
||||
@@ -369,7 +416,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++
|
||||
@@ -381,19 +428,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: aws.ToInt32(port),
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
}
|
||||
@@ -442,48 +489,12 @@ func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGrou
|
||||
Endpoint: "N/A",
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) getDocDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
func isNeptune(engine *string) bool {
|
||||
return *engine == "neptune"
|
||||
}
|
||||
|
||||
}()
|
||||
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)),
|
||||
}
|
||||
}
|
||||
func isDocDB(engine *string) bool {
|
||||
return *engine == "docdb"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+149
-290
@@ -930,10 +930,6 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
var PaginationControl2 *string
|
||||
var PaginationControl3 *string
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
@@ -943,16 +939,20 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
GetDomainNames, err := m.APIGatewayClient.GetDomainNames(
|
||||
context.TODO(),
|
||||
&apigateway.GetDomainNamesInput{
|
||||
Position: PaginationControl2,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
GetDomainNames, err := sdk.CachedApiGatewayGetDomainNames(m.APIGatewayClient, 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++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
GetBasePathMappings, err := sdk.CachedApiGatewayGetBasePathMappings(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, item.DomainName)
|
||||
|
||||
if err != nil {
|
||||
if errors.As(err, &oe) {
|
||||
@@ -963,82 +963,43 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
break
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames.Items {
|
||||
for _, mapping := range GetBasePathMappings {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
basePath := aws.ToString(mapping.BasePath)
|
||||
if basePath == "(none)" {
|
||||
basePath = "" // Empty string since '/' is already prepended
|
||||
}
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
for _, api := range Items {
|
||||
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
|
||||
endpoints := m.getEndpointsPerAPIGateway(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
|
||||
|
||||
for {
|
||||
GetBasePathMappings, err := m.APIGatewayClient.GetBasePathMappings(
|
||||
context.TODO(),
|
||||
&apigateway.GetBasePathMappingsInput{
|
||||
DomainName: item.DomainName,
|
||||
Position: PaginationControl3,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = 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++
|
||||
break
|
||||
}
|
||||
|
||||
for _, mapping := range GetBasePathMappings.Items {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
basePath := aws.ToString(mapping.BasePath)
|
||||
if basePath == "(none)" {
|
||||
basePath = "" // Empty string since '/' is already prepended
|
||||
}
|
||||
|
||||
for _, api := range Items {
|
||||
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
|
||||
endpoints := m.getEndpointsPerAPIGateway(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
old := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.RestApiId), r, stage)
|
||||
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if basePath == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
|
||||
}
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if basePath == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, basePath)
|
||||
}
|
||||
break
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if GetBasePathMappings.Position != nil {
|
||||
PaginationControl3 = GetBasePathMappings.Position
|
||||
} else {
|
||||
PaginationControl3 = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if GetDomainNames.Position != nil {
|
||||
PaginationControl2 = GetDomainNames.Position
|
||||
} else {
|
||||
PaginationControl2 = nil
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.RestApi) []Endpoint {
|
||||
var endpoints []Endpoint
|
||||
|
||||
var PaginationControl2 *string
|
||||
awsService := "APIGateway"
|
||||
var public string
|
||||
|
||||
@@ -1056,15 +1017,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
|
||||
public = "True"
|
||||
}
|
||||
|
||||
GetStages, err := m.APIGatewayClient.GetStages(
|
||||
context.TODO(),
|
||||
&apigateway.GetStagesInput{
|
||||
RestApiId: &id,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
GetStages, err := sdk.CachedApiGatewayGetStages(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
if err != nil {
|
||||
if errors.As(err, &oe) {
|
||||
@@ -1074,54 +1027,38 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for {
|
||||
GetResources, err := m.APIGatewayClient.GetResources(
|
||||
context.TODO(),
|
||||
&apigateway.GetResourcesInput{
|
||||
RestApiId: &id,
|
||||
Position: PaginationControl2,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
GetResources, err := sdk.CachedApiGatewayGetResources(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
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++
|
||||
break
|
||||
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++
|
||||
|
||||
for _, stage := range GetStages.Item {
|
||||
stageName := aws.ToString(stage.StageName)
|
||||
for _, resource := range GetResources.Items {
|
||||
if len(resource.ResourceMethods) != 0 {
|
||||
path := aws.ToString(resource.Path)
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
|
||||
for _, stage := range GetStages.Item {
|
||||
stageName := aws.ToString(stage.StageName)
|
||||
for _, resource := range GetResources {
|
||||
if len(resource.ResourceMethods) != 0 {
|
||||
path := aws.ToString(resource.Path)
|
||||
|
||||
endpoints = append(endpoints, Endpoint{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
})
|
||||
}
|
||||
endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path)
|
||||
|
||||
endpoints = append(endpoints, Endpoint{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
})
|
||||
}
|
||||
}
|
||||
if GetResources.Position != nil {
|
||||
PaginationControl2 = GetResources.Position
|
||||
} else {
|
||||
PaginationControl2 = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
@@ -1170,10 +1107,6 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
|
||||
var PaginationControl2 *string
|
||||
var PaginationControl3 *string
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
@@ -1183,16 +1116,20 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
GetDomainNames, err := m.APIGatewayv2Client.GetDomainNames(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetDomainNamesInput{
|
||||
NextToken: PaginationControl2,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
GetDomainNames, err := sdk.CachedAPIGatewayv2GetDomainNames(m.APIGatewayv2Client, 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++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
GetApiMappings, err := sdk.CachedAPIGatewayv2GetApiMappings(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, domain)
|
||||
|
||||
if err != nil {
|
||||
if errors.As(err, &oe) {
|
||||
@@ -1203,87 +1140,46 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
break
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames.Items {
|
||||
for _, mapping := range GetApiMappings {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
if stage == "$default" {
|
||||
stage = ""
|
||||
}
|
||||
path := aws.ToString(mapping.ApiMappingKey)
|
||||
|
||||
domain := aws.ToString(item.DomainName)
|
||||
|
||||
for {
|
||||
GetApiMappings, err := m.APIGatewayv2Client.GetApiMappings(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetApiMappingsInput{
|
||||
DomainName: item.DomainName,
|
||||
NextToken: PaginationControl3,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = 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++
|
||||
break
|
||||
}
|
||||
|
||||
for _, mapping := range GetApiMappings.Items {
|
||||
stage := aws.ToString(mapping.Stage)
|
||||
if stage == "$default" {
|
||||
stage = ""
|
||||
}
|
||||
path := aws.ToString(mapping.ApiMappingKey)
|
||||
|
||||
for _, api := range Items {
|
||||
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
|
||||
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
var old string
|
||||
if stage == "" {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
|
||||
} else {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
|
||||
}
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if path == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, path)
|
||||
}
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
for _, api := range Items {
|
||||
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
|
||||
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
var old string
|
||||
if stage == "" {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/", aws.ToString(mapping.ApiId), r)
|
||||
} else {
|
||||
old = fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", aws.ToString(mapping.ApiId), r, stage)
|
||||
}
|
||||
if strings.HasPrefix(endpoint.Endpoint, old) {
|
||||
var new string
|
||||
if path == "" {
|
||||
new = fmt.Sprintf("https://%s/", domain)
|
||||
} else {
|
||||
new = fmt.Sprintf("https://%s/%s/", domain, path)
|
||||
}
|
||||
break
|
||||
endpoint.Endpoint = strings.Replace(endpoint.Endpoint, old, new, 1)
|
||||
endpoint.Name = domain
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if GetApiMappings.NextToken != nil {
|
||||
PaginationControl3 = GetApiMappings.NextToken
|
||||
} else {
|
||||
PaginationControl3 = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if GetDomainNames.NextToken != nil {
|
||||
PaginationControl2 = GetDomainNames.NextToken
|
||||
} else {
|
||||
PaginationControl2 = nil
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Types.Api) []Endpoint {
|
||||
var endpoints []Endpoint
|
||||
|
||||
var PaginationControl2 *string
|
||||
var PaginationControl3 *string
|
||||
awsService := "APIGatewayv2"
|
||||
|
||||
var public string
|
||||
@@ -1295,98 +1191,61 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2
|
||||
protocol := "https"
|
||||
|
||||
var stages []string
|
||||
for {
|
||||
GetStages, err := m.APIGatewayv2Client.GetStages(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetStagesInput{
|
||||
ApiId: &id,
|
||||
NextToken: PaginationControl2,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
GetStages, err := sdk.CachedAPIGatewayv2GetStages(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
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++
|
||||
break
|
||||
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++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages.Items {
|
||||
s := aws.ToString(stage.StageName)
|
||||
if s == "$default" {
|
||||
s = ""
|
||||
}
|
||||
stages = append(stages, s)
|
||||
for _, stage := range GetStages {
|
||||
s := aws.ToString(stage.StageName)
|
||||
if s == "$default" {
|
||||
s = ""
|
||||
}
|
||||
stages = append(stages, s)
|
||||
}
|
||||
|
||||
if GetStages.NextToken != nil {
|
||||
PaginationControl2 = GetStages.NextToken
|
||||
} else {
|
||||
PaginationControl2 = nil
|
||||
break
|
||||
GetRoutes, err := sdk.CachedAPIGatewayv2GetRoutes(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r, id)
|
||||
|
||||
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++
|
||||
}
|
||||
|
||||
for _, stage := range stages {
|
||||
for _, route := range GetRoutes {
|
||||
routeKey := route.RouteKey
|
||||
var path string
|
||||
if len(strings.Fields(*routeKey)) == 2 {
|
||||
path = strings.Fields(*routeKey)[1]
|
||||
}
|
||||
var endpoint string
|
||||
if stage == "" {
|
||||
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
|
||||
}
|
||||
public = "True"
|
||||
|
||||
endpoints = append(endpoints, Endpoint{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
GetRoutes, err := m.APIGatewayv2Client.GetRoutes(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetRoutesInput{
|
||||
ApiId: &id,
|
||||
NextToken: PaginationControl3,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = 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++
|
||||
continue
|
||||
}
|
||||
|
||||
for _, stage := range stages {
|
||||
for _, route := range GetRoutes.Items {
|
||||
routeKey := route.RouteKey
|
||||
var path string
|
||||
if len(strings.Fields(*routeKey)) == 2 {
|
||||
path = strings.Fields(*routeKey)[1]
|
||||
}
|
||||
var endpoint string
|
||||
if stage == "" {
|
||||
endpoint = fmt.Sprintf("%s%s", raw_endpoint, path)
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path)
|
||||
}
|
||||
public = "True"
|
||||
|
||||
endpoints = append(endpoints, Endpoint{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
})
|
||||
}
|
||||
}
|
||||
if GetRoutes.NextToken != nil {
|
||||
PaginationControl3 = GetRoutes.NextToken
|
||||
} else {
|
||||
PaginationControl3 = nil
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
|
||||
+75
-12
@@ -66,6 +66,7 @@ type EnvironmentVariable struct {
|
||||
region string
|
||||
environmentVarName string
|
||||
environmentVarValue string
|
||||
interesting bool
|
||||
}
|
||||
|
||||
func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
@@ -161,19 +162,35 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
}
|
||||
}
|
||||
|
||||
m.determineIfInteresting()
|
||||
//Table rows
|
||||
for _, envVar := range m.EnvironmentVariables {
|
||||
m.output.Body = append(
|
||||
m.output.Body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
envVar.name,
|
||||
envVar.environmentVarName,
|
||||
envVar.environmentVarValue,
|
||||
},
|
||||
)
|
||||
|
||||
if envVar.interesting {
|
||||
m.output.Body = append(
|
||||
m.output.Body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
envVar.name,
|
||||
magenta(envVar.environmentVarName),
|
||||
magenta(envVar.environmentVarValue),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
m.output.Body = append(
|
||||
m.output.Body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
envVar.name,
|
||||
envVar.environmentVarName,
|
||||
envVar.environmentVarValue,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -185,14 +202,27 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
// Create another table file that only contains the environment variables that seem to be secrets.
|
||||
body := m.interestingEnvVarsOnly()
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: body,
|
||||
TableCols: tableCols,
|
||||
Name: fmt.Sprintf("%s-interesting", m.output.CallingModule),
|
||||
SkipPrintToScreen: true,
|
||||
})
|
||||
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
fmt.Printf("[%s][%s] %s environment variables found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
@@ -202,6 +232,20 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
}
|
||||
|
||||
func (m *EnvsModule) determineIfInteresting() {
|
||||
for i, envVar := range m.EnvironmentVariables {
|
||||
// If the environment variable name contains the word secret, key, token, or password, we'll make it magenta to make it stand out.
|
||||
lowerEnvVar := strings.ToLower(envVar.environmentVarName)
|
||||
if (strings.Contains(lowerEnvVar, "secret") && !strings.Contains(lowerEnvVar, "arn")) ||
|
||||
strings.Contains(lowerEnvVar, "key") ||
|
||||
strings.Contains(lowerEnvVar, "token") ||
|
||||
strings.Contains(lowerEnvVar, "user") ||
|
||||
strings.Contains(lowerEnvVar, "pass") {
|
||||
m.EnvironmentVariables[i].interesting = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func EnvVarsContains(element EnvironmentVariable, array []EnvironmentVariable) bool {
|
||||
for _, v := range array {
|
||||
if v == element {
|
||||
@@ -786,3 +830,22 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (m *EnvsModule) interestingEnvVarsOnly() [][]string {
|
||||
var interestingBody [][]string
|
||||
for _, envVar := range m.EnvironmentVariables {
|
||||
if envVar.interesting {
|
||||
interestingBody = append(
|
||||
interestingBody, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
envVar.name,
|
||||
magenta(envVar.environmentVarName),
|
||||
magenta(envVar.environmentVarValue),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return interestingBody
|
||||
}
|
||||
+73
-31
@@ -27,9 +27,10 @@ type IamSimulatorModule struct {
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Goroutines int
|
||||
AWSProfileProvided string
|
||||
AWSProfileStub string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
SimulatorResults []SimulatorResult
|
||||
@@ -76,14 +77,19 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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.AWSProfileStub, aws.ToString(m.Caller.Account)))
|
||||
var filename string
|
||||
var actionList []string
|
||||
var pmapperCommands []string
|
||||
var pmapperOutFileName string
|
||||
var inputArn string
|
||||
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = internal.BuildAWSPath(m.Caller)
|
||||
if m.AWSProfileProvided == "" {
|
||||
m.AWSProfileStub = internal.BuildAWSPath(m.Caller)
|
||||
} else {
|
||||
m.AWSProfileStub = m.AWSProfileProvided
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -102,37 +108,69 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
if principal != "" {
|
||||
if action != "" {
|
||||
// The user specified a specific --principal and a specific --action
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal, action)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal, action)
|
||||
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
actionList = append(actionList, action)
|
||||
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
|
||||
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
|
||||
if !strings.Contains(principal, "arn:") {
|
||||
// try as a role
|
||||
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
|
||||
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
|
||||
// try as a user
|
||||
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
|
||||
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
|
||||
} else {
|
||||
// the arn was supplied so just run it
|
||||
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
|
||||
}
|
||||
|
||||
} else {
|
||||
// The user specified a specific --principal, but --action was empty
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal)
|
||||
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
|
||||
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
|
||||
if !strings.Contains(principal, "arn:") {
|
||||
// try as a role
|
||||
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
|
||||
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
|
||||
// try as a user
|
||||
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
|
||||
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
|
||||
} else {
|
||||
// the arn was supplied so just run it
|
||||
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if action != "" {
|
||||
// The did not specify a specific --principal, but they did specify an --action
|
||||
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), action)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), action)
|
||||
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
actionList = append(actionList, action)
|
||||
wg.Add(1)
|
||||
m.getIAMUsers(wg, actionList, resource, dataReceiver)
|
||||
wg.Add(1)
|
||||
m.getIAMRoles(wg, actionList, resource, dataReceiver)
|
||||
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
|
||||
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
if m.AWSProfileProvided != "" {
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
|
||||
} else {
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
|
||||
}
|
||||
} else {
|
||||
// Both --principal and --action are empty. Run in default mode!
|
||||
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
m.output.FullFilename = m.output.CallingModule
|
||||
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
|
||||
filename = m.output.CallingModule
|
||||
m.executeChecks(wg, resource, dataReceiver)
|
||||
for _, action := range defaultActionNames {
|
||||
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
|
||||
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
if m.AWSProfileProvided != "" {
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
|
||||
} else {
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -199,7 +237,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
@@ -212,12 +250,12 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
Name: filename,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
o.PrefixIdentifier = m.AWSProfileStub
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
|
||||
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
|
||||
// for _, line := range pmapperCommands {
|
||||
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
|
||||
@@ -225,9 +263,9 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
m.writeLoot(o.Table.DirectoryName, verbosity, pmapperCommands)
|
||||
|
||||
} else if principal != "" || action != "" {
|
||||
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
|
||||
}
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), m.output.CallingModule)
|
||||
}
|
||||
|
||||
func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pmapperCommands []string) {
|
||||
@@ -240,7 +278,11 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
|
||||
|
||||
outFile := filepath.Join(path, "iam-simulator-pmapper-commands.txt")
|
||||
var out string
|
||||
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfile)
|
||||
if m.AWSProfileProvided != "" {
|
||||
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfileProvided)
|
||||
} else {
|
||||
out = out + fmt.Sprintf("pmapper graph create\n")
|
||||
}
|
||||
for _, line := range pmapperCommands {
|
||||
out = out + line
|
||||
}
|
||||
@@ -252,12 +294,12 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), outFile)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), outFile)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+43
-27
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -490,9 +491,8 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
|
||||
var profile string
|
||||
var externalIP string
|
||||
var name string = ""
|
||||
var adminRole string = ""
|
||||
var roleArn string = ""
|
||||
var profileArn, profileName, name string
|
||||
|
||||
// The name is in a tag so we have to do this to grab the value from the right tag
|
||||
for _, tag := range instance.Tags {
|
||||
@@ -512,34 +512,50 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
|
||||
if instance.IamInstanceProfile == nil {
|
||||
profile = "NoInstanceProfile"
|
||||
} else {
|
||||
// This returns only the role name without the preceding forward slash.
|
||||
profileARN := aws.ToString(instance.IamInstanceProfile.Arn)
|
||||
profileID := aws.ToString(instance.IamInstanceProfile.Id)
|
||||
profile = strings.Split(profileARN, "/")[len(strings.Split(profileARN, "/"))-1]
|
||||
|
||||
if roles, ok := m.InstanceProfileToRolesMap[profileID]; ok {
|
||||
for _, role := range roles {
|
||||
roleArn = aws.ToString(role.Arn)
|
||||
}
|
||||
dataReceiver <- MappedInstance{
|
||||
ID: aws.ToString(instance.InstanceId),
|
||||
Name: aws.ToString(&name),
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
|
||||
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
|
||||
State: string(instance.State.Name),
|
||||
ExternalIP: externalIP,
|
||||
PrivateIP: aws.ToString(instance.PrivateIpAddress),
|
||||
Profile: profile,
|
||||
Role: "",
|
||||
Region: region,
|
||||
Admin: adminRole,
|
||||
CanPrivEsc: "",
|
||||
}
|
||||
|
||||
}
|
||||
dataReceiver <- MappedInstance{
|
||||
ID: aws.ToString(instance.InstanceId),
|
||||
Name: aws.ToString(&name),
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
|
||||
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
|
||||
State: string(instance.State.Name),
|
||||
ExternalIP: externalIP,
|
||||
PrivateIP: aws.ToString(instance.PrivateIpAddress),
|
||||
Profile: profile,
|
||||
Role: roleArn,
|
||||
Region: region,
|
||||
Admin: adminRole,
|
||||
CanPrivEsc: "",
|
||||
}
|
||||
} else {
|
||||
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)
|
||||
|
||||
// Extracting instance profile name from ARN
|
||||
profileName = strings.Split(profileArn, "/")[len(strings.Split(profileArn, "/"))-1]
|
||||
|
||||
// Describe the IAM instance profile
|
||||
profileOutput, err := sdk.CachedIamGetInstanceProfile(m.IAMClient, aws.ToString(m.Caller.Account), profileName)
|
||||
if err != nil {
|
||||
log.Printf("failed to get instance profile for %s, %v", profileArn, err)
|
||||
}
|
||||
|
||||
for _, role := range profileOutput.Roles {
|
||||
dataReceiver <- MappedInstance{
|
||||
ID: aws.ToString(instance.InstanceId),
|
||||
Name: aws.ToString(&name),
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
|
||||
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
|
||||
State: string(instance.State.Name),
|
||||
ExternalIP: externalIP,
|
||||
PrivateIP: aws.ToString(instance.PrivateIpAddress),
|
||||
Profile: profile,
|
||||
Role: aws.ToString(role.Arn),
|
||||
Region: region,
|
||||
Admin: adminRole,
|
||||
CanPrivEsc: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InstancesModule) getRolesFromInstanceProfiles() {
|
||||
|
||||
+3
-3
@@ -90,7 +90,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "permissions"
|
||||
m.output.FullFilename = m.output.CallingModule
|
||||
filename := m.output.CallingModule
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
@@ -101,7 +101,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
|
||||
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
if principal != "" {
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
}
|
||||
|
||||
m.getGAAD()
|
||||
@@ -192,7 +192,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
|
||||
Header: m.output.Headers,
|
||||
TableCols: tableCols,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Name: filename,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+34
-30
@@ -20,16 +20,17 @@ import (
|
||||
|
||||
type ResourceTrustsModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
AWSMFAToken string
|
||||
|
||||
AWSProfile string
|
||||
CloudFoxVersion string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
AWSMFAToken string
|
||||
AWSConfig aws.Config
|
||||
AWSProfileProvided string
|
||||
AWSProfileStub string
|
||||
CloudFoxVersion string
|
||||
|
||||
Resources2 []Resource2
|
||||
CommandCounter internal.CommandCounter
|
||||
@@ -62,13 +63,16 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = internal.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
if m.AWSProfileProvided == "" {
|
||||
m.AWSProfileStub = internal.BuildAWSPath(m.Caller)
|
||||
} else {
|
||||
m.AWSProfileStub = m.AWSProfileProvided
|
||||
}
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileProvided, aws.ToString(m.Caller.Account)))
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
@@ -172,16 +176,16 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
o.PrefixIdentifier = m.AWSProfileStub
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
//m.writeLoot(o.Table.DirectoryName, verbosity)
|
||||
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s resource policies found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), strconv.Itoa(len(m.output.Body)))
|
||||
//fmt.Printf("[%s][%s] Resource policies stored to: %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.getLootDir())
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] No resource policies found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
|
||||
}
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), m.output.CallingModule)
|
||||
|
||||
}
|
||||
|
||||
@@ -292,7 +296,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable, m.AWSMFAToken)
|
||||
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.WrapTable, m.AWSMFAToken)
|
||||
|
||||
ListTopics, err := cloudFoxSNSClient.listTopics(r)
|
||||
if err != nil {
|
||||
@@ -368,7 +372,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.AWSMFAToken)
|
||||
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.AWSMFAToken)
|
||||
|
||||
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
@@ -451,7 +455,7 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListQueues, err := cloudFoxSQSClient.listQueues(r)
|
||||
if err != nil {
|
||||
@@ -513,7 +517,7 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
DescribeRepositories, err := sdk.CachedECRDescribeRepositories(cloudFoxECRClient.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -577,7 +581,7 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -651,7 +655,7 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
|
||||
if err != nil {
|
||||
@@ -716,7 +720,7 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -786,7 +790,7 @@ func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r strin
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSecretsManagerClient := InitSecretsManagerClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxSecretsManagerClient := InitSecretsManagerClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListSecrets, err := sdk.CachedSecretsManagerListSecrets(cloudFoxSecretsManagerClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -852,7 +856,7 @@ func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sy
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxGlueClient := InitGlueClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
cloudFoxGlueClient := InitGlueClient(m.Caller, m.AWSProfileProvided, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ResourcePolicies, err := sdk.CachedGlueGetResourcePolicies(cloudFoxGlueClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
|
||||
+227
-132
@@ -1,22 +1,19 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -42,6 +39,8 @@ type RoleTrustsModule struct {
|
||||
AnalyzedRoles []AnalyzedRole
|
||||
RoleTrustTable []RoleTrustRow
|
||||
|
||||
vendors *knownawsaccountslookup.Vendors
|
||||
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
|
||||
@@ -62,46 +61,12 @@ type RoleTrustRow struct {
|
||||
|
||||
type AnalyzedRole struct {
|
||||
roleARN *string
|
||||
trustsDoc trustPolicyDocument
|
||||
trustsDoc policy.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
|
||||
@@ -113,6 +78,9 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int
|
||||
m.AWSProfile = internal.BuildAWSPath(m.Caller)
|
||||
}
|
||||
localAdminMap := make(map[string]bool)
|
||||
m.vendors = knownawsaccountslookup.NewVendorMap()
|
||||
m.vendors.PopulateKnownAWSAccounts()
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
//fmt.Printf("[%s][%s] Looking for pmapper data for this account and building a PrivEsc graph in golang if it exists.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
|
||||
@@ -134,6 +102,7 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -152,7 +121,14 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int
|
||||
TableCols: principalTableCols,
|
||||
Name: "role-trusts-principals",
|
||||
})
|
||||
|
||||
rootPrincipalsHeader, rootPrincipalsBody, rootPrincipalTableCols := m.printPrincipalTrustsRootOnly(outputDirectory)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: rootPrincipalsHeader,
|
||||
Body: rootPrincipalsBody,
|
||||
TableCols: rootPrincipalTableCols,
|
||||
Name: "role-trusts-principals-root-trusts-without-external-id",
|
||||
SkipPrintToScreen: true,
|
||||
})
|
||||
servicesHeader, servicesBody, serviceTableCols := m.printServiceTrusts(outputDirectory)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: servicesHeader,
|
||||
@@ -176,12 +152,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 principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
|
||||
fmt.Printf("[%s][%s] %s service role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(servicesBody)))
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
if len(federatedBody) > 0 {
|
||||
fmt.Printf("[%s][%s] %s principal role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(federatedBody)))
|
||||
fmt.Printf("[%s][%s] %s federated 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))
|
||||
}
|
||||
@@ -228,6 +204,14 @@ 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") {
|
||||
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),
|
||||
@@ -247,6 +231,7 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
@@ -254,6 +239,78 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin
|
||||
|
||||
}
|
||||
|
||||
// printPrincipalTrusts but only those that have a trusted principal that contains :root and also does not have an external ID
|
||||
func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string) ([]string, [][]string, []string) {
|
||||
var header []string
|
||||
var body [][]string
|
||||
m.output.FullFilename = ""
|
||||
m.output.Body = nil
|
||||
m.output.CallingModule = "role-trusts"
|
||||
m.output.FullFilename = "role-trusts-principals-root-trusts-without-external-id"
|
||||
header = []string{
|
||||
"Account",
|
||||
"Role Arn",
|
||||
"Role Name",
|
||||
"Trusted Principal",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
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 Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
for _, principal := range statement.Principal.AWS {
|
||||
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == "" {
|
||||
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,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedPrincipal,
|
||||
RoleTrustRow.ExternalID,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
return header, body, tableCols
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) printServiceTrusts(outputDirectory string) ([]string, [][]string, []string) {
|
||||
var header []string
|
||||
var body [][]string
|
||||
@@ -279,7 +336,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{"Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
tableCols = []string{"Account", "Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
@@ -347,7 +404,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{"Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
tableCols = []string{"Account", "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?"}
|
||||
@@ -360,23 +417,25 @@ 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, 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,
|
||||
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})
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedFederatedProvider,
|
||||
RoleTrustRow.TrustedFederatedSubject,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -387,47 +446,120 @@ func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]strin
|
||||
|
||||
}
|
||||
|
||||
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!!!"
|
||||
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...)
|
||||
} else {
|
||||
column3 = trustedRepos
|
||||
subjects = append(subjects, "ALL REPOS!!!")
|
||||
}
|
||||
} 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
|
||||
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 {
|
||||
column3 = "ALL SERVICE ACCOUNTS!"
|
||||
subjects = append(subjects, "ALL SERVICE ACCOUNTS!!!")
|
||||
}
|
||||
} else if statement.Principal.Federated[0] == "cognito-identity.amazonaws.com" {
|
||||
column2 = "Cognito" // (" + statement.Principal.Federated[0] + ")"
|
||||
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
|
||||
column3 = statement.Condition.ForAnyValueStringLike.CognitoAMR
|
||||
// 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")
|
||||
}
|
||||
} 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] + ")"
|
||||
// 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"
|
||||
if statement.Condition.ForAnyValueStringLike.CognitoAMR != "" {
|
||||
subjects = append(subjects, statement.Condition.ForAnyValueStringLike.CognitoAMR)
|
||||
} else {
|
||||
subjects = append(subjects, "ALL IDENTITIES")
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
// 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 column2, column3
|
||||
return provider, subjects
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) sortTrustsTablePerTrustedPrincipal() {
|
||||
@@ -447,7 +579,7 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
|
||||
}
|
||||
|
||||
for _, role := range ListRoles {
|
||||
trustsdoc, err := parseRoleTrustPolicyDocument(role)
|
||||
trustsdoc, err := policy.ParseRoleTrustPolicyDocument(role)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
@@ -467,40 +599,3 @@ 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")
|
||||
}
|
||||
+340
-1
@@ -6,16 +6,51 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"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 {
|
||||
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
|
||||
GetStages(context.Context, *apigateway.GetStagesInput, ...func(*apigateway.Options)) (*apigateway.GetStagesOutput, error)
|
||||
GetResources(context.Context, *apigateway.GetResourcesInput, ...func(*apigateway.Options)) (*apigateway.GetResourcesOutput, error)
|
||||
GetDomainNames(context.Context, *apigateway.GetDomainNamesInput, ...func(*apigateway.Options)) (*apigateway.GetDomainNamesOutput, error)
|
||||
GetBasePathMappings(context.Context, *apigateway.GetBasePathMappingsInput, ...func(*apigateway.Options)) (*apigateway.GetBasePathMappingsOutput, error)
|
||||
GetMethod(context.Context, *apigateway.GetMethodInput, ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error)
|
||||
GetUsagePlans(context.Context, *apigateway.GetUsagePlansInput, ...func(*apigateway.Options)) (*apigateway.GetUsagePlansOutput, error)
|
||||
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([]apiGatewayTypes.Resource{})
|
||||
gob.Register([]apiGatewayTypes.DomainName{})
|
||||
gob.Register([]apiGatewayTypes.BasePathMapping{})
|
||||
gob.Register(CachedGetMethodOutput{})
|
||||
gob.Register([]apiGatewayTypes.UsagePlan{})
|
||||
gob.Register([]apiGatewayTypes.UsagePlanKey{})
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
|
||||
@@ -51,6 +86,310 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
GetStages, err := client.GetStages(
|
||||
context.TODO(),
|
||||
&apigateway.GetStagesInput{
|
||||
RestApiId: &restAPIID,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
func CachedApiGatewayGetResources(client APIGatewayClientInterface, accountID string, region string, restAPIID string) ([]apiGatewayTypes.Resource, error) {
|
||||
var PaginationControl *string
|
||||
var resources []apiGatewayTypes.Resource
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetResources-%s-%s", accountID, region, restAPIID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatewayTypes.Resource), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetResources, err := client.GetResources(
|
||||
context.TODO(),
|
||||
&apigateway.GetResourcesInput{
|
||||
RestApiId: &restAPIID,
|
||||
Position: PaginationControl,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return resources, err
|
||||
}
|
||||
|
||||
resources = append(resources, GetResources.Items...)
|
||||
|
||||
//pagination
|
||||
if GetResources.Position == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetResources.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, resources, cache.DefaultExpiration)
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetDomainNames function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
|
||||
func CachedApiGatewayGetDomainNames(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.DomainName, error) {
|
||||
var PaginationControl *string
|
||||
var domainNames []apiGatewayTypes.DomainName
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetDomainNames-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatewayTypes.DomainName), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetDomainNames, err := client.GetDomainNames(
|
||||
context.TODO(),
|
||||
&apigateway.GetDomainNamesInput{
|
||||
Position: PaginationControl,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return domainNames, err
|
||||
}
|
||||
|
||||
domainNames = append(domainNames, GetDomainNames.Items...)
|
||||
|
||||
//pagination
|
||||
if GetDomainNames.Position == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetDomainNames.Position
|
||||
}
|
||||
internal.Cache.Set(cacheKey, domainNames, cache.DefaultExpiration)
|
||||
return domainNames, nil
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetBasePathMappings function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination if needed
|
||||
func CachedApiGatewayGetBasePathMappings(client APIGatewayClientInterface, accountID string, region string, domain *string) ([]apiGatewayTypes.BasePathMapping, error) {
|
||||
var PaginationControl *string
|
||||
var basePathMappings []apiGatewayTypes.BasePathMapping
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetBasePathMappings-%s-%s", accountID, region, aws.ToString(domain))
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatewayTypes.BasePathMapping), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetBasePathMappings, err := client.GetBasePathMappings(
|
||||
context.TODO(),
|
||||
&apigateway.GetBasePathMappingsInput{
|
||||
DomainName: domain,
|
||||
Position: PaginationControl,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return basePathMappings, err
|
||||
}
|
||||
|
||||
basePathMappings = append(basePathMappings, GetBasePathMappings.Items...)
|
||||
|
||||
//pagination
|
||||
if GetBasePathMappings.Position == nil {
|
||||
break
|
||||
}
|
||||
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) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
GetMethod, err := client.GetMethod(
|
||||
context.TODO(),
|
||||
&apigateway.GetMethodInput{
|
||||
RestApiId: &restAPIID,
|
||||
ResourceId: &resourceID,
|
||||
HttpMethod: &method,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return &apigateway.GetMethodOutput{}, err
|
||||
}
|
||||
|
||||
// Convert GetMethodOutput to CachedGetMethodOutput before caching
|
||||
cachedVersion := toCachedGetMethodOutput(GetMethod)
|
||||
internal.Cache.Set(cacheKey, cachedVersion, cache.DefaultExpiration)
|
||||
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
|
||||
var usagePlans []apiGatewayTypes.UsagePlan
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetUsagePlans-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatewayTypes.UsagePlan), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetUsagePlans, err := client.GetUsagePlans(
|
||||
context.TODO(),
|
||||
&apigateway.GetUsagePlansInput{
|
||||
Position: PaginationControl,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return usagePlans, err
|
||||
}
|
||||
|
||||
usagePlans = append(usagePlans, GetUsagePlans.Items...)
|
||||
|
||||
//pagination
|
||||
if GetUsagePlans.Position == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetUsagePlans.Position
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, usagePlans, cache.DefaultExpiration)
|
||||
return usagePlans, nil
|
||||
}
|
||||
|
||||
// create a CachedApiGatewayGetUsagePlanKeys function that accepts a client, account id, region, and usage plan id. Make sure it handles caching, the region option and pagination if needed
|
||||
func CachedApiGatewayGetUsagePlanKeys(client APIGatewayClientInterface, accountID string, region string, usagePlanID string) ([]apiGatewayTypes.UsagePlanKey, error) {
|
||||
var PaginationControl *string
|
||||
var usagePlanKeys []apiGatewayTypes.UsagePlanKey
|
||||
cacheKey := fmt.Sprintf("%s-apigateway-GetUsagePlanKeys-%s-%s", accountID, region, usagePlanID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatewayTypes.UsagePlanKey), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetUsagePlanKeys, err := client.GetUsagePlanKeys(
|
||||
context.TODO(),
|
||||
&apigateway.GetUsagePlanKeysInput{
|
||||
UsagePlanId: &usagePlanID,
|
||||
Position: PaginationControl,
|
||||
},
|
||||
func(o *apigateway.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return usagePlanKeys, err
|
||||
}
|
||||
|
||||
usagePlanKeys = append(usagePlanKeys, GetUsagePlanKeys.Items...)
|
||||
|
||||
//pagination
|
||||
if GetUsagePlanKeys.Position == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetUsagePlanKeys.Position
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, usagePlanKeys, cache.DefaultExpiration)
|
||||
return usagePlanKeys, nil
|
||||
}
|
||||
+117
-2
@@ -15,12 +15,127 @@ func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apig
|
||||
return &apigateway.GetRestApisOutput{
|
||||
Items: []apiGatewayTypes.RestApi{
|
||||
{
|
||||
Id: aws.String("api1"),
|
||||
Id: aws.String("abcdefg"),
|
||||
Name: aws.String("api1"),
|
||||
EndpointConfiguration: &apiGatewayTypes.EndpointConfiguration{
|
||||
Types: []apiGatewayTypes.EndpointType{
|
||||
apiGatewayTypes.EndpointTypePrivate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: aws.String("api2"),
|
||||
Id: aws.String("qwerty"),
|
||||
Name: aws.String("api2"),
|
||||
EndpointConfiguration: &apiGatewayTypes.EndpointConfiguration{
|
||||
Types: []apiGatewayTypes.EndpointType{
|
||||
apiGatewayTypes.EndpointTypeRegional,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetStages(ctx context.Context, input *apigateway.GetStagesInput, options ...func(*apigateway.Options)) (*apigateway.GetStagesOutput, error) {
|
||||
return &apigateway.GetStagesOutput{
|
||||
Item: []apiGatewayTypes.Stage{
|
||||
{
|
||||
StageName: aws.String("stage1"),
|
||||
},
|
||||
{
|
||||
StageName: aws.String("stage2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetResources(ctx context.Context, input *apigateway.GetResourcesInput, options ...func(*apigateway.Options)) (*apigateway.GetResourcesOutput, error) {
|
||||
return &apigateway.GetResourcesOutput{
|
||||
Items: []apiGatewayTypes.Resource{
|
||||
{
|
||||
Id: aws.String("resource1"),
|
||||
Path: aws.String("/path1"),
|
||||
ResourceMethods: map[string]apiGatewayTypes.Method{
|
||||
"GET": {
|
||||
ApiKeyRequired: aws.Bool(true),
|
||||
AuthorizerId: aws.String("authorizer1"),
|
||||
OperationName: aws.String("operation1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: aws.String("resource2"),
|
||||
Path: aws.String("/path2"),
|
||||
ResourceMethods: map[string]apiGatewayTypes.Method{
|
||||
"ANY": {
|
||||
ApiKeyRequired: aws.Bool(true),
|
||||
AuthorizerId: aws.String("authorizer2"),
|
||||
OperationName: aws.String("operation2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetDomainNames(ctx context.Context, input *apigateway.GetDomainNamesInput, options ...func(*apigateway.Options)) (*apigateway.GetDomainNamesOutput, error) {
|
||||
return &apigateway.GetDomainNamesOutput{
|
||||
Items: []apiGatewayTypes.DomainName{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetBasePathMappings(ctx context.Context, input *apigateway.GetBasePathMappingsInput, options ...func(*apigateway.Options)) (*apigateway.GetBasePathMappingsOutput, error) {
|
||||
return &apigateway.GetBasePathMappingsOutput{
|
||||
Items: []apiGatewayTypes.BasePathMapping{
|
||||
{
|
||||
BasePath: aws.String("basepath1"),
|
||||
RestApiId: aws.String("abcdefg"),
|
||||
Stage: aws.String("stage1"),
|
||||
},
|
||||
{
|
||||
BasePath: aws.String("basepath2"),
|
||||
RestApiId: aws.String("qwerty"),
|
||||
Stage: aws.String("stage2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetMethod(ctx context.Context, input *apigateway.GetMethodInput, options ...func(*apigateway.Options)) (*apigateway.GetMethodOutput, error) {
|
||||
return &apigateway.GetMethodOutput{
|
||||
ApiKeyRequired: aws.Bool(true),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetUsagePlans(ctx context.Context, input *apigateway.GetUsagePlansInput, options ...func(*apigateway.Options)) (*apigateway.GetUsagePlansOutput, error) {
|
||||
return &apigateway.GetUsagePlansOutput{
|
||||
Items: []apiGatewayTypes.UsagePlan{
|
||||
{
|
||||
Id: aws.String("usageplan1"),
|
||||
Name: aws.String("usageplan1"),
|
||||
ApiStages: []apiGatewayTypes.ApiStage{
|
||||
{
|
||||
ApiId: aws.String("abcdefg"),
|
||||
Stage: aws.String("stage2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetUsagePlanKeys(ctx context.Context, input *apigateway.GetUsagePlanKeysInput, options ...func(*apigateway.Options)) (*apigateway.GetUsagePlanKeysOutput, error) {
|
||||
return &apigateway.GetUsagePlanKeysOutput{
|
||||
Items: []apiGatewayTypes.UsagePlanKey{
|
||||
{
|
||||
Id: aws.String("usageplankey1"),
|
||||
Type: aws.String("API_KEY"),
|
||||
Value: aws.String("23oieuwefo3rfs"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("usageplankey2"),
|
||||
Type: aws.String("API_KEY"),
|
||||
Value: aws.String("982yf98fdv8dlds"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -14,10 +14,22 @@ import (
|
||||
|
||||
type APIGatewayv2ClientInterface interface {
|
||||
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
|
||||
GetDomainNames(context.Context, *apigatewayv2.GetDomainNamesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetDomainNamesOutput, error)
|
||||
GetApiMappings(context.Context, *apigatewayv2.GetApiMappingsInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApiMappingsOutput, error)
|
||||
GetStages(context.Context, *apigatewayv2.GetStagesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetStagesOutput, error)
|
||||
GetRoutes(context.Context, *apigatewayv2.GetRoutesInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetRoutesOutput, error)
|
||||
}
|
||||
|
||||
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.Route{})
|
||||
}
|
||||
|
||||
func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID string, region string) ([]apiGatwayV2Types.Api, error) {
|
||||
@@ -55,3 +67,154 @@ func CachedAPIGatewayv2GetAPIs(client APIGatewayv2ClientInterface, accountID str
|
||||
internal.Cache.Set(cacheKey, apis, cache.DefaultExpiration)
|
||||
return apis, nil
|
||||
}
|
||||
|
||||
func CachedAPIGatewayv2GetDomainNames(client APIGatewayv2ClientInterface, accountID string, region string) ([]domainName, error) {
|
||||
var PaginationControl *string
|
||||
var domainNames []domainName
|
||||
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetDomainNames-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]domainName), nil
|
||||
}
|
||||
for {
|
||||
GetDomainNames, err := client.GetDomainNames(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetDomainNamesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return domainNames, err
|
||||
}
|
||||
// convert []apiGatwayV2Types.DomainName to []domainName
|
||||
|
||||
// Convert each element to domainName before appending to avoid conflicting with apigateway's domainname type
|
||||
for _, item := range GetDomainNames.Items {
|
||||
domainNames = append(domainNames, domainName(item))
|
||||
}
|
||||
|
||||
//pagination
|
||||
if GetDomainNames.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetDomainNames.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, domainNames, cache.DefaultExpiration)
|
||||
return domainNames, nil
|
||||
}
|
||||
|
||||
func CachedAPIGatewayv2GetApiMappings(client APIGatewayv2ClientInterface, accountID string, region string, domain string) ([]apiGatwayV2Types.ApiMapping, error) {
|
||||
var PaginationControl *string
|
||||
var apiMappings []apiGatwayV2Types.ApiMapping
|
||||
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetApiMappings-%s-%s", accountID, region, domain)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatwayV2Types.ApiMapping), nil
|
||||
}
|
||||
for {
|
||||
GetApiMappings, err := client.GetApiMappings(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetApiMappingsInput{
|
||||
DomainName: &domain,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return apiMappings, err
|
||||
}
|
||||
|
||||
apiMappings = append(apiMappings, GetApiMappings.Items...)
|
||||
|
||||
//pagination
|
||||
if GetApiMappings.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetApiMappings.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, apiMappings, cache.DefaultExpiration)
|
||||
return apiMappings, nil
|
||||
}
|
||||
|
||||
func CachedAPIGatewayv2GetStages(client APIGatewayv2ClientInterface, accountID string, region string, apiID string) ([]apiGatwayV2Types.Stage, error) {
|
||||
var PaginationControl *string
|
||||
var stages []apiGatwayV2Types.Stage
|
||||
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetStages-%s-%s", accountID, region, apiID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatwayV2Types.Stage), nil
|
||||
}
|
||||
for {
|
||||
GetStages, err := client.GetStages(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetStagesInput{
|
||||
ApiId: &apiID,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return stages, err
|
||||
}
|
||||
|
||||
stages = append(stages, GetStages.Items...)
|
||||
|
||||
//pagination
|
||||
if GetStages.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetStages.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, stages, cache.DefaultExpiration)
|
||||
return stages, nil
|
||||
}
|
||||
|
||||
func CachedAPIGatewayv2GetRoutes(client APIGatewayv2ClientInterface, accountID string, region string, apiID string) ([]apiGatwayV2Types.Route, error) {
|
||||
var PaginationControl *string
|
||||
var routes []apiGatwayV2Types.Route
|
||||
cacheKey := fmt.Sprintf("%s-apigatewayv2-GetRoutes-%s-%s", accountID, region, apiID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]apiGatwayV2Types.Route), nil
|
||||
}
|
||||
for {
|
||||
GetRoutes, err := client.GetRoutes(
|
||||
context.TODO(),
|
||||
&apigatewayv2.GetRoutesInput{
|
||||
ApiId: &apiID,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *apigatewayv2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return routes, err
|
||||
}
|
||||
|
||||
routes = append(routes, GetRoutes.Items...)
|
||||
|
||||
//pagination
|
||||
if GetRoutes.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = GetRoutes.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, routes, cache.DefaultExpiration)
|
||||
return routes, nil
|
||||
}
|
||||
@@ -15,12 +15,68 @@ func (m *MockedAWSAPIGatewayv2Client) GetApis(ctx context.Context, input *apigat
|
||||
return &apigatewayv2.GetApisOutput{
|
||||
Items: []apiGatwayV2Types.Api{
|
||||
{
|
||||
ApiId: aws.String("api1"),
|
||||
Name: aws.String("api1"),
|
||||
ApiId: aws.String("asdfsdfasdf"),
|
||||
Name: aws.String("api1"),
|
||||
ApiEndpoint: aws.String("https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com"),
|
||||
},
|
||||
{
|
||||
ApiId: aws.String("api2"),
|
||||
Name: aws.String("api2"),
|
||||
ApiId: aws.String("qwertyqwerty"),
|
||||
Name: aws.String("api2"),
|
||||
ApiEndpoint: aws.String("https://qwertyqwerty.execute-api.us-east-1.amazonaws.com"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayv2Client) GetDomainNames(ctx context.Context, input *apigatewayv2.GetDomainNamesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetDomainNamesOutput, error) {
|
||||
return &apigatewayv2.GetDomainNamesOutput{
|
||||
Items: []apiGatwayV2Types.DomainName{
|
||||
{
|
||||
DomainName: aws.String("domain1"),
|
||||
},
|
||||
{
|
||||
DomainName: aws.String("domain2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayv2Client) GetApiMappings(ctx context.Context, input *apigatewayv2.GetApiMappingsInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApiMappingsOutput, error) {
|
||||
return &apigatewayv2.GetApiMappingsOutput{
|
||||
Items: []apiGatwayV2Types.ApiMapping{
|
||||
{
|
||||
ApiMappingId: aws.String("apiMapping1"),
|
||||
},
|
||||
{
|
||||
ApiMappingId: aws.String("apiMapping2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayv2Client) GetStages(ctx context.Context, input *apigatewayv2.GetStagesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetStagesOutput, error) {
|
||||
return &apigatewayv2.GetStagesOutput{
|
||||
Items: []apiGatwayV2Types.Stage{
|
||||
{
|
||||
StageName: aws.String("stage1"),
|
||||
},
|
||||
{
|
||||
StageName: aws.String("stage2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayv2Client) GetRoutes(ctx context.Context, input *apigatewayv2.GetRoutesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetRoutesOutput, error) {
|
||||
return &apigatewayv2.GetRoutesOutput{
|
||||
Items: []apiGatwayV2Types.Route{
|
||||
{
|
||||
RouteId: aws.String("route1"),
|
||||
RouteKey: aws.String("POST /route1"),
|
||||
},
|
||||
{
|
||||
RouteId: aws.String("route2"),
|
||||
RouteKey: aws.String("GET /route2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -87,7 +87,7 @@ func CachedCodeBuildBatchGetProjects(CodeBuildClient CodeBuildClientInterface, a
|
||||
return project, err
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, BatchGetProjects.Projects[0], cache.DefaultExpiration)
|
||||
internal.Cache.Set(cacheKey, BatchGetProjects.Projects, cache.DefaultExpiration)
|
||||
return BatchGetProjects.Projects[0], nil
|
||||
}
|
||||
|
||||
|
||||
+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,10 +10,6 @@ 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{
|
||||
@@ -21,3 +17,9 @@ 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
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type AWSIAMClientInterface interface {
|
||||
SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error)
|
||||
ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
|
||||
ListGroups(ctx context.Context, params *iam.ListGroupsInput, optFns ...func(*iam.Options)) (*iam.ListGroupsOutput, error)
|
||||
GetInstanceProfile(ctx context.Context, params *iam.GetInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -34,6 +35,7 @@ func init() {
|
||||
gob.Register([]iamTypes.InstanceProfile{})
|
||||
gob.Register([]iamTypes.EvaluationResult{})
|
||||
gob.Register(customGAADOutput{})
|
||||
gob.Register(iamTypes.InstanceProfile{})
|
||||
}
|
||||
|
||||
func CachedIamListUsers(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.User, error) {
|
||||
@@ -288,3 +290,28 @@ func CachedIamListGroups(IAMClient AWSIAMClientInterface, accountID string) ([]i
|
||||
return Groups, nil
|
||||
|
||||
}
|
||||
|
||||
func CachedIamGetInstanceProfile(IAMClient AWSIAMClientInterface, accountID string, instanceProfileName string) (iamTypes.InstanceProfile, error) {
|
||||
var InstanceProfile iamTypes.InstanceProfile
|
||||
cacheKey := fmt.Sprintf("%s-iam-GetInstanceProfile-%s", accountID, instanceProfileName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(iamTypes.InstanceProfile), nil
|
||||
}
|
||||
|
||||
GetInstanceProfile, err := IAMClient.GetInstanceProfile(
|
||||
context.TODO(),
|
||||
&iam.GetInstanceProfileInput{
|
||||
InstanceProfileName: &instanceProfileName,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return InstanceProfile, err
|
||||
}
|
||||
|
||||
InstanceProfile = *GetInstanceProfile.InstanceProfile
|
||||
|
||||
internal.Cache.Set(cacheKey, InstanceProfile, cache.DefaultExpiration)
|
||||
return InstanceProfile, nil
|
||||
|
||||
}
|
||||
@@ -21,7 +21,6 @@ func init() {
|
||||
//need to do this to avoid conflicts with the Instance type in the ec2 package
|
||||
type lightsailInstance lightsailTypes.Instance
|
||||
gob.Register([]lightsailInstance{})
|
||||
|
||||
gob.Register([]lightsailTypes.ContainerService{})
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,13 @@ 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) {
|
||||
@@ -55,3 +58,39 @@ 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
|
||||
}
|
||||
+52
-2
@@ -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,3 +32,53 @@ 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
|
||||
}
|
||||
+4
-4
@@ -66,10 +66,10 @@ func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, input *s3.GetBucke
|
||||
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, input *s3.GetPublicAccessBlockInput, options ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
|
||||
return &s3.GetPublicAccessBlockOutput{
|
||||
PublicAccessBlockConfiguration: &s3Types.PublicAccessBlockConfiguration{
|
||||
BlockPublicAcls: true,
|
||||
BlockPublicPolicy: true,
|
||||
IgnorePublicAcls: true,
|
||||
RestrictPublicBuckets: true,
|
||||
BlockPublicAcls: aws.Bool(true),
|
||||
BlockPublicPolicy: aws.Bool(true),
|
||||
IgnorePublicAcls: aws.Bool(true),
|
||||
RestrictPublicBuckets: aws.Bool(true),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/bishopfox/awsservicemap"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type WorkloadsModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
SkipAdminCheck bool
|
||||
|
||||
// Service Clients
|
||||
EC2Client sdk.AWSEC2ClientInterface
|
||||
ECSClient sdk.AWSECSClientInterface
|
||||
LambdaClient sdk.LambdaClientInterface
|
||||
AppRunnerClient sdk.AppRunnerClientInterface
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
//LightsailClient sdk.MockedLightsailClient
|
||||
//SagemakerClient *sagemaker.Client
|
||||
|
||||
pmapperMod PmapperModule
|
||||
pmapperError error
|
||||
iamSimClient IamSimulatorModule
|
||||
InstanceProfileToRolesMap map[string][]iamTypes.Role
|
||||
|
||||
// Main module data
|
||||
Workloads []Workload
|
||||
CommandCounter internal.CommandCounter
|
||||
modLog *logrus.Entry
|
||||
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
}
|
||||
|
||||
type Workload struct {
|
||||
AWSService string
|
||||
Region string
|
||||
Type string
|
||||
Name string
|
||||
Arn string
|
||||
Role string
|
||||
Admin string
|
||||
CanPrivEsc string
|
||||
Public string
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) PrintWorkloads(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "workloads"
|
||||
localAdminMap := make(map[string]bool)
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = internal.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating compute workloads in all regions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: App Runner, EC2, ECS, Lambda \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
m.pmapperMod, m.pmapperError = initPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines)
|
||||
m.iamSimClient = initIAMSimClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan Workload)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
// Get roles from instance profiles to use for EC2 instance to role lookups
|
||||
m.getRolesFromInstanceProfiles()
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Perform role analysis
|
||||
if m.pmapperError == nil {
|
||||
for i := range m.Workloads {
|
||||
m.Workloads[i].Admin, m.Workloads[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Workloads[i].Role)
|
||||
}
|
||||
} else {
|
||||
for i := range m.Workloads {
|
||||
m.Workloads[i].Admin, m.Workloads[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Workloads[i].Role, m.iamSimClient, localAdminMap)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// Table headers
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Arn",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Arn",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
sort.Slice(m.Workloads, func(i, j int) bool {
|
||||
return m.Workloads[i].AWSService < m.Workloads[j].AWSService
|
||||
})
|
||||
|
||||
// Table rows
|
||||
for i := range m.Workloads {
|
||||
// If the role is an admin or can privesc to admin, make it magenta
|
||||
if m.Workloads[i].Admin == "YES" || m.Workloads[i].CanPrivEsc == "YES" {
|
||||
m.Workloads[i].AWSService = magenta(m.Workloads[i].AWSService)
|
||||
m.Workloads[i].Region = magenta(m.Workloads[i].Region)
|
||||
m.Workloads[i].Name = magenta(m.Workloads[i].Name)
|
||||
m.Workloads[i].Role = magenta(m.Workloads[i].Role)
|
||||
m.Workloads[i].Admin = magenta(m.Workloads[i].Admin)
|
||||
m.Workloads[i].CanPrivEsc = magenta(m.Workloads[i].CanPrivEsc)
|
||||
}
|
||||
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Workloads[i].AWSService,
|
||||
m.Workloads[i].Region,
|
||||
m.Workloads[i].Name,
|
||||
m.Workloads[i].Arn,
|
||||
m.Workloads[i].Role,
|
||||
m.Workloads[i].Admin,
|
||||
m.Workloads[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
|
||||
// Create another table file that contains only the admin roles or the ones that can privesc to admin. Call another function to do this.
|
||||
|
||||
body := m.adminOnlyResults(m.output.Headers, m.output.Body)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: body,
|
||||
TableCols: tableCols,
|
||||
Name: fmt.Sprintf("%s-admin", m.output.CallingModule),
|
||||
SkipPrintToScreen: true,
|
||||
})
|
||||
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
fmt.Printf("[%s][%s] %s compute workloads found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No compute workloads found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) Receiver(receiver chan Workload, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
case data := <-receiver:
|
||||
m.Workloads = append(m.Workloads, data)
|
||||
case <-receiverDone:
|
||||
receiverDone <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer wg.Done()
|
||||
servicemap := &awsservicemap.AwsServiceMap{
|
||||
JsonFileSource: "DOWNLOAD_FROM_AWS",
|
||||
}
|
||||
|
||||
res, _ := servicemap.IsServiceInRegion("ec2", r)
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getEC2WorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
res, _ = servicemap.IsServiceInRegion("ecs", r)
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getECSWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
res, err := servicemap.IsServiceInRegion("lambda", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLambdaWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
// AppRunner is not supported in the aws service region catalog so we have to run it in all regions
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAppRunnerWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) getEC2WorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
|
||||
// Get EC2 instances
|
||||
ec2Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
for _, instance := range ec2Instances {
|
||||
var profileArn, profileName, name string
|
||||
|
||||
// The name is in a tag so we have to do this to grab the value from the right tag
|
||||
for _, tag := range instance.Tags {
|
||||
if *tag.Key == "Name" {
|
||||
name = *tag.Value
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = aws.ToString(instance.InstanceId)
|
||||
}
|
||||
|
||||
if instance.IamInstanceProfile != nil {
|
||||
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)
|
||||
|
||||
// Extracting instance profile name from ARN
|
||||
profileName = strings.Split(profileArn, "/")[len(strings.Split(profileArn, "/"))-1]
|
||||
|
||||
// Describe the IAM instance profile
|
||||
profileOutput, err := sdk.CachedIamGetInstanceProfile(m.IAMClient, aws.ToString(m.Caller.Account), profileName)
|
||||
if err != nil {
|
||||
log.Printf("failed to get instance profile for %s, %v", profileArn, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, role := range profileOutput.Roles {
|
||||
dataReceiver <- Workload{
|
||||
AWSService: "EC2",
|
||||
Region: r,
|
||||
Type: "instance",
|
||||
Name: name,
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", r, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
|
||||
Role: aws.ToString(role.Arn),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) getECSWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
|
||||
// Get ECS clusters
|
||||
ecsClusters, err := sdk.CachedECSListClusters(m.ECSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
for _, cluster := range ecsClusters {
|
||||
|
||||
// Get ECS tasks
|
||||
taskARNs, err := sdk.CachedECSListTasks(m.ECSClient, aws.ToString(m.Caller.Account), r, cluster)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
|
||||
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), r, cluster, taskARNs)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, task := range Tasks {
|
||||
var role string
|
||||
// Get ECS task definition
|
||||
ecsTaskDefinition, err := sdk.CachedECSDescribeTaskDefinition(m.ECSClient, aws.ToString(m.Caller.Account), r, aws.ToString(task.TaskDefinitionArn))
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if ecsTaskDefinition.TaskRoleArn != nil {
|
||||
role = aws.ToString(ecsTaskDefinition.TaskRoleArn)
|
||||
}
|
||||
|
||||
dataReceiver <- Workload{
|
||||
AWSService: "ECS",
|
||||
Region: r,
|
||||
Type: "task",
|
||||
Name: getNameFromARN(aws.ToString(task.TaskDefinitionArn)),
|
||||
Arn: aws.ToString(task.TaskArn),
|
||||
Role: role,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) getLambdaWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
|
||||
// Get Lambda functions
|
||||
lambdaFunctions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
for _, function := range lambdaFunctions {
|
||||
//var role string
|
||||
// if function.Role != nil {
|
||||
// role = aws.ToString(function.Role)
|
||||
// }
|
||||
dataReceiver <- Workload{
|
||||
AWSService: "Lambda",
|
||||
Region: r,
|
||||
Type: "function",
|
||||
Name: aws.ToString(function.FunctionName),
|
||||
Arn: aws.ToString(function.FunctionArn),
|
||||
Role: aws.ToString(function.Role),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) getAppRunnerWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
|
||||
// Get App Runner services
|
||||
appRunnerServices, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
for _, service := range appRunnerServices {
|
||||
var role string
|
||||
if service.ServiceArn != nil {
|
||||
role = aws.ToString(service.ServiceArn)
|
||||
}
|
||||
dataReceiver <- Workload{
|
||||
AWSService: "App Runner",
|
||||
Region: r,
|
||||
Type: "service",
|
||||
Name: aws.ToString(service.ServiceName),
|
||||
Arn: aws.ToString(service.ServiceArn),
|
||||
Role: role,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) adminOnlyResults(headers []string, body [][]string) [][]string {
|
||||
var adminOnlyBody [][]string
|
||||
var adminOnlyHeaders []string
|
||||
var adminOnlyTableCols []string
|
||||
var adminColIndex int
|
||||
var canPrivEscColIndex int
|
||||
|
||||
// First find which col is the admin col and which is the can privesc col and simply note which index they are in. Then we can use those indexes to see if the value in the body is "YES".
|
||||
// For any row where isAdmin or CanPrivEsc is "YES", we will append that row to the adminOnlyBody.
|
||||
|
||||
// Find the index of the admin col
|
||||
for i, header := range headers {
|
||||
if header == "IsAdminRole?" {
|
||||
adminOnlyHeaders = append(adminOnlyHeaders, header)
|
||||
adminOnlyTableCols = append(adminOnlyTableCols, header)
|
||||
adminColIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Find the index of the can privesc col
|
||||
for i, header := range headers {
|
||||
if header == "CanPrivEscToAdmin?" {
|
||||
adminOnlyHeaders = append(adminOnlyHeaders, header)
|
||||
adminOnlyTableCols = append(adminOnlyTableCols, header)
|
||||
canPrivEscColIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the indexes of the admin and can privesc cols, we can iterate through the body and append the rows where isAdmin or CanPrivEsc is "YES" to the adminOnlyBody
|
||||
for _, row := range body {
|
||||
if row[adminColIndex] == "YES" || row[canPrivEscColIndex] == "YES" {
|
||||
adminOnlyBody = append(adminOnlyBody, row)
|
||||
}
|
||||
}
|
||||
|
||||
return adminOnlyBody
|
||||
}
|
||||
|
||||
func (m *WorkloadsModule) getRolesFromInstanceProfiles() {
|
||||
|
||||
// The "PaginationControl" value is nil when there's no more data to return.
|
||||
var PaginationMarker *string
|
||||
PaginationControl := true
|
||||
m.InstanceProfileToRolesMap = map[string][]iamTypes.Role{}
|
||||
|
||||
for PaginationControl {
|
||||
ListInstanceProfiles, err := m.IAMClient.ListInstanceProfiles(
|
||||
context.TODO(),
|
||||
&(iam.ListInstanceProfilesInput{
|
||||
Marker: PaginationMarker,
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, instanceProfile := range ListInstanceProfiles.InstanceProfiles {
|
||||
m.InstanceProfileToRolesMap[aws.ToString(instanceProfile.InstanceProfileId)] = instanceProfile.Roles
|
||||
}
|
||||
if aws.ToString(ListInstanceProfiles.Marker) != "" {
|
||||
PaginationMarker = ListInstanceProfiles.Marker
|
||||
} else {
|
||||
PaginationMarker = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+162
-52
@@ -23,7 +23,6 @@ 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"
|
||||
@@ -108,10 +107,21 @@ var (
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
ApiGwCommand = &cobra.Command{
|
||||
Use: "api-gw",
|
||||
Aliases: []string{"gw", "gateways", "api-gws"},
|
||||
Short: "Enumerate API gateways. Get a loot file with formatted cURL requests.",
|
||||
Long: "\nUse case examples:\n" +
|
||||
os.Args[0] + " aws api-gw --profile readonly_profile",
|
||||
PreRun: awsPreRun,
|
||||
Run: runApiGwCommand,
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
CheckBucketPolicies bool
|
||||
BucketsCommand = &cobra.Command{
|
||||
Use: "buckets",
|
||||
Aliases: []string{"bucket"},
|
||||
Aliases: []string{"bucket", "s3"},
|
||||
Short: "Enumerate all of the buckets. Get loot file with s3 commands to list/download bucket contents",
|
||||
Long: "\nUse case examples:\n" +
|
||||
"List all buckets create a file with pre-populated aws s3 commands:\n" +
|
||||
@@ -145,7 +155,7 @@ var (
|
||||
DatabasesCommand = &cobra.Command{
|
||||
Use: "databases",
|
||||
Aliases: []string{"db", "rds", "redshift", "dbs"},
|
||||
Short: "Enumerate RDS databases. Get a loot file with connection strings.",
|
||||
Short: "Enumerate databases. Get a loot file with connection strings.",
|
||||
Long: "\nUse case examples:\n" +
|
||||
os.Args[0] + " aws databases --profile readonly_profile",
|
||||
PreRun: awsPreRun,
|
||||
@@ -437,7 +447,7 @@ var (
|
||||
|
||||
Use: "pmapper",
|
||||
Aliases: []string{"Pmapper", "pmapperParse"},
|
||||
Short: "",
|
||||
Short: "Looks for pmapper data for the account and builds a PrivEsc graph in golang if it exists.",
|
||||
Long: "\nUse case examples:\n" +
|
||||
os.Args[0] + " aws ",
|
||||
PreRun: awsPreRun,
|
||||
@@ -445,6 +455,15 @@ var (
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
WorkloadsCommand = &cobra.Command{
|
||||
Use: "workloads",
|
||||
Short: "Finds workloads with admin permissions or a path to admin permissions",
|
||||
Long: "\nUse case examples:\n" + os.Args[0] + " aws workloads --profile readonly_profile",
|
||||
PreRun: awsPreRun,
|
||||
Run: runWorkloadsCommand,
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
AllChecksCommand = &cobra.Command{
|
||||
|
||||
Use: "all-checks",
|
||||
@@ -484,7 +503,7 @@ func awsPreRun(cmd *cobra.Command, args []string) {
|
||||
|
||||
// if multiple profiles were used, ensure the management account is first
|
||||
// if AWSProfilesList != "" || AWSAllProfiles {
|
||||
// AWSProfiles = FindOrgMgmtAccountAndReorderAccounts(AWSProfiles, cmd.Root().Version)
|
||||
// AWSProfiles = FindOrgMgmtAccountAndReorderAccounts(AWSProfiles, cmd.Root().Version, AWSMFAToken)
|
||||
// } else {
|
||||
|
||||
// loop through every profile in AWSProfiles and run isCallerMgmtAccountPartofOrg.
|
||||
@@ -494,34 +513,46 @@ func awsPreRun(cmd *cobra.Command, args []string) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("[%s] AWS Caller Identity: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), *caller.Arn)
|
||||
fmt.Printf("[%s][%s] AWS Caller Identity: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), *caller.Arn)
|
||||
}
|
||||
for _, profile := range AWSProfiles {
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if AWSUseCache {
|
||||
cacheDirectory := filepath.Join(AWSOutputDirectory, "cached-data", "aws", ptr.ToString(caller.Account))
|
||||
err = internal.LoadCacheFromGobFiles(cacheDirectory)
|
||||
if err != nil {
|
||||
if err == internal.ErrDirectoryDoesNotExist {
|
||||
fmt.Printf("[%s] No cache directory for %s. Skipping loading cached data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), ptr.ToString(caller.Account))
|
||||
fmt.Printf("[%s][%s] No cache directory for %s. Skipping loading cached data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), ptr.ToString(caller.Account))
|
||||
} else {
|
||||
fmt.Printf("[%s] No cache data for %s. Error: %v\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), ptr.ToString(caller.Account), err)
|
||||
fmt.Printf("[%s][%s] No cache data for %s. Error: %v\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), ptr.ToString(caller.Account), err)
|
||||
// Possibly return/exit here, depending on your requirements.
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[%s] Loaded cached AWS data for to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), ptr.ToString(caller.Account))
|
||||
fmt.Printf("[%s][%s] Loaded cached AWS data for to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), ptr.ToString(caller.Account))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, profile := range AWSProfiles {
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
orgModuleClient := aws.InitOrgClient(*caller, profile, cmd.Root().Version, Goroutines, AWSMFAToken)
|
||||
isPartOfOrg := orgModuleClient.IsCallerAccountPartOfAnOrg()
|
||||
if isPartOfOrg {
|
||||
isMgmtAccount := orgModuleClient.IsManagementAccount(orgModuleClient.DescribeOrgOutput, ptr.ToString(caller.Account))
|
||||
if isMgmtAccount {
|
||||
fmt.Printf("[%s] Account is part of an Organization and is the Management account\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
|
||||
fmt.Printf("[%s][%s] Account is part of an Organization and is the Management account\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile))
|
||||
} else {
|
||||
fmt.Printf("[%s] Account is part of an Organization and is a child account. Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), ptr.ToString(orgModuleClient.DescribeOrgOutput.MasterAccountId))
|
||||
fmt.Printf("[%s][%s] Account is part of an Organization and is a child account. Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), ptr.ToString(orgModuleClient.DescribeOrgOutput.MasterAccountId))
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[%s] Account is not part of an Organization\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
|
||||
fmt.Printf("[%s][%s] Account is not part of an Organization\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile))
|
||||
}
|
||||
//}
|
||||
}
|
||||
@@ -543,7 +574,7 @@ func awsPostRun(cmd *cobra.Command, args []string) {
|
||||
log.Fatalf("failed to save cache: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Cached AWS data written to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), outputDirectory)
|
||||
fmt.Printf("[%s][%s] Cached AWS data written to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), cyan(profile), outputDirectory)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -562,13 +593,13 @@ func FindOrgMgmtAccountAndReorderAccounts(AWSProfiles []string, version string)
|
||||
err = internal.LoadCacheFromGobFiles(cacheDirectory)
|
||||
if err != nil {
|
||||
if err == internal.ErrDirectoryDoesNotExist {
|
||||
fmt.Printf("[%s] No cache directory for %s. Skipping loading cached data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), ptr.ToString(caller.Account))
|
||||
fmt.Printf("[%s][%s] No cache directory for %s. Skipping loading cached data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(profile), ptr.ToString(caller.Account))
|
||||
} else {
|
||||
fmt.Printf("[%s] No cache data for %s. Error: %v\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), ptr.ToString(caller.Account), err)
|
||||
fmt.Printf("[%s][%s] No cache data for %s. Error: %v\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(profile), ptr.ToString(caller.Account), err)
|
||||
// Possibly return/exit here, depending on your requirements.
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[%s] Loaded cached AWS data for to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), ptr.ToString(caller.Account))
|
||||
fmt.Printf("[%s][%s] Loaded cached AWS data for to %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(profile), ptr.ToString(caller.Account))
|
||||
}
|
||||
}
|
||||
orgModuleClient := aws.InitOrgClient(*caller, profile, version, Goroutines, AWSMFAToken)
|
||||
@@ -579,7 +610,7 @@ func FindOrgMgmtAccountAndReorderAccounts(AWSProfiles []string, version string)
|
||||
isMgmtAccount := orgModuleClient.IsManagementAccount(orgModuleClient.DescribeOrgOutput, ptr.ToString(caller.Account))
|
||||
if isMgmtAccount {
|
||||
mgmtAccount := ptr.ToString(caller.Account)
|
||||
fmt.Printf("[%s] Found an Organization Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), mgmtAccount)
|
||||
fmt.Printf("[%s][%s] Found an Organization Management Account: %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(profile), mgmtAccount)
|
||||
AWSProfiles = internal.ReorganizeAWSProfiles(AWSProfiles, profile)
|
||||
} else {
|
||||
// add each child account to the mgmtAccounts map which uses the mgmt account as the key
|
||||
@@ -610,6 +641,26 @@ func runAccessKeysCommand(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func runApiGwCommand(cmd *cobra.Command, args []string) {
|
||||
for _, profile := range AWSProfiles {
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m := aws.ApiGwModule{
|
||||
APIGatewayClient: apigateway.NewFromConfig(internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken)),
|
||||
APIGatewayv2Client: apigatewayv2.NewFromConfig(internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken)),
|
||||
|
||||
Caller: *caller,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
}
|
||||
m.PrintApiGws(AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
}
|
||||
|
||||
func runBucketsCommand(cmd *cobra.Command, args []string) {
|
||||
for _, profile := range AWSProfiles {
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
@@ -688,7 +739,6 @@ 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,
|
||||
@@ -875,13 +925,13 @@ func runIamSimulatorCommand(cmd *cobra.Command, args []string) {
|
||||
continue
|
||||
}
|
||||
m := aws.IamSimulatorModule{
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
}
|
||||
m.PrintIamSimulator(SimulatorPrincipal, SimulatorAction, SimulatorResource, AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
@@ -1117,19 +1167,21 @@ func runRAMCommand(cmd *cobra.Command, args []string) {
|
||||
|
||||
func runResourceTrustsCommand(cmd *cobra.Command, args []string) {
|
||||
for _, profile := range AWSProfiles {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken)
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m := aws.ResourceTrustsModule{
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
WrapTable: AWSWrapTable,
|
||||
CloudFoxVersion: cmd.Root().Version,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
WrapTable: AWSWrapTable,
|
||||
CloudFoxVersion: cmd.Root().Version,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
AWSConfig: AWSConfig,
|
||||
}
|
||||
m.PrintResources(AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
@@ -1223,6 +1275,32 @@ func runTagsCommand(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func runWorkloadsCommand(cmd *cobra.Command, args []string) {
|
||||
for _, profile := range AWSProfiles {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken)
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m := aws.WorkloadsModule{
|
||||
ECSClient: ecs.NewFromConfig(AWSConfig),
|
||||
EC2Client: ec2.NewFromConfig(AWSConfig),
|
||||
LambdaClient: lambda.NewFromConfig(AWSConfig),
|
||||
AppRunnerClient: apprunner.NewFromConfig(AWSConfig),
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
SkipAdminCheck: AWSSkipAdminCheck,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
}
|
||||
m.PrintWorkloads(AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
}
|
||||
|
||||
func runECSTasksCommand(cmd *cobra.Command, args []string) {
|
||||
for _, profile := range AWSProfiles {
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
@@ -1296,6 +1374,7 @@ func runNetworkPortsCommand(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
Verbosity = 1
|
||||
for _, profile := range AWSProfiles {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken)
|
||||
caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken)
|
||||
@@ -1315,7 +1394,6 @@ 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)
|
||||
@@ -1507,11 +1585,23 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
|
||||
endpoints.PrintEndpoints(AWSOutputDirectory, Verbosity)
|
||||
|
||||
gateways := aws.ApiGwModule{
|
||||
APIGatewayv2Client: apiGatewayv2Client,
|
||||
APIGatewayClient: apiGatewayClient,
|
||||
|
||||
Caller: *caller,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
}
|
||||
|
||||
gateways.PrintApiGws(AWSOutputDirectory, Verbosity)
|
||||
|
||||
databases := aws.DatabasesModule{
|
||||
RDSClient: rdsClient,
|
||||
RedshiftClient: redshiftClient,
|
||||
DynamoDBClient: dynamodbClient,
|
||||
DocDBClient: docdbClient,
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
@@ -1708,15 +1798,16 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
cloudFoxSNSClient.PrintSNS(AWSOutputDirectory, Verbosity)
|
||||
|
||||
resourceTrustsCommand := aws.ResourceTrustsModule{
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
WrapTable: AWSWrapTable,
|
||||
CloudFoxVersion: cmd.Root().Version,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
AWSMFAToken: AWSMFAToken,
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
WrapTable: AWSWrapTable,
|
||||
CloudFoxVersion: cmd.Root().Version,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
AWSMFAToken: AWSMFAToken,
|
||||
AWSConfig: AWSConfig,
|
||||
}
|
||||
resourceTrustsCommand.PrintResources(AWSOutputDirectory, Verbosity)
|
||||
|
||||
@@ -1786,16 +1877,33 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
pmapperCommand.PrintPmapperData(AWSOutputDirectory, Verbosity)
|
||||
|
||||
iamSimulator := aws.IamSimulatorModule{
|
||||
IAMClient: iamClient,
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
IAMClient: iamClient,
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
}
|
||||
iamSimulator.PrintIamSimulator(SimulatorPrincipal, SimulatorAction, SimulatorResource, AWSOutputDirectory, Verbosity)
|
||||
|
||||
workloads := aws.WorkloadsModule{
|
||||
ECSClient: ecsClient,
|
||||
EC2Client: ec2Client,
|
||||
LambdaClient: lambdaClient,
|
||||
AppRunnerClient: appRunnerClient,
|
||||
IAMClient: iamClient,
|
||||
Caller: *caller,
|
||||
AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken),
|
||||
SkipAdminCheck: AWSSkipAdminCheck,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
}
|
||||
workloads.PrintWorkloads(AWSOutputDirectory, Verbosity)
|
||||
|
||||
fmt.Printf("[%s] %s\n", cyan(emoji.Sprintf(":fox:cloudfox :fox:")), green("That's it! Check your output files for situational awareness and check your loot files for next steps."))
|
||||
fmt.Printf("[%s] %s\n\n", cyan(emoji.Sprintf(":fox:cloudfox :fox:")), green("FYI, we skipped the outbound-assumed-roles module in all-checks (really long run time). Make sure to try it out manually."))
|
||||
}
|
||||
@@ -1846,7 +1954,7 @@ func init() {
|
||||
AWSCommands.PersistentFlags().BoolVarP(&AWSAllProfiles, "all-profiles", "a", false, "Use all AWS CLI profiles in AWS credentials file")
|
||||
AWSCommands.PersistentFlags().BoolVarP(&AWSConfirm, "yes", "y", false, "Non-interactive mode (like apt/yum)")
|
||||
AWSCommands.PersistentFlags().StringVarP(&AWSOutputType, "output", "o", "brief", "[\"brief\" | \"wide\" ]")
|
||||
AWSCommands.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")
|
||||
AWSCommands.PersistentFlags().IntVarP(&Verbosity, "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")
|
||||
AWSCommands.PersistentFlags().StringVar(&AWSOutputDirectory, "outdir", defaultOutputDir, "Output Directory ")
|
||||
AWSCommands.PersistentFlags().IntVarP(&Goroutines, "max-goroutines", "g", 30, "Maximum number of concurrent goroutines")
|
||||
AWSCommands.PersistentFlags().BoolVar(&AWSSkipAdminCheck, "skip-admin-check", false, "Skip check to determine if role is an Admin")
|
||||
@@ -1857,6 +1965,7 @@ func init() {
|
||||
|
||||
AWSCommands.AddCommand(
|
||||
AllChecksCommand,
|
||||
ApiGwCommand,
|
||||
RoleTrustCommand,
|
||||
AccessKeysCommand,
|
||||
InstancesCommand,
|
||||
@@ -1887,6 +1996,7 @@ func init() {
|
||||
ResourceTrustsCommand,
|
||||
OrgsCommand,
|
||||
DatabasesCommand,
|
||||
WorkloadsCommand,
|
||||
)
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package bigqueryservice_test
|
||||
@@ -0,0 +1,117 @@
|
||||
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
|
||||
// }
|
||||
@@ -0,0 +1 @@
|
||||
package cloudstorageservice_test
|
||||
@@ -0,0 +1,99 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
package computeengineservice
|
||||
@@ -0,0 +1,93 @@
|
||||
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
|
||||
// }
|
||||
@@ -0,0 +1,189 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package iamservice_test
|
||||
@@ -0,0 +1,5 @@
|
||||
package models
|
||||
|
||||
type GenericIterator[T any] interface {
|
||||
Next() (*T, error)
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
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() {}
|
||||
@@ -0,0 +1,35 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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,17 @@
|
||||
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"
|
||||
@@ -4,3 +4,4 @@ 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"
|
||||
@@ -1,73 +1,119 @@
|
||||
module github.com/BishopFox/cloudfox
|
||||
|
||||
go 1.20
|
||||
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.1.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
|
||||
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.21.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.18.2
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.15.0
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.21.2
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.18.11
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.36.0
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.28.7
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.29.2
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.22.2
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.23.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.127.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.4
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.21.9
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.30.0
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.30.0
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.22.0
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.34.0
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.64.0
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.15.7
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.23.0
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.41.0
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.29.0
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.17.0
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.22.0
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.20.8
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.20.7
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.58.0
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.31.0
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.16.2
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.31.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.112.0
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.6
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.22.2
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.24.7
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.40.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2
|
||||
github.com/aws/smithy-go v1.15.0
|
||||
github.com/bishopfox/awsservicemap v1.0.2
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
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
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6
|
||||
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.15.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
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/afero v1.10.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
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 github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
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/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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
@@ -78,35 +124,35 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.32.0
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.20.2
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.16.4
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.18.3
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.16.2
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.24.0
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.17.2
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.29.0
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.20.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.19.8
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect
|
||||
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/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
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6
|
||||
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/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
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/go-openapi/errors v0.20.4 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.7 // indirect
|
||||
github.com/go-openapi/errors v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.10 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
@@ -119,9 +165,9 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.1 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
@@ -1,56 +1,44 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
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.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.4.0 h1:TuEMD+E+1aTjjLICGQOW6vLe8UWES7kopac9mUXL56Y=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.4.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/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/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4=
|
||||
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=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
@@ -81,245 +69,228 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
||||
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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 h1:Sc82v7tDQ/vdU1WtuSyzZ1I7y/68j//HJ6uozND1IDs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14/go.mod h1:9NCTOURS8OpxvoAVHq79LK81/zC78hfRWFn+aL0SPcY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1 h1:oe3vqcGftyk40icfLymhhhNysAwk0NfiwkDi2GTPMXs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 h1:wmGLw2i8ZTlHLw7a9ULGfQbuccw8uIiNr6sol5bFzc8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6/go.mod h1:Q0Hq2X/NuL7z8b1Dww8rmOFl+jzusKEcyvkKspwdpyc=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.18.2 h1:d1T1J5oR9zHMHbXMs22nxR6YCBQEf0rAHyaZvv34XEM=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.18.2/go.mod h1:hfIgsvZ+nvnfBqu6T1H1YTeFkkPr0I/+UTyekLl054E=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.15.0 h1:q6PifcrJdhfbpARjoMRxVDzOJWBzkM0aBtewbAHYpMI=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.15.0/go.mod h1:MfSzpqLwryvxfGpwi9w2Re7DqkLG4qv+qzGu9a+GKJ0=
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.21.2 h1:n2SWTOTKAlh5Wi78ypl8dy3iW0kFe3OATPDrf2pjkhE=
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.21.2/go.mod h1:GvCOU2jyq+MgUD5v99Lw/jjjC0NKKCq7LVDeebOpGj4=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.32.0 h1:n4ui9xL0VbmOVNi2+hiWIrVLVhew/1/4BdzjJY3Px58=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.32.0/go.mod h1:vKT/ZbLHt0S5hlmf0T5OzcPhs4LJoEmqlr49JP+9Hy8=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.18.11 h1:stATlP32EI69+sNDOKw30IbEhgXAu6bxwM/NvQcA1SU=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.18.11/go.mod h1:e1xHnjdRt/ZaqrtO6gUwpbtPF8PtkRVyN1zCmz1IbNI=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.36.0 h1:9ls+8DHLhF0E4/xtZDNF3iYY9Ibqh+fG1y2ueKFDeEo=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.36.0/go.mod h1:EV06EPuSb3m40bD1suX/QSj3o161aG/6Wwbodk2vqzA=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.28.7 h1:rbzZROs9b8Fpo0jtdPy2Qfxh8SRAAXx6sEMX4yxqF6g=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.28.7/go.mod h1:0rod2DMcm5wuAlNJn99o/dtwuQKul9asVbcUn4nBIZs=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.29.2 h1:S9sfsAGkT/5e0bzqLMK7k3DkoZEz0EioL336dT+sCIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.29.2/go.mod h1:N6mA9PPSL2C2drFl+Yx8oEUiuSGhOiplOixRGWc2OYM=
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.20.2 h1:iOcHl9tgrHxvPVOW1cRHwIj9T7/FrkPE6bM0aDKOxE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.20.2/go.mod h1:dZQurYLoNZG5Px5uhyf64g2th0tYkGihn+0pXOmMA0Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.22.2 h1:Xg0xMsQYw/8g+A6RQs6w9HtwQ5SgDojYjYbnKFmnp0Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.22.2/go.mod h1:WeDpKFqkjJU6RVUXL0M4MjLjNKQ5ZUWKmn1VOtbVplc=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.16.4 h1:xfkjR+oSH2FWuBqIuatV+ALSIjlX/0ro8JF3X5MzsXo=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.16.4/go.mod h1:WGVkzcLWQmGahUjgZDieFVFt07OSmaNMCW5pp+DLZYw=
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.18.3 h1:QpPAopwlgH0ODSxpVabR+TmR/32NcD/KMEvhE5ohGsw=
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.18.3/go.mod h1:XS0bHX1J7EFeoYPTXdg+GipSRP20erPm6WxTGj/xPoU=
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.16.2 h1:c5+t2S4JCTBzj3/y9Z15TagZTK0vJFYvdNUYvk9UeAo=
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.16.2/go.mod h1:RggMwt9IKmOq49+9okMMIGzoy/ZyEMZ23XmG8hq6oHs=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.24.0 h1:Oto0KebWdPEtGUA7IB2pizLWCeQ82NasO2mv/UcrwSk=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.24.0/go.mod h1:A7PqVcedUBaK/y89wiWysaEw+WqFPjgkcrxIZTOy/Yo=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.23.0 h1:xmSAn14nM6IdHyuWO/bsrAagOQtnqzuUCLxdVmj9nhg=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.23.0/go.mod h1:1HkLh8vaL4obF95fne7ZOu7sxomS/+vkBt3/+gqqwE4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.127.0 h1:4xtATQuR0qIvX+QTWHlgTUnwlDPNzHcvMsB+qkRSPRo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.127.0/go.mod h1:raUdIDoNuDPn9dMG3cCmIm8RoWOmZUqQPzuw8xpmB8Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 h1:y6LX9GUoEA3mO0qpFl1ZQHj1rFyPWVphlzebiSt2tKE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2/go.mod h1:Q0LcmaN/Qr8+4aSBrdrXXePqoX0eOuYpJLbYpilmWnA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.4 h1:j0VhL2v86gbsOKLQ1EDMhS2Lb0TROVIep7eFobc2Qq0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.4/go.mod h1:1pSCxO2RQKwIg2ibxUcSmg9jbIZtfrXrVU72nY2jF3g=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.21.9 h1:mlF3jj8aWvnd/o/KEJqzFUQEGqw2a7UOm1S4gzqWe/o=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.21.9/go.mod h1:yXsn9Nd+xBewfLNGxu2HBjP8amz3/L6lGIsPZP1fhfc=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.30.0 h1:d6WbsOHHsEMryKLc9oYCmvu4lrV9z9QLSQ5S44KSn0o=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.30.0/go.mod h1:Nt5l6Vn68Hv0JWJ6dcQDKuBAKAfHUZSC9Ln8X/1fUMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.30.0 h1:isOi+s5w3lUupgMUnBd/Y1oZ04T7NG835vkQg75hnpo=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.30.0/go.mod h1:9fw8OlW99pw/i4qUkO7bNYYqznIPPuH/tsCHwa1ZYHc=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.17.2 h1:a5T0ZZARvpJBSF3NTMG1Oh7MVrFqjaJrbTE7/Ro1iDM=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.17.2/go.mod h1:RWRjHldUoeUDce3D6OIjCcIC1dJ5DeOHs0Y0inI2Xt8=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.18.0 h1:rw3OZB7wAdl0OA0w0dAy2kdsUSjUe4wSxNbu3rwi8zU=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.18.0/go.mod h1:T7I3S3o4CWa9sSV2DIK+5ZmuMXarrcrE8RYKGDaQ1I4=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.22.0 h1:DEdgH+R4MCPiuYW0G11pzU4U6kn+1WprM8N7gx1wnko=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.22.0/go.mod h1:/ZlJt5r04rRWDg/7K6cQ6Tq0ZUnUMVR2FRg0GGTy/e0=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.29.0 h1:a+70mGT17GMYDek7hU4bDN8BE5zhd7JE5Dzth+0BR00=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.29.0/go.mod h1:TUlGxOXunqrsVPdN98ergwcW9ZeR1+Fg0/qsQTho1ww=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.34.0 h1:P9vHaltsJyddWNcD8zl3ixyGxQjlY9PdfKtHunV5LQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.34.0/go.mod h1:EjIvHf1MOUWLXk5Gv2ot6BwlEyPHM4kceJ8IJv7KMME=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.64.0 h1:fwarWmCqZ40V+CU/EsZ9r+f1RMVxqz3BVnwJtz9OUN0=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.64.0/go.mod h1:U6H3cQ/25CnRt1rEsQ+Tk2q5Dnl3IXJZFHoRwc2WNE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.15.7 h1:6JIuHCZ1aBYbwqypQ1omjx0+cJh6iDVVY0lHnGdr3no=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.15.7/go.mod h1:L1GGWtZRULrM/5b4dtyakgcoPzIOMuxyNLSYcHA8SdU=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.23.0 h1:rHRl/C8gMedaF3XEZ9SgXkdgEgd3ir9t6upZqSZ1F+8=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.23.0/go.mod h1:d4c7P+mola/qBIgxgtVHK/w77vn+BlCsC/tbJ3m8m4Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 h1:7R8uRYyXzdD71KWVCL78lJZltah6VVznXBazvKjfH58=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15/go.mod h1:26SQUPcTNgV1Tapwdt4a1rOsYRsnBsJHLMPoxK2b0d8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 h1:skaFGzv+3kA+v2BPKhuekeb1Hbb105+44r8ASC+q5SE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38/go.mod h1:epIZoRSSbRIwLPJU5F+OldHhwZPBdpDeQkRdCeY3+00=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.37 h1:4LoizcvPT9A0tiAFhepxn0bGZXkzvN0pG0epydY3Pno=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.37/go.mod h1:7xBUZyP6LeLc+5Ym9PG7atqw4sR28sBtYcHETik+bPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 h1:9ulSU5ClouoPIYhDQdg9tpl83d5Yb91PXTKK+17q+ow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6/go.mod h1:lnc2taBsR9nTlz9meD+lhFZZ9EWY712QHrRflWpTcOA=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.20.0 h1:OCYjSomi2Q8ttimk0DB4nNSAvoVOXfpSAwB0ZM4g1K0=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.20.0/go.mod h1:IKAdoalibJPPhb+riPJyKh9z/6V8n4J2X1yUto/W90Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.41.0 h1:AQK0v7JA0stOOi1cNOq4n9N3X1a3N3A2Ezt3UEyHL9o=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.41.0/go.mod h1:kFs07FNyTowZkz+dGBR33xJbzGs2mkC5Kfm6/lyR5CA=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.29.0 h1:Io+ezo97hWDmcg3tSMJbMoL1zd5zJ8BxK8Y8yB2Chfs=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.29.0/go.mod h1:u2yl3D9ygNXthorLQOj2wmMPSxWBGZ+XTlkVbsK2Bh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.17.0 h1:SseTkq+6TtZv05ez/rBfQJQDueAa9DQosluNGF5ZHzo=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.17.0/go.mod h1:GrFG5/W01G5Z2OXb+Qe40PjSzHQsXJTWSCNmvqAq6Rw=
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.22.0 h1:ATE++PFLW5V2CAujoI846MVD8MIFtrpSvCXdeXloY84=
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.22.0/go.mod h1:LPkOrJqBDqIzzNpnToYR79f5JQD/udkxyC9nECuHA3s=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.20.8 h1:FUd2lRsLCF+hKf7Ve9I10in/N0f+EVqZEXB/VZm8BZI=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.20.8/go.mod h1:0zR2FnFXmQBI+aHBNr6iQ9WuzssOkl7deBA+1c004Gk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.20.7 h1:afbEGsuDyeU+svV28+V3wozbHRoRNn5GSGxgXaUUjqU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.20.7/go.mod h1:hxfoKaeAQq9ybg0ZX+FK16nepP3+Ck7GmYrfBgPKcQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.58.0 h1:ErFOugr4ORe+d6OGNk3Csr5ItF9DRcHJMFZnuh4isUM=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.58.0/go.mod h1:NNx09yR8B7z4I5xTt2rUq+5h2lmA9T9bbm7NME/74Ac=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.31.0 h1:61PxIJHRpNVHypYy4+8WNx7q9izeMvw7C3+OLM4dZ4c=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.31.0/go.mod h1:WOT17e05/tPt7VV0Thz/sqtA1wE/C4iuxb2wsXyc5Qc=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.16.2 h1:03EH0RIXqyhwLZFjyJBG4hMTBw4rHMUtIESEwizxkg8=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.16.2/go.mod h1:qqGaDe21Tbh/b9vRv61ykdn/JM/xQr78SL4nYfRzh4E=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.31.0 h1:2u6uM0+E/bowkcCBe6KXhAFc5++tHuCZqJC3br1sI1c=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.31.0/go.mod h1:TQZBt/WaQy+zTHoW++rnl8JBrmZ0VO6EUbVua1+foCA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2 h1:Ll5/YVCOzRB+gxPqs2uD0R7/MyATC0w85626glSKmp4=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2/go.mod h1:Zjfqt7KhQK+PO1bbOsFNzKgaq7TcxzmEoDWN8lM0qzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.112.0 h1:ZN0GhpC0JEhWemCyXpUDEhpYaANwgs1Rr/Xot6mjHGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.112.0/go.mod h1:gbX8YRNFupoP6GuDH609e+QXejEPyQKrf/chjLTHYTk=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.6 h1:y3n83jEM6EuawrD5HZCh3eMj9RsfxniVLcXlyFMNITM=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.21.6/go.mod h1:A108ijf0IFtqhYApU+Gia80aPSAUfi9dItm+h5fWGJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.19.8 h1:qyewIfqnYEIvYx4SwH/0ObX6PpJ9SEEUUHmUQ3CNois=
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.19.8/go.mod h1:rxuLnlLghUuQWgzHnqq2lXEwYmPnEFkX+RxC4QflXXs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.22.2 h1:zU+iUkj72bZFuIgUTCcAyVXs7Le1uX2LopHMnvZfn04=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.22.2/go.mod h1:gLVePJ104BrkWKr4aU3CURZYZnZN7BQGDsB668Uh3ZY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.24.7 h1:NZhGz9eHNTLPK9Bhq3wrRSUIu9BqcjWzC8UNK6MwUfI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.24.7/go.mod h1:iWb2iGUERRXX3kEyKVtkjuMOW2YkDBcuhKCp5y37ys0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.40.0 h1:DHZFzwbFXlfw15I0ERlTVB/YH9iHNr2C1axjRpB7/Gg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.40.0/go.mod h1:qpnJ98BgJ3YUEvHMgJ1OADwaOgqhgv0nxnqAjTKupeY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ=
|
||||
github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
|
||||
github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/bishopfox/awsservicemap v1.0.2 h1:+NnuInVcQkja6y6h6eGseneU3m5Nzp9O++gR1s+FiyM=
|
||||
github.com/bishopfox/awsservicemap v1.0.2/go.mod h1:oy9Fyqh6AozQjShSx+zRNouTlp7k3z3YEMoFkN8rquc=
|
||||
github.com/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/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=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.2/go.mod h1:l6xqvUxt0Oj7PI/SUXYLNyZ9T/yBPn3YTQcJLLOdtR8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.13 h1:WLABQ4Cp4vXtXfOWOS3MEZKr6AAYUpMczLhgKtAjQ/8=
|
||||
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/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=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 h1:ePPaOVn92r5n8Neecdpy93hDmR0PBH6H6b7VQCE5vKE=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6/go.mod h1:P/zwE9uiC6eK/kL3CS60lxTTVC2zAvaS4iW31io41V4=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 h1:bCdxKjM8DpkNJXnOLVx+Hnav0eM4yJK8kof56VvIjMc=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6/go.mod h1:zQ6tOYz7oGI7MbLRDBXfo63puDoTroVcVNXWfmRDA1E=
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5 h1:YpUU6SUj7EYB+l4kGQk2ch5Vz+HK/cn2bfYsJk81+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/apprunner v1.25.5/go.mod h1:W1Z87aVGwtb3egdiWLrnMyj5oHzShACix2iebsixuVA=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 h1:qNLkDi/rOaauOuh33a4MNZjyfxvwIgC5qsDiHPvjDk0=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.37.3/go.mod h1:MlpC6swcjh1Il80u6XoeY2BTHIZRZWvoXOfaq3rfh8I=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5 h1:eSu3COqcATUvidX7AfqOhp6fJadqGD2gTX2Z0oWGUXQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloud9 v1.22.5/go.mod h1:R80rQE5F5/Qf+l4pTpxq8GiEd0OqLo3/S5bNBNEs04A=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5 h1:5+m0XrCIwjjeP4f3AdC1wyQBc2ClIJi2mP4e3Wkdgvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.5/go.mod h1:oPk8ZMctRUtGC13pOE83Zp0baZgJsmzuKm4IRR+zQOI=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5 h1:synDXYpTr5FA80g8twNr49Dd7iAKnxerp93l/kNm/cQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5/go.mod h1:Dil6nVeCPyPc1gF5EeCrVUTtXexn80MpfqhgSp/Zb64=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 h1:Yc+avPLGARzp4A9Oi9VRxvlcGqI+0MYIg4tPSupKv2U=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6/go.mod h1:zrqdG1b+4AGoTwTMVFzvzY7ARB3GPo4gKRuK8WPEo8w=
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5 h1:KKuWOQhF/0JPurE6c4Uaem2/Vd221FI0io5gv+rXty0=
|
||||
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.5/go.mod h1:P7DrCurmyD78GbU8IRD+JIK3f9FkeuTe9A8/zXDJv7Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 h1:EPnlDd4V2EXywlOPAw/pMUW4PHUgSulKm4zXFU6bixE=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5/go.mod h1:G2JUWf01sbb5/A8qGcM4dqy4nbl4y4IGWmaCDWAvA2Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0 h1:bKW3m6CmWzV/HNqgHiv+HNH0epvyUhnfGAkMzIegUIo=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.20.0/go.mod h1:pI87yFGLN6qjkKhI4Hdh1cpcak8bu/QAJFesJNCSj7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2 h1:3b8fwDhM0bJoOVglvM1w4665Ry6mrb3Rp+AH8iGI6qg=
|
||||
github.com/aws/aws-sdk-go-v2/service/codedeploy v1.22.2/go.mod h1:RiusqJl55/p7S8LNMh2J3ZsDHDqxRiPdsfIaZRKeEUo=
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5 h1:zkIKbco/gM4fmbU90ajz4ah3ErgAgot7aTE0PpIxNuE=
|
||||
github.com/aws/aws-sdk-go-v2/service/datapipeline v1.19.5/go.mod h1:+G+TF84SIgMdGDjZKKYPpx6LwXSN4QcywQGa8e2vJ1U=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5 h1:txsajy47TIyoL7/BQt0VwqmzLPIsfAT/RWI9iD4q5vU=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.29.5/go.mod h1:8d1RpdlgxFU6VO2aWru1ckR0Vsm4EgqCZgOamw5OHpw=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7 h1:X60rMbnylU1xmmhv4+/N78t+lKOCC4ELst5eR25dyqg=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7/go.mod h1:o7TD9sjdgrl8l/g2a2IkYjuhxjPy9DMP2sWo7piaRBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0 h1:VrFC1uEZjX4ghkm/et8ATVGb1mT75Iv8aPKPjUE+F8A=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.142.0/go.mod h1:qjhtI9zjpUHRc6khtrIM9fb48+ii6+UikL3/b+MKYn0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6 h1:cT7h+GWP2k0hJSsPmppKgxl4C9R6gCC5/oF4oHnmpK4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6/go.mod h1:AOHmGMoPtSY9Zm2zBuwUJQBisIvYAZeA1n7b6f4e880=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5 h1:N1ezZV2yy7NV2w/bA4s4I/+0n2xpL4DzlmroEg5qFsg=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.26.5/go.mod h1:PJHqaboMcF/eLy1F/Y9hyls4CQGP5+T5f0iRq6CPXu4=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0 h1:tCIkZ/ZdJMGZ1MOwdcioYhOUkkD4F58KFvQTgR3ZIlc=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.37.0/go.mod h1:L1uv3UgQlAkdM9v0gpec7nnfUiQkCnGMjBE7MJArfWQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6 h1:Y/5eE9Sc+OBID9pZ4EVFzyQviv1d1RbqB17HRur9ySg=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6/go.mod h1:iPx2i26hgUULkNh1Jk4QzYzzQKd2nXl/rD9Fm5hQ2uk=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5 h1:wUyVpgx2Rs8wYFbzeZTN2uzAuORh8Ea5iV1eIDjatM0=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.20.5/go.mod h1:EhlhcjyCljBHkCR17DvnvSnmVYrdgsIzPTH3+vnhaV0=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6 h1:pTyTNb1QVqMT0livj/Goj+68cnJg7fe4o+wFZvasB4M=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.21.6/go.mod h1:N37+67ROdmH7BgLyp1cwCjRpKism3cwkeDlOktRLXMQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6 h1:twI2uRmpbm0KBog3Ay61IqOtNp6+QxKfSA78zftME/o=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6/go.mod h1:Tpt4kC8x1HfYuh2rG/6yXZrxjABETERrUl9IdA/IS98=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6 h1:eJ1K3IaNErKlR8MXg0c4aMjPpvp9qI39WCzx50u+XtQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.35.6/go.mod h1:Drh6y2qLaw/wnDKTIcdqM2m358MIRXsZ2Bj2tjhVLq0=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0 h1:bH7oEgVZ4pR27sciaYeNuACsZy6wQXjqRjli54tunOg=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.40.0/go.mod h1:GnnK4J/WQajWEAwXd/82wpF275XVhNgsYsXtK1baOjE=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0 h1:KMA3nvkeQf5I40cFjjl3GaoasKRPtph+J8RfcWlLlWU=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.73.0/go.mod h1:ALQEuXs/XUdwrkAucRl3juNFbiomoaPICShOoGzHNiE=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5 h1:1HGLgX6zvanwVsruEyamDDxXZKQlIPFZYzQVR5Zhv3Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.18.5/go.mod h1:g9Hl5MYDQtTfNldyymQL5/D93cxP8PGu2B/776IMml0=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5 h1:Ts2eDDuMLrrmd0ARlg5zSoBQUvhdthgiNnPdiykTJs0=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.28.5/go.mod h1:kKI0gdVsf+Ev9knh/3lBJbchtX5LLNH25lAzx3KDj3Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
|
||||
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/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=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6/go.mod h1:Sj7qc+P/GOGOPMDn8+B7Cs+WPq1Gk+R6CXRXVhZtWcA=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 h1:w8lI9zlVwRTL9f4KB9fRThddhRivv+EQQzv2nU8JDQo=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5 h1:0KVnA62WGcVdeJKH+DTUkxNms2OsIky+AmB2iX93eAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.32.5/go.mod h1:wI7palPB84YaqCYglfNiyAlDcXTFbcJ9rDHMu15cFto=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 h1:n86T5yw0kS6a5nbpkEpDzLPCBXXb35lx3iDkmQWlizA=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.20.6/go.mod h1:phfKOOpMQhlBv2KE8gF17P82zLcSedA9b7fMSGTLBdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1 h1:qmxbwQY/gnN7eBElvy9saejXWaKD6ENU8JF5SuoAWLQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/opensearch v1.27.1/go.mod h1:l89g2sqHGQRg0UZ1ldFfOxh96mZAGn8ZBlaXpoJ2psQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5 h1:4sW8XPTtuH6PX8CUcpUxBKg0Pf67k1MOOgq9Y+v4ls8=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.23.5/go.mod h1:AMzAwJifk4gEft+ElIMFjOb2qUNqHODfjSszVL5Nfeo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7 h1:xZdtfxmMRR65HqUFJ3Lfhzkmd9KhVI30TM8Sjj25Pio=
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.23.7/go.mod h1:VgmJolFAkLhZ5Qnnmyfqqyu6+Dt+5E761HzU2ou0Tjg=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0 h1:WUQ6kmnta31GhQvRJtHPVoO4hSNF8Yh2CQIFCZbhZ8g=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.66.0/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6 h1:wiM6xGxWTPI8Yck4efgQGS0lanuMILbng8oukqa4bNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.19.6/go.mod h1:Nngchp1Q7LNBS8J10r4P0npfroNRaCVz6wWNfBz7j4E=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0 h1:7wh6KdJnej4T7sE/xfnZf5T+GQzp6GfoZi+5r6ZPlW8=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.0/go.mod h1:F9El48+5Tf+TkYJB/6M9H7oqXw9Mr9eVetwJ6SUql7g=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7 h1:o0ASbVwUAIrfp/WcCac+6jioZt4Hd8k/1X8u7GJ/QeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0 h1:YSTmNx+/qyprl8gkBdkxuKW7lI1zPRG7QBSDJVdiZic=
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.122.0/go.mod h1:aGWPLBmB3O7wnYYJLHZdahmPt/ai3QAnOTjxxNmMB2A=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6 h1:agEKwGJ+CyvQ2oARsHsA8fn/CCz7I402CgfWcnhIPGE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sfn v1.24.6/go.mod h1:goJW4NkHiLfCWTNykK9w7PkACje1y9OIT1IOn8kmRvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6 h1:EZw+TRx/4qlfp6VJ0P1sx04Txd9yGNK+NiO1upaXmh4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.6/go.mod h1:uXndCJoDO9gpuK24rNWVCnrGNUydKFEAYAZ7UU9S0rQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6 h1:HJeiuZ2fldpd0WqngyMR6KW7ofkXNLyOaHwEIGm39Cs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.6/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/bishopfox/awsservicemap v1.0.3 h1:0T+mJLwG+vQV9+o3dzwzxhWJWE40VpoCLWtaPBwixYc=
|
||||
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dnaeon/go-vcr v1.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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
|
||||
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
|
||||
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
|
||||
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
|
||||
github.com/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.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.4.1/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.1/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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/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/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/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=
|
||||
@@ -328,14 +299,16 @@ 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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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=
|
||||
@@ -348,202 +321,113 @@ 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=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/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=
|
||||
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
|
||||
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/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-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
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.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -552,158 +436,62 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
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=
|
||||
@@ -712,25 +500,19 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
+10
-3
@@ -60,15 +60,22 @@ func AWSConfigFileLoader(AWSProfile string, version string, AwsMfaToken string)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
TxtLog.Println(err)
|
||||
//fmt.Println(err)
|
||||
if AWSProfile != "" {
|
||||
TxtLog.Println(err)
|
||||
fmt.Printf("[%s][%s] The specified profile [%s] does not exist or there was an error loading the credentials.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile), AWSProfile)
|
||||
TxtLog.Fatalf("Could not retrieve the specified profile name %s", err)
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
|
||||
TxtLog.Fatalf("Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
|
||||
}
|
||||
//os.Exit(1)
|
||||
}
|
||||
|
||||
_, err := cfg.Credentials.Retrieve(context.TODO())
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
|
||||
TxtLog.Printf("Could not retrieve the specified profile name %s", err)
|
||||
|
||||
} else {
|
||||
// update the config map with the new config for future lookups
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
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
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -8,7 +9,10 @@ import (
|
||||
|
||||
"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/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -30,3 +34,54 @@ func GetLogDirPath() *string {
|
||||
}
|
||||
return ptr.String(dir)
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
version string
|
||||
txtLog *logrus.Logger
|
||||
}
|
||||
|
||||
func NewLogger() Logger {
|
||||
var logger = Logger{
|
||||
version: globals.CLOUDFOX_VERSION,
|
||||
txtLog: TxtLog,
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
func (l *Logger) Info(text string) {
|
||||
l.InfoM(text, "config")
|
||||
}
|
||||
|
||||
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) 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) Error(text string) {
|
||||
l.ErrorM(text, "config")
|
||||
}
|
||||
|
||||
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) Fatal(text string) {
|
||||
l.FatalM(text, "config")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+65
-13
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -38,13 +39,14 @@ type TableClient struct {
|
||||
}
|
||||
|
||||
type TableFile struct {
|
||||
Name string
|
||||
TableFilePointer afero.File
|
||||
CSVFilePointer afero.File
|
||||
JSONFilePointer afero.File
|
||||
TableCols []string
|
||||
Header []string
|
||||
Body [][]string
|
||||
Name string
|
||||
TableFilePointer afero.File
|
||||
CSVFilePointer afero.File
|
||||
JSONFilePointer afero.File
|
||||
TableCols []string
|
||||
Header []string
|
||||
Body [][]string
|
||||
SkipPrintToScreen bool
|
||||
}
|
||||
|
||||
type LootClient struct {
|
||||
@@ -58,6 +60,49 @@ 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`)
|
||||
@@ -96,7 +141,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)
|
||||
@@ -126,7 +171,8 @@ func (o *OutputClient) WriteFullOutput(tables []TableFile, lootFiles []LootFile)
|
||||
}
|
||||
|
||||
for _, path := range outputPaths {
|
||||
fmt.Printf("[%s][%s] Output written to %s\n", cyan(o.CallingModule), cyan(o.PrefixIdentifier), path)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +189,11 @@ func (l *LootClient) createLootFiles(lootFiles []LootFile) {
|
||||
if l.DirectoryName == "" {
|
||||
l.DirectoryName = "."
|
||||
}
|
||||
if _, err := fileSystem.Stat(l.DirectoryName); os.IsNotExist(err) {
|
||||
err = fileSystem.MkdirAll(l.DirectoryName, 0700)
|
||||
|
||||
lootDirectory := path.Join(l.DirectoryName, "loot")
|
||||
|
||||
if _, err := fileSystem.Stat(lootDirectory); os.IsNotExist(err) {
|
||||
err = fileSystem.MkdirAll(lootDirectory, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -155,7 +204,7 @@ func (l *LootClient) createLootFiles(lootFiles []LootFile) {
|
||||
|
||||
l.LootFiles[i].Name = fmt.Sprintf("%s.txt", file.Name)
|
||||
|
||||
filePointer, err := fileSystem.OpenFile(path.Join(l.DirectoryName, l.LootFiles[i].Name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
filePointer, err := fileSystem.OpenFile(path.Join(lootDirectory, l.LootFiles[i].Name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating output file: %s", err)
|
||||
}
|
||||
@@ -168,7 +217,7 @@ func (l *LootClient) writeLootFiles() []string {
|
||||
var fullFilePaths []string
|
||||
for _, file := range l.LootFiles {
|
||||
contents := []byte(file.Contents)
|
||||
fullPath := path.Join(l.DirectoryName, file.Name)
|
||||
fullPath := path.Join(l.DirectoryName, "loot", file.Name)
|
||||
|
||||
err := os.WriteFile(fullPath, contents, 0644)
|
||||
if err != nil {
|
||||
@@ -181,6 +230,9 @@ func (l *LootClient) writeLootFiles() []string {
|
||||
|
||||
func (b *TableClient) printTablesToScreen(tableFiles []TableFile) {
|
||||
for _, tf := range tableFiles {
|
||||
if tf.SkipPrintToScreen {
|
||||
continue
|
||||
}
|
||||
tf.Body, tf.Header = adjustBodyForTable(tf.TableCols, tf.Header, tf.Body)
|
||||
standardColumnWidth := 1000
|
||||
t := table.New(os.Stdout)
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: os.Args[0],
|
||||
Version: "1.12.3",
|
||||
Version: "1.14.0-prerelease",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
rootCmd.AddCommand(cli.AWSCommands, cli.AzCommands)
|
||||
rootCmd.AddCommand(cli.AWSCommands, cli.AzCommands, cli.GCPCommands)
|
||||
rootCmd.Execute()
|
||||
}
|
||||
Reference in new issue
Block a user