Skip to content

Commit

Permalink
feat: Support scan latest revision for replicationController
Browse files Browse the repository at this point in the history
In OCP, there is a kind called DeploymentConfig, which manages the replicationController, and as we have revisionHistory for Deployments, similarly there are revision history for Deployment Config as well. Due to which many replication contollers will be present in the cluster. We dont want to scan them as they are not active resource. So as part of this change, I have added following change
- consider latest revision scan option to limit scanning of old replication controller
- ignore system pods created from deploymentConfig
  • Loading branch information
deven0t committed Jun 27, 2023
1 parent 7244d21 commit 4a0c6df
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 1 deletion.
8 changes: 8 additions & 0 deletions deploy/helm/generated/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ rules:
- get
- list
- watch
- apiGroups:
- apps.openshift.io
resources:
- deploymentconfigs
verbs:
- get
- list
- watch
- apiGroups:
- aquasecurity.github.io
resources:
Expand Down
8 changes: 8 additions & 0 deletions deploy/static/trivy-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,14 @@ rules:
- get
- list
- watch
- apiGroups:
- apps.openshift.io
resources:
- deploymentconfigs
verbs:
- get
- list
- watch
- apiGroups:
- aquasecurity.github.io
resources:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/openshift/api v0.0.0-20221013123533-341d389bd4a7 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
Expand Down
94 changes: 94 additions & 0 deletions go.sum

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion pkg/kube/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"
ocpappsv1 "github.com/openshift/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
Expand Down Expand Up @@ -40,6 +41,7 @@ const (
KindReplicaSet Kind = "ReplicaSet"
KindReplicationController Kind = "ReplicationController"
KindDeployment Kind = "Deployment"
KindDeploymentConfig Kind = "DeploymentConfig"
KindStatefulSet Kind = "StatefulSet"
KindDaemonSet Kind = "DaemonSet"
KindCronJob Kind = "CronJob"
Expand All @@ -60,7 +62,10 @@ const (
)

const (
deploymentAnnotation string = "deployment.kubernetes.io/revision"
deploymentAnnotation string = "deployment.kubernetes.io/revision"
deploymentConfigAnnotation string = "openshift.io/deployment-config.latest-version"

DeployerPodForDeploymentAnnotation string = "openshift.io/deployment-config.name"
)
const (
cronJobResource = "cronjobs"
Expand Down Expand Up @@ -645,6 +650,27 @@ func (o *ObjectResolver) IsActiveReplicaSet(ctx context.Context, workloadObj cli
return true, nil
}

// +kubebuilder:rbac:groups=apps.openshift.io,resources=deploymentconfigs,verbs=get;list;watch

func (o *ObjectResolver) IsActiveReplicationController(ctx context.Context, workloadObj client.Object, controller *metav1.OwnerReference) (bool, error) {
if controller != nil && controller.Kind == string(KindDeploymentConfig) {
deploymentConfigObj := &ocpappsv1.DeploymentConfig{}

err := o.Client.Get(ctx, client.ObjectKey{
Namespace: workloadObj.GetNamespace(),
Name: controller.Name,
}, deploymentConfigObj)

if err != nil {
return false, err
}
replicasetRevisionAnnotation := workloadObj.GetAnnotations()
latestRevision := fmt.Sprintf("%d", deploymentConfigObj.Status.LatestVersion)
return replicasetRevisionAnnotation[deploymentConfigAnnotation] == latestRevision, nil
}
return true, nil
}

func (o *ObjectResolver) getPodsMatchingLabels(ctx context.Context, namespace string,
labels map[string]string) ([]corev1.Pod, error) {
podList := &corev1.PodList{}
Expand Down
225 changes: 225 additions & 0 deletions pkg/kube/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/aquasecurity/trivy-operator/pkg/kube"
"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"
ocpappsv1 "github.com/openshift/api/apps/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -1310,6 +1311,230 @@ func TestObjectResolver_IsActiveReplicaSet(t *testing.T) {
}
}

func TestObjectResolver_IsActiveReplicationController(t *testing.T) {
busyboxDeploymentConfig := &ocpappsv1.DeploymentConfig{
TypeMeta: metav1.TypeMeta{
Kind: "DeploymentConfig",
APIVersion: "apps.openshift.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "busybox",
Namespace: "test",
UID: "3767b9a7-c2ad-4a3e-a115-b72edda9084b",
ResourceVersion: "212822",
Generation: 3,
},
Spec: ocpappsv1.DeploymentConfigSpec{
Strategy: ocpappsv1.DeploymentStrategy{
Type: "Recreate",
},
MinReadySeconds: 0,
Triggers: nil,
Replicas: 1,
Test: false,
Paused: false,
Selector: map[string]string{"deploymentconfig": "busybox"},
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"deploymentconfig": "busybox"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{corev1.Container{
Name: "busybox",
Image: "busybox",
Command: []string{"/bin/sh", "-c", "while true ; do date; sleep 1; done;"},
}},
},
},
},
Status: ocpappsv1.DeploymentConfigStatus{
LatestVersion: 3,
ObservedGeneration: 3,
Replicas: 1,
UpdatedReplicas: 1,
AvailableReplicas: 1,
UnavailableReplicas: 0,
ReadyReplicas: 1,
},
}

busyboxReplicaController := &corev1.ReplicationController{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "busybox-3",
Namespace: "test",
UID: "69f2c92a-3032-4b7a-ba60-63be0bffdbe5",
ResourceVersion: "212821",
Generation: 2,
Labels: map[string]string{"openshift.io/deployment-config.name": "busybox"},
Annotations: map[string]string{
"openshift.io/deployer-pod.completed-at": "2023-05-25 06:57:18 +0000 UTC",
"openshift.io/deployer-pod.created-at": "2023-05-25 06:56:39 +0000 UTC",
"openshift.io/deployer-pod.name": "busybox-3-deploy",
"openshift.io/deployment-config.latest-version": "3",
"openshift.io/deployment-config.name": "busybox",
"openshift.io/deployment.phase": "Complete",
"openshift.io/deployment.replicas": "1",
"openshift.io/deployment.status-reason": "config change",
},
OwnerReferences: []metav1.OwnerReference{{
APIVersion: "apps.openshift.io/v1",
Kind: "DeploymentConfig",
Name: "busybox",
UID: "3767b9a7-c2ad-4a3e-a115-b72edda9084b",
Controller: pointer.Bool(true),
BlockOwnerDeletion: pointer.Bool(true)},
},
},
Spec: corev1.ReplicationControllerSpec{
Replicas: pointer.Int32(1),
Selector: map[string]string{
"deployment": "busybox-3",
"deploymentconfig": "busybox",
},
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"deploymentconfig": "busybox"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{corev1.Container{
Name: "busybox",
Image: "busybox",
Command: []string{"/bin/sh", "-c", "while true ; do date; sleep 1; done;"},
}},
},
},
},
Status: corev1.ReplicationControllerStatus{},
}

notActivebusyboxReplicaController := &corev1.ReplicationController{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "busybox-2",
Namespace: "test",
UID: "69f2c92a-3032-4b7a-ba60-63be0bffdbe5",
ResourceVersion: "212821",
Generation: 1,
Labels: map[string]string{"openshift.io/deployment-config.name": "busybox"},
Annotations: map[string]string{
"openshift.io/deployer-pod.completed-at": "2023-05-25 06:57:18 +0000 UTC",
"openshift.io/deployer-pod.created-at": "2023-05-25 06:56:39 +0000 UTC",
"openshift.io/deployer-pod.name": "busybox-3-deploy",
"openshift.io/deployment-config.latest-version": "2",
"openshift.io/deployment-config.name": "busybox",
"openshift.io/deployment.phase": "Complete",
"openshift.io/deployment.replicas": "1",
"openshift.io/deployment.status-reason": "config change",
},
OwnerReferences: []metav1.OwnerReference{{
APIVersion: "apps.openshift.io/v1",
Kind: "DeploymentConfig",
Name: "busybox",
UID: "3767b9a7-c2ad-4a3e-a115-b72edda9084b",
Controller: pointer.Bool(true),
BlockOwnerDeletion: pointer.Bool(true)},
},
},
Spec: corev1.ReplicationControllerSpec{
Replicas: pointer.Int32(1),
Selector: map[string]string{
"deployment": "busybox-2",
"deploymentconfig": "busybox",
},
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"deploymentconfig": "busybox"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{corev1.Container{
Name: "busybox",
Image: "busybox",
Command: []string{"/bin/sh", "-c", "while true ; do date; sleep 1; done;"},
}},
},
},
},
Status: corev1.ReplicationControllerStatus{},
}

standalonebusyboxReplicaController := &corev1.ReplicationController{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "busybox-3",
Namespace: "test",
UID: "69f2c92a-3032-4b7a-ba60-63be0bffdbe5",
ResourceVersion: "212821",
Generation: 2,
Labels: map[string]string{"openshift.io/deployment-config.name": "busybox"},
},
Spec: corev1.ReplicationControllerSpec{
Replicas: pointer.Int32(1),
Selector: map[string]string{
"deployment": "busybox-3",
"deploymentconfig": "busybox",
},
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"deploymentconfig": "busybox"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{corev1.Container{
Name: "busybox",
Image: "busybox",
Command: []string{"/bin/sh", "-c", "while true ; do date; sleep 1; done;"},
}},
},
},
},
Status: corev1.ReplicationControllerStatus{},
}
testClient := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(
busyboxDeploymentConfig,
busyboxReplicaController,
notActivebusyboxReplicaController,
).Build()
tests := []struct {
name string
resource *corev1.ReplicationController
result bool
}{
{
name: "active Replication Controller",
resource: busyboxReplicaController,
result: true,
},
{
name: "Non active replication controller",
resource: notActivebusyboxReplicaController,
result: false,
},
{
name: "Standalone replication controller",
resource: standalonebusyboxReplicaController,
result: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
or := kube.NewObjectResolver(testClient, &kube.CompatibleObjectMapper{})
controller := metav1.GetControllerOf(tc.resource)
isActive, err := or.IsActiveReplicationController(context.TODO(), tc.resource, controller)
require.NoError(t, err)
assert.Equal(t, isActive, tc.result)
})
}
}

func TestIsRoleTypes(t *testing.T) {
testCases := []struct {
kind string
Expand Down
22 changes: 22 additions & 0 deletions pkg/operator/workload/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ func SkipProcessing(ctx context.Context, resource client.Object, or kube.ObjectR
}
return true, err
}
if scanOnlyCurrentRevisions {
controller := metav1.GetControllerOf(resource)
activeReplicationController, err := or.IsActiveReplicationController(ctx, resource, controller)
if err != nil {
return true, fmt.Errorf("failed checking current revision: %w", err)
}
if !activeReplicationController {
log.V(1).Info("Ignoring inactive ReplicationController", "controllerKind", controller.Kind, "controllerName", controller.Name)
err := MarkOldReportForImmediateDeletion(ctx, or, resource.GetNamespace(), resource.GetName())
if err != nil {
return true, fmt.Errorf("failed marking old reports for immediate deletion : %w", err)
}
return true, nil
}
}
case *appsv1.StatefulSet:
_, err := or.GetActivePodsMatchingLabels(ctx, resource.GetNamespace(), r.Spec.Selector.MatchLabels)
if err != nil {
Expand All @@ -81,6 +96,13 @@ func SkipProcessing(ctx context.Context, resource client.Object, or kube.ObjectR
"controllerName", controller.Name)
return true, nil
}
annotations := resource.GetAnnotations()
// Ignore scanning of system pod which is created for deploymentConfig
if value, ok := annotations[kube.DeployerPodForDeploymentAnnotation]; ok {
log.V(1).Info("Ignoring system pod created for deployment config",
"deploymentConfigName", value)
return true, nil
}
case *batchv1.Job:
controller := metav1.GetControllerOf(resource)
if controller != nil && controller.Kind == string(kube.KindCronJob) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/trivyoperator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
containerimage "github.com/google/go-containerregistry/pkg/name"
ocpappsv1 "github.com/openshift/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
Expand All @@ -35,6 +36,7 @@ func NewScheme() *runtime.Scheme {
_ = v1alpha1.AddToScheme(scheme)
_ = coordinationv1.AddToScheme(scheme)
_ = apiextensionsv1.AddToScheme(scheme)
_ = ocpappsv1.Install(scheme)
return scheme
}

Expand Down

0 comments on commit 4a0c6df

Please sign in to comment.