From 56e64d8e621dc3bd1c58c37efa6efd3f18cdeb57 Mon Sep 17 00:00:00 2001 From: edops973 Date: Sat, 15 Mar 2025 22:41:45 +0700 Subject: [PATCH 1/7] Merge conflict --- aws/resource-trusts.go | 4 ++-- aws/resource-trusts_test.go | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/aws/resource-trusts.go b/aws/resource-trusts.go index 0ec73774..1577f244 100644 --- a/aws/resource-trusts.go +++ b/aws/resource-trusts.go @@ -78,10 +78,10 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity 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)) // 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, SecretsManager, S3, SNS, SQS, VpcEndpoint\n", + 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, SecretsManager, S3, SNS, "+ + fmt.Printf("[%s][%s] Supported Services: APIGateway, CodeBuild, ECR, EFS, Glue, Lambda, Opensearch, SecretsManager, S3, SNS, "+ "SQS (KMS requires --include-kms feature flag), VpcEndpoint\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub)) } diff --git a/aws/resource-trusts_test.go b/aws/resource-trusts_test.go index 4cf4b7ab..a14f3e47 100644 --- a/aws/resource-trusts_test.go +++ b/aws/resource-trusts_test.go @@ -189,7 +189,7 @@ func TestVpcEndpointResourceTrusts(t *testing.T) { } for _, tc := range testCases { - tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity) + 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") @@ -197,9 +197,6 @@ func TestVpcEndpointResourceTrusts(t *testing.T) { 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") - } } } } From e54ffe8ce7e2dda8910530a47ea25a9a775f07c6 Mon Sep 17 00:00:00 2001 From: edops973 Date: Sat, 15 Mar 2025 22:43:21 +0700 Subject: [PATCH 2/7] Merge conflict --- cli/aws.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/aws.go b/cli/aws.go index 8fac100a..047f2ec6 100644 --- a/cli/aws.go +++ b/cli/aws.go @@ -1630,9 +1630,10 @@ func runResourceTrustsCommandWithProfile(cmd *cobra.Command, args []string, prof return } m := aws.ResourceTrustsModule{ - KMSClient: &KMSClient, - APIGatewayClient: &APIGatewayClient, - EC2Client: &EC2Client, + KMSClient: &KMSClient, + APIGatewayClient: &APIGatewayClient, + EC2Client: &EC2Client, + Caller: *caller, AWSProfileProvided: profile, Goroutines: Goroutines, From 2806f7ea089b4f580b0087117fd892631c7b3c12 Mon Sep 17 00:00:00 2001 From: edops973 Date: Sat, 15 Mar 2025 22:45:10 +0700 Subject: [PATCH 3/7] Merge conflict --- aws/resource-trusts_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws/resource-trusts_test.go b/aws/resource-trusts_test.go index a14f3e47..b7667242 100644 --- a/aws/resource-trusts_test.go +++ b/aws/resource-trusts_test.go @@ -197,6 +197,9 @@ func TestVpcEndpointResourceTrusts(t *testing.T) { 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") + } } } } From 0deb568e412ddc2ef5529407a08f170afcc598cd Mon Sep 17 00:00:00 2001 From: edops973 Date: Sat, 15 Mar 2025 22:47:43 +0700 Subject: [PATCH 4/7] Get open search policies --- aws/endpoints.go | 86 ++++++++++++-------------- aws/resource-trusts.go | 113 +++++++++++++++++++++++++++++++++- aws/sdk/opensearch.go | 9 ++- aws/sdk/opensearch_mocks.go | 60 ++++++++++++++++-- cli/aws.go | 7 ++- go.mod | 2 +- go.sum | 4 +- internal/aws/policy/policy.go | 11 ++-- 8 files changed, 219 insertions(+), 73 deletions(-) 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/resource-trusts.go b/aws/resource-trusts.go index 1577f244..d5a60493 100644 --- a/aws/resource-trusts.go +++ b/aws/resource-trusts.go @@ -22,6 +22,7 @@ type ResourceTrustsModule struct { KMSClient *sdk.KMSClientInterface APIGatewayClient *sdk.APIGatewayClientInterface EC2Client *sdk.AWSEC2ClientInterface + OpenSearchClient *sdk.OpenSearchClientInterface // General configuration data Caller sts.GetCallerIdentityOutput @@ -321,6 +322,18 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap 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) { @@ -356,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()) @@ -366,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() { @@ -1114,6 +1128,99 @@ func (m *ResourceTrustsModule) getVPCEndpointPoliciesPerRegion(r string, wg *syn } } +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-- 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 047f2ec6..cdd552cb 100644 --- a/cli/aws.go +++ b/cli/aws.go @@ -569,7 +569,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) @@ -921,7 +921,6 @@ func runEnvsCommand(cmd *cobra.Command, args []string) { continue } m := aws.EnvsModule{ - Caller: *caller, AWSRegions: internal.GetEnabledRegions(profile, cmd.Root().Version, AWSMFAToken), AWSProfile: profile, @@ -1201,7 +1200,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") @@ -1625,6 +1624,7 @@ func runResourceTrustsCommandWithProfile(cmd *cobra.Command, args []string, prof 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 @@ -1633,6 +1633,7 @@ func runResourceTrustsCommandWithProfile(cmd *cobra.Command, args []string, prof KMSClient: &KMSClient, APIGatewayClient: &APIGatewayClient, EC2Client: &EC2Client, + OpenSearchClient: &OpenSearchClient, Caller: *caller, AWSProfileProvided: profile, diff --git a/go.mod b/go.mod index 47ef6ab4..1ee97261 100644 --- a/go.mod +++ b/go.mod @@ -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.1 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 diff --git a/go.sum b/go.sum index 19580b16..28e2f460 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.3 h1:dy4sbyGy7BS4c0KaPZwg1P 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/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= diff --git a/internal/aws/policy/policy.go b/internal/aws/policy/policy.go index 24f20da2..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) @@ -109,9 +109,6 @@ func MatchesAfterExpansion(stringFromPolicyToCheck, stringToCheckAgainst string) return pattern.MatchString(stringFromPolicyToCheck) } - - - func (p *Policy) DoesPolicyHaveMatchingStatement(effect string, actionToCheck string, resourceToCheck string) bool { for _, statement := range p.Statement { From 0c858e6cd4c3c38519a0bc06a9acb2f9e542bfa6 Mon Sep 17 00:00:00 2001 From: edops973 Date: Thu, 13 Mar 2025 00:50:55 +0700 Subject: [PATCH 5/7] Add missing method in codebuild_mocks.go --- aws/sdk/codebuild_mocks.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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(`{ From 7671e7e717d339271a8ad823b5a3ae4ea6973000 Mon Sep 17 00:00:00 2001 From: edops973 Date: Sat, 15 Mar 2025 22:53:01 +0700 Subject: [PATCH 6/7] Add opensearch unittest --- aws/resource-trusts_test.go | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/aws/resource-trusts_test.go b/aws/resource-trusts_test.go index b7667242..8f331381 100644 --- a/aws/resource-trusts_test.go +++ b/aws/resource-trusts_test.go @@ -203,3 +203,60 @@ func TestVpcEndpointResourceTrusts(t *testing.T) { } } } + +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") + } + } + } +} From d0de59dbe4d30641f2a1a88829df97b99bc7f517 Mon Sep 17 00:00:00 2001 From: Seth Art Date: Mon, 21 Apr 2025 10:19:06 -0400 Subject: [PATCH 7/7] update opensearch mod --- cli/aws.go | 3 ++- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/aws.go b/cli/aws.go index cdd552cb..045fcbbc 100644 --- a/cli/aws.go +++ b/cli/aws.go @@ -4,11 +4,12 @@ import ( "encoding/gob" "errors" "fmt" - "github.com/aws/aws-sdk-go-v2/service/kms" "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" diff --git a/go.mod b/go.mod index 1ee97261..ff686089 100644 --- a/go.mod +++ b/go.mod @@ -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.46.1 + 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 diff --git a/go.sum b/go.sum index 28e2f460..fde97b69 100644 --- a/go.sum +++ b/go.sum @@ -181,6 +181,8 @@ github.com/aws/aws-sdk-go-v2/service/mq v1.25.3 h1:SyRcb9GRPcoNKCuLnpj1qGIr/8stn 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=