Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions library/general/jfrog-evidence/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
resources:
- template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sJfrogCheckEvidence
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"]
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions library/general/jfrog-evidence/suite.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
kind: Suite
apiVersion: test.gatekeeper.sh/v1alpha1
metadata:
name: replicalimits
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The suite metadata name is incorrect. It should be "jfrogcheckevidence" (or preferably "k8sjfrogcheckevidence" to follow conventions) instead of "replicalimits", which appears to be copy-pasted from another test suite.

Suggested change
name: replicalimits
name: k8sjfrogcheckevidence

Copilot uses AI. Check for mistakes.
tests:
- name: replica-limit
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The test name is incorrect. It should relate to jfrog evidence checking instead of "replica-limit", which appears to be copy-pasted from another test suite.

Suggested change
- name: replica-limit
- name: jfrog-evidence-check

Copilot uses AI. Check for mistakes.
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

116 changes: 116 additions & 0 deletions library/general/jfrog-evidence/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sjfrogcheckevidence
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: 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
rego: |
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[_]
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)

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)
}
35 changes: 35 additions & 0 deletions src/general/jfrog-evidence/constraint.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sjfrogcheckevidence
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: K8sJfrogCheckEvidence
validation:
openAPIV3Schema:
type: object
properties:
checkedPredicateTypes:
type: array
items:
type: string
checkedRegistries:
type: array
items:
type: string
checkedRepositories:
type: array
Comment thread
carmithersh marked this conversation as resolved.
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
{{ file.Read "src/general/jfrog-evidence/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }}
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The constraint template is reading src.rego which contains a YAML "libs:" section (lines 50-63 in src.rego). This will result in invalid Rego code being embedded in the template. The libs section should be removed from src.rego and instead be defined in the constraint template after the rego source, similar to how it's done in other policies. The library code (lines 52-63 in src.rego) should be in a separate file like "lib_filter_images.rego" and referenced in the constraint template's libs section. See example: src/general/imagedigests/constraint.tmpl:39-41.

Suggested change
{{ file.Read "src/general/jfrog-evidence/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }}
{{ file.Read "src/general/jfrog-evidence/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }}
libs:
- rego: |
{{ file.Read "src/general/jfrog-evidence/lib_filter_images.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }}

Copilot uses AI. Check for mistakes.
79 changes: 79 additions & 0 deletions src/general/jfrog-evidence/src.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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[_]
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
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The comment contains a typo. "arreay" should be "array".

Suggested change
# convert arreay input.parameters.checkedPredicateTypes to string
# convert array input.parameters.checkedPredicateTypes to string

Copilot uses AI. Check for mistakes.
types := concat(",", input.parameters.checkedPredicateTypes)
typesArray := [types]

Comment on lines +33 to +36
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

If the checkedPredicateTypes parameter is an empty array, the concat function will produce an empty string, which may not be the intended behavior when communicating with the JFrog provider. Consider adding validation to ensure checkedPredicateTypes is not empty, or handle the empty case explicitly to avoid unexpected behavior from the external provider.

Suggested change
# convert arreay input.parameters.checkedPredicateTypes to string
types := concat(",", input.parameters.checkedPredicateTypes)
typesArray := [types]
# build typesArray from input.parameters.checkedPredicateTypes only when non-empty
typesArray := [types] {
params := object.get(input, "parameters", {})
checked_predicate_types := object.get(params, "checkedPredicateTypes", [])
count(checked_predicate_types) > 0
types := concat(",", checked_predicate_types)
}
default typesArray := []

Copilot uses AI. Check for mistakes.
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)
}
Comment on lines +66 to +79
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The "libs:" section should not be in the src.rego file. Based on codebase conventions, the libs should be defined in the constraint.tmpl file. See examples: src/general/imagedigests/constraint.tmpl:39-41, src/general/containerresources/constraint.tmpl:52-54. The library code should be in a separate file and referenced in the constraint template.

Copilot uses AI. Check for mistakes.
Loading