From 521daf3853338638541955d3c2d08a3a9a0dcc32 Mon Sep 17 00:00:00 2001 From: jbarciabf Date: Tue, 5 Aug 2025 15:13:10 -0400 Subject: [PATCH] Replace unsigned commits with single signed commit --- .github/CODEOWNERS | 2 +- Makefile | 46 ++- README.md | 2 +- aws/cape-tui.go | 2 +- aws/cape.go | 78 +++- aws/databases.go | 78 ++++ aws/databases_test.go | 9 +- aws/ecs-tasks.go | 20 +- aws/ecs-tasks_test.go | 13 +- aws/endpoints.go | 86 ++--- aws/env-vars.go | 86 ++--- aws/graph.go | 2 +- aws/iam-simulator.go | 20 +- aws/principals.go | 41 +++ aws/resource-trusts.go | 393 ++++++++++++++++++++- aws/resource-trusts_test.go | 219 ++++++++++++ aws/role-trusts.go | 11 +- aws/route53.go | 22 +- aws/sdk/apigateway.go | 12 +- aws/sdk/apigateway_mocks.go | 1 + aws/sdk/codebuild_mocks.go | 14 + aws/sdk/ec2.go | 34 +- aws/sdk/ec2_mocks.go | 44 +++ aws/sdk/ecs_mocks.go | 18 + aws/sdk/kms.go | 80 +++++ aws/sdk/kms_mocks.go | 42 +++ aws/sdk/opensearch.go | 9 +- aws/sdk/opensearch_mocks.go | 60 +++- aws/sdk/rds_mocks.go | 8 + cli/aws.go | 249 +++++++------ gcp/services/iamService/access-tokens.go | 2 +- globals/utils.go | 2 +- go.mod | 137 +++---- go.sum | 268 +++++++------- internal/aws.go | 2 +- internal/aws/policy/policy.go | 8 +- internal/aws/policy/policy_test.go | 48 +++ internal/aws/policy/role-trust-policies.go | 2 +- 38 files changed, 1691 insertions(+), 479 deletions(-) create mode 100644 aws/sdk/kms.go create mode 100644 aws/sdk/kms_mocks.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9139b04a..0810f720 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @bishopfaure @dbravo-bishopfox +* @bishopfaure @jbarciabf diff --git a/Makefile b/Makefile index e96cf9e6..4bb32136 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,12 @@ linux: zip cloudfox-linux.zip cloudfox sha1sum-linux.txt rm cloudfox sha1sum-linux.txt +linux-arm64: + GOOS=linux GOARCH=arm64 go build + sha1sum cloudfox > sha1sum-linux-arm64.txt + zip cloudfox-linux-arm64.zip cloudfox sha1sum-linux-arm64.txt + rm cloudfox sha1sum-linux-arm64.txt + macos: GOOS=darwin GOARCH=amd64 go build sha1sum cloudfox > sha1sum-mac.txt @@ -24,22 +30,36 @@ all: windows linux macos .PHONY: release release: clean mkdir -p ./cloudfox - mkdir -p ./cloudfox + GOOS=windows GOARCH=amd64 go build -o ./cloudfox/cloudfox.exe . - zip ./cloudfox/cloudfox-windows-amd64.zip ./cloudfox/cloudfox.exe - rm -rf ./cloudfox/cloudfox.exe - + sha1sum ./cloudfox/cloudfox.exe > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-windows-amd64.zip ./cloudfox/cloudfox.exe ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox.exe ./cloudfox/sha1sum.txt + GOOS=linux GOARCH=amd64 go build -o ./cloudfox/cloudfox . - zip ./cloudfox/cloudfox-linux-amd64.zip ./cloudfox/cloudfox . - rm -rf ./cloudfox/cloudfox + sha1sum ./cloudfox/cloudfox > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-linux-amd64.zip ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + + GOOS=linux GOARCH=386 go build -o ./cloudfox/cloudfox . + sha1sum ./cloudfox/cloudfox > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-linux-386.zip ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + + GOOS=linux GOARCH=arm64 go build -o ./cloudfox/cloudfox . + sha1sum ./cloudfox/cloudfox > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-linux-arm64.zip ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox ./cloudfox/sha1sum.txt GOOS=darwin GOARCH=amd64 go build -o ./cloudfox/cloudfox . - zip ./cloudfox/cloudfox-macos-amd64.zip ./cloudfox/cloudfox - rm -rf ./cloudfox/cloudfox - + sha1sum ./cloudfox/cloudfox > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-macos-amd64.zip ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + GOOS=darwin GOARCH=arm64 go build -o ./cloudfox/cloudfox . - zip ./cloudfox/cloudfox-macos-arm64.zip ./cloudfox/cloudfox - rm -rf ./cloudfox/cloudfox + sha1sum ./cloudfox/cloudfox > ./cloudfox/sha1sum.txt + zip ./cloudfox/cloudfox-macos-arm64.zip ./cloudfox/cloudfox ./cloudfox/sha1sum.txt + rm -rf ./cloudfox/cloudfox ./cloudfox/sha1sum.txt -clean: - rm -rf ./cloudfox \ No newline at end of file +clean: + rm -rf ./cloudfox diff --git a/README.md b/README.md index 5c8baa6d..99c7085a 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Additional policy notes (as of 09/2022): | 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 | [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. KMS is supported but disabled by default. To include KMS resource policies in the output, add this flag to the command: `cloudfox aws resource-trusts --include-kms`.| | 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`. | diff --git a/aws/cape-tui.go b/aws/cape-tui.go index 7b87aa8c..31b74e0f 100644 --- a/aws/cape-tui.go +++ b/aws/cape-tui.go @@ -301,7 +301,7 @@ func preloadData(filePaths []string) (*AllAccountData, error) { for _, filePath := range filePaths { fileRecords, err := loadFileRecords(filePath) - if err != nil { + if err != nil { return nil, err } appData.Files[filePath] = fileRecords diff --git a/aws/cape.go b/aws/cape.go index 5e9e502b..f4e07d84 100644 --- a/aws/cape.go +++ b/aws/cape.go @@ -1,7 +1,9 @@ package aws import ( + "bufio" "fmt" + "os" "path/filepath" "strings" @@ -200,7 +202,12 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s s, sourceVertexWithProperties, _ := m.GlobalGraph.VertexWithProperties(source) //for the source vertex, we only want to deal with the ones that are NOT in this account if sourceVertexWithProperties.Attributes["AccountID"] != aws.ToString(m.Caller.Account) { + // skip if the source Name contains AWSSSO- + if strings.Contains(sourceVertexWithProperties.Attributes["Name"], "AWSSSO-") { + continue + } // now let's see if there is a path from this source to our destination + path, _ := graph.ShortestPath(m.GlobalGraph, s, d) // if we have a path, then lets document this source as having a path to our destination if path != nil { @@ -235,7 +242,7 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s privescPathsBody = append(privescPathsBody, []string{ aws.ToString(m.Caller.Account), s, - magenta(d), + d, magenta(destinationVertexWithProperties.Attributes["IsAdminString"]), paths}) } else { @@ -289,7 +296,7 @@ func ConvertIAMRoleToNode(role types.Role, vendors *knownawsaccountslookup.Vendo TrustedPrincipals = append(TrustedPrincipals, TrustedPrincipal{ TrustedPrincipal: principal, - ExternalID: statement.Condition.StringEquals.StsExternalID, + ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"), VendorName: vendorName, //IsAdmin: false, //CanPrivEscToAdmin: false, @@ -650,6 +657,48 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) { } } + // if the role trusts a principal in another account explicitly, then the principal can assume the role + if thisAccount != trustedPrincipalAccount { + // make a CAN_ASSUME relationship between the trusted principal and this role + + err := GlobalGraph.AddEdge( + TrustedPrincipal.TrustedPrincipal, + a.Arn, + //graph.EdgeAttribute("AssumeRole", "Cross account explicit trust"), + graph.EdgeAttribute("AssumeRole", "can assume (because of an explicit cross account trust) "), + ) + if err != nil { + //fmt.Println(err) + //fmt.Println(TrustedPrincipal.TrustedPrincipal + a.Arn + "Cross account explicit trust") + if err == graph.ErrEdgeAlreadyExists { + // update the edge by copying the existing graph.Edge with attributes and add the new attributes + //fmt.Println("Edge already exists") + + // get the existing edge + existingEdge, _ := GlobalGraph.Edge(TrustedPrincipal.TrustedPrincipal, a.Arn) + // get the map of attributes + existingProperties := existingEdge.Properties + // add the new attributes to attributes map within the properties struct + // Check if the Attributes map is initialized, if not, initialize it + if existingProperties.Attributes == nil { + existingProperties.Attributes = make(map[string]string) + } + + // Add or update the attribute + existingProperties.Attributes["AssumeRole"] = "can assume (because of an explicit cross account trust) " + err = GlobalGraph.UpdateEdge( + TrustedPrincipal.TrustedPrincipal, + a.Arn, + graph.EdgeAttributes(existingProperties.Attributes), + ) + if err != nil { + fmt.Println(err) + } + } + + } + } + // If the role trusts a principal in this account or another account using the :root notation, then we need to iterate over all of the rows in AllPermissionsRows to find the principals that have sts:AssumeRole permissions on this role // if the role we are looking at trusts root in it's own account @@ -667,6 +716,7 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) { if PermissionsRowAccount == thisAccount { // lets only look for rows that have sts:AssumeRole permissions if policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") { + // lets only focus on rows that have an effect of Allow if strings.EqualFold(PermissionsRow.Effect, "Allow") { // if the resource is * or the resource is this role arn, then this principal can assume this role @@ -820,10 +870,10 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) { fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn) } if PermissionsRowAccount == trustedPrincipalAccount { - // lets only look for rows that have sts:AssumeRole permissions - if policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") { + // lets only look for rows that have sts:AssumeRole permis sions + if policy.MatchesAfterExpansion("sts:AssumeRole", PermissionsRow.Action) { // if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") || - // strings.EqualFold(PermissionsRow.Action, "*") || + // strings.EqualFold(PermissionsRow.Action, "*") { // strings.EqualFold(PermissionsRow.Action, "sts:Assume*") || // strings.EqualFold(PermissionsRow.Action, "sts:*") { // lets only focus on rows that have an effect of Allow @@ -979,3 +1029,21 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) { } } + +// function to read file specified in CapeArnIgnoreList which is separated by newlines, and convert it to a slice of strings with each line as an entry in the slice. +// the function accepts a string with the filename + +func ReadArnIgnoreListFile(filename string) ([]string, error) { + var arnIgnoreList []string + file, err := os.Open(filename) + if err != nil { + return arnIgnoreList, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + arnIgnoreList = append(arnIgnoreList, scanner.Text()) + } + return arnIgnoreList, scanner.Err() +} diff --git a/aws/databases.go b/aws/databases.go index 6ae877fd..a1f782bd 100644 --- a/aws/databases.go +++ b/aws/databases.go @@ -271,6 +271,15 @@ func (m *DatabasesModule) executeRdsCheck(r string, wg *sync.WaitGroup, semaphor service: "rds", executor: m.getRdsClustersPerRegion, }) + m.executeCheck(check{ + region: r, + wg: wg, + semaphore: semaphore, + dataReceiver: dataReceiver, + serviceMap: servicemap, + service: "rds", + executor: m.getRdsInstancesPerRegion, + }) } func (m *DatabasesModule) executeRedshiftCheck(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database, servicemap *awsservicemap.AwsServiceMap) { @@ -395,6 +404,75 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, } } +func (m *DatabasesModule) getRdsInstancesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) { + defer func() { + m.CommandCounter.Executing-- + m.CommandCounter.Complete++ + wg.Done() + + }() + semaphore <- struct{}{} + defer func() { + <-semaphore + }() + m.CommandCounter.Pending-- + m.CommandCounter.Executing++ + + DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r) + + if err != nil { + m.modLog.Error(err.Error()) + m.CommandCounter.Error++ + return + } + + for _, instance := range DBInstances { + var public string + var service string + var roles string + if instance.Endpoint == nil { + continue + } + + 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" + } + + if isNeptune(instance.Engine) { + service = "Neptune" + } else if isDocDB(instance.Engine) { + service = "DocsDB" + } else { + service = "RDS" + } + + associatedRoles := instance.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(instance.MasterUsername), + Port: aws.ToInt32(port), + Protocol: aws.ToString(instance.Engine), + Public: public, + Roles: roles, + } + } +} + func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) { defer func() { m.CommandCounter.Executing-- diff --git a/aws/databases_test.go b/aws/databases_test.go index 026d7297..24425afa 100644 --- a/aws/databases_test.go +++ b/aws/databases_test.go @@ -42,10 +42,11 @@ func TestDatabasesCommand(t *testing.T) { } 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", + "db1.cluster-123456789012.us-west-2.rds.amazonaws.com", // make sure it includes the Aurora clusters + "db2.cluster-123456789012.us-west-2.rds.amazonaws.com", // make sure it includes the Aurora clusters + "db3.cluster-123456789012.us-west-2.neptune.amazonaws.com", // make sure it includes the Neptune instances + "db4.cluster-123456789012.us-west-2.docdb.amazonaws.com", // make sure it includes the DocumentDB instances + "db1-instances-1.blah.us-west-2.rds.amazonaws.com", // make sure it includes the RDS instances } for _, expected := range expectedResults { diff --git a/aws/ecs-tasks.go b/aws/ecs-tasks.go index 49c3fa3f..2254ca2d 100644 --- a/aws/ecs-tasks.go +++ b/aws/ecs-tasks.go @@ -50,6 +50,7 @@ type MappedECSTask struct { Cluster string TaskDefinitionName string TaskDefinitionContent string + ContainerName string LaunchType string ID string ExternalIP string @@ -145,6 +146,7 @@ func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver c "Account", "Cluster", "TaskDefinition", + "ContainerName", "LaunchType", "ID", "External IP", @@ -171,6 +173,7 @@ func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver c "Account", "Cluster", "TaskDefinition", + "ContainerName", "LaunchType", "ID", "External IP", @@ -184,6 +187,7 @@ func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver c tableCols = []string{ "Cluster", "TaskDefinition", + "ContainerName", "LaunchType", "External IP", "Internal IP", @@ -206,6 +210,7 @@ func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver c aws.ToString(m.Caller.Account), ecsTask.Cluster, ecsTask.TaskDefinitionName, + ecsTask.ContainerName, ecsTask.LaunchType, ecsTask.ID, ecsTask.ExternalIP, @@ -368,7 +373,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg return } - eniIDs := []string{} + var eniIDs []string for _, task := range Tasks { eniID := getElasticNetworkInterfaceIDOfECSTask(task) if eniID != "" { @@ -394,6 +399,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg Cluster: getNameFromARN(clusterARN), TaskDefinitionName: getNameFromARN(aws.ToString(task.TaskDefinitionArn)), TaskDefinitionContent: getTaskDefinitionContent(taskDefinition), + ContainerName: getContainerNamesFromECSTask(task), LaunchType: string(task.LaunchType), ID: getIDFromECSTask(aws.ToString(task.TaskArn)), PrivateIP: getPrivateIPv4AddressFromECSTask(task), @@ -505,8 +511,18 @@ func getIDFromECSTask(arn string) string { return tokens[2] } +func getContainerNamesFromECSTask(task types.Task) string { + var names []string + + for _, container := range task.Containers { + names = append(names, aws.ToString(container.Name)) + } + + return strings.Join(names, "|") +} + func getPrivateIPv4AddressFromECSTask(task types.Task) string { - ips := []string{} + var ips []string for _, attachment := range task.Attachments { if aws.ToString(attachment.Type) != "ElasticNetworkInterface" || aws.ToString(attachment.Status) != "ATTACHED" { diff --git a/aws/ecs-tasks_test.go b/aws/ecs-tasks_test.go index e5b5495e..e65cd4b8 100644 --- a/aws/ecs-tasks_test.go +++ b/aws/ecs-tasks_test.go @@ -23,7 +23,6 @@ func TestECSTasks(t *testing.T) { outputDirectory: ".", verbosity: 2, testModule: ECSTasksModule{ - AWSProfile: "default", AWSRegions: []string{"us-east-1", "us-west-1"}, Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")}, @@ -33,10 +32,11 @@ func TestECSTasks(t *testing.T) { ECSClient: &sdk.MockedECSClient{}, }, expectedResult: []MappedECSTask{{ - Cluster: "MyCluster", - ID: "74de0355a10a4f979ac495c14EXAMPLE", - ExternalIP: "203.0.113.12", - Role: "test123", + Cluster: "MyCluster", + ID: "74de0355a10a4f979ac495c14EXAMPLE", + ContainerName: "web", + ExternalIP: "203.0.113.12", + Role: "test123", }}, }, } @@ -48,6 +48,9 @@ func TestECSTasks(t *testing.T) { if expectedTask.Cluster != subtest.testModule.MappedECSTasks[index].Cluster { log.Fatal("Cluster name does not match expected value") } + if expectedTask.ContainerName != subtest.testModule.MappedECSTasks[index].ContainerName { + log.Fatal("Container name does not match expected value") + } } }) } diff --git a/aws/endpoints.go b/aws/endpoints.go index 1fadc66d..2ebe6824 100644 --- a/aws/endpoints.go +++ b/aws/endpoints.go @@ -105,7 +105,7 @@ func (m *EndpointsModule) PrintEndpoints(outputDirectory string, verbosity int) 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 @@ -447,7 +447,7 @@ func (m *EndpointsModule) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGro FunctionDetails, err := sdk.CachedLambdaGetFunctionUrlConfig(m.LambdaClient, aws.ToString(m.Caller.Account), r, name) 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++ @@ -503,7 +503,7 @@ func (m *EndpointsModule) getEksClustersPerRegion(r string, wg *sync.WaitGroup, 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++ @@ -576,7 +576,7 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se ) 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++ @@ -629,36 +629,27 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s for _, domainName := range DomainNames { name := aws.ToString(domainName.DomainName) - //TODO: convert this to cacehd function - DomainNameDetails, err := m.OpenSearchClient.DescribeDomain( - context.TODO(), - &(opensearch.DescribeDomainInput{ - DomainName: &name, - }), - func(o *opensearch.Options) { - o.Region = r - }, - ) + DomainStatus, err := sdk.CachedOpenSearchDescribeDomain(m.OpenSearchClient, aws.ToString(m.Caller.Account), r, name) + 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++ return } - raw_endpoint := DomainNameDetails.DomainStatus.Endpoint + rawEndpoint := DomainStatus.Endpoint var endpoint string - var kibana_endpoint string + var kibanaEndpoint string // This exits the function if an opensearch domain exists but there is no endpoint - if raw_endpoint == nil { + if rawEndpoint == nil { return } else { - - endpoint = fmt.Sprintf("https://%s", aws.ToString(raw_endpoint)) - kibana_endpoint = fmt.Sprintf("https://%s/_plugin/kibana/", aws.ToString(raw_endpoint)) + endpoint = fmt.Sprintf("https://%s", aws.ToString(rawEndpoint)) + kibanaEndpoint = fmt.Sprintf("https://%s/_plugin/kibana/", aws.ToString(rawEndpoint)) } //fmt.Println(endpoint) @@ -689,7 +680,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s AWSService: "OpenSearch", Region: r, Name: name, - Endpoint: kibana_endpoint, + Endpoint: kibanaEndpoint, Port: 443, Protocol: "https", Public: public, @@ -788,7 +779,7 @@ func (m *EndpointsModule) getELBv2ListenersPerRegion(r string, wg *sync.WaitGrou ) 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++ @@ -943,7 +934,7 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou 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++ @@ -956,7 +947,7 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou 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++ @@ -1005,7 +996,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType name := aws.ToString(api.Name) id := aws.ToString(api.Id) - raw_endpoint := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com", id, r) + rawEndpoint := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com", id, r) var port int32 = 443 protocol := "https" @@ -1021,7 +1012,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType 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++ @@ -1031,11 +1022,10 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType 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++ - } for _, stage := range GetStages.Item { @@ -1044,7 +1034,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType if len(resource.ResourceMethods) != 0 { path := aws.ToString(resource.Path) - endpoint := fmt.Sprintf("%s/%s%s", raw_endpoint, stageName, path) + endpoint := fmt.Sprintf("%s/%s%s", rawEndpoint, stageName, path) endpoints = append(endpoints, Endpoint{ AWSService: awsService, @@ -1120,7 +1110,7 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr 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++ @@ -1133,7 +1123,7 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr 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++ @@ -1185,7 +1175,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2 var public string name := aws.ToString(api.Name) - raw_endpoint := aws.ToString(api.ApiEndpoint) + rawEndpoint := aws.ToString(api.ApiEndpoint) id := aws.ToString(api.ApiId) var port int32 = 443 protocol := "https" @@ -1195,7 +1185,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2 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++ @@ -1213,7 +1203,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2 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++ @@ -1228,9 +1218,9 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2 } var endpoint string if stage == "" { - endpoint = fmt.Sprintf("%s%s", raw_endpoint, path) + endpoint = fmt.Sprintf("%s%s", rawEndpoint, path) } else { - endpoint = fmt.Sprintf("%s/%s%s", raw_endpoint, stage, path) + endpoint = fmt.Sprintf("%s/%s%s", rawEndpoint, stage, path) } public = "True" @@ -1267,7 +1257,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r) if err != nil { if errors.As(err, &oe) { - m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))) + m.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++ @@ -1320,12 +1310,12 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG awsService := "Redshift" protocol := "https" - // This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop). + // This for loop exits at the end depending on whether the output hits its last page (see pagination control block at the end of the loop). Clusters, err := sdk.CachedRedShiftDescribeClusters(m.RedshiftClient, aws.ToString(m.Caller.Account), r) if err != nil { if errors.As(err, &oe) { - m.Errors = append(m.Errors, (fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))) + m.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++ @@ -1453,12 +1443,12 @@ func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore c m.CommandCounter.Executing++ // "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages. var PaginationControl *string - var awsService string = "Cloudfront" - var protocol string = "https" - var r string = "Global" - var public string = "True" + var awsService = "Cloudfront" + var protocol = "https" + var r = "Global" + var public = "True" - // This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop). + // This for loop exits at the end depending on whether the output hits its last page (see pagination control block at the end of the loop). for { ListDistributions, err := m.CloudfrontClient.ListDistributions( context.TODO(), @@ -1468,7 +1458,7 @@ func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore c ) 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++ @@ -1684,8 +1674,8 @@ func (m *EndpointsModule) getLightsailContainerEndpointsPerRegion(r string, wg * // m.CommandCounter.Total++ m.CommandCounter.Pending-- m.CommandCounter.Executing++ - var public string = "True" - var protocol string = "https" + var public = "True" + var protocol = "https" var port int32 = 443 containerServices, err := sdk.CachedLightsailGetContainerServices(m.LightsailClient, aws.ToString(m.Caller.Account), r) diff --git a/aws/env-vars.go b/aws/env-vars.go index 4a02ef3e..b471ff37 100644 --- a/aws/env-vars.go +++ b/aws/env-vars.go @@ -62,6 +62,7 @@ type EnvsModule struct { type EnvironmentVariable struct { service string + taskDefinition string name string region string environmentVarName string @@ -166,13 +167,20 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) { //Table rows for _, envVar := range m.EnvironmentVariables { + var finalName string + if envVar.taskDefinition != "" { + finalName = fmt.Sprintf("%s/%s", envVar.name, envVar.taskDefinition) + } else { + finalName = envVar.name + } + if envVar.interesting { m.output.Body = append( m.output.Body, []string{ aws.ToString(m.Caller.Account), envVar.service, envVar.region, - envVar.name, + finalName, magenta(envVar.environmentVarName), magenta(envVar.environmentVarValue), }, @@ -183,7 +191,7 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) { aws.ToString(m.Caller.Account), envVar.service, envVar.region, - envVar.name, + finalName, envVar.environmentVarName, envVar.environmentVarValue, }, @@ -355,7 +363,7 @@ func (m *EnvsModule) getECSEnvironmentVariablesPerRegion(region string, wg *sync break } for _, containerDefinition := range DescribeTaskDefinition.TaskDefinition.ContainerDefinitions { - m.getECSEnvironmentVariablesPerDefinition(containerDefinition, region, dataReceiver) + m.getECSEnvironmentVariablesPerDefinition(*DescribeTaskDefinition.TaskDefinition, containerDefinition, region, dataReceiver) } } } @@ -394,12 +402,13 @@ func (m *EnvsModule) getTaskDefinitionFamilies(region string) []string { } -func (m *EnvsModule) getECSEnvironmentVariablesPerDefinition(containerDefinition ecsTypes.ContainerDefinition, region string, dataReceiver chan EnvironmentVariable) { +func (m *EnvsModule) getECSEnvironmentVariablesPerDefinition(taskDefinition ecsTypes.TaskDefinition, containerDefinition ecsTypes.ContainerDefinition, region string, dataReceiver chan EnvironmentVariable) { if containerDefinition.Environment != nil { for _, x := range containerDefinition.Environment { dataReceiver <- EnvironmentVariable{ service: "ECS", + taskDefinition: getNameFromARN(aws.ToString(taskDefinition.TaskDefinitionArn)), name: aws.ToString(containerDefinition.Name), region: region, environmentVarName: aws.ToString(x.Name), @@ -538,25 +547,22 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn return } - if err == nil { - - if len(ContainerServices) > 0 { + if len(ContainerServices) > 0 { - for _, containerService := range ContainerServices { - for _, container := range containerService.CurrentDeployment.Containers { - for k, v := range container.Environment { - name := aws.ToString(containerService.ContainerServiceName) - dataReceiver <- EnvironmentVariable{ - service: awsService, - name: name, - region: r, - environmentVarName: k, - environmentVarValue: v, - } + for _, containerService := range ContainerServices { + for _, container := range containerService.CurrentDeployment.Containers { + for k, v := range container.Environment { + name := aws.ToString(containerService.ContainerServiceName) + dataReceiver <- EnvironmentVariable{ + service: awsService, + name: name, + region: r, + environmentVarName: k, + environmentVarValue: v, } } - } + } } } @@ -675,18 +681,16 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn m.CommandCounter.Error++ break } - if err == nil { - if len(DescribeTransformJob.Environment) > 0 { - name := fmt.Sprintf("[Transform Job] %s", aws.ToString(DescribeTransformJob.TransformJobName)) - for k, v := range DescribeTransformJob.Environment { - dataReceiver <- EnvironmentVariable{ - service: awsService, - name: name, - region: r, - environmentVarName: k, - environmentVarValue: v, - } + if len(DescribeTransformJob.Environment) > 0 { + name := fmt.Sprintf("[Transform Job] %s", aws.ToString(DescribeTransformJob.TransformJobName)) + for k, v := range DescribeTransformJob.Environment { + dataReceiver <- EnvironmentVariable{ + service: awsService, + name: name, + region: r, + environmentVarName: k, + environmentVarValue: v, } } } @@ -738,20 +742,18 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn m.CommandCounter.Error++ break } - if err == nil { - - if len(DescribeTrainingJob.Environment) > 0 { - name := fmt.Sprintf("[Training Job] %s", aws.ToString(DescribeTrainingJob.TrainingJobName)) - for k, v := range DescribeTrainingJob.Environment { - dataReceiver <- EnvironmentVariable{ - service: awsService, - name: name, - region: r, - environmentVarName: k, - environmentVarValue: v, - } + if len(DescribeTrainingJob.Environment) > 0 { + name := fmt.Sprintf("[Training Job] %s", aws.ToString(DescribeTrainingJob.TrainingJobName)) + for k, v := range DescribeTrainingJob.Environment { + dataReceiver <- EnvironmentVariable{ + service: awsService, + name: name, + region: r, + environmentVarName: k, + environmentVarValue: v, } + } } } diff --git a/aws/graph.go b/aws/graph.go index bf5a2b6f..0f695521 100644 --- a/aws/graph.go +++ b/aws/graph.go @@ -331,7 +331,7 @@ func (m *GraphCommand) collectRoleDataForGraph() []models.Role { TrustedPrincipals = append(TrustedPrincipals, models.TrustedPrincipal{ TrustedPrincipal: principal, - ExternalID: statement.Condition.StringEquals.StsExternalID, + ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"), VendorName: vendorName, //IsAdmin: false, //CanPrivEscToAdmin: false, diff --git a/aws/iam-simulator.go b/aws/iam-simulator.go index ce1fc60a..decaac8e 100644 --- a/aws/iam-simulator.go +++ b/aws/iam-simulator.go @@ -33,8 +33,9 @@ type IamSimulatorModule struct { WrapTable bool // Main module data - SimulatorResults []SimulatorResult - CommandCounter internal.CommandCounter + SimulatorResults []SimulatorResult + CommandCounter internal.CommandCounter + IamSimulatorAdminCheckOnly bool // Used to store output data for pretty printing output internal.OutputData2 modLog *logrus.Entry @@ -104,6 +105,11 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, go m.Receiver(dataReceiver, receiverDone) + if m.IamSimulatorAdminCheckOnly { + // set defaultActionNames to an empty slice + defaultActionNames = []string{} + } + // This double if/else section is here to handle the cases where --principal or --action (or both) are specified. if principal != "" { if action != "" { @@ -354,7 +360,9 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r Decision: "", } } else { - m.getPolicySimulatorResult(principal, actions, resource, dataReceiver) + if !m.IamSimulatorAdminCheckOnly { + m.getPolicySimulatorResult(principal, actions, resource, dataReceiver) + } } } @@ -394,7 +402,9 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r Decision: "", } } else { - m.getPolicySimulatorResult(principal, actions, resource, dataReceiver) + if !m.IamSimulatorAdminCheckOnly { + m.getPolicySimulatorResult(principal, actions, resource, dataReceiver) + } } } @@ -443,7 +453,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool { "iam:PutRolePolicy", "iam:AttachRolePolicy", "secretsmanager:GetSecretValue", - "ssm:GetDocument", + "ssm:GetParameters", } for { SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy( diff --git a/aws/principals.go b/aws/principals.go index a403cf98..e00340ad 100644 --- a/aws/principals.go +++ b/aws/principals.go @@ -26,6 +26,12 @@ type IamPrincipalsModule struct { AWSProfile string WrapTable bool + SkipAdminCheck bool + iamSimClient IamSimulatorModule + pmapperMod PmapperModule + pmapperError error + PmapperDataBasePath string + // Main module data Users []User Roles []Role @@ -43,6 +49,8 @@ type User struct { Name string AttachedPolicies []string InlinePolicies []string + Admin string + CanPrivEsc string } type Group struct { @@ -62,6 +70,8 @@ type Role struct { Name string AttachedPolicies []string InlinePolicies []string + Admin string + CanPrivEsc string } func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) { @@ -69,6 +79,7 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi m.output.Verbosity = verbosity m.output.Directory = outputDirectory m.output.CallingModule = "principals" + localAdminMap := make(map[string]bool) m.modLog = internal.TxtLog.WithFields(logrus.Fields{ "module": m.output.CallingModule, }) @@ -78,6 +89,9 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi fmt.Printf("[%s][%s] Enumerating IAM Users and Roles for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account)) + m.pmapperMod, m.pmapperError = InitPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines, m.PmapperDataBasePath) + m.iamSimClient = InitIamCommandClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines) + // wg := new(sync.WaitGroup) // done := make(chan bool) @@ -101,6 +115,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi "Arn", "AttachedPolicies", "InlinePolicies", + "IsAdminRole?", + "CanPrivEscToAdmin?", } // If the user specified table columns, use those. @@ -122,6 +138,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi "Arn", //"AttachedPolicies", //"InlinePolicies", + "IsAdminRole?", + "CanPrivEscToAdmin?", } // Otherwise, use the default columns. @@ -132,11 +150,25 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi "Arn", // "AttachedPolicies", // "InlinePolicies", + "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?") + } + //Table rows for i := range m.Users { + if m.pmapperError == nil { + m.Users[i].Admin, m.Users[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Users[i].Arn) + } else { + m.Users[i].Admin, m.Users[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Users[i].Arn, m.iamSimClient, localAdminMap) + } + m.output.Body = append( m.output.Body, []string{ @@ -146,12 +178,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi m.Users[i].Arn, strings.Join(m.Users[i].AttachedPolicies, " , "), strings.Join(m.Users[i].InlinePolicies, " , "), + m.Users[i].Admin, + m.Users[i].CanPrivEsc, }, ) } for i := range m.Roles { + if m.pmapperError == nil { + m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Roles[i].Arn) + } else { + m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Roles[i].Arn, m.iamSimClient, localAdminMap) + } m.output.Body = append( m.output.Body, []string{ @@ -161,6 +200,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi m.Roles[i].Arn, strings.Join(m.Roles[i].AttachedPolicies, " , "), strings.Join(m.Roles[i].InlinePolicies, " , "), + m.Roles[i].Admin, + m.Roles[i].CanPrivEsc, }, ) diff --git a/aws/resource-trusts.go b/aws/resource-trusts.go index 71063d4d..b4bc46f7 100644 --- a/aws/resource-trusts.go +++ b/aws/resource-trusts.go @@ -19,6 +19,11 @@ import ( ) type ResourceTrustsModule struct { + KMSClient *sdk.KMSClientInterface + APIGatewayClient *sdk.APIGatewayClientInterface + EC2Client *sdk.AWSEC2ClientInterface + OpenSearchClient *sdk.OpenSearchClientInterface + // General configuration data Caller sts.GetCallerIdentityOutput AWSRegions []string @@ -55,7 +60,7 @@ type Resource2 struct { HasConditions string } -func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity int) { +func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity int, includeKms bool) { // These struct values are used by the output module m.output.Verbosity = verbosity m.output.Directory = outputDirectory @@ -72,13 +77,21 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity 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)) + // if kms feature flag is enabled include kms in the supported services + if includeKms { + fmt.Printf("[%s][%s] Supported Services: APIGateway, CodeBuild, ECR, EFS, Glue, KMS, Lambda, Opensearch, SecretsManager, S3, SNS, SQS, VpcEndpoint\n", + cyan(m.output.CallingModule), cyan(m.AWSProfileStub)) + } else { + fmt.Printf("[%s][%s] Supported Services: APIGateway, CodeBuild, ECR, EFS, Glue, Lambda, Opensearch, SecretsManager, S3, SNS, "+ + "SQS, VpcEndpoint (KMS requires --include-kms feature flag)\n", + cyan(m.output.CallingModule), cyan(m.AWSProfileStub)) + } 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 @@ -91,7 +104,7 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity for _, region := range m.AWSRegions { wg.Add(1) m.CommandCounter.Pending++ - go m.executeChecks(region, wg, semaphore, dataReceiver) + go m.executeChecks(region, wg, semaphore, dataReceiver, includeKms) } wg.Add(1) @@ -186,10 +199,9 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity 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.AWSProfileStub), m.output.CallingModule) - } -func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { +func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2, includeKms bool) { defer wg.Done() servicemap := &awsservicemap.AwsServiceMap{ @@ -234,6 +246,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap wg.Add(1) m.getCodeBuildResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver) } + res, err = servicemap.IsServiceInRegion("lambda", r) if err != nil { m.modLog.Error(err) @@ -243,6 +256,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap wg.Add(1) m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver) } + res, err = servicemap.IsServiceInRegion("efs", r) if err != nil { m.modLog.Error(err) @@ -252,6 +266,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap wg.Add(1) m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver) } + res, err = servicemap.IsServiceInRegion("secretsmanager", r) if err != nil { m.modLog.Error(err) @@ -261,6 +276,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap wg.Add(1) m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver) } + res, err = servicemap.IsServiceInRegion("glue", r) if err != nil { m.modLog.Error(err) @@ -271,6 +287,53 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver) } + if includeKms && m.KMSClient != nil { + res, err = servicemap.IsServiceInRegion("kms", r) + if err != nil { + m.modLog.Error(err) + } + if res { + m.CommandCounter.Total++ + wg.Add(1) + m.getKMSPoliciesPerRegion(r, wg, semaphore, dataReceiver) + } + } + + if m.APIGatewayClient != nil { + res, err = servicemap.IsServiceInRegion("apigateway", r) + if err != nil { + m.modLog.Error(err) + } + if res { + m.CommandCounter.Total++ + wg.Add(1) + m.getAPIGatewayPoliciesPerRegion(r, wg, semaphore, dataReceiver) + } + } + + if m.EC2Client != nil { + res, err = servicemap.IsServiceInRegion("ec2", r) + if err != nil { + m.modLog.Error(err) + } + if res { + m.CommandCounter.Total++ + wg.Add(1) + m.getVPCEndpointPoliciesPerRegion(r, wg, semaphore, dataReceiver) + } + } + + if m.OpenSearchClient != nil { + res, err = servicemap.IsServiceInRegion("es", r) + if err != nil { + m.modLog.Error(err) + } + if res { + m.CommandCounter.Total++ + wg.Add(1) + m.getOpenSearchPoliciesPerRegion(r, wg, semaphore, dataReceiver) + } + } } func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone chan bool) { @@ -306,7 +369,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou for _, t := range ListTopics { var statementSummaryInEnglish string - var isInteresting string = "No" + var isInteresting = "No" topic, err := cloudFoxSNSClient.getTopicWithAttributes(aws.ToString(t.TopicArn), r) if err != nil { m.modLog.Error(err.Error()) @@ -316,9 +379,10 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou parsedArn, err := arn.Parse(aws.ToString(t.TopicArn)) if err != nil { topic.Name = aws.ToString(t.TopicArn) + } else { + topic.Name = parsedArn.Resource + topic.Region = parsedArn.Region } - topic.Name = parsedArn.Resource - topic.Region = parsedArn.Region // check if topic is public or not if topic.Policy.IsPublic() { @@ -846,6 +910,315 @@ func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r strin } } +// getKMSPoliciesPerRegion retrieves the resource policies for all KMS keys in a specified region. +// It sends the resulting Resource2 objects to the dataReceiver channel. +// It uses a semaphore to limit the number of concurrent requests and a WaitGroup to wait for all requests to complete. +// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to. +func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { + defer func() { + m.CommandCounter.Executing-- + m.CommandCounter.Complete++ + wg.Done() + }() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + listKeys, err := sdk.CachedKMSListKeys(*m.KMSClient, aws.ToString(m.Caller.Account), r) + if err != nil { + sharedLogger.Error(err.Error()) + return + } + + for _, key := range listKeys { + var isPublic string + var statementSummaryInEnglish string + var isInteresting = "No" + + keyPolicy, err := sdk.CachedKMSGetKeyPolicy(*m.KMSClient, aws.ToString(m.Caller.Account), r, aws.ToString(key.KeyId)) + if err != nil { + sharedLogger.Error(err.Error()) + m.CommandCounter.Error++ + continue + } + + if keyPolicy.IsPublic() { + isPublic = magenta("Yes") + isInteresting = magenta("Yes") + } else { + isPublic = "No" + } + + if !keyPolicy.IsEmpty() { + for i, statement := range keyPolicy.Statement { + prefix := "" + if len(keyPolicy.Statement) > 1 { + prefix = fmt.Sprintf("Statement %d says: ", i) + statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n" + } else { + statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account) + } + + statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n") + if isResourcePolicyInteresting(statementSummaryInEnglish) { + //magenta(statementSummaryInEnglish) + isInteresting = magenta("Yes") + } + + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: aws.ToString(key.KeyArn), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(key.KeyId), + Region: r, + Interesting: isInteresting, + } + } + } + } +} + +func (m *ResourceTrustsModule) getAPIGatewayPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { + defer func() { + m.CommandCounter.Executing-- + m.CommandCounter.Complete++ + wg.Done() + }() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + restAPIs, err := sdk.CachedApiGatewayGetRestAPIs(*m.APIGatewayClient, aws.ToString(m.Caller.Account), r) + if err != nil { + sharedLogger.Error(err.Error()) + return + } + + for _, restAPI := range restAPIs { + + if sdk.IsPublicApiGateway(&restAPI) { + continue + } + + var isPublic = "No" + var statementSummaryInEnglish string + var isInteresting = "No" + + if restAPI.Policy != nil && *restAPI.Policy != "" { + + // remove backslashes from the policy JSON + policyJson := strings.ReplaceAll(aws.ToString(restAPI.Policy), `\"`, `"`) + + restAPIPolicy, err := policy.ParseJSONPolicy([]byte(policyJson)) + if err != nil { + sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(restAPI.Name), err)) + m.CommandCounter.Error++ + continue + } + + if !restAPIPolicy.IsEmpty() { + for i, statement := range restAPIPolicy.Statement { + prefix := "" + if len(restAPIPolicy.Statement) > 1 { + prefix = fmt.Sprintf("Statement %d says: ", i) + statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n" + } else { + statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account) + } + + statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n") + if isResourcePolicyInteresting(statementSummaryInEnglish) { + //magenta(statementSummaryInEnglish) + isInteresting = magenta("Yes") + } + + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: fmt.Sprintf("arn:aws:execute-api:%s:%s:%s/*", r, *m.Caller.Account, *restAPI.Id), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(restAPI.Name), + Region: r, + Interesting: isInteresting, + } + } + } + } else { + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: fmt.Sprintf("arn:aws:execute-api:%s:%s:%s/*", r, *m.Caller.Account, *restAPI.Id), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(restAPI.Name), + Region: r, + Interesting: isInteresting, + } + } + } +} + +func (m *ResourceTrustsModule) getVPCEndpointPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { + defer func() { + m.CommandCounter.Executing-- + m.CommandCounter.Complete++ + wg.Done() + }() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + vpcEndpoints, err := sdk.CachedEC2DescribeVpcEndpoints(*m.EC2Client, aws.ToString(m.Caller.Account), r) + if err != nil { + sharedLogger.Error(err.Error()) + return + } + + for _, vpcEndpoint := range vpcEndpoints { + var isPublic = "No" + var statementSummaryInEnglish string + var isInteresting = "No" + + if vpcEndpoint.PolicyDocument != nil && *vpcEndpoint.PolicyDocument != "" { + vpcEndpointPolicyJson := aws.ToString(vpcEndpoint.PolicyDocument) + vpcEndpointPolicy, err := policy.ParseJSONPolicy([]byte(vpcEndpointPolicyJson)) + if err != nil { + sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(vpcEndpoint.VpcEndpointId), err)) + m.CommandCounter.Error++ + continue + } + + if !vpcEndpointPolicy.IsEmpty() { + for i, statement := range vpcEndpointPolicy.Statement { + prefix := "" + if len(vpcEndpointPolicy.Statement) > 1 { + prefix = fmt.Sprintf("Statement %d says: ", i) + statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n" + } else { + statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account) + } + + statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n") + if isResourcePolicyInteresting(statementSummaryInEnglish) { + //magenta(statementSummaryInEnglish) + isInteresting = magenta("Yes") + } + + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: fmt.Sprintf("arn:aws:ec2:%s:%s:vpc-endpoint/%s", r, aws.ToString(m.Caller.Account), aws.ToString(vpcEndpoint.VpcEndpointId)), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(vpcEndpoint.VpcEndpointId), + Region: r, + Interesting: isInteresting, + } + } + } + } else { + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: fmt.Sprintf("arn:aws:ec2:%s:%s:vpc-endpoint/%s", r, aws.ToString(m.Caller.Account), aws.ToString(vpcEndpoint.VpcEndpointId)), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(vpcEndpoint.VpcEndpointId), + Region: r, + Interesting: isInteresting, + } + } + } +} + +func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { + defer func() { + m.CommandCounter.Executing-- + m.CommandCounter.Complete++ + wg.Done() + }() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + openSearchDomains, err := sdk.CachedOpenSearchListDomainNames(*m.OpenSearchClient, aws.ToString(m.Caller.Account), r) + if err != nil { + sharedLogger.Error(err.Error()) + return + } + for _, openSearchDomain := range openSearchDomains { + var isPublic string + var statementSummaryInEnglish string + var isInteresting = "No" + + openSearchDomainConfig, err := sdk.CachedOpenSearchDescribeDomainConfig(*m.OpenSearchClient, aws.ToString(m.Caller.Account), r, aws.ToString(openSearchDomain.DomainName)) + if err != nil { + sharedLogger.Error(err.Error()) + m.CommandCounter.Error++ + continue + } + + if aws.ToBool(openSearchDomainConfig.AdvancedSecurityOptions.Options.Enabled) { + isPublic = "No" + } else { + isPublic = magenta("Yes") + isInteresting = magenta("Yes") + } + + openSearchDomainStatus, err := sdk.CachedOpenSearchDescribeDomain(*m.OpenSearchClient, aws.ToString(m.Caller.Account), r, aws.ToString(openSearchDomain.DomainName)) + if err != nil { + sharedLogger.Error(err.Error()) + m.CommandCounter.Error++ + continue + } + + if openSearchDomainStatus.AccessPolicies != nil && *openSearchDomainStatus.AccessPolicies != "" { + + // remove backslashes from the policy JSON + policyJson := strings.ReplaceAll(aws.ToString(openSearchDomainStatus.AccessPolicies), `\"`, `"`) + + openSearchDomainPolicy, err := policy.ParseJSONPolicy([]byte(policyJson)) + if err != nil { + sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(openSearchDomainStatus.ARN), err)) + m.CommandCounter.Error++ + continue + } + + if !openSearchDomainPolicy.IsEmpty() { + for i, statement := range openSearchDomainPolicy.Statement { + prefix := "" + if len(openSearchDomainPolicy.Statement) > 1 { + prefix = fmt.Sprintf("Statement %d says: ", i) + statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n" + } else { + statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account) + } + + statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n") + if isResourcePolicyInteresting(statementSummaryInEnglish) { + //magenta(statementSummaryInEnglish) + isInteresting = magenta("Yes") + } + + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: aws.ToString(openSearchDomainStatus.ARN), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(openSearchDomain.DomainName), + Region: r, + Interesting: isInteresting, + } + } + } + } else { + dataReceiver <- Resource2{ + AccountID: aws.ToString(m.Caller.Account), + ARN: aws.ToString(openSearchDomainStatus.ARN), + ResourcePolicySummary: statementSummaryInEnglish, + Public: isPublic, + Name: aws.ToString(openSearchDomain.DomainName), + Region: r, + Interesting: isInteresting, + } + } + } +} + func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) { defer func() { m.CommandCounter.Executing-- @@ -905,7 +1278,7 @@ func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sy } func isResourcePolicyInteresting(statementSummaryInEnglish string) bool { - // check if the statement has any of the following items, but make sure the check is case insensitive + // check if the statement has any of the following items, but make sure the check is case-insensitive // if it does, then return true // if it doesn't, then return false diff --git a/aws/resource-trusts_test.go b/aws/resource-trusts_test.go index 4a0db90f..6eb23c2d 100644 --- a/aws/resource-trusts_test.go +++ b/aws/resource-trusts_test.go @@ -2,6 +2,10 @@ package aws import ( "testing" + + "github.com/BishopFox/cloudfox/aws/sdk" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sts" ) func TestIsResourcePolicyInteresting(t *testing.T) { @@ -41,3 +45,218 @@ func TestIsResourcePolicyInteresting(t *testing.T) { }) } } + +func TestKMSResourceTrusts(t *testing.T) { + + mockedKMSClient := &sdk.MockedKMSClient{} + var kmsClient sdk.KMSClientInterface = mockedKMSClient + + testCases := []struct { + outputDirectory string + verbosity int + testModule ResourceTrustsModule + expectedResult []Resource2 + }{ + { + outputDirectory: ".", + verbosity: 2, + testModule: ResourceTrustsModule{ + KMSClient: &kmsClient, + APIGatewayClient: nil, + EC2Client: nil, + AWSRegions: []string{"us-west-2"}, + Caller: sts.GetCallerIdentityOutput{ + Account: aws.String("123456789012"), + Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"), + }, + Goroutines: 30, + }, + expectedResult: []Resource2{ + { + Name: "key1", + ARN: "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab", + }, + }, + }, + } + + for _, tc := range testCases { + tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity, true) + for index, expectedResource2 := range tc.expectedResult { + if expectedResource2.Name != tc.testModule.Resources2[index].Name { + t.Fatal("Resource name does not match expected value") + } + if expectedResource2.ARN != tc.testModule.Resources2[index].ARN { + t.Fatal("Resource ARN does not match expected value") + } + } + } +} + +func TestAPIGatewayResourceTrusts(t *testing.T) { + + mockedAPIGatewayClient := &sdk.MockedAWSAPIGatewayClient{} + var apiGatewayClient sdk.APIGatewayClientInterface = mockedAPIGatewayClient + + testCases := []struct { + outputDirectory string + verbosity int + testModule ResourceTrustsModule + expectedResult []Resource2 + }{ + { + outputDirectory: ".", + verbosity: 2, + testModule: ResourceTrustsModule{ + KMSClient: nil, + APIGatewayClient: &apiGatewayClient, + EC2Client: nil, + AWSRegions: []string{"us-west-2"}, + Caller: sts.GetCallerIdentityOutput{ + Account: aws.String("123456789012"), + Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"), + }, + Goroutines: 30, + }, + expectedResult: []Resource2{ + { + Name: "api1", + ARN: "arn:aws:execute-api:us-west-2:123456789012:abcdefg/*", + Public: "No", + Interesting: "Yes", + }, + }, + }, + } + + for _, tc := range testCases { + tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity, false) + for index, expectedResource2 := range tc.expectedResult { + if expectedResource2.Name != tc.testModule.Resources2[index].Name { + t.Fatal("Resource name does not match expected value") + } + if expectedResource2.ARN != tc.testModule.Resources2[index].ARN { + t.Fatal("Resource ARN does not match expected value") + } + if expectedResource2.Public != tc.testModule.Resources2[index].Public { + t.Fatal("Resource Public does not match expected value") + } + if expectedResource2.Interesting != tc.testModule.Resources2[index].Interesting { + t.Fatal("Resource Interesting does not match expected value") + } + } + } +} + +func TestVpcEndpointResourceTrusts(t *testing.T) { + + mockedEC2Client := &sdk.MockedEC2Client2{} + var ec2Client sdk.AWSEC2ClientInterface = mockedEC2Client + + testCases := []struct { + outputDirectory string + verbosity int + testModule ResourceTrustsModule + expectedResult []Resource2 + }{ + { + outputDirectory: ".", + verbosity: 2, + testModule: ResourceTrustsModule{ + KMSClient: nil, + APIGatewayClient: nil, + EC2Client: &ec2Client, + AWSRegions: []string{"us-west-2"}, + Caller: sts.GetCallerIdentityOutput{ + Account: aws.String("123456789012"), + Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"), + }, + Goroutines: 30, + }, + expectedResult: []Resource2{ + { + Name: "vpce-1234567890abcdefg", + ARN: "vpce-1234567890abcdefg", + Public: "No", + }, + { + Name: "vpce-1234567890abcdefh", + ARN: "vpce-1234567890abcdefh", + Public: "No", + }, + }, + }, + } + + for _, tc := range testCases { + tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity, false) + for index, expectedResource2 := range tc.expectedResult { + if expectedResource2.Name != tc.testModule.Resources2[index].Name { + t.Fatal("Resource name does not match expected value") + } + if expectedResource2.ARN != tc.testModule.Resources2[index].ARN { + t.Fatal("Resource ARN does not match expected value") + } + if expectedResource2.Public != tc.testModule.Resources2[index].Public { + t.Fatal("Resource Public does not match expected value") + } + } + } +} + +func TestOpenSearchResourceTrusts(t *testing.T) { + + mockedOpenSearchClient := &sdk.MockedOpenSearchClient{} + var openSearchClient sdk.OpenSearchClientInterface = mockedOpenSearchClient + + testCases := []struct { + outputDirectory string + verbosity int + testModule ResourceTrustsModule + expectedResult []Resource2 + }{ + { + outputDirectory: ".", + verbosity: 2, + testModule: ResourceTrustsModule{ + KMSClient: nil, + APIGatewayClient: nil, + EC2Client: nil, + OpenSearchClient: &openSearchClient, + AWSRegions: []string{"us-west-2"}, + Caller: sts.GetCallerIdentityOutput{ + Account: aws.String("123456789012"), + Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"), + }, + Goroutines: 30, + }, + expectedResult: []Resource2{ + { + Name: "domain1", + ARN: "arn:aws:es:us-east-1:123456789012:domain/domain1", + Public: "No", + }, + { + Name: "domain2", + ARN: "arn:aws:es:us-east-1:123456789012:domain/domain2", + Public: "Yes", + }, + }, + }, + } + + for _, tc := range testCases { + tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity, false) + for index, expectedResource2 := range tc.expectedResult { + if expectedResource2.Name != tc.testModule.Resources2[index].Name { + t.Fatal("Resource name does not match expected value") + } + if expectedResource2.ARN != tc.testModule.Resources2[index].ARN { + t.Fatal("Resource ARN does not match expected value") + } + if expectedResource2.Public != tc.testModule.Resources2[index].Public { + t.Fatal("Resource Public does not match expected value") + } + } + } +} diff --git a/aws/role-trusts.go b/aws/role-trusts.go index 85b351d6..48b773de 100644 --- a/aws/role-trusts.go +++ b/aws/role-trusts.go @@ -219,9 +219,10 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin RoleARN: aws.ToString(role.roleARN), RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)), TrustedPrincipal: principal, - ExternalID: statement.Condition.StringEquals.StsExternalID, - IsAdmin: role.Admin, - CanPrivEsc: role.CanPrivEsc, + // if there is more than one externalID concat them using newlines + ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"), + IsAdmin: role.Admin, + CanPrivEsc: role.CanPrivEsc, } body = append(body, []string{ aws.ToString(m.Caller.Account), @@ -281,7 +282,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string) 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 == "" { + if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == nil { accountID := strings.Split(principal, ":")[4] vendorName := m.vendors.GetVendorNameFromAccountID(accountID) if vendorName != "" { @@ -292,7 +293,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string) RoleARN: aws.ToString(role.roleARN), RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)), TrustedPrincipal: principal, - ExternalID: statement.Condition.StringEquals.StsExternalID, + ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"), IsAdmin: role.Admin, CanPrivEsc: role.CanPrivEsc, } diff --git a/aws/route53.go b/aws/route53.go index 1a7f2ac8..652d76db 100644 --- a/aws/route53.go +++ b/aws/route53.go @@ -235,19 +235,31 @@ func (m *Route53Module) getRoute53Records() { recordName = aws.ToString(record.Name) recordType = string(record.Type) - for _, resourceRecord := range record.ResourceRecords { - recordValue := resourceRecord.Value + if record.AliasTarget != nil { m.Records = append( m.Records, Record{ AWSService: "Route53", Name: recordName, - Type: recordType, - Value: aws.ToString(recordValue), + Type: fmt.Sprintf("Alias[%s]", recordType), + Value: aws.ToString(record.AliasTarget.DNSName), PrivateZone: privateZone, }) - + } else { + for _, resourceRecord := range record.ResourceRecords { + recordValue := resourceRecord.Value + m.Records = append( + m.Records, + Record{ + AWSService: "Route53", + Name: recordName, + Type: recordType, + Value: aws.ToString(recordValue), + PrivateZone: privateZone, + }) + } } + } } diff --git a/aws/sdk/apigateway.go b/aws/sdk/apigateway.go index 7ce368a3..f5e8c80d 100644 --- a/aws/sdk/apigateway.go +++ b/aws/sdk/apigateway.go @@ -53,7 +53,17 @@ func init() { 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 +func IsPublicApiGateway(ra *apiGatewayTypes.RestApi) bool { + for _, endpointType := range ra.EndpointConfiguration.Types { + if endpointType == apiGatewayTypes.EndpointTypeRegional || endpointType == apiGatewayTypes.EndpointTypeEdge { + return true + } + } + + return false +} + +// CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.RestApi, error) { var PaginationControl *string var restAPIs []apiGatewayTypes.RestApi diff --git a/aws/sdk/apigateway_mocks.go b/aws/sdk/apigateway_mocks.go index d4397c60..6981a522 100644 --- a/aws/sdk/apigateway_mocks.go +++ b/aws/sdk/apigateway_mocks.go @@ -22,6 +22,7 @@ func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apig apiGatewayTypes.EndpointTypePrivate, }, }, + Policy: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"execute-api:Invoke\",\"Resource\":\"arn:aws:execute-api:us-west-2:123456789012:abcdefg/*/*/*\"}]}"), }, { Id: aws.String("qwerty"), diff --git a/aws/sdk/codebuild_mocks.go b/aws/sdk/codebuild_mocks.go index efbaea2d..d0065021 100644 --- a/aws/sdk/codebuild_mocks.go +++ b/aws/sdk/codebuild_mocks.go @@ -5,6 +5,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/codebuild" + codeBuildTypes "github.com/aws/aws-sdk-go-v2/service/codebuild/types" ) type MockedCodeBuildClient struct { @@ -19,6 +20,19 @@ func (m *MockedCodeBuildClient) ListProjects(ctx context.Context, input *codebui }, nil } +func (m *MockedCodeBuildClient) BatchGetProjects(ctx context.Context, input *codebuild.BatchGetProjectsInput, options ...func(*codebuild.Options)) (*codebuild.BatchGetProjectsOutput, error) { + return &codebuild.BatchGetProjectsOutput{ + Projects: []codeBuildTypes.Project{ + { + Name: aws.String("project1"), + }, + { + Name: aws.String("project2"), + }, + }, + }, nil +} + func (m *MockedCodeBuildClient) GetResourcePolicy(ctx context.Context, input *codebuild.GetResourcePolicyInput, options ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) { return &codebuild.GetResourcePolicyOutput{ Policy: aws.String(`{ diff --git a/aws/sdk/ec2.go b/aws/sdk/ec2.go index 1deb5ae9..32476583 100644 --- a/aws/sdk/ec2.go +++ b/aws/sdk/ec2.go @@ -19,6 +19,7 @@ type AWSEC2ClientInterface interface { DescribeVolumes(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error) DescribeInstanceAttribute(context.Context, *ec2.DescribeInstanceAttributeInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error) + DescribeVpcEndpoints(context.Context, *ec2.DescribeVpcEndpointsInput, ...func(options *ec2.Options)) (*ec2.DescribeVpcEndpointsOutput, error) } func init() { @@ -27,7 +28,7 @@ func init() { gob.Register([]ec2Types.Snapshot{}) gob.Register([]ec2Types.Volume{}) gob.Register([]ec2Types.Image{}) - + gob.Register([]ec2Types.VpcEndpoint{}) } func CachedEC2DescribeInstances(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) { @@ -224,5 +225,36 @@ func CachedEC2DescribeImages(client AWSEC2ClientInterface, accountID string, reg internal.Cache.Set(cacheKey, Images, cache.DefaultExpiration) return Images, nil +} + +func CachedEC2DescribeVpcEndpoints(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.VpcEndpoint, error) { + var PaginationControl *string + var VpcEndpoints []ec2Types.VpcEndpoint + cacheKey := fmt.Sprintf("%s-ec2-DescribeVpcEndpoints-%s", accountID, region) + cached, found := internal.Cache.Get(cacheKey) + if found { + return cached.([]ec2Types.VpcEndpoint), nil + } + for { + DescribeVpcEndpoints, err := client.DescribeVpcEndpoints( + context.TODO(), + &(ec2.DescribeVpcEndpointsInput{ + NextToken: PaginationControl, + }), + func(o *ec2.Options) { + o.Region = region + }, + ) + if err != nil { + return VpcEndpoints, err + } + VpcEndpoints = append(VpcEndpoints, DescribeVpcEndpoints.VpcEndpoints...) + if DescribeVpcEndpoints.NextToken == nil { + break + } + PaginationControl = DescribeVpcEndpoints.NextToken + } + internal.Cache.Set(cacheKey, VpcEndpoints, cache.DefaultExpiration) + return VpcEndpoints, nil } diff --git a/aws/sdk/ec2_mocks.go b/aws/sdk/ec2_mocks.go index 51546cfd..d63165fa 100644 --- a/aws/sdk/ec2_mocks.go +++ b/aws/sdk/ec2_mocks.go @@ -25,6 +25,7 @@ type MockedEC2Client2 struct { describeSnapshots DescribeSnapshots describeVolumes DescribeVolumes describeImages DescribeImages + describeVpcEndpoints DescribeVpcEndpoints } type DescribeNetworkInterfaces struct { @@ -271,6 +272,9 @@ type DescribeInstanceAttribute struct { InstanceID string `json:"InstanceId"` } +type DescribeVpcEndpoints struct { +} + func (c *MockedEC2Client2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) { var nics []ec2types.NetworkInterface err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces) @@ -386,3 +390,43 @@ func (c *MockedEC2Client2) DescribeInstanceAttribute(ctx context.Context, input }, }, nil } + +func (c *MockedEC2Client2) DescribeVpcEndpoints(ctx context.Context, input *ec2.DescribeVpcEndpointsInput, f ...func(o *ec2.Options)) (*ec2.DescribeVpcEndpointsOutput, error) { + return &ec2.DescribeVpcEndpointsOutput{ + VpcEndpoints: []ec2types.VpcEndpoint{ + { + VpcEndpointId: aws.String("vpce-1234567890abcdefg"), + PolicyDocument: aws.String(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": "*", + "Resource": "*" + } + ] + }`), + }, + { + VpcEndpointId: aws.String("vpce-1234567890abcdefh"), + PolicyDocument: aws.String(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": "*", + "Resource": "*", + "Condition": { + "StringEquals": { + "aws:SourceVpce": "vpce-1234567890abcdefg" + } + } + } + ] + }`), + }, + }, + }, nil +} diff --git a/aws/sdk/ecs_mocks.go b/aws/sdk/ecs_mocks.go index 9ee5a22a..c765214d 100644 --- a/aws/sdk/ecs_mocks.go +++ b/aws/sdk/ecs_mocks.go @@ -135,12 +135,30 @@ func (c *MockedECSClient) DescribeTasks(ctx context.Context, input *ecs.Describe Status: aws.String(a.Status), }) } + + var containers []ecsTypes.Container + + for _, container := range mockedTask.Containers { + containers = append(containers, ecsTypes.Container{ + ContainerArn: aws.String(container.ContainerArn), + Cpu: aws.String(container.CPU), + HealthStatus: ecsTypes.HealthStatus(container.HealthStatus), + Image: aws.String(container.Image), + LastStatus: aws.String(container.LastStatus), + Memory: aws.String(container.Memory), + Name: aws.String(container.Name), + RuntimeId: aws.String(container.RuntimeID), + TaskArn: aws.String(container.TaskArn), + }) + } + tasks = append(tasks, ecsTypes.Task{ ClusterArn: aws.String(mockedTask.ClusterArn), TaskDefinitionArn: aws.String(mockedTask.TaskDefinitionArn), LaunchType: ecsTypes.LaunchType(*aws.String(mockedTask.LaunchType)), TaskArn: aws.String(mockedTask.TaskArn), Attachments: attachments, + Containers: containers, }) } } diff --git a/aws/sdk/kms.go b/aws/sdk/kms.go new file mode 100644 index 00000000..98506852 --- /dev/null +++ b/aws/sdk/kms.go @@ -0,0 +1,80 @@ +package sdk + +import ( + "context" + "encoding/gob" + "fmt" + "github.com/BishopFox/cloudfox/internal" + "github.com/BishopFox/cloudfox/internal/aws/policy" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kms" + kmsTypes "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/patrickmn/go-cache" +) + +// KMSClientInterface is an interface for the AWS SDK KMS client. +type KMSClientInterface interface { + ListKeys(context.Context, *kms.ListKeysInput, ...func(options *kms.Options)) (*kms.ListKeysOutput, error) + GetKeyPolicy(context.Context, *kms.GetKeyPolicyInput, ...func(options *kms.Options)) (*kms.GetKeyPolicyOutput, error) +} + +func init() { + gob.Register([]kmsTypes.KeyListEntry{}) + gob.Register(policy.Policy{}) +} + +// CachedKMSListKeys returns a list of KMS keys for the given account and region. +func CachedKMSListKeys(client KMSClientInterface, accountID string, region string) ([]kmsTypes.KeyListEntry, error) { + var keys []kmsTypes.KeyListEntry + cacheKey := fmt.Sprintf("%s-kms-ListKeys-%s", accountID, region) + cached, found := internal.Cache.Get(cacheKey) + if found { + return cached.([]kmsTypes.KeyListEntry), nil + } + + keyPaginator := kms.NewListKeysPaginator(client.(kms.ListKeysAPIClient), &kms.ListKeysInput{}, func(options *kms.ListKeysPaginatorOptions) {}) + + for keyPaginator.HasMorePages() { + output, err := keyPaginator.NextPage(context.TODO()) + + if err != nil { + return nil, err + } + + for _, key := range output.Keys { + keys = append(keys, key) + } + } + + internal.Cache.Set(cacheKey, keys, cache.DefaultExpiration) + return keys, nil +} + +// CachedKMSGetKeyPolicy returns the policy for the given KMS key. +func CachedKMSGetKeyPolicy(client KMSClientInterface, accountID string, region string, keyID string) (policy.Policy, error) { + var keyPolicy policy.Policy + var policyJson string + cacheKey := fmt.Sprintf("%s-kms-GetKeyPolicy-%s-%s", accountID, region, keyID) + cached, found := internal.Cache.Get(cacheKey) + if found { + return cached.(policy.Policy), nil + } + + getKeyPolicy, err := client.GetKeyPolicy( + context.TODO(), + &kms.GetKeyPolicyInput{ + KeyId: &keyID, + }, + ) + if err != nil { + return keyPolicy, err + } + + policyJson = aws.ToString(getKeyPolicy.Policy) + keyPolicy, err = policy.ParseJSONPolicy([]byte(policyJson)) + if err != nil { + return keyPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", keyID, err) + } + internal.Cache.Set(cacheKey, keyPolicy, cache.DefaultExpiration) + return keyPolicy, nil +} diff --git a/aws/sdk/kms_mocks.go b/aws/sdk/kms_mocks.go new file mode 100644 index 00000000..3eb53a0f --- /dev/null +++ b/aws/sdk/kms_mocks.go @@ -0,0 +1,42 @@ +package sdk + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kms" + kmsTypes "github.com/aws/aws-sdk-go-v2/service/kms/types" +) + +type MockedKMSClient struct { +} + +func (m *MockedKMSClient) ListKeys(ctx context.Context, input *kms.ListKeysInput, options ...func(*kms.Options)) (*kms.ListKeysOutput, error) { + return &kms.ListKeysOutput{ + Keys: []kmsTypes.KeyListEntry{ + { + KeyId: aws.String("key1"), + KeyArn: aws.String("arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab"), + }, + }, + }, nil +} + +func (m *MockedKMSClient) GetKeyPolicy(ctx context.Context, input *kms.GetKeyPolicyInput, options ...func(options *kms.Options)) (*kms.GetKeyPolicyOutput, error) { + return &kms.GetKeyPolicyOutput{ + Policy: aws.String(`{ + "Version": "2012-10-17", + "Id": "key-default-1", + "Statement": [ + { + "Sid": "Enable IAM User Permissions", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::123456789012:root" + }, + "Action": "kms:*", + "Resource": "*" + } + ] + }`), + }, nil +} diff --git a/aws/sdk/opensearch.go b/aws/sdk/opensearch.go index 42b762b2..ac431f45 100644 --- a/aws/sdk/opensearch.go +++ b/aws/sdk/opensearch.go @@ -23,7 +23,7 @@ func init() { gob.Register(openSearchTypes.DomainStatus{}) } -// create CachedOpenSearchListDomainNames function that uses go-cache and pagination +// CachedOpenSearchListDomainNames function that uses go-cache and pagination func CachedOpenSearchListDomainNames(client OpenSearchClientInterface, accountID string, region string) ([]openSearchTypes.DomainInfo, error) { var domains []openSearchTypes.DomainInfo cacheKey := fmt.Sprintf("%s-opensearch-ListDomainNames-%s", accountID, region) @@ -50,8 +50,11 @@ func CachedOpenSearchListDomainNames(client OpenSearchClientInterface, accountID return domains, nil } -// create CachedOpenSearchDescribeDomainConfig function that uses go-cache and pagination and supports region option +// CachedOpenSearchDescribeDomainConfig function that uses go-cache and pagination and supports region option func CachedOpenSearchDescribeDomainConfig(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainConfig, error) { + + fmt.Printf("CachedOpenSearchDescribeDomainConfig: %s %s %s\n", accountID, region, domainName) + var DomainConfig openSearchTypes.DomainConfig cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomainConfig-%s-%s", accountID, region, domainName) cached, found := internal.Cache.Get(cacheKey) @@ -78,7 +81,7 @@ func CachedOpenSearchDescribeDomainConfig(client OpenSearchClientInterface, acco return DomainConfig, nil } -// create CachedOpenSearchDescribeDomain function that uses go-cache and pagination and supports region option +// CachedOpenSearchDescribeDomain function that uses go-cache and pagination and supports region option func CachedOpenSearchDescribeDomain(client OpenSearchClientInterface, accountID string, region string, domainName string) (openSearchTypes.DomainStatus, error) { var DomainStatus openSearchTypes.DomainStatus cacheKey := fmt.Sprintf("%s-opensearch-DescribeDomain-%s-%s", accountID, region, domainName) diff --git a/aws/sdk/opensearch_mocks.go b/aws/sdk/opensearch_mocks.go index 4da31895..d2864291 100644 --- a/aws/sdk/opensearch_mocks.go +++ b/aws/sdk/opensearch_mocks.go @@ -27,8 +27,35 @@ func (m *MockedOpenSearchClient) ListDomainNames(ctx context.Context, input *ope } func (m *MockedOpenSearchClient) DescribeDomainConfig(ctx context.Context, input *opensearch.DescribeDomainConfigInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error) { - return &opensearch.DescribeDomainConfigOutput{ - DomainConfig: &openSearchTypes.DomainConfig{ + domainConfigMap := map[string]*openSearchTypes.DomainConfig{ + "domain1": { + EngineVersion: &openSearchTypes.VersionStatus{ + Options: aws.String("OpenSearch-1.1"), + Status: &openSearchTypes.OptionStatus{ + PendingDeletion: aws.Bool(false), + }, + }, + ClusterConfig: &openSearchTypes.ClusterConfigStatus{ + Options: &openSearchTypes.ClusterConfig{ + DedicatedMasterCount: aws.Int32(3), + DedicatedMasterEnabled: aws.Bool(true), + InstanceCount: aws.Int32(3), + WarmCount: aws.Int32(3), + WarmEnabled: aws.Bool(true), + }, + }, + DomainEndpointOptions: &openSearchTypes.DomainEndpointOptionsStatus{ + Options: &openSearchTypes.DomainEndpointOptions{ + EnforceHTTPS: aws.Bool(true), + }, + }, + AdvancedSecurityOptions: &openSearchTypes.AdvancedSecurityOptionsStatus{ + Options: &openSearchTypes.AdvancedSecurityOptions{ + Enabled: aws.Bool(true), + }, + }, + }, + "domain2": { EngineVersion: &openSearchTypes.VersionStatus{ Options: aws.String("OpenSearch-1.1"), Status: &openSearchTypes.OptionStatus{ @@ -49,15 +76,36 @@ func (m *MockedOpenSearchClient) DescribeDomainConfig(ctx context.Context, input EnforceHTTPS: aws.Bool(true), }, }, + AdvancedSecurityOptions: &openSearchTypes.AdvancedSecurityOptionsStatus{ + Options: &openSearchTypes.AdvancedSecurityOptions{ + Enabled: aws.Bool(false), + }, + }, }, + } + + return &opensearch.DescribeDomainConfigOutput{ + DomainConfig: domainConfigMap[*input.DomainName], }, nil } func (m *MockedOpenSearchClient) DescribeDomain(ctx context.Context, input *opensearch.DescribeDomainInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainOutput, error) { - return &opensearch.DescribeDomainOutput{ - DomainStatus: &openSearchTypes.DomainStatus{ - DomainName: aws.String("domain1"), - Endpoint: aws.String("https://domain1.us-east-1.es.amazonaws.com"), + domainStatusMap := map[string]*openSearchTypes.DomainStatus{ + "domain1": { + DomainName: aws.String("domain1"), + Endpoint: aws.String("https://domain1.us-east-1.es.amazonaws.com"), + ARN: aws.String("arn:aws:es:us-east-1:123456789012:domain/domain1"), + AccessPolicies: aws.String(`{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"es:ESHttpGet\",\"es:ESHttpHead\",\"es:ESHttpPost\"],\"Resource\":\"*\",\"Condition\":{\"IpAddress\":{\"aws:SourceIp\":\"192.168.1.100/32\"}}}]}`), + }, + "domain2": { + DomainName: aws.String("domain2"), + Endpoint: aws.String("https://domain2.us-east-1.es.amazonaws.com"), + ARN: aws.String("arn:aws:es:us-east-1:123456789012:domain/domain2"), + AccessPolicies: aws.String(`{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"es:ESHttpGet\",\"es:ESHttpHead\",\"es:ESHttpPost\"],\"Resource\":\"*\"}]}`), }, + } + + return &opensearch.DescribeDomainOutput{ + DomainStatus: domainStatusMap[*input.DomainName], }, nil } diff --git a/aws/sdk/rds_mocks.go b/aws/sdk/rds_mocks.go index b6ec344b..3829a85d 100644 --- a/aws/sdk/rds_mocks.go +++ b/aws/sdk/rds_mocks.go @@ -21,6 +21,10 @@ func (m *MockedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.De EngineVersion: aws.String("13.3"), InstanceCreateTime: aws.Time(time.Now()), MasterUsername: aws.String("postgres"), + Endpoint: &rdsTypes.Endpoint{ + Address: aws.String("db1-instances-1.blah.us-west-2.rds.amazonaws.com"), + Port: aws.Int32(5432), + }, }, { DBInstanceIdentifier: aws.String("db2"), @@ -28,6 +32,10 @@ func (m *MockedRDSClient) DescribeDBInstances(ctx context.Context, input *rds.De EngineVersion: aws.String("13.3"), InstanceCreateTime: aws.Time(time.Now()), MasterUsername: aws.String("postgres"), + Endpoint: &rdsTypes.Endpoint{ + Address: aws.String("db2-instances-1.blah.us-west-2.rds.amazonaws.com"), + Port: aws.Int32(5432), + }, }, }, }, nil diff --git a/cli/aws.go b/cli/aws.go index f75990b8..045fcbbc 100644 --- a/cli/aws.go +++ b/cli/aws.go @@ -2,11 +2,14 @@ package cli import ( "encoding/gob" + "errors" "fmt" "log" "os" "path/filepath" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/BishopFox/cloudfox/aws" "github.com/BishopFox/cloudfox/aws/sdk" "github.com/BishopFox/cloudfox/internal" @@ -138,9 +141,10 @@ var ( PostRun: awsPostRun, } - CapeAdminOnly bool - CapeJobName string - CapeCommand = &cobra.Command{ + CapeAdminOnly bool + CapeArnIgnoreList string + CapeJobName string + CapeCommand = &cobra.Command{ Use: "cape", Aliases: []string{"CAPE"}, Short: "Cross-Account Privilege Escalation Route finder. Needs to be run with multiple profiles using -l or -a flag. Needs pmapper data to be present", @@ -263,10 +267,11 @@ var ( PostRun: awsPostRun, } - SimulatorResource string - SimulatorAction string - SimulatorPrincipal string - IamSimulatorCommand = &cobra.Command{ + SimulatorResource string + SimulatorAction string + SimulatorPrincipal string + IamSimulatorAdminCheckOnly bool + IamSimulatorCommand = &cobra.Command{ Use: "iam-simulator", Aliases: []string{"iamsimulator", "simulator"}, Short: "Wrapper around the AWS IAM Simulate Principal Policy command", @@ -405,7 +410,8 @@ var ( PostRun: awsPostRun, } - ResourceTrustsCommand = &cobra.Command{ + ResourceTrustsIncludeKms bool + ResourceTrustsCommand = &cobra.Command{ Use: "resource-trusts", Aliases: []string{"resourcetrusts", "resourcetrust"}, Short: "Enumerate all resource trusts", @@ -564,7 +570,7 @@ func awsPreRun(cmd *cobra.Command, args []string) { cacheDirectory := filepath.Join(AWSOutputDirectory, "cached-data", "aws", ptr.ToString(caller.Account)) err = internal.LoadCacheFromGobFiles(cacheDirectory) if err != nil { - if err == internal.ErrDirectoryDoesNotExist { + if errors.Is(err, internal.ErrDirectoryDoesNotExist) { 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][%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) @@ -631,7 +637,7 @@ func FindOrgMgmtAccountAndReorderAccounts(AWSProfiles []string, version string) cacheDirectory := filepath.Join(AWSOutputDirectory, "cached-data", "aws", ptr.ToString(caller.Account)) err = internal.LoadCacheFromGobFiles(cacheDirectory) if err != nil { - if err == internal.ErrDirectoryDoesNotExist { + if errors.Is(err, internal.ErrDirectoryDoesNotExist) { 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][%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) @@ -916,7 +922,6 @@ func runEnvsCommand(cmd *cobra.Command, args []string) { continue } m := aws.EnvsModule{ - Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -1172,6 +1177,20 @@ func runCapeCommand(cmd *cobra.Command, args []string) { } } + // if the CapeArnIgnoreList arg is not empty, read the file and add the arns to the CapeArnIgnoreList + if CapeArnIgnoreList != "" { + // call ReadArnIgnoreListFile and add the arns to the CapeArnIgnoreList + arnsToIgnore, err := aws.ReadArnIgnoreListFile(CapeArnIgnoreList) + if err != nil { + fmt.Println("Error reading the arn ignore list file: " + err.Error()) + } + + // remove nodes that are in the CapeArnIgnoreList from the graph + for _, arn := range arnsToIgnore { + GlobalGraph.RemoveVertex(arn) + } + } + // make pmapper edges //you can update edges, so we can just merge attributes as needed // first we add the edges that already exist in pmapper, then later we will make more edges based on the cloudfox role trusts logic @@ -1182,7 +1201,7 @@ func runCapeCommand(cmd *cobra.Command, args []string) { graph.EdgeAttribute(edge.ShortReason, edge.Reason), ) if err != nil { - if err == graph.ErrEdgeAlreadyExists { + if errors.Is(err, graph.ErrEdgeAlreadyExists) { // update theedge by copying the existing graph.Edge with attributes and add the new attributes //fmt.Println("Edge already exists") @@ -1318,10 +1337,20 @@ func runCapeTUICommand(cmd *cobra.Command, args []string) { if _, err := os.Stat(filepath.Join(cloudfoxRunData.OutputLocation, "json", fileName)); os.IsNotExist(err) { fmt.Printf("[%s] Could not retrieve CAPE data for profile %s.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), profile) //remove the profile from the list of profiles to analyze - AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...) + if len(AWSProfiles) > 1 { + AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...) + } else { + if CapeAdminOnly { + fmt.Printf("[%s] Could not retrieve cape data. Did you run cape without the --admin-only flag? You'll need to run cape with --admin-only to use the tui with --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + } else { + fmt.Printf("[%s] Did you run cape with the --admin-only flag? You'll need to run cape without --admin-only to use the tui without --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + } + os.Exit(1) + } } } + if len(capeOutputFileLocations) == 0 { fmt.Printf("[%s] Could not retrieve CAPE data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) os.Exit(1) @@ -1338,13 +1367,14 @@ func runIamSimulatorCommand(cmd *cobra.Command, args []string) { continue } m := aws.IamSimulatorModule{ - IAMClient: iam.NewFromConfig(AWSConfig), - Caller: *caller, - AWSProfileProvided: 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, + IamSimulatorAdminCheckOnly: IamSimulatorAdminCheckOnly, } m.PrintIamSimulator(SimulatorPrincipal, SimulatorAction, SimulatorResource, AWSOutputDirectory, Verbosity) } @@ -1547,13 +1577,15 @@ func runPrincipalsCommand(cmd *cobra.Command, args []string) { continue } m := aws.IamPrincipalsModule{ - IAMClient: iam.NewFromConfig(AWSConfig), - Caller: *caller, - AWSProfile: profile, - Goroutines: Goroutines, - WrapTable: AWSWrapTable, - AWSOutputType: AWSOutputType, - AWSTableCols: AWSTableCols, + IAMClient: iam.NewFromConfig(AWSConfig), + Caller: *caller, + AWSProfile: profile, + Goroutines: Goroutines, + SkipAdminCheck: AWSSkipAdminCheck, + WrapTable: AWSWrapTable, + AWSOutputType: AWSOutputType, + AWSTableCols: AWSTableCols, + PmapperDataBasePath: PmapperDataBasePath, } m.PrintIamPrincipals(AWSOutputDirectory, Verbosity) } @@ -1583,26 +1615,40 @@ 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, - 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) + runResourceTrustsCommandWithProfile(cmd, args, profile) } } +func runResourceTrustsCommandWithProfile(cmd *cobra.Command, args []string, profile string) { + var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken) + caller, err := internal.AWSWhoami(profile, cmd.Root().Version, AWSMFAToken) + var KMSClient sdk.KMSClientInterface = kms.NewFromConfig(AWSConfig) + var APIGatewayClient sdk.APIGatewayClientInterface = apigateway.NewFromConfig(AWSConfig) + var EC2Client sdk.AWSEC2ClientInterface = ec2.NewFromConfig(AWSConfig) + var OpenSearchClient sdk.OpenSearchClientInterface = opensearch.NewFromConfig(AWSConfig) + + if err != nil { + return + } + m := aws.ResourceTrustsModule{ + KMSClient: &KMSClient, + APIGatewayClient: &APIGatewayClient, + EC2Client: &EC2Client, + OpenSearchClient: &OpenSearchClient, + + 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, ResourceTrustsIncludeKms) +} + func runRoleTrustCommand(cmd *cobra.Command, args []string) { for _, profile := range AWSProfiles { var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version, AWSMFAToken) @@ -1909,14 +1955,13 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { SQSClient: sqsClient, SSMClient: ssmClient, StepFunctionClient: stepFunctionClient, - - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - AWSProfile: profile, - Goroutines: Goroutines, - WrapTable: AWSWrapTable, - AWSOutputType: AWSOutputType, - AWSTableCols: AWSTableCols, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), + AWSProfile: profile, + Goroutines: Goroutines, + WrapTable: AWSWrapTable, + AWSOutputType: AWSOutputType, + AWSTableCols: AWSTableCols, } inventory2.PrintInventoryPerRegion(AWSOutputDirectory, Verbosity) @@ -1961,11 +2006,10 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { instances.Instances(InstancesFilter, AWSOutputDirectory, Verbosity) route53 := aws.Route53Module{ Route53Client: route53Client, - - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - AWSProfile: profile, - Goroutines: Goroutines, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), + AWSProfile: profile, + Goroutines: Goroutines, } lambdasMod := aws.LambdasModule{ @@ -1999,7 +2043,6 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { filesystems.PrintFilesystems(AWSOutputDirectory, Verbosity) endpoints := aws.EndpointsModule{ - EKSClient: eksClient, S3Client: s3Client, LambdaClient: lambdaClient, @@ -2015,14 +2058,13 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { CloudfrontClient: cloudfrontClient, AppRunnerClient: appRunnerClient, LightsailClient: lightsailClient, - - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - AWSProfile: profile, - Goroutines: Goroutines, - WrapTable: AWSWrapTable, - AWSOutputType: AWSOutputType, - AWSTableCols: AWSTableCols, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), + AWSProfile: profile, + Goroutines: Goroutines, + WrapTable: AWSWrapTable, + AWSOutputType: AWSOutputType, + AWSTableCols: AWSTableCols, } endpoints.PrintEndpoints(AWSOutputDirectory, Verbosity) @@ -2030,12 +2072,11 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { gateways := aws.ApiGwModule{ APIGatewayv2Client: apiGatewayv2Client, APIGatewayClient: apiGatewayClient, - - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - AWSProfile: profile, - Goroutines: Goroutines, - WrapTable: AWSWrapTable, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), + AWSProfile: profile, + Goroutines: Goroutines, + WrapTable: AWSWrapTable, } gateways.PrintApiGws(AWSOutputDirectory, Verbosity) @@ -2056,10 +2097,9 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { databases.PrintDatabases(AWSOutputDirectory, Verbosity) ecstasks := aws.ECSTasksModule{ - EC2Client: ec2Client, - ECSClient: ecsClient, - IAMClient: iamClient, - + EC2Client: ec2Client, + ECSClient: ecsClient, + IAMClient: iamClient, Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -2073,9 +2113,8 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { ecstasks.ECSTasks(AWSOutputDirectory, Verbosity) eksCommand := aws.EKSModule{ - EKSClient: eksClient, - IAMClient: iamClient, - + EKSClient: eksClient, + IAMClient: iamClient, Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -2103,11 +2142,10 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { fmt.Printf("[%s] %s\n", cyan(emoji.Sprintf(":fox:cloudfox :fox:")), green("Looking for secrets hidden between the seat cushions.")) ec2UserData := aws.InstancesModule{ - EC2Client: ec2Client, - IAMClient: iamClient, - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - + EC2Client: ec2Client, + IAMClient: iamClient, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), UserDataAttributesOnly: true, AWSProfile: profile, Goroutines: Goroutines, @@ -2117,7 +2155,6 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { } ec2UserData.Instances(InstancesFilter, AWSOutputDirectory, Verbosity) envsMod := aws.EnvsModule{ - Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -2183,14 +2220,13 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { secrets := aws.SecretsModule{ SecretsManagerClient: secretsManagerClient, SSMClient: ssmClient, - - Caller: *caller, - AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), - AWSProfile: profile, - Goroutines: Goroutines, - WrapTable: AWSWrapTable, - AWSOutputType: AWSOutputType, - AWSTableCols: AWSTableCols, + Caller: *caller, + AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), + AWSProfile: profile, + Goroutines: Goroutines, + WrapTable: AWSWrapTable, + AWSOutputType: AWSOutputType, + AWSTableCols: AWSTableCols, } secrets.PrintSecrets(AWSOutputDirectory, Verbosity) @@ -2224,10 +2260,8 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { networkPorts.PrintNetworkPorts(AWSOutputDirectory) sqsMod := aws.SQSModule{ - SQSClient: sqsClient, - + SQSClient: sqsClient, StorePolicies: StoreSQSAccessPolicies, - Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -2241,19 +2275,7 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { cloudFoxSNSClient := aws.InitCloudFoxSNSClient(*caller, profile, cmd.Root().Version, Goroutines, AWSWrapTable, AWSMFAToken) cloudFoxSNSClient.PrintSNS(AWSOutputDirectory, Verbosity) - resourceTrustsCommand := aws.ResourceTrustsModule{ - 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) + runResourceTrustsCommandWithProfile(cmd, args, profile) codeBuildCommand := aws.CodeBuildModule{ CodeBuildClient: codeBuildClient, @@ -2375,9 +2397,6 @@ func init() { // Map Access Keys Module Flags AccessKeysCommand.Flags().StringVarP(&AccessKeysFilter, "filter", "f", "none", "Access key ID to search for") - // IAM Simulator Module Flags - //IamSimulatorCommand.Flags().StringVarP(&IamSimulatorFilter, "filter", "f", "none", "Access key ID to search for") - // Instances Map Module Flags InstancesCommand.Flags().StringVarP(&InstancesFilter, "filter", "f", "all", "[InstanceID | InstanceIDsFile]") InstancesCommand.Flags().BoolVarP(&InstanceMapUserDataAttributesOnly, "userdata", "u", false, "Use this flag to retrieve only the userData attribute from EC2 instances.") @@ -2395,6 +2414,7 @@ func init() { IamSimulatorCommand.Flags().StringVar(&SimulatorPrincipal, "principal", "", "Principal Arn") IamSimulatorCommand.Flags().StringVar(&SimulatorAction, "action", "", "Action") IamSimulatorCommand.Flags().StringVar(&SimulatorResource, "resource", "*", "Resource") + IamSimulatorCommand.Flags().BoolVar(&IamSimulatorAdminCheckOnly, "admin-check-only", false, "Only check check if principals are admin") // iam-simulator module flags PermissionsCommand.Flags().StringVar(&PermissionsPrincipal, "principal", "", "Principal Arn") @@ -2408,10 +2428,15 @@ func init() { // cape command flags CapeCommand.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster") //CapeCommand.Flags().StringVar(&CapeJobName, "job-name", "", "Name of the cape job") + // flag that accepts a list of arns to ignore + CapeCommand.Flags().StringVar(&CapeArnIgnoreList, "arn-ignore-list", "", "File containing a list of ARNs to ignore separated by newlines") // cape tui command flags CapeTuiCmd.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster") + // Resource Trust command flags + ResourceTrustsCommand.Flags().BoolVar(&ResourceTrustsIncludeKms, "include-kms", false, "Include KMS keys in the output") + // Global flags for the AWS modules AWSCommands.PersistentFlags().StringVarP(&AWSProfile, "profile", "p", "", "AWS CLI Profile Name") AWSCommands.PersistentFlags().StringVarP(&AWSProfilesList, "profiles-list", "l", "", "File containing a AWS CLI profile names separated by newlines") diff --git a/gcp/services/iamService/access-tokens.go b/gcp/services/iamService/access-tokens.go index 5e8c19b9..834ad40c 100644 --- a/gcp/services/iamService/access-tokens.go +++ b/gcp/services/iamService/access-tokens.go @@ -17,7 +17,7 @@ package iamservice // Projects []string // } -// // Be incorportated in iam.go +// // Be incorporated 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() diff --git a/globals/utils.go b/globals/utils.go index 1fba2fec..cd5afd96 100644 --- a/globals/utils.go +++ b/globals/utils.go @@ -4,4 +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.14.0" +const CLOUDFOX_VERSION = "1.16.0" diff --git a/go.mod b/go.mod index 988b0b07..ff686089 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/BishopFox/cloudfox -go 1.21.2 +go 1.22 -toolchain go1.21.6 +toolchain go1.24.0 require ( cloud.google.com/go/artifactregistry v1.14.6 @@ -18,59 +18,59 @@ require ( github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 github.com/aquasecurity/table v1.8.0 - github.com/aws/aws-sdk-go-v2 v1.26.1 - github.com/aws/aws-sdk-go-v2/config v1.26.2 - github.com/aws/aws-sdk-go-v2/credentials v1.16.13 - 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/athena v1.37.3 - 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/codeartifact v1.23.5 - github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.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/directoryservice v1.24.4 - github.com/aws/aws-sdk-go-v2/service/docdb v1.29.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/elasticbeanstalk v1.20.5 - 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/emr v1.35.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/kinesis v1.24.6 - 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/sfn v1.24.6 - 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.20.2 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 + github.com/aws/aws-sdk-go-v2/service/apigateway v1.25.4 + github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.22.4 + github.com/aws/aws-sdk-go-v2/service/apprunner v1.30.3 + github.com/aws/aws-sdk-go-v2/service/athena v1.44.3 + github.com/aws/aws-sdk-go-v2/service/cloud9 v1.26.3 + github.com/aws/aws-sdk-go-v2/service/cloudformation v1.53.3 + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4 + github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.3 + github.com/aws/aws-sdk-go-v2/service/codeartifact v1.30.3 + github.com/aws/aws-sdk-go-v2/service/codebuild v1.40.3 + github.com/aws/aws-sdk-go-v2/service/codecommit v1.25.0 + github.com/aws/aws-sdk-go-v2/service/codedeploy v1.27.3 + github.com/aws/aws-sdk-go-v2/service/datapipeline v1.23.3 + github.com/aws/aws-sdk-go-v2/service/directoryservice v1.27.3 + github.com/aws/aws-sdk-go-v2/service/docdb v1.36.3 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0 + github.com/aws/aws-sdk-go-v2/service/ecs v1.44.3 + github.com/aws/aws-sdk-go-v2/service/efs v1.31.3 + github.com/aws/aws-sdk-go-v2/service/eks v1.47.0 + github.com/aws/aws-sdk-go-v2/service/elasticache v1.40.5 + github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.26.2 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.26.3 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.34.0 + github.com/aws/aws-sdk-go-v2/service/emr v1.42.2 + github.com/aws/aws-sdk-go-v2/service/fsx v1.47.2 + github.com/aws/aws-sdk-go-v2/service/glue v1.91.0 + github.com/aws/aws-sdk-go-v2/service/grafana v1.24.3 + github.com/aws/aws-sdk-go-v2/service/iam v1.34.3 + github.com/aws/aws-sdk-go-v2/service/kinesis v1.29.3 + github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.3 + github.com/aws/aws-sdk-go-v2/service/mq v1.25.3 + github.com/aws/aws-sdk-go-v2/service/opensearch v1.46.3 + github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2 + github.com/aws/aws-sdk-go-v2/service/ram v1.27.3 + github.com/aws/aws-sdk-go-v2/service/rds v1.82.0 + github.com/aws/aws-sdk-go-v2/service/redshift v1.46.4 + github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3 + github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 + github.com/aws/aws-sdk-go-v2/service/sagemaker v1.152.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 + github.com/aws/aws-sdk-go-v2/service/sfn v1.30.0 + github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 + github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 + github.com/aws/aws-sdk-go-v2/service/ssm v1.52.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 + github.com/aws/smithy-go v1.22.2 github.com/bishopfox/awsservicemap v1.0.3 github.com/bishopfox/knownawsaccountslookup v0.0.0-20231228165844-c37ef8df33cb github.com/dominikbraun/graph v0.23.0 @@ -102,6 +102,7 @@ require ( ) require ( + github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 golang.org/x/oauth2 v0.15.0 google.golang.org/api v0.152.0 google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 @@ -128,19 +129,19 @@ require ( github.com/apache/arrow/go/v12 v12.0.0 // indirect github.com/apache/thrift v0.16.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // 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/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/sso v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/lipgloss v0.9.1 diff --git a/go.sum b/go.sum index 6ec34b94..fde97b69 100644 --- a/go.sum +++ b/go.sum @@ -81,138 +81,142 @@ github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7 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.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= -github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -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.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= -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/directoryservice v1.24.4 h1:XBgx3sdaA0SoPXsZSNSUL14H0UnYnTSVArieaYNv0EI= -github.com/aws/aws-sdk-go-v2/service/directoryservice v1.24.4/go.mod h1:Lm/qj7nCC0zEFoAdjbun8xLkflPFNbbspQVZgQQiOz8= -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.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= -github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.25.4 h1:tya0sBEw+Sb9ztjykjX+InfZLufo4v1XyXhy4uPsyW4= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.25.4/go.mod h1:jmTl7BrsxCEUl4HwtL9tCDVfmSmCwatcUQA7QXgtT34= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.22.4 h1:CRu+uzE4qzjJBNkcwCKdzGzx1bMPsmulB7q8qyoa6FI= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.22.4/go.mod h1:IN1OJRdB0VVSXsx1wlEfaDPpuXwSPkAVjhj7R5iSKsU= +github.com/aws/aws-sdk-go-v2/service/apprunner v1.30.3 h1:x6wptcqKbH2eQw7v43MI25ILW3OtIyYwZ9gifEM0DW8= +github.com/aws/aws-sdk-go-v2/service/apprunner v1.30.3/go.mod h1:buTv8bJjlKxqALyK7/2G1206H/YYllu0R/F9Hz0rhv4= +github.com/aws/aws-sdk-go-v2/service/athena v1.44.3 h1:T2tJUqFEs8+2944NHspI3dRFELzKH4HfPXdrrIy18WA= +github.com/aws/aws-sdk-go-v2/service/athena v1.44.3/go.mod h1:Vn+X6oPpEMNBFAlGGHHNiNc+Tk10F3dPYLbtbED7fIE= +github.com/aws/aws-sdk-go-v2/service/cloud9 v1.26.3 h1:QBP3/69oA+0+j5oNHXL/V8Hj4NTEjYZaOXHPNFhbFv0= +github.com/aws/aws-sdk-go-v2/service/cloud9 v1.26.3/go.mod h1:ehJ9aR1QffkV/66jI90pJ05g2qCOIMuOLsuSkJ93cHc= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.53.3 h1:mIpL+FXa+2U6oc85b/15JwJhNUU+c/LHwxM3hpQIxXQ= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.53.3/go.mod h1:lcQ7+K0Q9x0ozhjBwDfBkuY8qexSP/QXLgp0jj+/NZg= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4 h1:I/sQ9uGOs72/483obb2SPoa9ZEsYGbel6jcTTwD/0zU= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4/go.mod h1:P6ByphKl2oNQZlv4WsCaLSmRncKEcOnbitYLtJPfqZI= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.3 h1:dtFepCqT+Lm3sFxracD6PvVJAMTuIKTRd3yqBpMOomk= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.3/go.mod h1:p+4/sHQpT3kcfY2LruQuVgVFKd72yLnqJUayHhwfStY= +github.com/aws/aws-sdk-go-v2/service/codeartifact v1.30.3 h1:9eAjfGKFWduKyCR94Qi/JfORoJLndGydph2dcLtM7gI= +github.com/aws/aws-sdk-go-v2/service/codeartifact v1.30.3/go.mod h1:AdirH4VV5v1ik2pOOU0WdEdojBBgzTdECBrOQl0ojOc= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.40.3 h1:v+CiUB5RsmyRpGQ5Tddwn3prS1Y+uCIKVAzZ0Wb3Nyk= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.40.3/go.mod h1:HDiBVjDHX2n7UGFgynZLkVGPXvEnurxlEeaxPF/Ql/0= +github.com/aws/aws-sdk-go-v2/service/codecommit v1.25.0 h1:EyXii3hsD7M6mLoZjVbnIo14NI+ig8lopPGYVua/a+M= +github.com/aws/aws-sdk-go-v2/service/codecommit v1.25.0/go.mod h1:VgBrrInGfpFZyyCfVJ+EhV57+I924PItEJ4/yqT34u8= +github.com/aws/aws-sdk-go-v2/service/codedeploy v1.27.3 h1:MSA1lrc/3I1rDQtLKmCe0P3J/jgc39jmN3SZBFVfJxA= +github.com/aws/aws-sdk-go-v2/service/codedeploy v1.27.3/go.mod h1:Zqk3aokH+BfnsAfJl10gz9zWU3TC28e5rR5N/U7yYDk= +github.com/aws/aws-sdk-go-v2/service/datapipeline v1.23.3 h1:kA26fZh30b6kOZZIkxr/1M4f4TnIsXBw3RcHEFuFxcs= +github.com/aws/aws-sdk-go-v2/service/datapipeline v1.23.3/go.mod h1:9Z4AiKwAlu2eXOPFEDfkLV/wTpI9o2FX09M4l6E4VE4= +github.com/aws/aws-sdk-go-v2/service/directoryservice v1.27.3 h1:Ua8NLsRNDm/HSotawG9MjeUEdo88uuTsEJ+EQB99G7c= +github.com/aws/aws-sdk-go-v2/service/directoryservice v1.27.3/go.mod h1:DeGGGnrVVVNQlfMpAqmIiEndGTlDVbUIzNI4MbyyH68= +github.com/aws/aws-sdk-go-v2/service/docdb v1.36.3 h1:6LabOycU59L+JfgCavDzfK1lheqj0wt/Fbta5OpeiUI= +github.com/aws/aws-sdk-go-v2/service/docdb v1.36.3/go.mod h1:cA+GYSfYfLSczv09u72Ger5kQ6JR5UHW3YmHD8c66tA= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4 h1:utG3S4T+X7nONPIpRoi1tVcQdAdJxntiVS2yolPJyXc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4/go.mod h1:q9vzW3Xr1KEXa8n4waHiFt1PrppNDlMymlYP+xpsFbY= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY5x1qYGq53ffxqD9Q= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0 h1:lZoKOTEQUf5Oi9qVaZM/Hb0Z6SHIwwpDjbLFOVgB2t8= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2/service/ecs v1.44.3 h1:JkVDQ9mfUSwMOGWIEmyB74mIznjKnHykJSq3uwusBBs= +github.com/aws/aws-sdk-go-v2/service/ecs v1.44.3/go.mod h1:MsQWy/90Xwn3cy5u+eiiXqC521xIm21wOODIweLo4hs= +github.com/aws/aws-sdk-go-v2/service/efs v1.31.3 h1:vHNTbv0pFB/E19MokZcWAxZIggWgcLlcixNePBe6iZc= +github.com/aws/aws-sdk-go-v2/service/efs v1.31.3/go.mod h1:P1X7sDHKpqZCLac7bRsFF/EN2REOgmeKStQTa14FpEA= +github.com/aws/aws-sdk-go-v2/service/eks v1.47.0 h1:u0VeIQ02COfhmp37ub8zv29bdRtosCYzXoWd+QRebbY= +github.com/aws/aws-sdk-go-v2/service/eks v1.47.0/go.mod h1:awleuSoavuUt32hemzWdSrI47zq7slFtIj8St07EXpE= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.40.5 h1:SIr8tXccDSncRPMK4Fifl9r6sBqHiHSFepSdIFxSfE8= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.40.5/go.mod h1:OcUtpbcNsyMdA/Wv5XenKl8aG3yrqA6HVIOF7ms+Ikc= +github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.26.2 h1:OA2kqnEcSqpnznO4hb4MKDXxeCRuEkADGgnihLwvn4E= +github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk v1.26.2/go.mod h1:N/YWNrjILpIoai7cZ4Uq2KCNvBPf25Y+vIhbm9QpwDc= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.26.3 h1:5B2Dq2zy/hgtEO3wITnOZiyh6e+GyuHTGw6bK/8+L3w= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.26.3/go.mod h1:mgU2kG+D5ybtfGhEuZRW8usYOGrNSgsimRt/hOSI65s= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.34.0 h1:8rDRtPOu3ax8jEctw7G926JQlnFdhZZA4KJzQ+4ks3Q= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.34.0/go.mod h1:L5bVuO4PeXuDuMYZfL3IW69E6mz6PDCYpp6IKDlcLMA= +github.com/aws/aws-sdk-go-v2/service/emr v1.42.2 h1:j3aHjEsxFGCNGOCJjJM6AtPhdvn1pw2i2hGqxLU0qeI= +github.com/aws/aws-sdk-go-v2/service/emr v1.42.2/go.mod h1:rN91rXF7gucnSnArDWbv9xDdZjBEetO4LFoJgGK/Wqw= +github.com/aws/aws-sdk-go-v2/service/fsx v1.47.2 h1:EDZ4UX4c8NJl5Zm2tj1OlbVdNA0wv2xNt55L6g38Va4= +github.com/aws/aws-sdk-go-v2/service/fsx v1.47.2/go.mod h1:OKCxqzNOd8LpwsIgoWIhjTkDONHuv3uLoObiT/fbS4Q= +github.com/aws/aws-sdk-go-v2/service/glue v1.91.0 h1:fJrpIIUxuWeyT22DgPN6GtNWwW28UDYsbm47AUJ4JcI= +github.com/aws/aws-sdk-go-v2/service/glue v1.91.0/go.mod h1:FewbVAhRiTt+/8nKDBFTY68lTmtKlI6QMPKMB6aMboQ= +github.com/aws/aws-sdk-go-v2/service/grafana v1.24.3 h1:riHLAJSqo5zczCyMSo8XDA46X2aDpQvB46F0seKuNEM= +github.com/aws/aws-sdk-go-v2/service/grafana v1.24.3/go.mod h1:2ipW9QX9MlePs99Dy8ohwfdW847hMJG6BU9jvixIpxE= +github.com/aws/aws-sdk-go-v2/service/iam v1.34.3 h1:p4L/tixJ3JUIxCteMGT6oMlqCbEv/EzSZoVwdiib8sU= +github.com/aws/aws-sdk-go-v2/service/iam v1.34.3/go.mod h1:rfOWxxwdecWvSC9C2/8K/foW3Blf+aKnIIPP9kQ2DPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 h1:lhAX5f7KpgwyieXjbDnRTjPEUI0l3emSRyxXj1PXP8w= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16/go.mod h1:AblAlCwvi7Q/SFowvckgN+8M3uFPlopSYeLlbNDArhA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.29.3 h1:ktR7RUdUQ8m9rkgCPRsS7iTJgFp9MXEX0nltrT8bxY4= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.29.3/go.mod h1:hufTMUGSlcBLGgs6leSPbDfY1sM3mrO2qjtVkPMTDhE= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= +github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3 h1:r/y4nQOln25cbjrD8Wmzhhvnvr2ObPjgcPvPdoU9yHs= +github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3/go.mod h1:/4Vaddp+wJc1AA8ViAqwWKAcYykPV+ZplhmLQuq3RbQ= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.3 h1:dy4sbyGy7BS4c0KaPZwg1P5ZP+lW+auTVcPiwrmbn8M= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.3/go.mod h1:EMgqMhof+RuaYvQavxKC0ZWvP7yB4B4NJhP+dbm13u0= +github.com/aws/aws-sdk-go-v2/service/mq v1.25.3 h1:SyRcb9GRPcoNKCuLnpj1qGIr/8stnVIf4DsuRhXIzEA= +github.com/aws/aws-sdk-go-v2/service/mq v1.25.3/go.mod h1:Xu8nT/Yj64z5Gj1ebVB3drPEIBsPNDoFhx2xZDrdGlc= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.46.1 h1:PJPORR5Y+Vdvz+JzR7P5BA/i+lHpGQOhtpuJyvDdK00= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.46.1/go.mod h1:51rUy2+lDiOQVlekScV044he709HMMhCdUDHqSBojgg= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.46.3 h1:vWClqL1dTCuPtWkaGDW7Y6P9ocqHtfFrjlkWYARm1qI= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.46.3/go.mod h1:51rUy2+lDiOQVlekScV044he709HMMhCdUDHqSBojgg= +github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2 h1:+tGF0JH2u4HwneqNFAKFHqENwfpBweKj67+LbwTKpqE= +github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2/go.mod h1:6wxO8s5wMumyNRsOgOgcIvqvF8rIf8Cj7Khhn/bFI0c= +github.com/aws/aws-sdk-go-v2/service/ram v1.27.3 h1:MoQ0up3IiE2fl0+qySx3Lb0swK6G6ESQ4S3w3WfJZ48= +github.com/aws/aws-sdk-go-v2/service/ram v1.27.3/go.mod h1:XymSCzlSx2QjdvU/KdV/+niPQBZRC1A8luPDFz3pjyg= +github.com/aws/aws-sdk-go-v2/service/rds v1.82.0 h1:+1qRsLNukmvIDNBjz5Osqy4dvIBLwpCeMhmrh9evOUw= +github.com/aws/aws-sdk-go-v2/service/rds v1.82.0/go.mod h1:j27FNXhbbHXC3ExFsJkoxq2Y+4dQypf8KFX1IkgwVvM= +github.com/aws/aws-sdk-go-v2/service/redshift v1.46.4 h1:wNBruTRRDfBv2Pz3Mvw6JIJS7ujfTd1ztCG5pIlrfRk= +github.com/aws/aws-sdk-go-v2/service/redshift v1.46.4/go.mod h1:AhuwOvTE4nMwWfJQNZ2khZGV9yXexB2MjNYtCuLQA4s= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3 h1:ByynKMsGZGmpUpnQ99y+lS7VxZrNt3mdagCnHd011Kk= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3/go.mod h1:ZR4h87npHPuVQ2SEeoWMe+CO/HcS9g2iYMLnT5HawW8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.152.0 h1:y3jRrFbGve0omxt5gDStki51bjYJ6gxhtXr7VFagVv4= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.152.0/go.mod h1:lDmK3DHWV6Y6hpzeUAaXq4w+ks6fFYXdkjavIe8STCE= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= +github.com/aws/aws-sdk-go-v2/service/sfn v1.30.0 h1:FIprHGk9sztofQcgyHrIOh4QQo0rO1kjHmksxDrXMtg= +github.com/aws/aws-sdk-go-v2/service/sfn v1.30.0/go.mod h1:+mtHHxsylrf+kjxcbvfnu6jtyTT8Fa9BlqjQk5XJZ80= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.3 h1:iu53lwRKbZOGCVUH09g3J0xU8A+bAGVo09VR9K4d0Yg= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.3/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bishopfox/awsservicemap v1.0.3 h1:0T+mJLwG+vQV9+o3dzwzxhWJWE40VpoCLWtaPBwixYc= diff --git a/internal/aws.go b/internal/aws.go index aae73aab..0811249a 100644 --- a/internal/aws.go +++ b/internal/aws.go @@ -155,7 +155,7 @@ func AWSConfigFileLoader(AWSProfile string, version string, AwsMfaToken string) 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)) + TxtLog.Fatalf("[%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)) } //os.Exit(1) } diff --git a/internal/aws/policy/policy.go b/internal/aws/policy/policy.go index 9a629c1e..1d965585 100644 --- a/internal/aws/policy/policy.go +++ b/internal/aws/policy/policy.go @@ -22,7 +22,7 @@ func ParseJSONPolicy(data []byte) (Policy, error) { return p, nil } -// IsNull returns true iff the Policy is empty +// IsEmpty returns true if the Policy is empty // you cannot do a comparison like this: `p == Policy{}' since we use custom types in the struct` func (p *Policy) IsEmpty() bool { out := true @@ -37,7 +37,7 @@ func (p *Policy) IsEmpty() bool { return out } -// true iff there is at least one statement with principal * and no conditions +// IsPublic true if there is at least one statement with principal * and no conditions func (p *Policy) IsPublic() bool { for _, s := range p.Statement { if s.IsAllow() && s.Principal.IsPublic() && s.Condition.IsEmpty() { @@ -48,7 +48,7 @@ func (p *Policy) IsPublic() bool { return false } -// true iff there is at least one statement with principal * with conditions that do not scope access down to AWS accounts or organizations +// IsConditionallyPublic true if there is at least one statement with principal * with conditions that do not scope access down to AWS accounts or organizations func (p *Policy) IsConditionallyPublic() bool { for _, s := range p.Statement { if s.IsAllow() && s.Principal.IsPublic() && !s.Condition.IsScopedOnAccountOrOrganization() && !s.Condition.IsEmpty() { @@ -99,8 +99,8 @@ func composePattern(stringToTransform string) *regexp.Regexp { return pattern } -// source: https://github.com/nccgroup/PMapper/blob/master/principalmapper/querying/local_policy_simulation.py // MatchesAfterExpansion checks the stringToCheck against stringToCheckAgainst. +// source: https://github.com/nccgroup/PMapper/blob/master/principalmapper/querying/local_policy_simulation.py func MatchesAfterExpansion(stringFromPolicyToCheck, stringToCheckAgainst string) bool { // Transform the stringToCheckAgainst into a regex pattern pattern := composePattern(stringToCheckAgainst) diff --git a/internal/aws/policy/policy_test.go b/internal/aws/policy/policy_test.go index 3870a46c..7ac12187 100644 --- a/internal/aws/policy/policy_test.go +++ b/internal/aws/policy/policy_test.go @@ -222,3 +222,51 @@ func TestDoesPolicyHaveMatchingStatement(t *testing.T) { } } } + +func TestDoesPermissionExpansionMatch(t *testing.T) { + + tests := []struct { + actionToExpand string + actionToCheckAgainst string + want bool + }{ + { + actionToExpand: "sts:AssumeRole", + actionToCheckAgainst: "sts:AssumeRole", + want: true, + }, + { + actionToExpand: "*", + actionToCheckAgainst: "sts:AssumeRole", + want: true, + }, + { + actionToExpand: "sts:Assume*", + actionToCheckAgainst: "sts:AssumeRole", + want: true, + }, + { + actionToExpand: "sts:*", + actionToCheckAgainst: "sts:AssumeRole", + want: true, + }, + { + actionToExpand: "s3:GetObject", + actionToCheckAgainst: "sts:AssumeRole", + want: false, + }, + { + actionToExpand: "s3:*", + actionToCheckAgainst: "sts:AssumeRole", + want: false, + }, + } + + for _, tt := range tests { + actual := MatchesAfterExpansion(tt.actionToCheckAgainst, tt.actionToExpand) + if tt.want != actual { + //fmt.Printf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want) + t.Errorf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want) + } + } +} diff --git a/internal/aws/policy/role-trust-policies.go b/internal/aws/policy/role-trust-policies.go index 40ddcdfe..5773b5c0 100644 --- a/internal/aws/policy/role-trust-policies.go +++ b/internal/aws/policy/role-trust-policies.go @@ -26,7 +26,7 @@ type RoleTrustStatementEntry struct { Action string `json:"Action"` Condition struct { StringEquals struct { - StsExternalID string `json:"sts:ExternalId"` + StsExternalID ListOfPrincipals `json:"sts:ExternalId"` SAMLAud string `json:"SAML:aud"` TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"` TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`