From 4c706c94a7b55a33ff2214130ee0ffabaaf68f0d Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Thu, 29 Jan 2026 12:27:11 +0200 Subject: [PATCH 1/2] Adding JFrog OPA policy allowing evidence checking on images, policy is based on JFog OPA provider Signed-off-by: Carmit Hershman --- .../general/jfrog-evidence/kustomization.yaml | 2 + .../jfrogcheckevidence/constraint.yaml | 14 +++ .../jfrogcheckevidence/example_allowed.yaml | 11 ++ .../example_disallowed.yaml | 11 ++ library/general/jfrog-evidence/suite.yaml | 18 ++++ library/general/jfrog-evidence/template.yaml | 100 ++++++++++++++++++ src/general/jfrog-evidence/constraint.tmpl | 38 +++++++ src/general/jfrog-evidence/src.rego | 63 +++++++++++ 8 files changed, 257 insertions(+) create mode 100644 library/general/jfrog-evidence/kustomization.yaml create mode 100644 library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml create mode 100644 library/general/jfrog-evidence/samples/jfrogcheckevidence/example_allowed.yaml create mode 100644 library/general/jfrog-evidence/samples/jfrogcheckevidence/example_disallowed.yaml create mode 100644 library/general/jfrog-evidence/suite.yaml create mode 100644 library/general/jfrog-evidence/template.yaml create mode 100644 src/general/jfrog-evidence/constraint.tmpl create mode 100644 src/general/jfrog-evidence/src.rego diff --git a/library/general/jfrog-evidence/kustomization.yaml b/library/general/jfrog-evidence/kustomization.yaml new file mode 100644 index 000000000..24dedaea1 --- /dev/null +++ b/library/general/jfrog-evidence/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml \ No newline at end of file diff --git a/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml b/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml new file mode 100644 index 000000000..aeeb09232 --- /dev/null +++ b/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: jfrogcheckevidence +metadata: + name: jfrog-check-evidence +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + checkedRegistries: ["my-jfrog-registry.jfrog.io"] + # add whitelist for repositories, uncomment to enable + # checkedRepositories: ["docker-local"] + checkedPredicateTypes: ["https://slsa.dev/provenance/v1"] \ No newline at end of file diff --git a/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_allowed.yaml b/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_allowed.yaml new file mode 100644 index 000000000..14e557e7d --- /dev/null +++ b/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_allowed.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: your-jfrog-testing-pod +spec: + containers: + - name: testing + image: my-jfrog-registry.jfrog.io/docker-local/your-valid-testing-image:1.0.0 + imagePullPolicy: Always + imagePullSecrets: + - name: jfrog-secret \ No newline at end of file diff --git a/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_disallowed.yaml b/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_disallowed.yaml new file mode 100644 index 000000000..5ae123376 --- /dev/null +++ b/library/general/jfrog-evidence/samples/jfrogcheckevidence/example_disallowed.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: your-jfrog-testing-pod +spec: + containers: + - name: testing + image: my-jfrog-registry.jfrog.io/docker-local/your-invalid-testing-image:1.0.0 + imagePullPolicy: Always + imagePullSecrets: + - name: jfrog-secret \ No newline at end of file diff --git a/library/general/jfrog-evidence/suite.yaml b/library/general/jfrog-evidence/suite.yaml new file mode 100644 index 000000000..d64c26c1c --- /dev/null +++ b/library/general/jfrog-evidence/suite.yaml @@ -0,0 +1,18 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: replicalimits +tests: +- name: replica-limit + template: template.yaml + constraint: samples/jfrogcheckevidence/constraint.yaml + cases: + - name: example-allowed + object: samples/jfrogcheckevidence/example_allowed.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/jfrogcheckevidence/example_disallowed.yaml + assertions: + - violations: yes + diff --git a/library/general/jfrog-evidence/template.yaml b/library/general/jfrog-evidence/template.yaml new file mode 100644 index 000000000..0d3a2145d --- /dev/null +++ b/library/general/jfrog-evidence/template.yaml @@ -0,0 +1,100 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: jfrogcheckevidence + annotations: + metadata.gatekeeper.sh/title: "Check JFrog Evidence" + metadata.gatekeeper.sh/version: 1.0.0 + description: >- + Checks if the images in a pod comply with regulations or corporate policies by validating they have required verified evidence. + This policy is based on the JFrog OPA provider. To deploy JFrog OPA Provider, please visit: https://github.com/jfrog/jfrog-opa-policy +spec: + crd: + spec: + names: + kind: jfrogcheckevidence + validation: + openAPIV3Schema: + type: object + properties: + checkedPredicateTypes: + type: array + items: + type: string + checkedRegistries: + type: array + items: + type: string + checkedRepositories: + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + code: + - engine: Rego + source: + rego: | + package jfrogcheckevidence + import data.lib.filter_images + import future.keywords.in + import future.keywords.contains + # get all images + all_images := [img | img = input.review.object.spec.containers[_].image] + target_images := [img | img = all_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) + ] + # get init images + init_images := [img | img = input.review.object.spec.initContainers[_].image] + target_init_images := [img | img = init_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) + ] + + # append target_images, target_init_images + checked_images := array.concat(target_images, target_init_images) + # convert arreay input.parameters.checkedPredicateTypes to string + types := concat(",", input.parameters.checkedPredicateTypes) + typesArray := [types] + + checked_keys := array.concat(typesArray, checked_images) + violation[{"msg": msg}] { + count(checked_images) > 0 + response := external_data({"provider": "jfrog-evidence-opa-provider", "keys": checked_keys}) + any_issues_found(response) + + msg := sprintf("TARGET IMAGES: %v, RESPONSE: %v", [checked_images, response]) + } + + any_issues_found(response) { + count(response.errors) > 0 + } else { + response.system_error != "" + } else { + response_has_invalid(response) + } + + response_has_invalid(response) { + some item in response.responses + count(item) == 2 + item[1] == "_invalid" + } + + has_check_registry_images_parameter { + input.parameters.checkedRegistries != null + } + libs: + - | + package lib.filter_images + import future.keywords.in + is_checked_registry(img) { + checked_registries := object.get(object.get(input, "parameters", {}), "checkedRegistries", []) + some registry in checked_registries + startswith(img, registry) + } + is_checked_repository(img) { + checked_repositories := object.get(object.get(input, "parameters", {}), "checkedRepositories", ["/"]) + some repository in checked_repositories + contains(img, repository) + } \ No newline at end of file diff --git a/src/general/jfrog-evidence/constraint.tmpl b/src/general/jfrog-evidence/constraint.tmpl new file mode 100644 index 000000000..b0d8a85b2 --- /dev/null +++ b/src/general/jfrog-evidence/constraint.tmpl @@ -0,0 +1,38 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: jfrogcheckevidence + annotations: + metadata.gatekeeper.sh/title: "Check JFrog Evidence" + metadata.gatekeeper.sh/version: 1.0.0 + description: >- + Checks if the images in a pod comply with regulations or corporate policies by validating they have required verified evidence. + This policy is based on the JFrog OPA provider. To deploy JFrog OPA Provider, please visit: https://github.com/jfrog/jfrog-opa-policy +spec: + crd: + spec: + names: + kind: jfrogcheckevidence + validation: + openAPIV3Schema: + type: object + properties: + checkedPredicateTypes: + type: array + items: + type: string + checkedRegistries: + type: array + items: + type: string + checkedRepositories: + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + code: + - engine: Rego + source: + rego: | +{{ file.Read "src/general/jfrog-evidence/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} diff --git a/src/general/jfrog-evidence/src.rego b/src/general/jfrog-evidence/src.rego new file mode 100644 index 000000000..bb3f89f78 --- /dev/null +++ b/src/general/jfrog-evidence/src.rego @@ -0,0 +1,63 @@ +package jfrogcheckevidence +import data.lib.filter_images +import future.keywords.in +import future.keywords.contains +# get all images +all_images := [img | img = input.review.object.spec.containers[_].image] +target_images := [img | img = all_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) +] +# get init images +init_images := [img | img = input.review.object.spec.initContainers[_].image] +target_init_images := [img | img = init_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) +] + +# append target_images, target_init_images +checked_images := array.concat(target_images, target_init_images) +# convert arreay input.parameters.checkedPredicateTypes to string +types := concat(",", input.parameters.checkedPredicateTypes) +typesArray := [types] + +checked_keys := array.concat(typesArray, checked_images) +violation[{"msg": msg}] { + count(checked_images) > 0 + response := external_data({"provider": "jfrog-evidence-opa-provider", "keys": checked_keys}) + any_issues_found(response) + + msg := sprintf("TARGET IMAGES: %v, RESPONSE: %v", [checked_images, response]) +} + +any_issues_found(response) { + count(response.errors) > 0 +} else { + response.system_error != "" +} else { + response_has_invalid(response) +} + +response_has_invalid(response) { + some item in response.responses + count(item) == 2 + item[1] == "_invalid" +} + +has_check_registry_images_parameter { + input.parameters.checkedRegistries != null +} +libs: +- | + package lib.filter_images + import future.keywords.in + is_checked_registry(img) { + checked_registries := object.get(object.get(input, "parameters", {}), "checkedRegistries", []) + some registry in checked_registries + startswith(img, registry) + } + is_checked_repository(img) { + checked_repositories := object.get(object.get(input, "parameters", {}), "checkedRepositories", ["/"]) + some repository in checked_repositories + contains(img, repository) + } \ No newline at end of file From 3931c24101571fd5319263eac8312a61932b59cd Mon Sep 17 00:00:00 2001 From: Carmit Hershman Date: Sun, 1 Feb 2026 16:31:50 +0200 Subject: [PATCH 2/2] JFrog OPA policy AI PR Review fixes Signed-off-by: Carmit Hershman --- .../jfrogcheckevidence/constraint.yaml | 2 +- library/general/jfrog-evidence/template.yaml | 148 ++++++++++-------- src/general/jfrog-evidence/constraint.tmpl | 9 +- src/general/jfrog-evidence/src.rego | 32 +++- 4 files changed, 110 insertions(+), 81 deletions(-) diff --git a/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml b/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml index aeeb09232..5cc52cb1f 100644 --- a/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml +++ b/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml @@ -1,5 +1,5 @@ apiVersion: constraints.gatekeeper.sh/v1beta1 -kind: jfrogcheckevidence +kind: K8sJfrogCheckEvidence metadata: name: jfrog-check-evidence spec: diff --git a/library/general/jfrog-evidence/template.yaml b/library/general/jfrog-evidence/template.yaml index 0d3a2145d..b2fd7d54f 100644 --- a/library/general/jfrog-evidence/template.yaml +++ b/library/general/jfrog-evidence/template.yaml @@ -1,7 +1,7 @@ apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: - name: jfrogcheckevidence + name: k8sjfrogcheckevidence annotations: metadata.gatekeeper.sh/title: "Check JFrog Evidence" metadata.gatekeeper.sh/version: 1.0.0 @@ -12,89 +12,105 @@ spec: crd: spec: names: - kind: jfrogcheckevidence + kind: K8sJfrogCheckEvidence validation: openAPIV3Schema: type: object properties: checkedPredicateTypes: type: array + description: "The predicate types to check for evidence" items: type: string checkedRegistries: type: array + description: "The registries for which to check for evidence" items: type: string checkedRepositories: type: array + description: "Optional, The repositories for which to check for evidence, remove parameter to check all repositories" items: type: string targets: - - target: admission.k8s.gatekeeper.sh - code: - - engine: Rego - source: - rego: | - package jfrogcheckevidence - import data.lib.filter_images - import future.keywords.in - import future.keywords.contains - # get all images - all_images := [img | img = input.review.object.spec.containers[_].image] - target_images := [img | img = all_images[_] - filter_images.is_checked_registry(img) - filter_images.is_checked_repository(img) - ] - # get init images - init_images := [img | img = input.review.object.spec.initContainers[_].image] - target_init_images := [img | img = init_images[_] - filter_images.is_checked_registry(img) - filter_images.is_checked_repository(img) - ] + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sjfrogcheckevidence + import data.lib.filter_images + import future.keywords.in + import future.keywords.contains - # append target_images, target_init_images - checked_images := array.concat(target_images, target_init_images) - # convert arreay input.parameters.checkedPredicateTypes to string - types := concat(",", input.parameters.checkedPredicateTypes) - typesArray := [types] + # fails if input.parameters.checkedRegistries is empty + violation[{"msg": msg}] { + not input.parameters.checkedRegistries + msg := "checkedRegistries parameter is empty" + } - checked_keys := array.concat(typesArray, checked_images) - violation[{"msg": msg}] { - count(checked_images) > 0 - response := external_data({"provider": "jfrog-evidence-opa-provider", "keys": checked_keys}) - any_issues_found(response) - - msg := sprintf("TARGET IMAGES: %v, RESPONSE: %v", [checked_images, response]) - } + # fails if input.parameters.checkedPredicateTypes is empty + violation[{"msg": msg}] { + not input.parameters.checkedPredicateTypes + msg := "checkedPredicateTypes parameter is empty" + } - any_issues_found(response) { - count(response.errors) > 0 - } else { - response.system_error != "" - } else { - response_has_invalid(response) - } + # get all images + all_images := [img | img = input.review.object.spec.containers[_].image] + target_images := [img | img = all_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) + ] + # get init images + init_images := [img | img = input.review.object.spec.initContainers[_].image] + target_init_images := [img | img = init_images[_] + filter_images.is_checked_registry(img) + filter_images.is_checked_repository(img) + ] + + # append target_images, target_init_images + checked_images := array.concat(target_images, target_init_images) + # convert arreay input.parameters.checkedPredicateTypes to string + types := concat(",", input.parameters.checkedPredicateTypes) + typesArray := [types] - response_has_invalid(response) { - some item in response.responses - count(item) == 2 - item[1] == "_invalid" - } - - has_check_registry_images_parameter { - input.parameters.checkedRegistries != null - } - libs: - - | - package lib.filter_images - import future.keywords.in - is_checked_registry(img) { - checked_registries := object.get(object.get(input, "parameters", {}), "checkedRegistries", []) - some registry in checked_registries - startswith(img, registry) - } - is_checked_repository(img) { - checked_repositories := object.get(object.get(input, "parameters", {}), "checkedRepositories", ["/"]) - some repository in checked_repositories - contains(img, repository) - } \ No newline at end of file + checked_keys := array.concat(typesArray, checked_images) + violation[{"msg": msg}] { + count(checked_images) > 0 + response := external_data({"provider": "jfrog-evidence-opa-provider", "keys": checked_keys}) + any_issues_found(response) + + errors_count := count(response.errors) + system_error := response.system_error + invalid_images := [img | some item in response.responses + count(item) == 2 + item[1] == "_invalid" + img := item[0]] + msg := sprintf("TARGET IMAGES: %v, ERROR_COUNT: %v, SYSTEM_ERROR: %v, INVALID_IMAGES: %v", [checked_images, errors_count, system_error, invalid_images]) + } + + any_issues_found(response) { + count(response.errors) > 0 + } else { + response.system_error != "" + } else { + response_has_invalid(response) + } + + response_has_invalid(response) { + some item in response.responses + count(item) == 2 + item[1] == "_invalid" + } + + libs: + - | + package lib.filter_images + import future.keywords.in + is_checked_registry(img) { + checked_registries := object.get(object.get(input, "parameters", {}), "checkedRegistries", []) + some registry in checked_registries + startswith(img, registry) + } + is_checked_repository(img) { + checked_repositories := object.get(object.get(input, "parameters", {}), "checkedRepositories", ["/"]) + some repository in checked_repositories + contains(img, repository) + } \ No newline at end of file diff --git a/src/general/jfrog-evidence/constraint.tmpl b/src/general/jfrog-evidence/constraint.tmpl index b0d8a85b2..425185361 100644 --- a/src/general/jfrog-evidence/constraint.tmpl +++ b/src/general/jfrog-evidence/constraint.tmpl @@ -1,7 +1,7 @@ apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: - name: jfrogcheckevidence + name: k8sjfrogcheckevidence annotations: metadata.gatekeeper.sh/title: "Check JFrog Evidence" metadata.gatekeeper.sh/version: 1.0.0 @@ -12,7 +12,7 @@ spec: crd: spec: names: - kind: jfrogcheckevidence + kind: K8sJfrogCheckEvidence validation: openAPIV3Schema: type: object @@ -31,8 +31,5 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - code: - - engine: Rego - source: - rego: | + rego: | {{ file.Read "src/general/jfrog-evidence/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} diff --git a/src/general/jfrog-evidence/src.rego b/src/general/jfrog-evidence/src.rego index bb3f89f78..38005acd1 100644 --- a/src/general/jfrog-evidence/src.rego +++ b/src/general/jfrog-evidence/src.rego @@ -1,7 +1,20 @@ -package jfrogcheckevidence +package k8sjfrogcheckevidence import data.lib.filter_images import future.keywords.in import future.keywords.contains + +# fails if input.parameters.checkedRegistries is empty +violation[{"msg": msg}] { + not input.parameters.checkedRegistries + msg := "checkedRegistries parameter is empty" +} + +# fails if input.parameters.checkedPredicateTypes is empty +violation[{"msg": msg}] { + not input.parameters.checkedPredicateTypes + msg := "checkedPredicateTypes parameter is empty" +} + # get all images all_images := [img | img = input.review.object.spec.containers[_].image] target_images := [img | img = all_images[_] @@ -15,7 +28,7 @@ target_init_images := [img | img = init_images[_] filter_images.is_checked_repository(img) ] -# append target_images, target_init_images +# append target_images, target_init_images checked_images := array.concat(target_images, target_init_images) # convert arreay input.parameters.checkedPredicateTypes to string types := concat(",", input.parameters.checkedPredicateTypes) @@ -26,8 +39,14 @@ violation[{"msg": msg}] { count(checked_images) > 0 response := external_data({"provider": "jfrog-evidence-opa-provider", "keys": checked_keys}) any_issues_found(response) - - msg := sprintf("TARGET IMAGES: %v, RESPONSE: %v", [checked_images, response]) + + errors_count := count(response.errors) + system_error := response.system_error + invalid_images := [img | some item in response.responses + count(item) == 2 + item[1] == "_invalid" + img := item[0]] + msg := sprintf("TARGET IMAGES: %v, ERROR_COUNT: %v, SYSTEM_ERROR: %v, INVALID_IMAGES: %v", [checked_images, errors_count, system_error, invalid_images]) } any_issues_found(response) { @@ -42,11 +61,8 @@ response_has_invalid(response) { some item in response.responses count(item) == 2 item[1] == "_invalid" -} +} -has_check_registry_images_parameter { - input.parameters.checkedRegistries != null -} libs: - | package lib.filter_images