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..5cc52cb1f --- /dev/null +++ b/library/general/jfrog-evidence/samples/jfrogcheckevidence/constraint.yaml @@ -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"] \ 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..b2fd7d54f --- /dev/null +++ b/library/general/jfrog-evidence/template.yaml @@ -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) + } \ 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..425185361 --- /dev/null +++ b/src/general/jfrog-evidence/constraint.tmpl @@ -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 + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + 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..38005acd1 --- /dev/null +++ b/src/general/jfrog-evidence/src.rego @@ -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 +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) + } \ No newline at end of file