diff --git a/providers/aws/connection/filters.go b/providers/aws/connection/filters.go index 91654079b3..b4fcaafbd2 100644 --- a/providers/aws/connection/filters.go +++ b/providers/aws/connection/filters.go @@ -132,12 +132,48 @@ type EcrDiscoveryFilters struct { ExcludeTags []string } +func (f EcrDiscoveryFilters) IsFilteredOutByTags(imageTags []string) bool { + return !f.MatchesIncludeTags(imageTags) || f.MatchesExcludeTags(imageTags) +} + +func (f EcrDiscoveryFilters) MatchesIncludeTags(imageTags []string) bool { + if len(f.Tags) == 0 { + return true + } + + for _, filterTag := range f.Tags { + if slices.Contains(imageTags, filterTag) { + return true + } + } + + return false +} + +// note: if this function returns `true`, it means that the resource should be skipped +func (f EcrDiscoveryFilters) MatchesExcludeTags(imageTags []string) bool { + for _, filterTag := range f.ExcludeTags { + if slices.Contains(imageTags, filterTag) { + return true + } + } + + return false +} + type EcsDiscoveryFilters struct { OnlyRunningContainers bool DiscoverImages bool DiscoverInstances bool } +func (f EcsDiscoveryFilters) MatchesOnlyRunningContainers(containerState string) bool { + if !f.OnlyRunningContainers { + return true + } + return containerState == "RUNNING" +} + // Given a key-value pair that matches a key, return the boolean value of the key. // If the key is not found or the value cannot be parsed as a boolean, return the default value. // Example: key = "ecs:only-running-containers", opts = {"ecs:only-running-containers": "true"} diff --git a/providers/aws/connection/filters_test.go b/providers/aws/connection/filters_test.go index a1496ab33b..98cc243a1f 100644 --- a/providers/aws/connection/filters_test.go +++ b/providers/aws/connection/filters_test.go @@ -94,7 +94,7 @@ func TestMatchesIncludeTags(t *testing.T) { } func TestMatchesExcludeTags(t *testing.T) { - t.Run("no exclude tags matches", func(t *testing.T) { + t.Run("no exclude tags does not match", func(t *testing.T) { filters := GeneralDiscoveryFilters{ ExcludeTags: map[string]string{}, } @@ -154,6 +154,78 @@ func TestMatchesExcludeInstanceIds(t *testing.T) { }) } +func TestEcsMatchesOnlyRunningContainers(t *testing.T) { + t.Run("OnlyRunningContainers is false, any container state matches", func(t *testing.T) { + filters := EcsDiscoveryFilters{ + OnlyRunningContainers: false, + } + require.True(t, filters.MatchesOnlyRunningContainers("RUNNING")) + require.True(t, filters.MatchesOnlyRunningContainers("STOPPED")) + require.True(t, filters.MatchesOnlyRunningContainers("PENDING")) + }) + + t.Run("OnlyRunningContainers is true, only RUNNING container state matches", func(t *testing.T) { + filters := EcsDiscoveryFilters{ + OnlyRunningContainers: true, + } + require.True(t, filters.MatchesOnlyRunningContainers("RUNNING")) + require.False(t, filters.MatchesOnlyRunningContainers("STOPPED")) + require.False(t, filters.MatchesOnlyRunningContainers("PENDING")) + }) +} + +func TestEcrMatchesIncludeTags(t *testing.T) { + t.Run("no include tags matches", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + Tags: []string{}, + } + resourceTags := []string{"tag1", "tag2"} + require.True(t, filters.MatchesIncludeTags(resourceTags)) + }) + + t.Run("include tags do not match", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + Tags: []string{"tag3", "tag4"}, + } + resourceTags := []string{"tag1", "tag2"} + require.False(t, filters.MatchesIncludeTags(resourceTags)) + }) + + t.Run("include tags match", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + Tags: []string{"tag1", "tag3"}, + } + resourceTags := []string{"tag1", "tag2"} + require.True(t, filters.MatchesIncludeTags(resourceTags)) + }) +} + +func TestEcrMatchesExcludeTags(t *testing.T) { + t.Run("no exclude tags does not match", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + ExcludeTags: []string{}, + } + resourceTags := []string{"tag1", "tag2"} + require.False(t, filters.MatchesExcludeTags(resourceTags)) + }) + + t.Run("exclude tags do not match", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + ExcludeTags: []string{"tag3", "tag4"}, + } + resourceTags := []string{"tag1", "tag2"} + require.False(t, filters.MatchesExcludeTags(resourceTags)) + }) + + t.Run("exclude tags match", func(t *testing.T) { + filters := EcrDiscoveryFilters{ + ExcludeTags: []string{"tag1", "tag3"}, + } + resourceTags := []string{"tag1", "tag2"} + require.True(t, filters.MatchesExcludeTags(resourceTags)) + }) +} + func TestDiscoveryFiltersFromOpts(t *testing.T) { t.Run("all opts are mapped to discovery filters correctly", func(t *testing.T) { opts := map[string]string{ diff --git a/providers/aws/resources/aws_ecr.go b/providers/aws/resources/aws_ecr.go index 1b6a2d191c..7acd98c76c 100644 --- a/providers/aws/resources/aws_ecr.go +++ b/providers/aws/resources/aws_ecr.go @@ -163,6 +163,10 @@ func (a *mqlAwsEcrRepository) images() ([]any, error) { return nil, err } for _, image := range res.ImageDetails { + if conn.Filters.Ecr.IsFilteredOutByTags(image.ImageTags) { + log.Debug().Str("repository", name).Strs("tags", image.ImageTags).Msg("skipping ecr public image due to tag filters") + continue + } mqlImage, err := CreateResource(a.MqlRuntime, ResourceAwsEcrImage, map[string]*llx.RawData{ "digest": llx.StringDataPtr(image.ImageDigest), @@ -196,6 +200,10 @@ func (a *mqlAwsEcrRepository) images() ([]any, error) { return nil, err } for _, image := range res.ImageDetails { + if conn.Filters.Ecr.IsFilteredOutByTags(image.ImageTags) { + log.Debug().Str("repository", name).Strs("tags", image.ImageTags).Msg("skipping ecr private image due to tag filters") + continue + } mqlImage, err := CreateResource(a.MqlRuntime, ResourceAwsEcrImage, map[string]*llx.RawData{ "arn": llx.StringData(ecrImageArn(ImageInfo{Region: region, RegistryId: convert.ToValue(image.RegistryId), RepoName: name, Digest: convert.ToValue(image.ImageDigest)})), diff --git a/providers/aws/resources/aws_ecs.go b/providers/aws/resources/aws_ecs.go index 240d0cd7ea..4cad649a1e 100644 --- a/providers/aws/resources/aws_ecs.go +++ b/providers/aws/resources/aws_ecs.go @@ -408,6 +408,11 @@ func (t *mqlAwsEcsTask) containers() ([]any, error) { name = name + "-" + publicIp } + if !conn.Filters.Ecs.MatchesOnlyRunningContainers(convert.ToValue(c.LastStatus)) { + log.Debug().Str("container", name).Str("state", convert.ToValue(c.LastStatus)).Msg("skipping ecs container due to not being in a running state") + continue + } + mqlContainer, err := CreateResource(t.MqlRuntime, "aws.ecs.container", map[string]*llx.RawData{ "arn": llx.StringDataPtr(c.ContainerArn), diff --git a/providers/aws/resources/discovery.go b/providers/aws/resources/discovery.go index a529c59f9e..0b06133ce5 100644 --- a/providers/aws/resources/discovery.go +++ b/providers/aws/resources/discovery.go @@ -100,40 +100,13 @@ var AllAPIResources = []string{ DiscoverySagemakerNotebookInstances, } -func containsInterfaceSlice(sl []any, s string) bool { - for i := range sl { - if ss, ok := sl[i].(string); ok && ss == s { - return true - } - } - return false -} - -func imageMatchesFilters(image *mqlAwsEcrImage, filters connection.EcrDiscoveryFilters) bool { - for _, t := range filters.Tags { - if !containsInterfaceSlice(image.Tags.Data, t) { - return false - } - } - for _, t := range filters.ExcludeTags { - if containsInterfaceSlice(image.Tags.Data, t) { - return false - } - } - return true -} - -func containerMatchesFilters(container *mqlAwsEcsContainer, ecsFilters connection.EcsDiscoveryFilters) bool { - return container.Status.Data == "RUNNING" || !ecsFilters.OnlyRunningContainers -} - func Discover(runtime *plugin.Runtime) (*inventory.Inventory, error) { conn := runtime.Connection.(*connection.AwsConnection) in := &inventory.Inventory{Spec: &inventory.InventorySpec{ Assets: []*inventory.Asset{}, }} - res, err := NewResource(runtime, "aws.account", map[string]*llx.RawData{"id": llx.StringData("aws.account/" + conn.AccountId())}) + res, err := NewResource(runtime, ResourceAwsAccount, map[string]*llx.RawData{"id": llx.StringData("aws.account/" + conn.AccountId())}) if err != nil { return nil, err } @@ -141,8 +114,7 @@ func Discover(runtime *plugin.Runtime) (*inventory.Inventory, error) { awsAccount := res.(*mqlAwsAccount) targets := getDiscoveryTargets(conn.Conf) - for i := range targets { - target := targets[i] + for _, target := range targets { list, err := discover(runtime, awsAccount, target, conn.Filters) if err != nil { log.Error().Err(err).Msg("error during discovery") @@ -257,9 +229,6 @@ func discover(runtime *plugin.Runtime, awsAccount *mqlAwsAccount, target string, for i := range images.Data { a := images.Data[i].(*mqlAwsEcrImage) - if !imageMatchesFilters(a, filters.Ecr) { - continue - } ecrAsset := addConnectionInfoToEcrAsset(a, conn) if len(ecrAsset.Connections) > 0 { assetList = append(assetList, ecrAsset) @@ -282,9 +251,6 @@ func discover(runtime *plugin.Runtime, awsAccount *mqlAwsAccount, target string, for i := range containers.Data { c := containers.Data[i].(*mqlAwsEcsContainer) - if !containerMatchesFilters(c, filters.Ecs) { - continue - } assetList = append(assetList, addConnectionInfoToECSContainerAsset(c, accountId, conn)) } if filters.Ecs.DiscoverInstances { @@ -316,9 +282,6 @@ func discover(runtime *plugin.Runtime, awsAccount *mqlAwsAccount, target string, for i := range containers.Data { c := containers.Data[i].(*mqlAwsEcsContainer) - if !containerMatchesFilters(c, filters.Ecs) { - continue - } assetList = append(assetList, MqlObjectToAsset(accountId, mqlObject{ name: c.ContainerName.Data, labels: map[string]string{}, @@ -344,9 +307,6 @@ func discover(runtime *plugin.Runtime, awsAccount *mqlAwsAccount, target string, for i := range images.Data { a := images.Data[i].(*mqlAwsEcrImage) - if !imageMatchesFilters(a, filters.Ecr) { - continue - } l := make(map[string]string) for i := range a.Tags.Data { l[a.Tags.Data[i].(string)] = "" diff --git a/providers/aws/resources/discovery_test.go b/providers/aws/resources/discovery_test.go index 431b526084..ad3f15d942 100644 --- a/providers/aws/resources/discovery_test.go +++ b/providers/aws/resources/discovery_test.go @@ -10,38 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/stretchr/testify/require" "go.mondoo.com/cnquery/v12/providers-sdk/v1/inventory" - "go.mondoo.com/cnquery/v12/providers-sdk/v1/plugin" - "go.mondoo.com/cnquery/v12/providers/aws/connection" ) -func TestFilters(t *testing.T) { - // image filters - require.True(t, imageMatchesFilters(&mqlAwsEcrImage{ - Tags: plugin.TValue[[]any]{Data: []any{"latest"}}, - }, connection.EcrDiscoveryFilters{})) - - require.True(t, imageMatchesFilters(&mqlAwsEcrImage{ - Tags: plugin.TValue[[]any]{Data: []any{"latest"}}, - }, connection.EcrDiscoveryFilters{Tags: []string{"latest"}})) - - require.False(t, imageMatchesFilters(&mqlAwsEcrImage{ - Tags: plugin.TValue[[]any]{Data: []any{"ubu", "test"}}, - }, connection.EcrDiscoveryFilters{Tags: []string{"latest"}})) - - // container filters - require.True(t, containerMatchesFilters(&mqlAwsEcsContainer{ - Status: plugin.TValue[string]{Data: "RUNNING"}, - }, connection.EcsDiscoveryFilters{})) - - require.True(t, containerMatchesFilters(&mqlAwsEcsContainer{ - Status: plugin.TValue[string]{Data: "RUNNING"}, - }, connection.EcsDiscoveryFilters{OnlyRunningContainers: true})) - - require.False(t, containerMatchesFilters(&mqlAwsEcsContainer{ - Status: plugin.TValue[string]{Data: "STOPPED"}, - }, connection.EcsDiscoveryFilters{OnlyRunningContainers: true})) -} - func TestAddConnInfoToEc2Instances(t *testing.T) { info := instanceInfo{} a := &inventory.Asset{}