diff --git a/examples/validation/disallow-anonymous/kcl.mod b/examples/validation/disallow-anonymous/kcl.mod new file mode 100644 index 0000000..fe650fc --- /dev/null +++ b/examples/validation/disallow-anonymous/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "disallow-anonymous" +version = "0.0.1" diff --git a/examples/validation/disallow-anonymous/main.k b/examples/validation/disallow-anonymous/main.k new file mode 100644 index 0000000..5d37d08 --- /dev/null +++ b/examples/validation/disallow-anonymous/main.k @@ -0,0 +1,21 @@ +"""Disallows associating ClusterRole and Role resources +to the system:anonymous user and system:unauthenticated group. +""" + +schema Params: + allowedRoles?: [str] + +params: Params = option("params") + +# Define the validation function +validate = lambda item { + if item.kind in ["ClusterRoleBinding"]: + if item.roleRef.name not in (params.allowedRoles or []): + if any subject in item.subjects { + subject.name in ["system:unauthenticated", "system:anonymous"] + }: + assert False, "Unauthenticated user reference is not allowed in for ${item.kind}: ${item.metadata.name}" + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/disallow-anonymous/suite/bad.yaml b/examples/validation/disallow-anonymous/suite/bad.yaml new file mode 100644 index 0000000..554bf52 --- /dev/null +++ b/examples/validation/disallow-anonymous/suite/bad.yaml @@ -0,0 +1,31 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-anonymous + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallows associating ClusterRole and Role resources + to the system:anonymous user and system:unauthenticated group. +spec: + params: + allowedRoles: + - cluster-role-1 + source: ./examples/validation/disallow-anonymous/main.k +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-2 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated diff --git a/examples/validation/disallow-anonymous/suite/good.yaml b/examples/validation/disallow-anonymous/suite/good.yaml new file mode 100644 index 0000000..8524bf1 --- /dev/null +++ b/examples/validation/disallow-anonymous/suite/good.yaml @@ -0,0 +1,32 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-anonymous + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallows associating ClusterRole and Role resources + to the system:anonymous user and system:unauthenticated group. +spec: + params: + allowedRoles: + - cluster-role-1 + source: ./examples/validation/disallow-anonymous/main.k +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-1 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated + diff --git a/examples/validation/disallowed-image-repos/kcl.mod b/examples/validation/disallowed-image-repos/kcl.mod new file mode 100644 index 0000000..c124964 --- /dev/null +++ b/examples/validation/disallowed-image-repos/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "disallowed-image-repos" +version = "0.0.1" diff --git a/examples/validation/disallowed-image-repos/main.k b/examples/validation/disallowed-image-repos/main.k new file mode 100644 index 0000000..45428e4 --- /dev/null +++ b/examples/validation/disallowed-image-repos/main.k @@ -0,0 +1,23 @@ +"""Disallowed container repositories that begin with a string from the specified list. +""" + +# The list of prefixes a container image is allowed to have. +repos: [str] = option("params").repos or [] + +# Define the validation function +validate = lambda item { + containers = [] + if item.kind == "Pod" and repos: + containers = (item.spec.containers or []) + (item.spec.phemeralContainers or []) + (item.spec.initContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.phemeralContainers or []) + (item.spec.template.spec.initContainers or []) + images: [str] = [c.image for c in containers] + assert all image in images { + all repo in repos { + not image.startswith(repo) + } + } if images and repos, """Use of image is disallowed for ${item.kind}: ${item.metadata.name}, valid repos ${repos}""" + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/disallowed-image-repos/suite/bad.yaml b/examples/validation/disallowed-image-repos/suite/bad.yaml new file mode 100644 index 0000000..73fa609 --- /dev/null +++ b/examples/validation/disallowed-image-repos/suite/bad.yaml @@ -0,0 +1,28 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallowed-image-repos + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallowed container repositories that begin with a string from the specified list. +spec: + params: + repos: + - "k8s.gcr.io/" + source: ./examples/validation/disallowed-image-repos/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: pod +spec: + containers: + - name: kcl + image: k8s.gcr.io/kcl + args: + - "kcl" + ephemeralContainers: + - name: kcl + image: k8s.gcr.io/kcl diff --git a/examples/validation/disallowed-image-repos/suite/good.yaml b/examples/validation/disallowed-image-repos/suite/good.yaml new file mode 100644 index 0000000..c5f07d2 --- /dev/null +++ b/examples/validation/disallowed-image-repos/suite/good.yaml @@ -0,0 +1,28 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallowed-image-repos + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallowed container repositories that begin with a string from the specified list. +spec: + params: + repos: + - "k8s.gcr.io/" + source: ./examples/validation/disallowed-image-repos/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: pod +spec: + containers: + - name: kcl + image: kcllang/kcl + args: + - "kcl" + ephemeralContainers: + - name: kcl + image: kcllang/kcl diff --git a/examples/validation/external-ips/kcl.mod.lock b/examples/validation/external-ips/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/horizontal-pod-auto-scaler/kcl.mod b/examples/validation/horizontal-pod-auto-scaler/kcl.mod new file mode 100644 index 0000000..8fe6c43 --- /dev/null +++ b/examples/validation/horizontal-pod-auto-scaler/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "horizontal-pod-auto-scaler" +version = "0.0.1" diff --git a/examples/validation/horizontal-pod-auto-scaler/main.k b/examples/validation/horizontal-pod-auto-scaler/main.k new file mode 100644 index 0000000..75a4f98 --- /dev/null +++ b/examples/validation/horizontal-pod-auto-scaler/main.k @@ -0,0 +1,35 @@ +"""Disallow the following scenarios when deploying `HorizontalPodAutoscalers` +1. Deployment of HorizontalPodAutoscalers with `.spec.minReplicas` or `.spec.maxReplicas` outside the ranges defined in the constraint +2. Deployment of HorizontalPodAutoscalers where the difference between `.spec.minReplicas` and `.spec.maxReplicas` is less than the configured `minimumReplicaSpread` +3. Deployment of HorizontalPodAutoscalers that do not reference a valid `scaleTargetRef` (e.g. Deployment, ReplicationController, ReplicaSet, StatefulSet). +""" + +schema Params: + minimumReplicaSpread: int = 0 + ranges: [Range] + + check: + minimumReplicaSpread >= 0 + len(ranges) > 0 + +schema Range: + min_replicas: int + max_replicas: int + + check: + 0 <= min_replicas < max_replicas + +params: Params = option("params") + +# Define the validation function +validate = lambda item { + containers = [] + if item.kind == "HorizontalPodAutoscaler": + assert item.spec.maxReplicas - item.spec.minReplicas >= params.minimumReplicaSpread, "The {} <{}> minReplicas {} or maxReplicas {} is not allowed. Allowed ranges: {}".format(item.kind, item.metadata.name, item.spec.minReplicas, item.spec.maxReplicas, params.ranges) + assert all r in params.ranges { + item.spec.minReplicas >= r.min_replicas and item.spec.maxReplicas <= r.max_replicas + }, "The {} <{}> minReplicas {} or maxReplicas {} is not allowed. Allowed ranges: {}".format(item.kind, item.metadata.name, item.spec.minReplicas, item.spec.maxReplicas, params.ranges) + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/horizontal-pod-auto-scaler/suite/bad.yaml b/examples/validation/horizontal-pod-auto-scaler/suite/bad.yaml new file mode 100644 index 0000000..0f662d3 --- /dev/null +++ b/examples/validation/horizontal-pod-auto-scaler/suite/bad.yaml @@ -0,0 +1,40 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: horizontal-pod-auto-scaler + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallow the following scenarios when deploying `HorizontalPodAutoscalers` + 1. Deployment of HorizontalPodAutoscalers with `.spec.minReplicas` or `.spec.maxReplicas` outside the ranges defined in the constraint + 2. Deployment of HorizontalPodAutoscalers where the difference between `.spec.minReplicas` and `.spec.maxReplicas` is less than the configured `minimumReplicaSpread` + 3. Deployment of HorizontalPodAutoscalers that do not reference a valid `scaleTargetRef` (e.g. Deployment, ReplicationController, ReplicaSet, StatefulSet). +spec: + params: + minimumReplicaSpread: 1 + ranges: + - min_replicas: 3 + max_replicas: 6 + source: ./examples/validation/horizontal-pod-auto-scaler/main.k +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: nginx-hpa-disallowed-replicas + namespace: default +spec: + minReplicas: 2 + maxReplicas: 7 + metrics: + - resource: + name: cpu + target: + averageUtilization: 900 + type: Utilization + type: Resource + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: nginx-deployment + diff --git a/examples/validation/horizontal-pod-auto-scaler/suite/good.yaml b/examples/validation/horizontal-pod-auto-scaler/suite/good.yaml new file mode 100644 index 0000000..72dd1b3 --- /dev/null +++ b/examples/validation/horizontal-pod-auto-scaler/suite/good.yaml @@ -0,0 +1,39 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: horizontal-pod-auto-scaler + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Disallow the following scenarios when deploying `HorizontalPodAutoscalers` + 1. Deployment of HorizontalPodAutoscalers with `.spec.minReplicas` or `.spec.maxReplicas` outside the ranges defined in the constraint + 2. Deployment of HorizontalPodAutoscalers where the difference between `.spec.minReplicas` and `.spec.maxReplicas` is less than the configured `minimumReplicaSpread` + 3. Deployment of HorizontalPodAutoscalers that do not reference a valid `scaleTargetRef` (e.g. Deployment, ReplicationController, ReplicaSet, StatefulSet). +spec: + params: + minimumReplicaSpread: 1 + ranges: + - min_replicas: 3 + max_replicas: 6 + source: ./examples/validation/horizontal-pod-auto-scaler/main.k +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: nginx-hpa-allowed + namespace: default +spec: + minReplicas: 3 + maxReplicas: 6 + metrics: + - resource: + name: cpu + target: + averageUtilization: 900 + type: Utilization + type: Resource + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: nginx-deployment diff --git a/examples/validation/validate-container-requests/kcl.mod b/examples/validation/validate-container-requests/kcl.mod new file mode 100644 index 0000000..11af2cc --- /dev/null +++ b/examples/validation/validate-container-requests/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "validate-container-requests" +version = "0.0.1" diff --git a/examples/validation/validate-container-requests/main.k b/examples/validation/validate-container-requests/main.k new file mode 100644 index 0000000..31b7919 --- /dev/null +++ b/examples/validation/validate-container-requests/main.k @@ -0,0 +1,44 @@ +"""Requires containers to have memory and CPU requests set and constrains +requests to be within the specified maximum values. + +https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +""" + +schema Params: + cpu?: str + memory?: str + +params: Params = option("params") + +canonify_cpu = lambda cpu: str -> float { + result = 0 + if cpu: + if cpu[-1] == "m": + result = int(cpu[:-1]) + else: + result = int(cpu) * 1000 + result +} + +# Define the validation function +validate = lambda item { + cpu = "" + memory = "" + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + if containers: + cpu_list: [str] = [c.resources.requests.cpu for c in containers if c?.resources?.requests?.cpu] + memory_list: [str] = [c.resources.requests.memory for c in containers if c?.resources?.requests?.memory] + if params.cpu: + disallowed_cpu_list = [cpu for cpu in cpu_list if canonify_cpu(cpu) > canonify_cpu(params.cpu)] + assert not disallowed_cpu_list, "container cpu limit list '${disallowed_cpu_list}' is higher than the maximum allowed of ${params.cpu}" + if params.memory: + disallowed_memory_list = [memory for memory in memory_list if int(memory) > int(params.memory)] + assert not disallowed_memory_list, "container memory limit list '${disallowed_memory_list}' is higher than the maximum allowed of ${params.memory}" + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/validate-container-requests/suite/bad.yaml b/examples/validation/validate-container-requests/suite/bad.yaml new file mode 100644 index 0000000..29db87b --- /dev/null +++ b/examples/validation/validate-container-requests/suite/bad.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-container-requests + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires containers to have memory and CPU requests set and constrains + requests to be within the specified maximum values. + + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +spec: + params: + cpu: "200m" + memory: "1Gi" + source: ./examples/validation/validate-container-requests/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: allowed +spec: + containers: + - name: nginx + image: nginx + resources: + requests: + cpu: "100m" + memory: "2Gi" diff --git a/examples/validation/validate-container-requests/suite/good.yaml b/examples/validation/validate-container-requests/suite/good.yaml new file mode 100644 index 0000000..6b1809a --- /dev/null +++ b/examples/validation/validate-container-requests/suite/good.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-container-requests + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires containers to have memory and CPU requests set and constrains + requests to be within the specified maximum values. + + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +spec: + params: + cpu: "200m" + memory: "1Gi" + source: ./examples/validation/validate-container-requests/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: allowed +spec: + containers: + - name: nginx + image: nginx + resources: + requests: + cpu: "100m" + memory: "1Gi"