Repository: stackrox/stackrox
Pull request #14773: ROX-28716: Added "Days Since CVE Was Published" policy criterion
Author: login: clickboo
Labels: name: area/ui
name: backport release-4.7
Created: 2025-03-26T20:01:37Z | Merged: 2025-03-27T21:29:12Z

### Description

Adding a new policy criteria "Days since CVE was published".  This allows customers to build a policy to allow teams a grace period from the CVE published date to fix vulnerabilities in images, or get violations if the grace period has elapsed.

@kcarmichael08  This criteria will need to be documented.

### User-facing documentation

- [x] CHANGELOG is updated **OR** update is not needed
- [x] [documentation PR](https://spaces.redhat.com/display/StackRox/Submitting+a+User+Documentation+Pull+Request) is created and is linked above **OR** is not needed

### Testing and quality

<!--
General Availability requirements: https://github.com/stackrox/stackrox/blob/master/PR_GA.md
Feature Flags usage: https://github.com/stackrox/stackrox/blob/master/pkg/features/README.md
-->

- [ ] the change is production ready: the change is GA or otherwise the functionality is gated by a feature flag
- [x] CI results are inspected

#### Automated testing

<!--
If no tests have been contributed, please explain why unless it's obvious,
e.g., the PR is a one-line comment change.
-->

- [x] added unit tests
- [ ] added e2e tests
- [ ] added regression tests
- [ ] added compatibility tests
- [ ] modified existing tests

#### How I validated my change
Manually created a policy on the cluster. 


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6634e648a749..9780eccfb1a98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp
 
 - ROX-13493: Support for scale subresource in the admission controller to enable policy detection and enforcement on admission review requests on the scale subresource.
 - RHPF-98: Log creation of API token. The token creation log message will trigger an administration event.
+- ROX-28716: New policy criterion "Days Since CVE Was Published" to allow creation of a policy that offers a grace period to teams to fix vulnerabilities within the number of days from when the CVE was published in the vulnerability feeds.
 
 ### Removed Features
 
diff --git a/pkg/booleanpolicy/default_policies_test.go b/pkg/booleanpolicy/default_policies_test.go
index 690e4c3d0ca12..41c34be98a224 100644
--- a/pkg/booleanpolicy/default_policies_test.go
+++ b/pkg/booleanpolicy/default_policies_test.go
@@ -209,6 +209,55 @@ func (suite *DefaultPoliciesTestSuite) TestFixableAndImageFirstOccurenceCriteria
 
 }
 
+func (suite *DefaultPoliciesTestSuite) TestDaysSinceCVEPublishedCriteria() {
+	heartbleedDep := &storage.Deployment{
+		Id: "HEARTBLEEDDEPID",
+		Containers: []*storage.Container{
+			{
+				Name:            "nginx",
+				SecurityContext: &storage.SecurityContext{Privileged: true},
+				Image:           &storage.ContainerImage{Id: "HEARTBLEEDDEPSHA"},
+			},
+		},
+	}
+
+	ts := time.Now().AddDate(0, 0, -5)
+	protoTs, err := protocompat.ConvertTimeToTimestampOrError(ts)
+	require.NoError(suite.T(), err)
+
+	suite.addDepAndImages(heartbleedDep, &storage.Image{
+		Id:   "HEARTBLEEDDEPSHA",
+		Name: &storage.ImageName{FullName: "heartbleed"},
+		Scan: &storage.ImageScan{
+			Components: []*storage.EmbeddedImageScanComponent{
+				{Name: "heartbleed", Version: "1.2", Vulns: []*storage.EmbeddedVulnerability{
+					{Cve: "CVE-2014-0160", Link: "https://heartbleed", Cvss: 6, SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{FixedBy: "v1.2"},
+						PublishedOn: protoTs},
+				}},
+			},
+		},
+	})
+
+	fixablePolicyGroup := &storage.PolicyGroup{
+		FieldName: fieldnames.Fixable,
+		Values:    []*storage.PolicyValue{{Value: "true"}},
+	}
+	cvePublishedGroup := &storage.PolicyGroup{
+		FieldName: fieldnames.DaysSincePublished,
+		Values:    []*storage.PolicyValue{{Value: "2"}},
+	}
+
+	policy := policyWithGroups(storage.EventSource_NOT_APPLICABLE, fixablePolicyGroup, cvePublishedGroup)
+
+	deployment := suite.deployments["HEARTBLEEDDEPID"]
+	depMatcher, err := BuildDeploymentMatcher(policy)
+	require.NoError(suite.T(), err)
+	violations, err := depMatcher.MatchDeployment(nil, enhancedDeployment(deployment, suite.getImagesForDeployment(deployment)))
+	require.Len(suite.T(), violations.AlertViolations, 1)
+	require.NoError(suite.T(), err)
+
+}
+
 func (suite *DefaultPoliciesTestSuite) TestNoDuplicatePolicyIDs() {
 	ids := set.NewStringSet()
 	for _, p := range suite.defaultPolicies {
diff --git a/pkg/booleanpolicy/field_metadata.go b/pkg/booleanpolicy/field_metadata.go
index 992c105103ec3..29785cbd63530 100644
--- a/pkg/booleanpolicy/field_metadata.go
+++ b/pkg/booleanpolicy/field_metadata.go
@@ -282,6 +282,15 @@ func initializeFieldMetadata() FieldMetadata {
 		[]storage.EventSource{storage.EventSource_NOT_APPLICABLE},
 		[]RuntimeFieldType{}, negationForbidden, operatorsForbidden)
 
+	f.registerFieldMetadata(fieldnames.DaysSincePublished,
+		querybuilders.ForDays(search.CVEPublishedOn),
+		violationmessages.VulnContextFields,
+		func(*validateConfiguration) *regexp.Regexp {
+			return integerValueRegex
+		},
+		[]storage.EventSource{storage.EventSource_NOT_APPLICABLE},
+		[]RuntimeFieldType{}, negationForbidden, operatorsForbidden)
+
 	f.registerFieldMetadata(fieldnames.DisallowedAnnotation,
 		querybuilders.ForFieldLabelMap(search.DeploymentAnnotation, query.MapShouldContain),
 		nil,
diff --git a/pkg/booleanpolicy/fieldnames/list.go b/pkg/booleanpolicy/fieldnames/list.go
index 7aaead2b0404c..45e3795945409 100644
--- a/pkg/booleanpolicy/fieldnames/list.go
+++ b/pkg/booleanpolicy/fieldnames/list.go
@@ -17,6 +17,7 @@ var (
 	ContainerMemLimit              = newFieldName("Container Memory Limit")
 	ContainerMemRequest            = newFieldName("Container Memory Request")
 	ContainerName                  = newFieldName("Container Name")
+	DaysSincePublished             = newFieldName("Days Since CVE Was Published")
 	DaysSinceImageFirstDiscovered  = newFieldName("Days Since CVE Was First Discovered In Image")
 	DaysSinceSystemFirstDiscovered = newFieldName("Days Since CVE Was First Discovered In System")
 	DisallowedAnnotation           = newFieldName("Disallowed Annotation")
diff --git a/pkg/booleanpolicy/violationmessages/printer.go b/pkg/booleanpolicy/violationmessages/printer.go
index f2cfe02a15f48..e475f3e4a18a4 100644
--- a/pkg/booleanpolicy/violationmessages/printer.go
+++ b/pkg/booleanpolicy/violationmessages/printer.go
@@ -30,6 +30,7 @@ var (
 		fieldnames.ContainerMemLimit:              {{required: set.NewStringSet(search.MemoryLimit.String()), printerFuncKey: printer.ResourceKey}},
 		fieldnames.ContainerMemRequest:            {{required: set.NewStringSet(search.MemoryRequest.String()), printerFuncKey: printer.ResourceKey}},
 		fieldnames.ContainerName:                  {{required: set.NewStringSet(search.ContainerName.String()), printerFuncKey: printer.ContainerNameKey}},
+		fieldnames.DaysSincePublished:             {{required: set.NewStringSet(search.CVE.String(), search.CVEPublishedOn.String()), printerFuncKey: printer.CveKey}},
 		fieldnames.DaysSinceImageFirstDiscovered:  {{required: set.NewStringSet(search.CVE.String(), search.FirstImageOccurrenceTimestamp.String()), printerFuncKey: printer.CveKey}},
 		fieldnames.DaysSinceSystemFirstDiscovered: {{required: set.NewStringSet(search.CVE.String(), search.FirstSystemOccurrenceTimestamp.String()), printerFuncKey: printer.CveKey}},
 		fieldnames.DisallowedAnnotation:           {{required: set.NewStringSet(search.DeploymentAnnotation.String()), printerFuncKey: printer.DisallowedAnnotationKey}},
diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx
index 77b01a42d1824..17ec7cc649bf2 100644
--- a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx
+++ b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx
@@ -581,6 +581,16 @@ export const policyCriteriaDescriptors: Descriptor[] = [
         canBooleanLogic: true,
         lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'],
     },
+    {
+        label: 'Days Since CVE Was Published',
+        name: 'Days Since CVE Was Published',
+        shortName: 'Days since CVE was published',
+        category: policyCriteriaCategories.IMAGE_CONTENTS,
+        type: 'number',
+        placeholder: '0',
+        canBooleanLogic: false,
+        lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'],
+    },
     {
         label: 'Days Since CVE Was First Discovered In Image',
         name: 'Days Since CVE Was First Discovered In Image',


Reviews:
user: login: alwayshooin
body: makes sense to me given you've considered the other fields on the policy criteria descriptor (see line 150 in `policyCriteriaDescriptors.tsx`)! 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T00:19:01Z
user: login: kcarmichael08
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:10:35Z
user: login: kcarmichael08
body: I had one question but lgtm from a doc perspective.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:11:40Z
user: login: charmik-redhat
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T15:38:57Z
user: login: dashrews78
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T15:52:07Z
user: login: clickboo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:57:08Z
user: login: dashrews78
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:15:31Z

Review comments:
diff_hunk: @@ -581,6 +581,16 @@ export const policyCriteriaDescriptors: Descriptor[] = [
         canBooleanLogic: true,
         lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'],
     },
+    {
+        label: 'Days Since CVE Was Published',
path: ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx
user: login: kcarmichael08
body: Not sure, but if this is the UI, should this be sentence case to match the others? (e.g., "Days since CVE was published")
<img width="391" alt="Screenshot 2025-03-27 at 10 09 44 AM" src="https://github.com/user-attachments/assets/85b76070-80e9-46a1-b871-fb87d9ef2aeb" />

I'm not sure if what shows up on the screen is this field (label) or the shortname field below, which *is* in sentence case, so please ignore if n/a.
created_at: 2025-03-27T14:10:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,6 +13,7 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp
 
 - ROX-13493: Support for scale subresource in the admission controller to enable policy detection and enforcement on admission review requests on the scale subresource.
 - RHPF-98: Log creation of API token. The token creation log message will trigger an administration event.
+- ROX-xxxxx: New policy criterion "Days Since CVE Was Published" to allow creation of a policy that offers a grace period to teams to fix vulnerabilities within the number of days from when the CVE was published in the vulnerability feeds.
path: CHANGELOG.md
user: login: dashrews78
body: Don't forget to put in the ticket.
created_at: 2025-03-27T15:52:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -581,6 +581,16 @@ export const policyCriteriaDescriptors: Descriptor[] = [
         canBooleanLogic: true,
         lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'],
     },
+    {
+        label: 'Days Since CVE Was Published',
path: ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx
user: login: clickboo
body: I changed this to "Days Since CVE was published"
created_at: 2025-03-27T16:57:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: rhacs-bot
created_at: 2025-03-26T20:27:14Z
body: [//]: # (meta:type=build)

Images are ready for the commit at 356d81a.

To use with deploy scripts, first `export MAIN_IMAGE_TAG=4.8.x-337-g356d81a2f4`.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-26T22:49:40Z
body: ## [Codecov](https://app.codecov.io/gh/stackrox/stackrox/pull/14773?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 48.89%. Comparing base [(`2cae7fa`)](https://app.codecov.io/gh/stackrox/stackrox/commit/2cae7fa625f7e858bb434193e315151bd4160083?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) to head [(`356d81a`)](https://app.codecov.io/gh/stackrox/stackrox/commit/356d81a2f435d4bec1ad5cd1eb9c15ad07ece7c2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master   #14773      +/-   ##
==========================================
- Coverage   48.89%   48.89%   -0.01%     
==========================================
  Files        2547     2547              
  Lines      186992   186998       +6     
==========================================
  Hits        91431    91431              
- Misses      88319    88324       +5     
- Partials     7242     7243       +1     
```

| [Flag](https://app.codecov.io/gh/stackrox/stackrox/pull/14773/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) | Coverage Δ | |
|---|---|---|
| [go-unit-tests](https://app.codecov.io/gh/stackrox/stackrox/pull/14773/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox) | `48.89% <100.00%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/stackrox/stackrox/pull/14773?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stackrox).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: vjwilson
created_at: 2025-03-27T12:45:07Z
body: @clickboo Does this field already exist in the CVE data we get from a scan, and if so, how are we handling it when the field is empty?
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-03-27T18:00:42Z
body: @clickboo: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/ocp-4-12-ui-e2e-tests | 8cccaa5202ac767c0ba3588fd00b40bfbe7abc51 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-12-ui-e2e-tests/1905014849377669120) | false | `/test ocp-4-12-ui-e2e-tests`
ci/prow/ocp-4-12-qa-e2e-tests | 8cccaa5202ac767c0ba3588fd00b40bfbe7abc51 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-12-qa-e2e-tests/1905014841823727616) | false | `/test ocp-4-12-qa-e2e-tests`
ci/prow/ocp-4-17-qa-e2e-tests | 8cccaa5202ac767c0ba3588fd00b40bfbe7abc51 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-17-qa-e2e-tests/1905014858600943616) | false | `/test ocp-4-17-qa-e2e-tests`
ci/prow/ocp-4-12-nongroovy-e2e-tests | 8cccaa5202ac767c0ba3588fd00b40bfbe7abc51 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-12-nongroovy-e2e-tests/1905014840427024384) | false | `/test ocp-4-12-nongroovy-e2e-tests`
ci/prow/ocp-4-17-nongroovy-e2e-tests | 19513b39669c592659398a7ff520566c725f8ed8 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-17-nongroovy-e2e-tests/1905303371133227008) | false | `/test ocp-4-17-nongroovy-e2e-tests`
ci/prow/ocp-4-17-ui-e2e-tests | 19513b39669c592659398a7ff520566c725f8ed8 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/stackrox_stackrox/14773/pull-ci-stackrox-stackrox-master-ocp-4-17-ui-e2e-tests/1905303380360695808) | false | `/test ocp-4-17-ui-e2e-tests`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=stackrox&repo=stackrox&pr=14773). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:clickboo).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-03T19:25:25Z
body: Please merge the changes to branch `release-4.7`.
author_association: CONTRIBUTOR
user: login: davdhacs
created_at: 2025-04-03T19:57:25Z
body: > Please merge the changes to branch `release-4.7`.

the cut-rc automation failed to cherry-pick this.

I'll use a backport pr and fix any merge conflicts there.
author_association: CONTRIBUTOR
user: login: rhacs-bot
created_at: 2025-04-03T19:58:06Z
body: The backport to `release-4.7` failed:
```
The process '/usr/bin/git' failed with exit code 1
```
To backport manually, run these commands in your terminal:
```bash
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-release-4.7 release-4.7
# Navigate to the new working tree
cd .worktrees/backport-release-4.7
# Create a new branch
git switch --create backport-14773-to-release-4.7
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 74f25f788ceb34216b1908a490b9dbe4a442d25a
# Push it to GitHub
git push --set-upstream origin backport-14773-to-release-4.7
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-release-4.7
```
Then, create a pull request where the `base` branch is `release-4.7` and the `compare`/`head` branch is `backport-14773-to-release-4.7`.
author_association: CONTRIBUTOR