diff --git a/Makefile b/Makefile index 7ff80489..b4c74b76 100644 --- a/Makefile +++ b/Makefile @@ -32,12 +32,24 @@ release: clean 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=linux GOARCH=386 go build -o ./cloudfox/cloudfox . zip ./cloudfox/cloudfox-linux-386.zip ./cloudfox/cloudfox . @@ -48,12 +60,14 @@ release: clean rm -rf ./cloudfox/cloudfox 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 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/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/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/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/cli/aws.go b/cli/aws.go index d405252f..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" @@ -407,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", @@ -566,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) @@ -633,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) @@ -918,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, @@ -1198,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") @@ -1612,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) @@ -1938,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) @@ -1990,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{ @@ -2028,7 +2043,6 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) { filesystems.PrintFilesystems(AWSOutputDirectory, Verbosity) endpoints := aws.EndpointsModule{ - EKSClient: eksClient, S3Client: s3Client, LambdaClient: lambdaClient, @@ -2044,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) @@ -2059,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) @@ -2085,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, @@ -2102,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, @@ -2132,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, @@ -2146,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, @@ -2212,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) @@ -2253,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, @@ -2270,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, @@ -2441,6 +2434,9 @@ func init() { // 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/globals/utils.go b/globals/utils.go index e9e7d018..384047ae 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.15.0" +const CLOUDFOX_VERSION = "1.16.0" \ No newline at end of file diff --git a/go.mod b/go.mod index e541a31c..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,7 +18,7 @@ require ( github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 github.com/aquasecurity/table v1.8.0 - github.com/aws/aws-sdk-go-v2 v1.30.3 + 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 @@ -55,7 +55,7 @@ require ( 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.39.2 + 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 @@ -70,7 +70,7 @@ require ( 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.20.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 @@ -130,8 +131,8 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // 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 diff --git a/go.sum b/go.sum index 6c78a734..fde97b69 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ 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.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +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= @@ -91,10 +91,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVO 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.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +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= @@ -171,14 +171,18 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/ 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.39.2 h1:px8DLC+DOd2fCLnMm6XlyeLU/9B0dXZWzYXzHSKAzZY= -github.com/aws/aws-sdk-go-v2/service/opensearch v1.39.2/go.mod h1:91AFffUmnw/bumAEE6Sf1yWgW3YdsjexH5c6hePGwSQ= +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= @@ -211,8 +215,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrA 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.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +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 24f20da2..ff6be700 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)