Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add using include/exclude kinds and namespaces #395

Merged
merged 10 commits into from
Jan 14, 2025
4 changes: 4 additions & 0 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ func IsBuiltInWorkload(resource *metav1.OwnerReference) bool {
resource.Kind == string(KindJob))
}

func GetAllResources() []string {
return append(getClusterResources(), getNamespaceResources()...)
}

func getClusterResources() []string {
return []string{
ClusterRoles,
Expand Down
89 changes: 79 additions & 10 deletions pkg/trivyk8s/trivyk8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,86 @@ func (c client) GetIncludeKinds() []string {
return c.includeKinds
}

// initResourceList collects scannable resources.
func (c *client) initResourceList() {
// skip if resources are already created
if len(c.resources) > 0 {
return
}

// collect only included kinds
if len(c.includeKinds) != 0 {
// `includeKinds` are already low cased.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment was improved eed731d

c.resources = c.includeKinds
return
}
// if there are no included and excluded kinds - don't collect resources
if len(c.excludeKinds) == 0 {
return
}
// skip excluded resources
for _, kind := range k8s.GetAllResources() {
if slices.Contains(c.excludeKinds, kind) {
continue
}
c.resources = append(c.resources, kind)
}
}

// getNamespaces collects scannable namespaces
func (c *client) getNamespaces() ([]string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function seems untested, can we add some tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 75fd906

if len(c.includeNamespaces) > 0 {
return c.includeNamespaces, nil
}

result := []string{}
if len(c.excludeNamespaces) == 0 {
return result, nil
}
namespaceGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "namespaces",
}
dClient := c.getDynamicClient(namespaceGVR)
namespaces, err := dClient.List(context.TODO(), v1.ListOptions{})
if err != nil {
return result, err
}
for _, ns := range namespaces.Items {
if slices.Contains(c.excludeNamespaces, ns.GetName()) {
continue
}
result = append(result, ns.GetName())
}
return result, nil
}

// ListArtifacts returns kubernetes scannable artifacs.
func (c *client) ListArtifacts(ctx context.Context) ([]*artifacts.Artifact, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some tests for this too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it.
ListArtifacts consists from several functions, which already tested: c.initResourceList() and c.getNamespaces().

c.ListSpecificArtifacts(ctx) had no tests, and I suggest to do it in a separated PR. wdyt?

c.initResourceList()
namespaces, err := c.getNamespaces()
if err != nil {
return nil, err
}
if len(namespaces) == 0 {
return c.ListSpecificArtifacts(ctx)
}
artifactList := make([]*artifacts.Artifact, 0)

for _, namespace := range namespaces {
c.namespace = namespace
arts, err := c.ListSpecificArtifacts(ctx)
if err != nil {
return nil, err
}
artifactList = append(artifactList, arts...)
}
return artifactList, nil
}

// ListSpecificArtifacts returns kubernetes scannable artifacs for a specific namespace or a cluster
func (c *client) ListSpecificArtifacts(ctx context.Context) ([]*artifacts.Artifact, error) {
artifactList := make([]*artifacts.Artifact, 0)

namespaced := isNamespaced(c.namespace, c.allNamespaces)
Expand Down Expand Up @@ -195,15 +273,6 @@ func (c *client) ListArtifacts(ctx context.Context) ([]*artifacts.Artifact, erro
if c.excludeOwned && c.hasOwner(resource) {
continue
}
// filter resources by kind
if FilterResources(c.includeKinds, c.excludeKinds, resource.GetKind()) {
continue
}

// filter resources by namespace
if FilterResources(c.includeNamespaces, c.excludeNamespaces, resource.GetNamespace()) {
continue
}

lastAppliedResource := resource
if jsonManifest, ok := resource.GetAnnotations()["kubectl.kubernetes.io/last-applied-configuration"]; ok { // required for outdated-api when k8s convert resources
Expand Down Expand Up @@ -470,7 +539,7 @@ func rawResource(resource interface{}) (map[string]interface{}, error) {
func (c *client) getDynamicClient(gvr schema.GroupVersionResource) dynamic.ResourceInterface {
dclient := c.cluster.GetDynamicClient()

// don't use namespace if it is a cluster levle resource,
// don't use namespace if it is a cluster level resource,
// or namespace is empty
if k8s.IsClusterResource(gvr) || len(c.namespace) == 0 {
return dclient.Resource(gvr)
Expand Down
51 changes: 50 additions & 1 deletion pkg/trivyk8s/trivyk8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package trivyk8s
import (
"testing"

"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
"github.com/stretchr/testify/assert"

"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
)

func TestIgnoreNodeByLabel(t *testing.T) {
Expand Down Expand Up @@ -87,3 +89,50 @@ func TestFilterResource(t *testing.T) {
})
}
}

func TestInitResources(t *testing.T) {
tests := []struct {
name string
includeKinds []string
excludeKinds []string
want []string
}{
{
"scan only pods",
[]string{"pods"},
nil,
[]string{k8s.Pods},
},
simar7 marked this conversation as resolved.
Show resolved Hide resolved
{
"skip ClusterRoles, Deployments and Ingresses",
nil,
[]string{"deployments", "ingresses", "clusterroles"},
[]string{
k8s.ClusterRoleBindings,
k8s.Nodes,
k8s.Pods,
k8s.ReplicaSets,
k8s.ReplicationControllers,
k8s.StatefulSets,
k8s.DaemonSets,
k8s.CronJobs,
k8s.Jobs,
k8s.Services,
k8s.ServiceAccounts,
k8s.ConfigMaps,
k8s.Roles,
k8s.RoleBindings,
k8s.NetworkPolicies,
k8s.ResourceQuotas,
k8s.LimitRanges,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &client{excludeKinds: tt.excludeKinds, includeKinds: tt.includeKinds}
c.initResourceList()
assert.Equal(t, tt.want, c.resources)
})
}
}
Loading