diff --git a/CHANGELOG.md b/CHANGELOG.md index 2328c64fd51fe..a4e540bf2cff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp - The service account "central", which is used by the central deployment, will now include `get` and `list` access to the following resources in the namespace where central is deployed to: `pods`, `events`, and `namespaces`. This fixes an issue when generating diagnostic bundles to now correctly include all relevant information within the namespace of central. - ROX-13265: Fix missing rationale and remediation texts for default policy "Deployments should have at least one ingress Network Policy" +- ROX-13500: Previously, deployment YAML check on V1 CronJob workload would cause Central to panic. This is now fixed. ## [3.72.0] diff --git a/central/detection/service/service_impl_test.go b/central/detection/service/service_impl_test.go index ee51c2157ee9f..abb1c8f531d0b 100644 --- a/central/detection/service/service_impl_test.go +++ b/central/detection/service/service_impl_test.go @@ -530,19 +530,44 @@ spec: adminPortalCredentialsRef: name: asecretname ` +const cronYaml = ` +apiVersion: batch/v1 +kind: CronJob +metadata: + name: example + namespace: sst-etcd-backup +spec: + schedule: '@daily' + jobTemplate: + spec: + template: + spec: + containers: + - name: hello + image: busybox + args: + - /bin/sh + - '-c' + - date; echo Hello from the Kubernetes cluster + restartPolicy: OnFailure +` func TestParseList_Success(t *testing.T) { - _, _, err := getObjectsFromYAML(listYAML) - require.NoError(t, err) - - _, _, err = getObjectsFromYAML(openshiftDeploymentConfigYaml) - require.NoError(t, err) - - _, _, err = getObjectsFromYAML(multiYaml) - require.NoError(t, err) - - _, _, err = getObjectsFromYAML(openshiftDeploymentConfigYaml) - require.NoError(t, err) + for name, yaml := range map[string]string{ + "listYaml": listYAML, + "openshiftDeploymentConfigYaml": openshiftDeploymentConfigYaml, + "multiYaml": multiYaml, + "openshiftDeployConfMultiYaml": openshiftDeploymentConfigYaml, + "operatorCRDMultiYaml": operatorCRDMultiYaml, + "operatorCRDYaml": operatorCRDYaml, + "openshiftRouteWithOperatorCRDYaml": openshiftRouteWithOperatorCRDYaml, + "cronYaml": cronYaml, + } { + t.Run(name, func(t *testing.T) { + _, _, err := getObjectsFromYAML(yaml) + require.NoError(t, err) + }) + } } func TestParseList_ConversionToOpenshiftObjects(t *testing.T) { diff --git a/pkg/protoconv/resources/resources.go b/pkg/protoconv/resources/resources.go index 821c14bc62a24..3ce422a136cc6 100644 --- a/pkg/protoconv/resources/resources.go +++ b/pkg/protoconv/resources/resources.go @@ -17,6 +17,7 @@ import ( "github.com/stackrox/rox/pkg/stringutils" "github.com/stackrox/rox/pkg/timestamp" "github.com/stackrox/rox/pkg/utils" + batchV1 "k8s.io/api/batch/v1" batchV1beta1 "k8s.io/api/batch/v1beta1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -122,6 +123,9 @@ func newWrap(meta metav1.Object, kind, clusterID, registryOverride string) *Depl // SpecToPodTemplateSpec turns a top level spec into a podTemplateSpec func SpecToPodTemplateSpec(spec reflect.Value) (v1.PodTemplateSpec, error) { templateInterface := spec.FieldByName("Template") + if !doesFieldExist(templateInterface) { + return v1.PodTemplateSpec{}, errors.Errorf("obj %+v does not have a Template field", spec) + } if templateInterface.Type().Kind() == reflect.Ptr && !templateInterface.IsNil() { templateInterface = templateInterface.Elem() } @@ -237,8 +241,11 @@ func (w *DeploymentWrap) populateFields(obj interface{}) { // types do. So, we need to directly access the Pod's Spec field, // instead of looking for it inside a PodTemplate. podSpec = o.Spec + // batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+. case *batchV1beta1.CronJob: podSpec = o.Spec.JobTemplate.Spec.Template.Spec + case *batchV1.CronJob: + podSpec = o.Spec.JobTemplate.Spec.Template.Spec default: podTemplate, err := SpecToPodTemplateSpec(spec) if err != nil { diff --git a/pkg/protoconv/resources/resources_test.go b/pkg/protoconv/resources/resources_test.go index 286a4c1e89797..4a5c1da479f66 100644 --- a/pkg/protoconv/resources/resources_test.go +++ b/pkg/protoconv/resources/resources_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" appsV1 "k8s.io/api/apps/v1" appsV1beta2 "k8s.io/api/apps/v1beta2" + batchV1 "k8s.io/api/batch/v1" + batchV1beta1 "k8s.io/api/batch/v1beta1" v1 "k8s.io/api/core/v1" extV1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -103,6 +105,42 @@ func TestDaemonSetReplicas(t *testing.T) { assert.Equal(t, int(deploymentWrap.Replicas), 0) } +func TestCronJobPopulateSpec(t *testing.T) { + deploymentWrap := &DeploymentWrap{ + Deployment: &storage.Deployment{ + Type: kubernetes.CronJob, + }, + } + + cronJob1 := &batchV1.CronJob{ + Spec: batchV1.CronJobSpec{ + JobTemplate: batchV1.JobTemplateSpec{ + Spec: batchV1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container1"}}}, + }, + }, + }, + }, + } + deploymentWrap.populateFields(cronJob1) + assert.Equal(t, deploymentWrap.Containers[0].Name, "container1") + + cronJob2 := &batchV1beta1.CronJob{ + Spec: batchV1beta1.CronJobSpec{ + JobTemplate: batchV1beta1.JobTemplateSpec{ + Spec: batchV1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container2"}}}, + }, + }, + }, + }, + } + deploymentWrap.populateFields(cronJob2) + assert.Equal(t, deploymentWrap.Containers[0].Name, "container2") +} + func TestIsTrackedReference(t *testing.T) { cases := []struct { ref metav1.OwnerReference