From 1fda8db14b6d2ac7496cc5755b6aead2fec95435 Mon Sep 17 00:00:00 2001 From: peefy Date: Fri, 10 Nov 2023 17:51:23 +0800 Subject: [PATCH 1/2] feat: add more container validation modules Signed-off-by: peefy --- k8s_manifests_containers/kcl.mod | 2 +- k8s_manifests_containers/main.k | 2 +- required-drop-all/README.md | 7 ++++ required-drop-all/kcl.mod | 5 +++ required-drop-all/kcl.mod.lock | 0 required-drop-all/main.k | 38 ++++++++++++++++++++ required-drop-cap-net-all/README.md | 7 ++++ required-drop-cap-net-all/kcl.mod | 5 +++ required-drop-cap-net-all/kcl.mod.lock | 0 required-drop-cap-net-all/main.k | 38 ++++++++++++++++++++ required-pod-requests-limits/README.md | 7 ++++ required-pod-requests-limits/kcl.mod | 5 +++ required-pod-requests-limits/kcl.mod.lock | 0 required-pod-requests-limits/main.k | 34 ++++++++++++++++++ required-root-fs/README.md | 7 ++++ required-root-fs/kcl.mod | 5 +++ required-root-fs/kcl.mod.lock | 0 required-root-fs/main.k | 18 ++++++++++ restrict-image-registries/README.md | 7 ++++ restrict-image-registries/kcl.mod | 5 +++ restrict-image-registries/kcl.mod.lock | 0 restrict-image-registries/main.k | 44 +++++++++++++++++++++++ restrict-service-external-ips/README.md | 7 ++++ restrict-service-external-ips/kcl.mod | 5 +++ restrict-service-external-ips/main.k | 37 +++++++++++++++++++ 25 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 required-drop-all/README.md create mode 100644 required-drop-all/kcl.mod create mode 100644 required-drop-all/kcl.mod.lock create mode 100644 required-drop-all/main.k create mode 100644 required-drop-cap-net-all/README.md create mode 100644 required-drop-cap-net-all/kcl.mod create mode 100644 required-drop-cap-net-all/kcl.mod.lock create mode 100644 required-drop-cap-net-all/main.k create mode 100644 required-pod-requests-limits/README.md create mode 100644 required-pod-requests-limits/kcl.mod create mode 100644 required-pod-requests-limits/kcl.mod.lock create mode 100644 required-pod-requests-limits/main.k create mode 100644 required-root-fs/README.md create mode 100644 required-root-fs/kcl.mod create mode 100644 required-root-fs/kcl.mod.lock create mode 100644 required-root-fs/main.k create mode 100644 restrict-image-registries/README.md create mode 100644 restrict-image-registries/kcl.mod create mode 100644 restrict-image-registries/kcl.mod.lock create mode 100644 restrict-image-registries/main.k create mode 100644 restrict-service-external-ips/README.md create mode 100644 restrict-service-external-ips/kcl.mod create mode 100644 restrict-service-external-ips/main.k diff --git a/k8s_manifests_containers/kcl.mod b/k8s_manifests_containers/kcl.mod index 2d82dc75..b719ce61 100644 --- a/k8s_manifests_containers/kcl.mod +++ b/k8s_manifests_containers/kcl.mod @@ -1,4 +1,4 @@ [package] name = "k8s_manifests_containers" -version = "0.1.0" +version = "0.1.1" description = "`k8s_manifests_containers` can be used to get all containers resources in a Pod resource." diff --git a/k8s_manifests_containers/main.k b/k8s_manifests_containers/main.k index 9b8ee25e..7aded69c 100644 --- a/k8s_manifests_containers/main.k +++ b/k8s_manifests_containers/main.k @@ -9,7 +9,7 @@ is_exempt = lambda image: str, exemptImages: [str] = [] -> bool { } # Get Containers from the input resource item. -get_containers = lambda item, exemptImages = [] -> [] { +get_containers = lambda item: {str:}, exemptImages: [str] = [] -> [] { containers = [] if item.kind == "Pod": containers = (item.spec.containers or []) + (item.spec.initContainers or []) + (item.spec.ephemeralContainers or []) diff --git a/required-drop-all/README.md b/required-drop-all/README.md new file mode 100644 index 00000000..d67df755 --- /dev/null +++ b/required-drop-all/README.md @@ -0,0 +1,7 @@ +## Introduction + +`require-pod-requests-limits` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/require-pod-requests-limits) diff --git a/required-drop-all/kcl.mod b/required-drop-all/kcl.mod new file mode 100644 index 00000000..cd3e873d --- /dev/null +++ b/required-drop-all/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "require-pod-requests-limits" +version = "0.1.0" +description = "`require-pod-requests-limits` is a KCL validation module" + diff --git a/required-drop-all/kcl.mod.lock b/required-drop-all/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/required-drop-all/main.k b/required-drop-all/main.k new file mode 100644 index 00000000..853b08e7 --- /dev/null +++ b/required-drop-all/main.k @@ -0,0 +1,38 @@ +"""Containers must drop `ALL` capabilities.""" +# 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: {str:}, exemptImages: [str] = [] -> [{str:}] { + 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)] +} + +validate_container = lambda container: {str:} -> bool { + drop: [str] = container?.securityContext?.capabilities?.drop or [] + any d in drop { + d.upper() == "ALL" + } +} + +# Define the validation function +validate = lambda item: {str:} { + containers = get_containers(item) + if containers: + container_list_disallow = [c.name for c in containers if not validate_container(c)] + assert len(container_list_disallow) == 0, "CPU and memory resource requests and limits are required. for containers {}".format(container_list_disallow) + item +} +# Validate All resource +items = [validate(i) for i in option("items") or []] diff --git a/required-drop-cap-net-all/README.md b/required-drop-cap-net-all/README.md new file mode 100644 index 00000000..76b6d5dd --- /dev/null +++ b/required-drop-cap-net-all/README.md @@ -0,0 +1,7 @@ +## Introduction + +`required-drop-cap-net-all` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/required-drop-cap-net-all) diff --git a/required-drop-cap-net-all/kcl.mod b/required-drop-cap-net-all/kcl.mod new file mode 100644 index 00000000..c5259e6d --- /dev/null +++ b/required-drop-cap-net-all/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "required-drop-cap-net-all" +version = "0.1.0" +description = "`required-drop-cap-net-all` is a KCL validation module" + diff --git a/required-drop-cap-net-all/kcl.mod.lock b/required-drop-cap-net-all/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/required-drop-cap-net-all/main.k b/required-drop-cap-net-all/main.k new file mode 100644 index 00000000..98b8fd6e --- /dev/null +++ b/required-drop-cap-net-all/main.k @@ -0,0 +1,38 @@ +"""Containers must drop `ALL` capabilities.""" +# 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: {str:}, exemptImages: [str] = [] -> [{str:}] { + 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)] +} + +validate_container = lambda container: {str:} -> bool { + drop: [str] = container?.securityContext?.capabilities?.drop or [] + any d in drop { + d.upper() == "CAP_NET_RAW" + } +} + +# Define the validation function +validate = lambda item: {str:} { + containers = get_containers(item) + if containers: + container_list_disallow = [c.name for c in containers if not validate_container(c)] + assert len(container_list_disallow) == 0, "CPU and memory resource requests and limits are required. for containers {}".format(container_list_disallow) + item +} +# Validate All resource +items = [validate(i) for i in option("items") or []] diff --git a/required-pod-requests-limits/README.md b/required-pod-requests-limits/README.md new file mode 100644 index 00000000..d67df755 --- /dev/null +++ b/required-pod-requests-limits/README.md @@ -0,0 +1,7 @@ +## Introduction + +`require-pod-requests-limits` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/require-pod-requests-limits) diff --git a/required-pod-requests-limits/kcl.mod b/required-pod-requests-limits/kcl.mod new file mode 100644 index 00000000..cd3e873d --- /dev/null +++ b/required-pod-requests-limits/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "require-pod-requests-limits" +version = "0.1.0" +description = "`require-pod-requests-limits` is a KCL validation module" + diff --git a/required-pod-requests-limits/kcl.mod.lock b/required-pod-requests-limits/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/required-pod-requests-limits/main.k b/required-pod-requests-limits/main.k new file mode 100644 index 00000000..b09ac34e --- /dev/null +++ b/required-pod-requests-limits/main.k @@ -0,0 +1,34 @@ +# 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: {str:}, exemptImages: [str] = [] -> [{str:}] { + 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)] +} + +validate_pod_resources = lambda container: {str:} -> bool { + container?.requests?.memory and container?.requests?.cpu and container?.limits?.memory +} + +# Define the validation function +validate = lambda item: {str:} { + containers = get_containers(item) + if containers: + container_list_disallow = [c.name for c in containers if not validate_pod_resources(c)] + assert len(container_list_disallow) == 0, "CPU and memory resource requests and limits are required. for containers {}".format(container_list_disallow) + item +} +# Validate All resource +items = [validate(i) for i in option("items") or []] \ No newline at end of file diff --git a/required-root-fs/README.md b/required-root-fs/README.md new file mode 100644 index 00000000..2e2375c5 --- /dev/null +++ b/required-root-fs/README.md @@ -0,0 +1,7 @@ +## Introduction + +`required-root-fs` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/required-root-fs) diff --git a/required-root-fs/kcl.mod b/required-root-fs/kcl.mod new file mode 100644 index 00000000..bfd4b27d --- /dev/null +++ b/required-root-fs/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "required-root-fs" +version = "0.1.0" +description = "`required-root-fs` is a KCL validation module" + diff --git a/required-root-fs/kcl.mod.lock b/required-root-fs/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/required-root-fs/main.k b/required-root-fs/main.k new file mode 100644 index 00000000..fed7674f --- /dev/null +++ b/required-root-fs/main.k @@ -0,0 +1,18 @@ +validate_root_fs = lambda container: {str:} -> bool { + container?.securityContext?.readOnlyRootFilesystem is True +} + +# Define the validation function +validate = lambda item { + containers: [{str:}] = [] + 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: + container_list_disallow = [c.name for c in containers if not validate_root_fs(c)] + assert len(container_list_disallow) == 0, "Root filesystem must be read-only for containers {}".format(container_list_disallow) + item +} +# Validate All resource +items = [validate(i) for i in option("items") or []] diff --git a/restrict-image-registries/README.md b/restrict-image-registries/README.md new file mode 100644 index 00000000..9d877f15 --- /dev/null +++ b/restrict-image-registries/README.md @@ -0,0 +1,7 @@ +## Introduction + +`restrict-image-registries` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/restrict-image-registries) diff --git a/restrict-image-registries/kcl.mod b/restrict-image-registries/kcl.mod new file mode 100644 index 00000000..ea2b447a --- /dev/null +++ b/restrict-image-registries/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "restrict-image-registries" +version = "0.1.0" +description = "`restrict-image-registries` is a KCL validation module" + diff --git a/restrict-image-registries/kcl.mod.lock b/restrict-image-registries/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/restrict-image-registries/main.k b/restrict-image-registries/main.k new file mode 100644 index 00000000..b8c68f67 --- /dev/null +++ b/restrict-image-registries/main.k @@ -0,0 +1,44 @@ +import yaml + +registries: [str] = option("params")?.registries or [] + +validate_image_registry = lambda image: str, registries: [str] -> bool { + any registry in registries { + image.startswith(registry) + } +} + +# Define the validation function +validate = lambda item, registries: [str] { + containers: [{str:}] = [] + 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: + image_list_disallow = [c.image for c in containers if not validate_image_registry(c.image, registries)] + assert len(image_list_disallow) == 0, "container images {} is not allowed, expected {}".format(image_list_disallow, registries) + item +} +# Validate All resource +items = [validate(i, registries) for i in option("items") or []] + +if option("__test__"): + validate(yaml.decode("""\ +apiVersion: v1 +kind: Pod +metadata: + name: goodpod02-registry + namespace: ir-pods-namespace +spec: + initContainers: + - name: nginx-init + image: bar.io/nginx + - name: busybox-init + image: eu.foo.io/busybox + containers: + - name: k8s-nginx + image: bar.io/nginx + - name: busybox + image: eu.foo.io1/busybox + """), ["bar.io/", "eu.foo.io/"]) diff --git a/restrict-service-external-ips/README.md b/restrict-service-external-ips/README.md new file mode 100644 index 00000000..f11688b3 --- /dev/null +++ b/restrict-service-external-ips/README.md @@ -0,0 +1,7 @@ +## Introduction + +`restrict-service-external-ips` is a KCL validation module + +## Resource + +The Code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/restrict-service-external-ips) diff --git a/restrict-service-external-ips/kcl.mod b/restrict-service-external-ips/kcl.mod new file mode 100644 index 00000000..d2d923c9 --- /dev/null +++ b/restrict-service-external-ips/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "restrict-service-external-ips" +version = "0.1.0" +description = "`restrict-service-external-ips` is a KCL validation module" + diff --git a/restrict-service-external-ips/main.k b/restrict-service-external-ips/main.k new file mode 100644 index 00000000..a4ee212b --- /dev/null +++ b/restrict-service-external-ips/main.k @@ -0,0 +1,37 @@ +"""Service externalIPs can be used for a MITM attack (CVE-2020-8554). +Restrict externalIPs or limit to a known set of addresses. +See: https://github.com/kyverno/kyverno/issues/1367. This policy validates +that the `externalIPs` field is not set on a Service. +""" +import yaml + +externalIPs: [str] = option("params")?.externalIPs or [] + +# Define the validation function +validate = lambda item, externalIPs: [str] { + if item.kind == "Service" and externalIPs: + input = item?.spec?.externalIPs or [] + assert all ip in input { + ip in externalIPs + } if input, "externalIPs ${item?.spec?.externalIPs} are not allowed, expected ${externalIPs}" + item +} +# Validate All resource +items = [validate(i, externalIPs) for i in option("items") or []] + +if option("__test__"): + validate(yaml.decode("""\ +apiVersion: v1 +kind: Service +metadata: + name: badservice01-eip +spec: + selector: + app: MyApp + ports: + - protocol: TCP + port: 80 + targetPort: 9376 + externalIPs: + - 127.0.0.1 # Error suite: 127.0.0.2 + """), ["127.0.0.1"]) From 346169a8cd2c5657b889003c656b4a6839f011a3 Mon Sep 17 00:00:00 2001 From: chengzw Date: Sat, 11 Nov 2023 22:39:05 +0800 Subject: [PATCH 2/2] fix typo ngnix to nginx Signed-off-by: chengzw --- .integration/artifacthub/web-service/0.1.0/README.md | 4 ++-- kcl-abstraction-example/main.k | 4 ++-- web-service/README.md | 4 ++-- web-service/suite/config.yaml | 4 ++-- web-service/suite/good.yaml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.integration/artifacthub/web-service/0.1.0/README.md b/.integration/artifacthub/web-service/0.1.0/README.md index 6c637908..60ebd8c0 100644 --- a/.integration/artifacthub/web-service/0.1.0/README.md +++ b/.integration/artifacthub/web-service/0.1.0/README.md @@ -11,8 +11,8 @@ spec: params: name: app containers: - ngnix: - image: ngnix + nginx: + image: nginx ports: - containerPort: 80 service: diff --git a/kcl-abstraction-example/main.k b/kcl-abstraction-example/main.k index 9da35231..dedb983e 100644 --- a/kcl-abstraction-example/main.k +++ b/kcl-abstraction-example/main.k @@ -2,8 +2,8 @@ import .app app.App { name = "app" - containers.ngnix = { - image = "ngnix" + containers.nginx = { + image = "nginx" ports = [{containerPort = 80}] } service.ports = [{ port = 80 }] diff --git a/web-service/README.md b/web-service/README.md index 9040f484..0f5ed2f0 100644 --- a/web-service/README.md +++ b/web-service/README.md @@ -11,8 +11,8 @@ spec: params: name: app containers: - ngnix: - image: ngnix + nginx: + image: nginx ports: - containerPort: 80 service: diff --git a/web-service/suite/config.yaml b/web-service/suite/config.yaml index 6aaf2e3a..5c65bb41 100644 --- a/web-service/suite/config.yaml +++ b/web-service/suite/config.yaml @@ -8,8 +8,8 @@ functionConfig: params: name: app containers: - ngnix: - image: ngnix + nginx: + image: nginx ports: - containerPort: 80 service: diff --git a/web-service/suite/good.yaml b/web-service/suite/good.yaml index 01945d79..fc4d7def 100644 --- a/web-service/suite/good.yaml +++ b/web-service/suite/good.yaml @@ -11,8 +11,8 @@ spec: params: name: app containers: - ngnix: - image: ngnix + nginx: + image: nginx ports: - containerPort: 80 service: