Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions providers/aws/connection/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
74 changes: 73 additions & 1 deletion providers/aws/connection/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
}
Expand Down Expand Up @@ -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{
Expand Down
8 changes: 8 additions & 0 deletions providers/aws/resources/aws_ecr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)})),
Expand Down
5 changes: 5 additions & 0 deletions providers/aws/resources/aws_ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
44 changes: 2 additions & 42 deletions providers/aws/resources/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,49 +100,21 @@ 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
}

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")
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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{},
Expand All @@ -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)] = ""
Expand Down
30 changes: 0 additions & 30 deletions providers/aws/resources/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
Loading