diff --git a/examples/abstraction/web-service/kcl.mod.lock b/examples/abstraction/web-service/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/abstraction/whoami-yaml/kcl.mod.lock b/examples/abstraction/whoami-yaml/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/abstraction/whoami/kcl.mod.lock b/examples/abstraction/whoami/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-capabilities/kcl.mod.lock b/examples/mutation/add-capabilities/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-certificates-volume/kcl.mod.lock b/examples/mutation/add-certificates-volume/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-default-resources/kcl.mod.lock b/examples/mutation/add-default-resources/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-default-securitycontext/kcl.mod.lock b/examples/mutation/add-default-securitycontext/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-emptydir-sizelimit/kcl.mod.lock b/examples/mutation/add-emptydir-sizelimit/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-image-as-env-var/kcl.mod.lock b/examples/mutation/add-image-as-env-var/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/add-runtime-class-name/kcl.mod.lock b/examples/mutation/add-runtime-class-name/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/conditionally-add-annotations/kcl.mod.lock b/examples/mutation/conditionally-add-annotations/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/conditionally-add-labels/kcl.mod.lock b/examples/mutation/conditionally-add-labels/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/insert-pod-antiaffinity/kcl.mod.lock b/examples/mutation/insert-pod-antiaffinity/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/pod-security-policy/selinux/kcl.mod.lock b/examples/mutation/pod-security-policy/selinux/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/readonly-root-fs/kcl.mod b/examples/mutation/readonly-root-fs/kcl.mod new file mode 100644 index 0000000..f241e73 --- /dev/null +++ b/examples/mutation/readonly-root-fs/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "readonly-root-fs" +version = "0.0.1" diff --git a/examples/mutation/readonly-root-fs/main.k b/examples/mutation/readonly-root-fs/main.k new file mode 100644 index 0000000..6574dc5 --- /dev/null +++ b/examples/mutation/readonly-root-fs/main.k @@ -0,0 +1,9 @@ +schema Params: + +params: Params = option("params") +items = [item | { + if item.kind == "Pod": + spec.containers: [{ + securityContext.readOnlyRootFilesystem = True + } for container in item.spec.containers] +} for item in option("items") or []] diff --git a/examples/mutation/readonly-root-fs/suite/good.yaml b/examples/mutation/readonly-root-fs/suite/good.yaml new file mode 100644 index 0000000..a96ae10 --- /dev/null +++ b/examples/mutation/readonly-root-fs/suite/good.yaml @@ -0,0 +1,22 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: readonly-root-fs + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: mutation + documentation: >- + Set read only root file system for containers +spec: + source: ./examples/mutation/readonly-root-fs/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/examples/mutation/set-annotations/kcl.mod.lock b/examples/mutation/set-annotations/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/set-labels/kcl.mod.lock b/examples/mutation/set-labels/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/mutation/set-replicas/kcl.mod.lock b/examples/mutation/set-replicas/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/allowed-image-repos/kcl.mod b/examples/validation/allowed-image-repos/kcl.mod new file mode 100644 index 0000000..1ab4801 --- /dev/null +++ b/examples/validation/allowed-image-repos/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "allowed-image-repos" +version = "0.0.1" diff --git a/examples/validation/allowed-image-repos/main.k b/examples/validation/allowed-image-repos/main.k new file mode 100644 index 0000000..dcbfcaf --- /dev/null +++ b/examples/validation/allowed-image-repos/main.k @@ -0,0 +1,25 @@ +"""Requires container images to begin with a string from the specified list. + +Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/allowedrepos/constraint.tmpl +""" + +# 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 { + 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/allowed-image-repos/suite/bad.yaml b/examples/validation/allowed-image-repos/suite/bad.yaml new file mode 100644 index 0000000..71bdc3d --- /dev/null +++ b/examples/validation/allowed-image-repos/suite/bad.yaml @@ -0,0 +1,38 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: allowed-image-repos + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires container images to begin with a string from the specified list. + + Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/allowedrepos/constraint.tmpl +spec: + params: + repos: + - nginx + source: ./examples/validation/allowed-image-repos/main.k +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy + labels: + app: kcl +spec: + replicas: 3 + selector: + matchLabels: + app: kcl + template: + metadata: + labels: + app: kcl + spec: + containers: + - name: kcl + image: kcllang/kcl + ports: + - containerPort: 80 diff --git a/examples/validation/allowed-image-repos/suite/good.yaml b/examples/validation/allowed-image-repos/suite/good.yaml new file mode 100644 index 0000000..fbce9a1 --- /dev/null +++ b/examples/validation/allowed-image-repos/suite/good.yaml @@ -0,0 +1,38 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: allowed-image-repos + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires container images to begin with a string from the specified list. + + Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/allowedrepos/constraint.tmpl +spec: + params: + repos: + - nginx + source: ./examples/validation/allowed-image-repos/main.k +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/examples/validation/deny-all/kcl.mod b/examples/validation/deny-all/kcl.mod new file mode 100644 index 0000000..857e6ee --- /dev/null +++ b/examples/validation/deny-all/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "deny-all" +version = "0.0.1" diff --git a/examples/validation/deny-all/main.k b/examples/validation/deny-all/main.k new file mode 100644 index 0000000..c9b0426 --- /dev/null +++ b/examples/validation/deny-all/main.k @@ -0,0 +1 @@ +assert False if option("items"), "Deny all objects and the input object list is ${option('items')}" diff --git a/examples/validation/deny-all/suite/bad.yaml b/examples/validation/deny-all/suite/bad.yaml new file mode 100644 index 0000000..3a1ce0f --- /dev/null +++ b/examples/validation/deny-all/suite/bad.yaml @@ -0,0 +1,26 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: deny-all + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Deny all objects if there are input objects. +spec: + source: ./examples/validation/deny-all/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 + livenessProbe: + exec: + command: + - ps diff --git a/examples/validation/deny-all/suite/good.yaml b/examples/validation/deny-all/suite/good.yaml new file mode 100644 index 0000000..f2c7be4 --- /dev/null +++ b/examples/validation/deny-all/suite/good.yaml @@ -0,0 +1,11 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: deny-all + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Deny all objects if there are input objects. +spec: + source: ./examples/validation/deny-all/main.k diff --git a/examples/validation/deny-commands-in-exec-probe/kcl.mod.lock b/examples/validation/deny-commands-in-exec-probe/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/deny-endpoint-edit-default-role/kcl.mod b/examples/validation/deny-endpoint-edit-default-role/kcl.mod new file mode 100644 index 0000000..b08e0c2 --- /dev/null +++ b/examples/validation/deny-endpoint-edit-default-role/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "deny-endpoint-edit-default-role" +version = "0.0.1" diff --git a/examples/validation/deny-endpoint-edit-default-role/main.k b/examples/validation/deny-endpoint-edit-default-role/main.k new file mode 100644 index 0000000..8a05efc --- /dev/null +++ b/examples/validation/deny-endpoint-edit-default-role/main.k @@ -0,0 +1,32 @@ +"""Many Kubernetes installations by default have a system:aggregate-to-edit +ClusterRole which does not properly restrict access to editing Endpoints. +This ConstraintTemplate forbids the system:aggregate-to-edit ClusterRole +from granting permission to create/patch/update Endpoints. + +ClusterRole/system:aggregate-to-edit should not allow +Endpoint edit permissions due to CVE-2021-25740, Endpoint & EndpointSlice +permissions allow cross-Namespace forwarding, +https://github.com/kubernetes/kubernetes/issues/103675 + +Reference: https://github.com/open-policy-agent/gatekeeper-library/blob/master/library/general/block-endpoint-edit-default-role/template.yaml +""" + +# Define the validation function +validate = lambda item { + if (item?.metadata?.name or "") == "system:aggregate-to-edit": + rules = item.rules or [] + # Check all rules if rules exist + assert all rule in rules { + # Check evrey rule verbs if rule resources exist "endpoints" + all res in rule.resources { + # Check every verbs + all verb in rule.verbs { + verb not in ["create", "patch", "update"] + } if rule.verbs and res == "endpoints" + } if rule.resources + } if rules, "ClusterRole system:aggregate-to-edit should not allow endpoint edit permissions. For k8s version < 1.22, the Cluster Role should be annotated with rbac.authorization.kubernetes.io/autoupdate=false to prevent autoreconciliation back to default permissions for this role." + item +} + +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/deny-endpoint-edit-default-role/suite/bad.yaml b/examples/validation/deny-endpoint-edit-default-role/suite/bad.yaml new file mode 100644 index 0000000..891ad47 --- /dev/null +++ b/examples/validation/deny-endpoint-edit-default-role/suite/bad.yaml @@ -0,0 +1,116 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: deny-endpoint-edit-default-role + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Many Kubernetes installations by default have a system:aggregate-to-edit + ClusterRole which does not properly restrict access to editing Endpoints. + This ConstraintTemplate forbids the system:aggregate-to-edit ClusterRole + from granting permission to create/patch/update Endpoints. + + ClusterRole/system:aggregate-to-edit should not allow + Endpoint edit permissions due to CVE-2021-25740, Endpoint & EndpointSlice + permissions allow cross-Namespace forwarding, + https://github.com/kubernetes/kubernetes/issues/103675 + + Reference: https://github.com/open-policy-agent/gatekeeper-library/blob/master/library/general/block-endpoint-edit-default-role/template.yaml +spec: + source: ./examples/validation/deny-endpoint-edit-default-role/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 + livenessProbe: + exec: + command: + - ps +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: system:aggregate-to-edit +rules: +- apiGroups: + - "" + resources: + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + - secrets + - services/proxy + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - impersonate +- apiGroups: + - "" + resources: + - pods + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - "" + resources: + - configmaps + - persistentvolumeclaims + - replicationcontrollers + - replicationcontrollers/scale + - secrets + - serviceaccounts + - services + - services/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - apps + resources: + - daemonsets + - deployments + - deployments/rollback + - deployments/scale + - endpoints + - replicasets + - replicasets/scale + - statefulsets + - statefulsets/scale + verbs: + - create + - delete + - deletecollection + - patch + - update diff --git a/examples/validation/deny-endpoint-edit-default-role/suite/good.yaml b/examples/validation/deny-endpoint-edit-default-role/suite/good.yaml new file mode 100644 index 0000000..0ba6d30 --- /dev/null +++ b/examples/validation/deny-endpoint-edit-default-role/suite/good.yaml @@ -0,0 +1,160 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: deny-endpoint-edit-default-role + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Many Kubernetes installations by default have a system:aggregate-to-edit + ClusterRole which does not properly restrict access to editing Endpoints. + This ConstraintTemplate forbids the system:aggregate-to-edit ClusterRole + from granting permission to create/patch/update Endpoints. + + ClusterRole/system:aggregate-to-edit should not allow + Endpoint edit permissions due to CVE-2021-25740, Endpoint & EndpointSlice + permissions allow cross-Namespace forwarding, + https://github.com/kubernetes/kubernetes/issues/103675 + + Reference: https://github.com/open-policy-agent/gatekeeper-library/blob/master/library/general/block-endpoint-edit-default-role/template.yaml +spec: + source: ./examples/validation/deny-endpoint-edit-default-role/main.k +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: system:aggregate-to-edit +rules: +- apiGroups: + - "" + resources: + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + - secrets + - services/proxy + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - impersonate +- apiGroups: + - "" + resources: + - pods + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - "" + resources: + - configmaps + - persistentvolumeclaims + - replicationcontrollers + - replicationcontrollers/scale + - secrets + - serviceaccounts + - services + - services/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - apps + resources: + - daemonsets + - deployments + - deployments/rollback + - deployments/scale + - replicasets + - replicasets/scale + - statefulsets + - statefulsets/scale + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - extensions + resources: + - daemonsets + - deployments + - deployments/rollback + - deployments/scale + - ingresses + - networkpolicies + - replicasets + - replicasets/scale + - replicationcontrollers/scale + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - delete + - deletecollection + - patch + - update +- apiGroups: + - networking.k8s.io + resources: + - ingresses + - networkpolicies + verbs: + - create + - delete + - deletecollection + - patch + - update 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/disallow-host-ports/kcl.mod.lock b/examples/validation/disallow-host-ports/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/disallow-ingress-wildcard/kcl.mod b/examples/validation/disallow-ingress-wildcard/kcl.mod new file mode 100644 index 0000000..0513328 --- /dev/null +++ b/examples/validation/disallow-ingress-wildcard/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "disallow-ingress-wildcard" +version = "0.0.1" diff --git a/examples/validation/disallow-ingress-wildcard/main.k b/examples/validation/disallow-ingress-wildcard/main.k new file mode 100644 index 0000000..c217611 --- /dev/null +++ b/examples/validation/disallow-ingress-wildcard/main.k @@ -0,0 +1,15 @@ +"""A validation that prevents the creation of Ingress resources with a blank or wildcard (*) hostname since +that would enable them to intercept traffic for other services in the cluster, even if they don't have +access to those services. +""" + +# Define the validation function +validate = lambda item { + if item.kind == "Ingress": + # Find the wildcard hostname list. + hostnames = [r.host for r in item.spec.rules if r.host and "*" in r.host] + assert not hostnames, "Hostnames '{}' is not allowed since it counts as a wildcard, which can be used to intercept traffic from other applications.".format(hostnames) + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/disallow-ingress-wildcard/suite/bad.yaml b/examples/validation/disallow-ingress-wildcard/suite/bad.yaml new file mode 100644 index 0000000..9e6d1a7 --- /dev/null +++ b/examples/validation/disallow-ingress-wildcard/suite/bad.yaml @@ -0,0 +1,40 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-ingress-wildcard + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Ingress resources with a blank or wildcard (*) hostname since + that would enable them to intercept traffic for other services in the cluster, even if they don't have + access to those services. +spec: + source: ./examples/validation/disallow-ingress-wildcard/main.k +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wildcard-ingress +spec: + rules: + - host: '*.example.com' + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: example + port: + number: 80 + - host: 'valid.example.com' + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: example + port: + number: 80 diff --git a/examples/validation/disallow-ingress-wildcard/suite/good.yaml b/examples/validation/disallow-ingress-wildcard/suite/good.yaml new file mode 100644 index 0000000..8c78ef3 --- /dev/null +++ b/examples/validation/disallow-ingress-wildcard/suite/good.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-ingress-wildcard + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Ingress resources with a blank or wildcard (*) hostname since + that would enable them to intercept traffic for other services in the cluster, even if they don't have + access to those services. +spec: + source: ./examples/validation/disallow-ingress-wildcard/main.k +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: non-wildcard-ingress +spec: + rules: + - host: 'myservice.example.com' + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: example + port: + number: 80 diff --git a/examples/validation/disallow-privileged-containers/kcl.mod.lock b/examples/validation/disallow-privileged-containers/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/disallow-svc-lb/kcl.mod b/examples/validation/disallow-svc-lb/kcl.mod new file mode 100644 index 0000000..3fbca88 --- /dev/null +++ b/examples/validation/disallow-svc-lb/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "disallow-svc-lb" +version = "0.0.1" diff --git a/examples/validation/disallow-svc-lb/main.k b/examples/validation/disallow-svc-lb/main.k new file mode 100644 index 0000000..604e6c1 --- /dev/null +++ b/examples/validation/disallow-svc-lb/main.k @@ -0,0 +1,12 @@ +"""A validation that prevents the creation of Service resources of type `LoadBalancer` +""" + +# Define the validation function +validate = lambda item { + if item.kind == "Service": + svc_ty = item.spec.type or "" + assert svc_ty != "LoadBalancer", """A validation that prevents the creation of Service resources of type `LoadBalancer`, for ${item.kind}: ${item.metadata.name}""" + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/disallow-svc-lb/suite/bad.yaml b/examples/validation/disallow-svc-lb/suite/bad.yaml new file mode 100644 index 0000000..e897b9d --- /dev/null +++ b/examples/validation/disallow-svc-lb/suite/bad.yaml @@ -0,0 +1,24 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-svc-lb + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Service resources of type `LoadBalancer` +spec: + source: ./examples/validation/disallow-svc-lb/main.k +--- +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - name: http + protocol: TCP + port: 80 + type: LoadBalancer diff --git a/examples/validation/disallow-svc-lb/suite/good.yaml b/examples/validation/disallow-svc-lb/suite/good.yaml new file mode 100644 index 0000000..d21d2f3 --- /dev/null +++ b/examples/validation/disallow-svc-lb/suite/good.yaml @@ -0,0 +1,23 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-svc-lb + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Service resources of type `LoadBalancer` +spec: + source: ./examples/validation/disallow-svc-lb/main.k +--- +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - name: http + protocol: TCP + port: 80 diff --git a/examples/validation/disallow-svc-node-port/kcl.mod b/examples/validation/disallow-svc-node-port/kcl.mod new file mode 100644 index 0000000..7f2f2b5 --- /dev/null +++ b/examples/validation/disallow-svc-node-port/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "disallow-svc-node-port" +version = "0.0.1" diff --git a/examples/validation/disallow-svc-node-port/main.k b/examples/validation/disallow-svc-node-port/main.k new file mode 100644 index 0000000..9e22f1e --- /dev/null +++ b/examples/validation/disallow-svc-node-port/main.k @@ -0,0 +1,12 @@ +"""A validation that prevents the creation of Service resources of type `NodePort` +""" + +# Define the validation function +validate = lambda item { + if item.kind == "Service": + svc_ty = item.spec.type or "" + assert svc_ty != "NodePort", """A validation that prevents the creation of Service resources of type `NodePort`, for ${item.kind}: ${item.metadata.name}""" + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/disallow-svc-node-port/suite/bad.yaml b/examples/validation/disallow-svc-node-port/suite/bad.yaml new file mode 100644 index 0000000..84611b3 --- /dev/null +++ b/examples/validation/disallow-svc-node-port/suite/bad.yaml @@ -0,0 +1,24 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-svc-node-port + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Service resources of type `NodePort` +spec: + source: ./examples/validation/disallow-svc-node-port/main.k +--- +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - name: http + protocol: TCP + port: 80 + type: NodePort diff --git a/examples/validation/disallow-svc-node-port/suite/good.yaml b/examples/validation/disallow-svc-node-port/suite/good.yaml new file mode 100644 index 0000000..7087ee6 --- /dev/null +++ b/examples/validation/disallow-svc-node-port/suite/good.yaml @@ -0,0 +1,23 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: disallow-svc-lb + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + A validation that prevents the creation of Service resources of type `NodePort` +spec: + source: ./examples/validation/disallow-svc-node-port/main.k +--- +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - name: http + protocol: TCP + port: 80 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/https-only/kcl.mod.lock b/examples/validation/https-only/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/nginx-ingress/restrict-ingress-annotations/kcl.mod.lock b/examples/validation/nginx-ingress/restrict-ingress-annotations/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/nginx-ingress/restrict-ingress-paths/kcl.mod.lock b/examples/validation/nginx-ingress/restrict-ingress-paths/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/psp-allow-privilege-escalation/kcl.mod b/examples/validation/psp-allow-privilege-escalation/kcl.mod new file mode 100644 index 0000000..b0947e0 --- /dev/null +++ b/examples/validation/psp-allow-privilege-escalation/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "psp-allow-privilege-escalation" +version = "0.0.1" diff --git a/examples/validation/psp-allow-privilege-escalation/main.k b/examples/validation/psp-allow-privilege-escalation/main.k new file mode 100644 index 0000000..4de5bc4 --- /dev/null +++ b/examples/validation/psp-allow-privilege-escalation/main.k @@ -0,0 +1,39 @@ +"""Controls restricting escalation to root privileges. Corresponds to the +`allowPrivilegeEscalation` field in a PodSecurityPolicy. For more +information, see +https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +""" +import regex + +schema Params: + exemptImages?: [str] + +params: Params = option("params") + +is_exempt = lambda image: str -> bool { + result = False + if params.exemptImages: + result = any exempt_image in params.exemptImages { + (image.startswith(exempt_image.removesuffix("*")) if exempt_image.endswith("*") else exempt_image == image) + } + result +} + +# Define the validation function +validate = lambda item { + cpu = "" + memory = "" + containers = [] + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + (item.spec.template.spec.ephemeralContainers or []) + if containers: + containers = [c for c in containers if not is_exempt(c.image)] + container_list_disallow_privilege_escalation = [c.name for c in containers if c.securityContext?.allowPrivilegeEscalation == True] + assert len(container_list_disallow_privilege_escalation) == 0, "Privilege escalation containers ${container_list_disallow_privilege_escalation} are not allowed for ${item.kind} <${item.metadata.name}>" + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/psp-allow-privilege-escalation/suite/bad.yaml b/examples/validation/psp-allow-privilege-escalation/suite/bad.yaml new file mode 100644 index 0000000..0b0b001 --- /dev/null +++ b/examples/validation/psp-allow-privilege-escalation/suite/bad.yaml @@ -0,0 +1,27 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-allow-privilege-escalation + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls restricting escalation to root privileges. Corresponds to the + `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +spec: + source: ./examples/validation/psp-allow-privilege-escalation/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-disallowed + labels: + app: nginx-privilege-escalation +spec: + ephemeralContainers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: true diff --git a/examples/validation/psp-allow-privilege-escalation/suite/good.yaml b/examples/validation/psp-allow-privilege-escalation/suite/good.yaml new file mode 100644 index 0000000..adf7a67 --- /dev/null +++ b/examples/validation/psp-allow-privilege-escalation/suite/good.yaml @@ -0,0 +1,27 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-allow-privilege-escalation + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls restricting escalation to root privileges. Corresponds to the + `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +spec: + source: ./examples/validation/psp-allow-privilege-escalation/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-allowed + labels: + app: nginx-privilege-escalation +spec: + containers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: false diff --git a/examples/validation/psp-app-armor/kcl.mod b/examples/validation/psp-app-armor/kcl.mod new file mode 100644 index 0000000..cdb07b8 --- /dev/null +++ b/examples/validation/psp-app-armor/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "psp-app-armor" +version = "0.0.1" diff --git a/examples/validation/psp-app-armor/main.k b/examples/validation/psp-app-armor/main.k new file mode 100644 index 0000000..4e8424d --- /dev/null +++ b/examples/validation/psp-app-armor/main.k @@ -0,0 +1,47 @@ +"""Configures an allow-list of AppArmor profiles for use by containers. +This corresponds to specific annotations applied to a PodSecurityPolicy. +For information on AppArmor, see +https://kubernetes.io/docs/tutorials/clusters/apparmor/ +""" +import regex + +schema Params: + exemptImages?: [str] + allowedProfiles?: [str] + +params: Params = option("params") + +# Judge a image in a container config is exempt +is_exempt = lambda image: str -> bool { + result = False + if params.exemptImages: + result = any exempt_image in params.exemptImages { + (image.startswith(exempt_image.removesuffix("*")) if exempt_image.endswith("*") else exempt_image == image) + } + result +} + +# Get Containers from the input resource item. +get_containers = lambda item { + containers = [] + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + (item.spec.template.spec.ephemeralContainers or []) + containers = [c for c in containers if not is_exempt(c.image)] +} + +# Define the validation function +validate = lambda item { + containers = get_containers(item) + if containers: + container_list_disallow = [ + c.name for c in containers\ + if (item.metadata.annotations?["container.apparmor.security.beta.kubernetes.io/${c.name}"] or "runtime/default") not in (params.allowedProfiles or []) + ] + assert len(container_list_disallow) == 0, "AppArmor profile is not allowed, pod: {}, containers: {}. Allowed profiles: {}".format(item.metadata.name, container_list_disallow, params.allowedProfiles) + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/psp-app-armor/suite/bad.yaml b/examples/validation/psp-app-armor/suite/bad.yaml new file mode 100644 index 0000000..a8400c8 --- /dev/null +++ b/examples/validation/psp-app-armor/suite/bad.yaml @@ -0,0 +1,31 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-app-armor + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Configures an allow-list of AppArmor profiles for use by containers. + This corresponds to specific annotations applied to a PodSecurityPolicy. + For information on AppArmor, see + https://kubernetes.io/docs/tutorials/clusters/apparmor/ +spec: + params: + allowedProfiles: + - runtime/default + source: ./examples/validation/psp-app-armor/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + annotations: + # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: unconfined + labels: + app: nginx-apparmor +spec: + ephemeralContainers: + - name: nginx + image: nginx diff --git a/examples/validation/psp-app-armor/suite/good.yaml b/examples/validation/psp-app-armor/suite/good.yaml new file mode 100644 index 0000000..12fb0e7 --- /dev/null +++ b/examples/validation/psp-app-armor/suite/good.yaml @@ -0,0 +1,31 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-app-armor + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Configures an allow-list of AppArmor profiles for use by containers. + This corresponds to specific annotations applied to a PodSecurityPolicy. + For information on AppArmor, see + https://kubernetes.io/docs/tutorials/clusters/apparmor/ +spec: + params: + allowedProfiles: + - runtime/default + source: ./examples/validation/psp-app-armor/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + annotations: + # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: runtime/default + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx diff --git a/examples/validation/psp-capabilities/kcl.mod b/examples/validation/psp-capabilities/kcl.mod new file mode 100644 index 0000000..d812bab --- /dev/null +++ b/examples/validation/psp-capabilities/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "psp-capabilities" +version = "0.0.1" diff --git a/examples/validation/psp-capabilities/main.k b/examples/validation/psp-capabilities/main.k new file mode 100644 index 0000000..071392a --- /dev/null +++ b/examples/validation/psp-capabilities/main.k @@ -0,0 +1,61 @@ +"""Controls Linux capabilities on containers. Corresponds to the +`allowedCapabilities` and `requiredDropCapabilities` fields in a +PodSecurityPolicy. For more information, see +https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +""" +import regex + +schema Params: + exemptImages?: [str] + allowedCapabilities?: [str] + requiredDropCapabilities?: [str] + +params: Params = option("params") + +# Judge a image in a container config is exempt +is_exempt = lambda image: str -> bool { + result = False + if params.exemptImages: + result = any exempt_image in params.exemptImages { + (image.startswith(exempt_image.removesuffix("*")) if exempt_image.endswith("*") else exempt_image == image) + } + result +} + +# Get Containers from the input resource item. +get_containers = lambda item { + containers = [] + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + (item.spec.template.spec.ephemeralContainers or []) + containers = [c for c in containers if not is_exempt(c.image)] +} + +missing_drop_capabilities = lambda container -> bool { + drop_list = container.securityContext?.capabilities?.drop or [] + all drop in drop_list { + drop in (params.requiredDropCapabilities or []) + } +} + +has_disallowed_capabilities = lambda container -> bool { + add_list = container.securityContext?.capabilities?.add or [] + any add in add_list { + add not in (params.allowedCapabilities or []) + } +} + +# Define the validation function +validate = lambda item { + containers = get_containers(item) + if containers: + container_missing_drop_capabilities = [c.name for c in containers if missing_drop_capabilities(c)] + assert len(container_missing_drop_capabilities) == 0, "containers <{}> is not dropping all required capabilities. Container must drop all of {} or \"ALL\"".format(container_missing_drop_capabilities, params.requiredDropCapabilities) + container_has_disallowed_capabilities = [c.name for c in containers if has_disallowed_capabilities(c)] + assert len(container_has_disallowed_capabilities) == 0, "containers <{}> has a disallowed capability. Allowed capabilities are {}".format(container_has_disallowed_capabilities, params.allowedCapabilities) + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] \ No newline at end of file diff --git a/examples/validation/psp-capabilities/suite/bad.yaml b/examples/validation/psp-capabilities/suite/bad.yaml new file mode 100644 index 0000000..7b295c6 --- /dev/null +++ b/examples/validation/psp-capabilities/suite/bad.yaml @@ -0,0 +1,35 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-capabilities + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls Linux capabilities on containers. Corresponds to the + `allowedCapabilities` and `requiredDropCapabilities` fields in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +spec: + params: + allowedCapabilities: ["something"] + requiredDropCapabilities: ["must_drop"] + source: ./examples/validation/psp-capabilities/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: disallowed + labels: + owner: me.agilebank.demo +spec: + containers: + - name: kcl + image: kcllang/kcl + securityContext: + capabilities: + add: ["disallowedcapability"] + resources: + limits: + cpu: "100m" + memory: "30Mi" diff --git a/examples/validation/psp-capabilities/suite/good.yaml b/examples/validation/psp-capabilities/suite/good.yaml new file mode 100644 index 0000000..4bebd68 --- /dev/null +++ b/examples/validation/psp-capabilities/suite/good.yaml @@ -0,0 +1,36 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-capabilities + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls Linux capabilities on containers. Corresponds to the + `allowedCapabilities` and `requiredDropCapabilities` fields in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +spec: + params: + allowedCapabilities: ["something"] + requiredDropCapabilities: ["must_drop"] + source: ./examples/validation/psp-capabilities/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: allowed + labels: + owner: me.agilebank.demo +spec: + containers: + - name: kcl + image: kcllang/kcl + securityContext: + capabilities: + add: ["something"] + drop: ["must_drop", "another_one"] + resources: + limits: + cpu: "100m" + memory: "30Mi" diff --git a/examples/validation/psp-flexvolume-drivers/kcl.mod b/examples/validation/psp-flexvolume-drivers/kcl.mod new file mode 100644 index 0000000..5f96629 --- /dev/null +++ b/examples/validation/psp-flexvolume-drivers/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "psp-flexvolume-drivers" +version = "0.0.1" diff --git a/examples/validation/psp-flexvolume-drivers/main.k b/examples/validation/psp-flexvolume-drivers/main.k new file mode 100644 index 0000000..ccc7744 --- /dev/null +++ b/examples/validation/psp-flexvolume-drivers/main.k @@ -0,0 +1,27 @@ +"""Controls the allowlist of FlexVolume drivers. Corresponds to the +`allowedFlexVolumes` field in PodSecurityPolicy. For more information, +see +https://kubernetes.io/docs/concepts/policy/pod-security-policy/#flexvolume-drivers +""" + +schema Params: + allowedFlexVolumes?: [AllowedFlexVolume] + +schema AllowedFlexVolume: + driver: str + +params: Params = option("params") + +# Define the validation function +validate = lambda item { + if item.kind == "Pod": + input_volumes = [v for v in item.spec.volumes if "flexVolume" in v] + drivers = [v.driver for v in params.allowedFlexVolumes] + assert all v in input_volumes { + v.flexVolume.driver in drivers + }, "FlexVolumes is not allowed, pod: {}. Allowed drivers: {}".format(item.metadata.name, drivers) + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/psp-flexvolume-drivers/suite/bad.yaml b/examples/validation/psp-flexvolume-drivers/suite/bad.yaml new file mode 100644 index 0000000..66247c9 --- /dev/null +++ b/examples/validation/psp-flexvolume-drivers/suite/bad.yaml @@ -0,0 +1,37 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-flexvolume-drivers + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls the allowlist of FlexVolume drivers. Corresponds to the + `allowedFlexVolumes` field in PodSecurityPolicy. For more information, + see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#flexvolume-drivers +spec: + params: + allowedFlexVolumes: #[] + - driver: "example/lvm" + - driver: "example/cifs" + source: ./examples/validation/psp-flexvolume-drivers/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-flexvolume-driver-disallowed + labels: + app: nginx-flexvolume-driver +spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /test + name: test-volume + readOnly: true + volumes: + - name: test-volume + flexVolume: + driver: "example/testdriver" #"example/lvm" diff --git a/examples/validation/psp-flexvolume-drivers/suite/good.yaml b/examples/validation/psp-flexvolume-drivers/suite/good.yaml new file mode 100644 index 0000000..51c973e --- /dev/null +++ b/examples/validation/psp-flexvolume-drivers/suite/good.yaml @@ -0,0 +1,37 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: psp-flexvolume-drivers + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls the allowlist of FlexVolume drivers. Corresponds to the + `allowedFlexVolumes` field in PodSecurityPolicy. For more information, + see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#flexvolume-drivers +spec: + params: + allowedFlexVolumes: #[] + - driver: "example/lvm" + - driver: "example/cifs" + source: ./examples/validation/psp-flexvolume-drivers/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-flexvolume-driver-allowed + labels: + app: nginx-flexvolume-driver +spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /test + name: test-volume + readOnly: true + volumes: + - name: test-volume + flexVolume: + driver: "example/lvm" diff --git a/examples/validation/replica-limits/kcl.mod.lock b/examples/validation/replica-limits/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/required-annotations/kcl.mod.lock b/examples/validation/required-annotations/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/required-image-digests/kcl.mod b/examples/validation/required-image-digests/kcl.mod new file mode 100644 index 0000000..f9b65c0 --- /dev/null +++ b/examples/validation/required-image-digests/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "required-image-digests" +version = "0.0.1" diff --git a/examples/validation/required-image-digests/main.k b/examples/validation/required-image-digests/main.k new file mode 100644 index 0000000..a3da1bc --- /dev/null +++ b/examples/validation/required-image-digests/main.k @@ -0,0 +1,39 @@ +"""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/ +""" +import regex + +schema Params: + exemptImages?: [str] + +params: Params = option("params") + +is_exempt = lambda image: str -> bool { + result = False + if params.exemptImages: + result = any exempt_image in params.exemptImages { + (image.startswith(exempt_image.removesuffix("*")) if exempt_image.endswith("*") else exempt_image == image) + } + result +} + +# Define the validation function +validate = lambda item { + cpu = "" + memory = "" + containers = [] + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + (item.spec.template.spec.ephemeralContainers or []) + if containers: + images: [str] = [c.image for c in containers if not is_exempt(c.image)] + image_list_without_digest: [str] = [image for image in images if not regex.match(image, "@[a-z0-9]+([+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+")] + assert len(image_list_without_digest) == 0, "images ${image_list_without_digest} without a digest" + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/required-image-digests/suite/bad.yaml b/examples/validation/required-image-digests/suite/bad.yaml new file mode 100644 index 0000000..4d768d4 --- /dev/null +++ b/examples/validation/required-image-digests/suite/bad.yaml @@ -0,0 +1,22 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: required-image-digests + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires container images to contain a digest. + + https://kubernetes.io/docs/concepts/containers/images/ +spec: + source: ./examples/validation/required-image-digests/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: bad-suite +spec: + containers: + - name: kcl + image: kcllang/kcl:0.6.0 diff --git a/examples/validation/required-image-digests/suite/good.yaml b/examples/validation/required-image-digests/suite/good.yaml new file mode 100644 index 0000000..0dd7fea --- /dev/null +++ b/examples/validation/required-image-digests/suite/good.yaml @@ -0,0 +1,22 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: required-image-digests + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires container images to contain a digest. + + https://kubernetes.io/docs/concepts/containers/images/ +spec: + source: ./examples/validation/required-image-digests/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: good-suite +spec: + containers: + - name: kcl + image: kcllang/kcl:0.6.0@sha256:04ff8fce2afd1a3bc26260348e5b290e8d945b1fad4b4c16d22834c2f3a1814a diff --git a/examples/validation/required-labels/kcl.mod.lock b/examples/validation/required-labels/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/examples/validation/required-probes/kcl.mod b/examples/validation/required-probes/kcl.mod new file mode 100644 index 0000000..d47378e --- /dev/null +++ b/examples/validation/required-probes/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "required-probes" +version = "0.0.1" diff --git a/examples/validation/required-probes/main.k b/examples/validation/required-probes/main.k new file mode 100644 index 0000000..a14d475 --- /dev/null +++ b/examples/validation/required-probes/main.k @@ -0,0 +1,39 @@ +"""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/ +""" +import regex + +schema Params: + # A list of probes that are required (ex: `readinessProbe`) + probes?: [str] + # The probe must define a field listed in `probeType` in order to satisfy the constraint (ex. `tcpSocket` satisfies `['tcpSocket', 'exec']`) + probeTypes?: [str] + +params: Params = option("params") + +probe_is_missing = lambda container: {str:}, probe: str -> bool { + result = bool(container[probe]) + if result: + probe_fields = [key for key in container[probe]] + result = all field in probe_fields { + field not in params.probeTypes + } + result +} + +# Define the validation function +validate = lambda item { + if item.kind == "Pod": + containers = item.spec.containers + probes = params.probes + probe_is_missing_containers = [[c.name, probe] for c in containers for probe in probes if probe_is_missing(c, probe)] + if len(probe_is_missing_containers) > 0: + msg = "\n".join(["Container {} field {} is invalid".format(c[0], c[1]) for c in probe_is_missing_containers]) + assert False, msg + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/required-probes/suite/bad.yaml b/examples/validation/required-probes/suite/bad.yaml new file mode 100644 index 0000000..d620e86 --- /dev/null +++ b/examples/validation/required-probes/suite/bad.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: required-probes + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires Pods to have readiness and/or liveness probes. +spec: + params: + probes: ["readinessProbe", "livenessProbe"] + probeTypes: ["tcpSocket"] + source: ./examples/validation/required-probes/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: test-pod1 +spec: + containers: + - name: nginx-1 + image: nginx:1.7.9 + ports: + - containerPort: 80 + livenessProbe: + # tcpSocket: + # port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/examples/validation/required-probes/suite/good.yaml b/examples/validation/required-probes/suite/good.yaml new file mode 100644 index 0000000..135099b --- /dev/null +++ b/examples/validation/required-probes/suite/good.yaml @@ -0,0 +1,35 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: required-probes + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires Pods to have readiness and/or liveness probes. +spec: + params: + probes: ["readinessProbe", "livenessProbe"] + probeTypes: ["tcpSocket"] + source: ./examples/validation/required-probes/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: test-pod1 +spec: + containers: + - name: nginx-1 + image: nginx:1.7.9 + ports: + - containerPort: 80 + livenessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/examples/validation/validate-auto-mount-service-account-token/kcl.mod b/examples/validation/validate-auto-mount-service-account-token/kcl.mod new file mode 100644 index 0000000..da72c10 --- /dev/null +++ b/examples/validation/validate-auto-mount-service-account-token/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "validate-auto-mount-service-account-token" +version = "0.0.1" diff --git a/examples/validation/validate-auto-mount-service-account-token/main.k b/examples/validation/validate-auto-mount-service-account-token/main.k new file mode 100644 index 0000000..92d77db --- /dev/null +++ b/examples/validation/validate-auto-mount-service-account-token/main.k @@ -0,0 +1,29 @@ +"""Requires container images to begin with a string from the specified list. + +Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/allowedrepos/constraint.tmpl +""" + +# 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 = [] + automountServiceAccountToken = False + if item.kind == "Pod" and repos: + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + automountServiceAccountToken = item.spec.automountServiceAccountToken + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + automountServiceAccountToken = item.spec.template.spec.automountServiceAccountToken + if automountServiceAccountToken == True: + assert all c in containers { + all m in c.volumeMounts { + m.mountPath == "/var/run/secrets/kubernetes.io/serviceaccount" + } + }, """Automounting service account token is disallowed for ${item.kind}: ${item.metadata.name}""" + # Return the resource + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/validate-auto-mount-service-account-token/suite/bad.yaml b/examples/validation/validate-auto-mount-service-account-token/suite/bad.yaml new file mode 100644 index 0000000..1351d4f --- /dev/null +++ b/examples/validation/validate-auto-mount-service-account-token/suite/bad.yaml @@ -0,0 +1,38 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-auto-mount-service-account-token + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires container images to begin with a string from the specified list. + + Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/automount-serviceaccount-token/constraint.tmpl +spec: + source: ./examples/validation/validate-auto-mount-service-account-token/main.k +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + automountServiceAccountToken: true + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/error_serviceaccount diff --git a/examples/validation/validate-auto-mount-service-account-token/suite/good.yaml b/examples/validation/validate-auto-mount-service-account-token/suite/good.yaml new file mode 100644 index 0000000..4a550da --- /dev/null +++ b/examples/validation/validate-auto-mount-service-account-token/suite/good.yaml @@ -0,0 +1,38 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-auto-mount-service-account-token + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Controls the ability of any Pod to enable automountServiceAccountToken. + + Ref: https://github.com/open-policy-agent/gatekeeper-library/blob/master/src/general/automount-serviceaccount-token/constraint.tmpl +spec: + source: ./examples/validation/validate-auto-mount-service-account-token/main.k +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + automountServiceAccountToken: true + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount diff --git a/examples/validation/validate-container-limits/kcl.mod b/examples/validation/validate-container-limits/kcl.mod new file mode 100644 index 0000000..b4b2f4f --- /dev/null +++ b/examples/validation/validate-container-limits/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "validate-container-limits" +version = "0.0.1" diff --git a/examples/validation/validate-container-limits/main.k b/examples/validation/validate-container-limits/main.k new file mode 100644 index 0000000..f808c55 --- /dev/null +++ b/examples/validation/validate-container-limits/main.k @@ -0,0 +1,44 @@ +"""Requires containers to have memory and CPU limits set and constrains +limits 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.limits.cpu for c in containers if c?.resources?.limits?.cpu] + memory_list: [str] = [c.resources.limits.memory for c in containers if c?.resources?.limits?.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-limits/suite/bad.yaml b/examples/validation/validate-container-limits/suite/bad.yaml new file mode 100644 index 0000000..7161ecf --- /dev/null +++ b/examples/validation/validate-container-limits/suite/bad.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-container-limits + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires containers to have memory and CPU limits set and constrains + limits 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-limits/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: allowed +spec: + containers: + - name: nginx + image: nginx + resources: + limits: + cpu: "100m" + memory: "2Gi" diff --git a/examples/validation/validate-container-limits/suite/good.yaml b/examples/validation/validate-container-limits/suite/good.yaml new file mode 100644 index 0000000..a18fdf5 --- /dev/null +++ b/examples/validation/validate-container-limits/suite/good.yaml @@ -0,0 +1,30 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-container-limits + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Requires containers to have memory and CPU limits set and constrains + limits 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-limits/main.k +--- +apiVersion: v1 +kind: Pod +metadata: + name: allowed +spec: + containers: + - name: nginx + image: nginx + resources: + limits: + cpu: "100m" + memory: "1Gi" 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" diff --git a/examples/validation/validate-deprecated-api/kcl.mod b/examples/validation/validate-deprecated-api/kcl.mod new file mode 100644 index 0000000..6b9783f --- /dev/null +++ b/examples/validation/validate-deprecated-api/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "validate-deprecated-api" +version = "0.0.1" diff --git a/examples/validation/validate-deprecated-api/main.k b/examples/validation/validate-deprecated-api/main.k new file mode 100644 index 0000000..04e0f61 --- /dev/null +++ b/examples/validation/validate-deprecated-api/main.k @@ -0,0 +1,29 @@ +"""Verifies deprecated Kubernetes APIs to ensure all the API versions are up to date. +This template does not apply to audit as audit looks at the resources which are already +present in the cluster with non-deprecated API versions. +Ref: https://open-policy-agent.github.io/gatekeeper-library/website/validation/verifydeprecatedapi +""" + +schema Params: + kvs: [KV] + k8sVersion: int | float | str + +schema KV: + deprecatedAPI: str + kinds: [str] + targetAPI: str + +params: Params = option("params") + +# Define the validation function +validate = lambda item { + if params.kvs: + [lambda item, kv: KV { + if item.kind in kv.kinds: + assert item.apiVersion != kv.deprecatedAPI, "API {} for {} is deprecated in Kubernetes version {}, please use {} instead".format(item.kind, item.apiVersion, params.k8sVersion, kv.targetAPI) + kv + }(item, kv) for kv in params.kvs] + item +} +# Validate All resource +items = [validate(i) for i in option("items")] diff --git a/examples/validation/validate-deprecated-api/suite/bad.yaml b/examples/validation/validate-deprecated-api/suite/bad.yaml new file mode 100644 index 0000000..84ea4f1 --- /dev/null +++ b/examples/validation/validate-deprecated-api/suite/bad.yaml @@ -0,0 +1,54 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-deprecated-api + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Verifies deprecated Kubernetes APIs to ensure all the API versions are up to date. + This template does not apply to audit as audit looks at the resources which are already + present in the cluster with non-deprecated API versions. + Ref: https://open-policy-agent.github.io/gatekeeper-library/website/validation/verifydeprecatedapi +spec: + params: + kvs: + - deprecatedAPI: "apps/v1beta1" + kinds: ["Deployment", "ReplicaSet", "StatefulSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["ReplicaSet", "Deployment", "DaemonSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["PodSecurityPolicy"] + targetAPI: "policy/v1beta1" + - deprecatedAPI: "apps/v1beta2" + kinds: ["ReplicaSet", "StatefulSet", "Deployment", "DaemonSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["NetworkPolicy"] + targetAPI: "networking.k8s.io/v1" + k8sVersion: 1.16 + source: ./examples/validation/validate-deprecated-api/main.k +--- +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: disallowed-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/examples/validation/validate-deprecated-api/suite/good.yaml b/examples/validation/validate-deprecated-api/suite/good.yaml new file mode 100644 index 0000000..7aea699 --- /dev/null +++ b/examples/validation/validate-deprecated-api/suite/good.yaml @@ -0,0 +1,54 @@ +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: validate-deprecated-api + annotations: + krm.kcl.dev/version: 0.0.1 + krm.kcl.dev/type: validation + documentation: >- + Verifies deprecated Kubernetes APIs to ensure all the API versions are up to date. + This template does not apply to audit as audit looks at the resources which are already + present in the cluster with non-deprecated API versions. + Ref: https://open-policy-agent.github.io/gatekeeper-library/website/validation/verifydeprecatedapi +spec: + params: + kvs: + - deprecatedAPI: "apps/v1beta1" + kinds: ["Deployment", "ReplicaSet", "StatefulSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["ReplicaSet", "Deployment", "DaemonSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["PodSecurityPolicy"] + targetAPI: "policy/v1beta1" + - deprecatedAPI: "apps/v1beta2" + kinds: ["ReplicaSet", "StatefulSet", "Deployment", "DaemonSet"] + targetAPI: "apps/v1" + - deprecatedAPI: "extensions/v1beta1" + kinds: ["NetworkPolicy"] + targetAPI: "networking.k8s.io/v1" + k8sVersion: 1.16 + source: ./examples/validation/validate-deprecated-api/main.k +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: disallowed-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/examples/validation/validate-probes/kcl.mod.lock b/examples/validation/validate-probes/kcl.mod.lock deleted file mode 100644 index e69de29..0000000 diff --git a/library/k8s_manifests_containers/kcl.mod b/library/k8s_manifests_containers/kcl.mod new file mode 100644 index 0000000..c6dda8d --- /dev/null +++ b/library/k8s_manifests_containers/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "k8s_manifests_containers" +version = "0.0.1" diff --git a/library/k8s_manifests_containers/main.k b/library/k8s_manifests_containers/main.k new file mode 100644 index 0000000..9b8ee25 --- /dev/null +++ b/library/k8s_manifests_containers/main.k @@ -0,0 +1,19 @@ +# Judge a image in a container config is exempt +is_exempt = lambda image: str, exemptImages: [str] = [] -> bool { + result = False + if exemptImages: + result = any exempt_image in exemptImages { + (image.startswith(exempt_image.removesuffix("*")) if exempt_image.endswith("*") else exempt_image == image) + } + result +} + +# Get Containers from the input resource item. +get_containers = lambda item, exemptImages = [] -> [] { + containers = [] + if item.kind == "Pod": + containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) + elif item.kind == "Deployment": + containers = (item.spec.template.spec.containers or []) + (item.spec.template.spec.initContainers or []) + (item.spec.template.spec.ephemeralContainers or []) + containers = [c for c in containers if not is_exempt(c.image, exemptImages)] +} diff --git a/library/merge/kcl.mod b/library/merge/kcl.mod new file mode 100644 index 0000000..2bec900 --- /dev/null +++ b/library/merge/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "merge" +edition = "0.0.1" +version = "0.0.1" + diff --git a/library/merge/main.k b/library/merge/main.k new file mode 100644 index 0000000..f255e56 --- /dev/null +++ b/library/merge/main.k @@ -0,0 +1,41 @@ +schema UnionAll[data, n]: + value?: {str:} = ((UnionAll(data=data, n=n - 1) {}).value | data[n] if n > 0 else data[0]) if data else {} + +schema MergeList[data]: + """Merge all elements in a list + + [{"key1": "value1"}, {"key2": "value2"}, {"key3": "value3"}] -> {"key1": "value1", "key2": "value2", "key3": "value3"} + """ + value?: {str:} = (UnionAll(data=data, n=len(data) - 1) {}).value if data else {} + +schema MergeAppend[a, b]: + """ + MergeAppend( + {"a": 1, "b": {"c": 2, "d": 3}, "e": [4, 5]} + {"a": 6, "b": {"c": 7, "f": 8}, "e": [6, 7]} + ).value => {"a": 6, "b": {"c": 7, "d": 3, "f": 8}, "e": [4, 5, 6, 7]} + """ + value?: {str:} = { + k: [*v, *b[k]] if typeof(v) == "list" and typeof(b[k]) == "list" else v | b[k] \ + if typeof(v) == typeof(b[k]) and typeof(v) not in ["str", "bool", "int", "float"] else b[k] or v \ + for k, v in a + } | {k: v for k, v in b if not a[k]} + +schema MergeAppendList[data: []]: + """Merge Append list + + MergeAppendList( + [ + {"a": [1]}, + {"a": [2,3]}, + {"a": [4]}, + ] + ).value => {"a": [1,2,3,4]} + """ + value?: {str:} + if not data: + value = None + elif len(data) == 1: + value = data[0] + else: + value = MergeAppend(data[0], MergeAppendList(data[1:]).value).value