From 79d6e2f08f71fcc4c51011067694bd7ace40cae9 Mon Sep 17 00:00:00 2001
From: Alain Atemnkeng <aatemnke@vt.edu>
Date: Mon, 15 Apr 2024 07:29:01 +0000
Subject: [PATCH] Update 4 files

- /Chart.yaml
- /templates/constraint_image_admission_controller.yaml
- /rego/image_admission_control/policy.rego
- /rego/image_admission_control/policy_test.rego
---
 Chart.yaml                                    |   2 +-
 rego/image_admission_control/policy.rego      |  64 +++++++++++
 rego/image_admission_control/policy_test.rego | 106 ++++++++++++++++++
 ...constraint_image_admission_controller.yaml |  28 +----
 4 files changed, 173 insertions(+), 27 deletions(-)
 create mode 100644 rego/image_admission_control/policy.rego
 create mode 100644 rego/image_admission_control/policy_test.rego

diff --git a/Chart.yaml b/Chart.yaml
index 1842a25..48fb1ce 100644
--- a/Chart.yaml
+++ b/Chart.yaml
@@ -1,4 +1,4 @@
 apiVersion: v2
 name: constraint-templates
-version: 1.5.5
+version: 1.5.9
 appVersion: 1.0.0
diff --git a/rego/image_admission_control/policy.rego b/rego/image_admission_control/policy.rego
new file mode 100644
index 0000000..5d72fb5
--- /dev/null
+++ b/rego/image_admission_control/policy.rego
@@ -0,0 +1,64 @@
+package k8simageprovenance
+
+# List of exactly allowed repositories
+allowed_repos := {
+    "docker.io",
+    "quay.io",
+    "public.ecr.aws",
+    "gcr.io",
+    "ghcr.io",
+    "harbor.io",
+    "harbor.it.vt.edu",
+    "gitlab.com",
+    "code.vt.edu",
+    "registry-1.docker.io",
+    "602401143452.dkr.ecr.us-east-1.amazonaws.com",
+    "code.vt.edu:5005",
+    "docker.elastic.co",
+    "harbor.platform.it.vt.edu",
+    "registry.gitlab.com",
+    "registry.k8s.io"
+}
+
+# Function to extract hostname from image
+get_hostname(image) = hostname {
+    trace(sprintf("Checking hostname for image: %v", [image]))
+    contains(image, "/")
+    parts := split(image, "/")
+    # If the first part contains a '.', assume it's a hostname
+    contains(parts[0], ".")
+    hostname := parts[0]
+    trace(sprintf("Extracted hostname: %v", [hostname]))
+} else = "docker.io" { # Default to Docker Hub if no hostname is identifiable
+    not contains(image, "/")
+    trace("Defaulting to docker.io (no hostname found)")
+} else = "docker.io" { # Default if no '.' in the first part of the name
+    contains(image, "/")
+    parts := split(image, "/")
+    not contains(parts[0], ".")
+    trace("Defaulting to docker.io (no '.' in first part of hostname)")
+}
+
+# Check if image is from an allowed repository
+is_from_allowed_repo(image) {
+    trace(sprintf("Checking if image %v is from an allowed repo", [image]))
+    hostname := get_hostname(image)
+    allowed := [repo | repo := allowed_repos[_]; hostname == repo]
+    result := count(allowed) > 0
+    trace(sprintf("Image %v allowed: %v", [image, result]))
+    result
+}
+
+# Main violation logic for all container types
+violation[{"msg": msg}] {
+    trace("Evaluating violation")
+    input.review.object.kind == "Pod"
+    # Check initContainers and ephemeralContainers alongside containers
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    container := input.review.object.spec[container_type][_]
+    trace(sprintf("Checking %v image: %v", [container_type, container.image]))
+    not is_from_allowed_repo(container.image)
+    msg := sprintf("Image %v in %v is not from an allowed repository. Please contact the platform team", [container.image, container_type])
+    trace(sprintf("Violation found: %v", [msg]))
+}
diff --git a/rego/image_admission_control/policy_test.rego b/rego/image_admission_control/policy_test.rego
new file mode 100644
index 0000000..340ab48
--- /dev/null
+++ b/rego/image_admission_control/policy_test.rego
@@ -0,0 +1,106 @@
+package k8simageprovenance
+
+# Helper function to create mock input for a Pod with different types of container images
+create_input(image, container_type) = {
+    "review": {
+        "object": {
+            "kind": "Pod",
+            "spec": {
+                container_type: [
+                    {
+                        "image": image
+                    }
+                ]
+            }
+        }
+    }
+}
+
+# Test with an allowed image repository
+test_with_allowed_image_repository {
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    test_input := create_input("docker.io/nginx", container_type)
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+# Test with a disallowed image repository
+test_with_disallowed_image_repository {
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    test_input := create_input("unlistedrepo.com/image", container_type)
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 1
+}
+
+# Test with no repository specified (defaults to Docker Hub)
+test_with_default_docker_hub_image {
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    test_input := create_input("nginx", container_type)
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+# Test with multiple containers, mixed repositories
+test_with_mixed_repositories {
+    test_input := {
+        "review": {
+            "object": {
+                "kind": "Pod",
+                "spec": {
+                    "containers": [
+                        {"image": "docker.io/nginx"},
+                        {"image": "unlistedrepo.com/image"}
+                    ],
+                    "initContainers": [
+                        {"image": "docker.io/initnginx"}
+                    ],
+                    "ephemeralContainers": [
+                        {"image": "quay.io/tempnginx"}
+                    ]
+                }
+            }
+        }
+    }
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 1
+}
+
+# Test with image from Docker Hub's default repository
+test_with_docker_hub_default_repository {
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    test_input := create_input("alpine", container_type)
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+# Test with image from non-standard repository path
+test_with_non_standard_repository_path {
+    container_types := ["containers", "initContainers", "ephemeralContainers"]
+    container_type := container_types[_]
+    test_input := create_input("docker.io/somepath/nginx", container_type)
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+# Separate tests for each container type with allowed images
+test_with_allowed_image_containers {
+    test_input := create_input("docker.io/nginx", "containers")
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+test_with_allowed_image_init_containers {
+    test_input := create_input("docker.io/nginx", "initContainers")
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
+
+test_with_allowed_image_ephemeral_containers {
+    test_input := create_input("docker.io/nginx", "ephemeralContainers")
+    results := data.k8simageprovenance.violation with input as test_input
+    count(results) == 0
+}
diff --git a/templates/constraint_image_admission_controller.yaml b/templates/constraint_image_admission_controller.yaml
index 132815c..7c51c30 100644
--- a/templates/constraint_image_admission_controller.yaml
+++ b/templates/constraint_image_admission_controller.yaml
@@ -10,32 +10,8 @@ spec:
       validation:
         openAPIV3Schema:
           type: object
-          properties:
+          properties: {}
   targets:
     - target: admission.k8s.gatekeeper.sh
       rego: |
-        package k8simageprovenance
-
-        violation[{"msg": msg}] {
-          input.review.object.kind == "Pod"
-          container := input.review.object.spec.containers[_]
-          not startswith(container.image, "docker.io/")
-          not startswith(container.image, "quay.io/")
-          not startswith(container.image, "public.ecr.aws/")
-          not startswith(container.image, "gcr.io/")
-          not startswith(container.image, "ghcr.io/")
-          not startswith(container.image, "harbor.")
-          not startswith(container.image, "gitlab.")
-          msg := sprintf("Image %v is not from an allowed repository. Please contact the platform team", [container.image])
-        }
----
-apiVersion: constraints.gatekeeper.sh/v1beta1
-kind: K8sImageProvenance
-metadata:
-  name: image-provenance-constraint
-spec:
-  enforcementAction: deny
-  match:
-    kinds:
-      - apiGroups: [""]
-        kinds: ["Pod"]
+{{.Files.Get "rego/image_provenance/policy.rego" | indent 8 }}
\ No newline at end of file
-- 
GitLab