From ea9629ffa683f66833ac944acaede68b61503ddf Mon Sep 17 00:00:00 2001 From: Tommy Barnes Date: Mon, 12 Feb 2024 18:17:40 -0500 Subject: [PATCH] saving wip dirs imported from rustam --- .../constraint.yaml | 11 ++ .../template.yaml | 64 ++++++++++ .../constraint.yaml | 11 ++ .../template.yaml | 111 ++++++++++++++++++ .../lib/v1.0.0/parameters/BUILD.bazel | 9 ++ pkg/safeguards/lib/v1.0.0/parameters/map.go | 38 ++++++ .../pod-enforce-antiaffinity/constraint.yaml | 9 ++ .../pod-enforce-antiaffinity/template.yaml | 25 ++++ .../v1.0.0/restricted-taints/constraint.yaml | 11 ++ .../v1.0.0/restricted-taints/template.yaml | 43 +++++++ .../unique-service-selectors/constraint.yaml | 9 ++ .../unique-service-selectors/template.yaml | 70 +++++++++++ 12 files changed, 411 insertions(+) create mode 100644 pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/constraint.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/template.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/constraint.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/template.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/parameters/BUILD.bazel create mode 100644 pkg/safeguards/lib/v1.0.0/parameters/map.go create mode 100644 pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/constraint.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/template.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/restricted-taints/template.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/unique-service-selectors/constraint.yaml create mode 100644 pkg/safeguards/lib/v1.0.0/unique-service-selectors/template.yaml diff --git a/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/constraint.yaml b/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/constraint.yaml new file mode 100644 index 00000000..5e772a07 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sAzureV1ContainerRestrictedImagePulls +metadata: + name: v1-container-restricted-image-pulls +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + excludedImages: {{ .Values.excludedImages }} \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/template.yaml b/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/template.yaml new file mode 100644 index 00000000..75cc86ea --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/container-restricted-image-pulls/template.yaml @@ -0,0 +1,64 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8sazurev1containerrestrictedimagepulls +spec: + crd: + spec: + names: + kind: K8sAzureV1ContainerRestrictedImagePulls + validation: + # Schema for the `parameters` field + openAPIV3Schema: + properties: + excludedImages: + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sazurev1containerrestrictedimagepulls + + has_key(object, key) { + _ = object[key] + } + + violation[{"msg": msg}] { + container := input_containers[_] + not is_excluded(container) + not has_key(input.review.object.spec, "imagePullSecrets") + namespace := input.review.namespace + pod := input.review.object.metadata.name + msg := sprintf("%s in %s does not have imagePullSecrets. Unauthenticated image pulls are not recommended.", [pod, namespace]) + } + + input_containers[c] { + c := input.review.object.spec.containers[_] + } + + input_containers[c] { + c := input.review.object.spec.initContainers[_] + } + + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + } + + is_excluded(container) { + exclude_images := object.get(object.get(input, "parameters", {}), "excludedImages", []) + img := container.image + exclusion := exclude_images[_] + matches_exclusion(img, exclusion) + } + + matches_exclusion(img, exclusion) { + not endswith(exclusion, "*") + exclusion == img + } + + matches_exclusion(img, exclusion) { + endswith(exclusion, "*") + prefix := trim_suffix(exclusion, "*") + startswith(img, prefix) + } diff --git a/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/constraint.yaml b/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/constraint.yaml new file mode 100644 index 00000000..b7cdf7da --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sAzureV1DisallowedBadPodDisruptionBudgets +metadata: + name: v1-disallowed-bad-pod-disruption-budgets +spec: + match: + kinds: + - apiGroups: ["apps"] + kinds: ["Deployment", "ReplicaSet", "StatefulSet"] + - apiGroups: ["policy"] + kinds: ["PodDisruptionBudget"] \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/template.yaml b/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/template.yaml new file mode 100644 index 00000000..57be9db9 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/disallowed-bad-pod-disruption-budgets/template.yaml @@ -0,0 +1,111 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8sazurev1disallowedbadpoddisruptionbudgets + annotations: + metadata.gatekeeper.sh/title: "Pod Disruption Budget" + metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/requires-sync-data: | + "[ + [ + { + "groups": ["policy"], + "versions": ["v1"], + "kinds": ["PodDisruptionBudget"] + }, + { + "groups": ["apps"], + "versions": ["v1"], + "kinds": ["StatefulSet", "ReplicaSet", "Deployment"] + } + ] + ]" + description: Prevents customers from applying bad Pod Disruption Budgets +spec: + crd: + spec: + names: + kind: K8sAzureV1DisallowedBadPodDisruptionBudgets + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sazurev1disallowedbadpoddisruptionbudgets + + violation[{"msg": msg}] { + input.review.kind.kind == "PodDisruptionBudget" + pdb := input.review.object + not valid_pdb_max_unavailable(pdb) + msg := sprintf( + "PodDisruptionBudget <%s> has maxUnavailable of 0, only positive integers are allowed for maxUnavailable", + [pdb.metadata.name] + ) + } + + violation[{"msg": msg}] { + obj := input.review.object + pdb := data.inventory.namespace[obj.metadata.namespace]["policy/v1"].PodDisruptionBudget[_] + obj.spec.selector.matchLabels == pdb.spec.selector.matchLabels + not valid_pdb_max_unavailable(pdb) + msg := sprintf( + "%s <%s> has been selected by PodDisruptionBudget <%s> but has maxUnavailable of 0, only positive integers are allowed for maxUnavailable", + [obj.kind, obj.metadata.name, pdb.metadata.name] + ) + } + + violation[{"msg": msg}] { + obj := input.review.object + pdb := data.inventory.namespace[obj.metadata.namespace]["policy/v1"].PodDisruptionBudget[_] + obj.spec.selector.matchLabels == pdb.spec.selector.matchLabels + not valid_pdb_min_available(obj, pdb) + msg := sprintf("%s <%s> has %d replica(s) but PodDisruptionBudget <%s> has minAvailable of %d, only positive integers less than %d are allowed for minAvailable", + [obj.kind, obj.metadata.name, obj.spec.replicas, pdb.metadata.name, pdb.spec.minAvailable, obj.spec.replicas]) + } + + violation[{"msg":msg}] { + input.review.kind.kind == "PodDisruptionBudget" + pdb := input.review.object + + matchingDeploys := {x | x.spec.selector.matchLabels == pdb.spec.selector.matchLabels; x = data.inventory.namespace[pdb.metadata.namespace]["apps/v1"].Deployment[_]} + deploy := matchingDeploys[_] + not valid_pdb_min_available(deploy, pdb) + + msg := sprintf("PodDisruptionBudget %s specifies minAvailable of %d, but matching Deployment %s has %d replicas. minAvailable should be less than %d",[pdb.metadata.name,pdb.spec.minAvailable,deploy.metadata.name,deploy.spec.replicas,deploy.spec.replicas]) + } + + violation[{"msg":msg}] { + input.review.kind.kind == "PodDisruptionBudget" + pdb := input.review.object + + matchingSS := {x | x.spec.selector.matchLabels == pdb.spec.selector.matchLabels; x = data.inventory.namespace[pdb.metadata.namespace]["apps/v1"].StatefulSet[_]} + ss := matchingSS[_] + not valid_pdb_min_available(ss, pdb) + + msg := sprintf("PodDisruptionBudget %s specifies minAvailable of %d, but matching StatefulSet %s has %d replicas. minAvailable should be less than %d",[pdb.metadata.name,pdb.spec.minAvailable,ss.metadata.name,ss.spec.replicas,ss.spec.replicas]) + } + + violation[{"msg":msg}] { + input.review.kind.kind == "PodDisruptionBudget" + pdb := input.review.object + + matchingRS := {x | x.spec.selector.matchLabels == pdb.spec.selector.matchLabels; x = data.inventory.namespace[pdb.metadata.namespace]["apps/v1"].ReplicaSet[_]} + rs := matchingRS[_] + not valid_pdb_min_available(rs, pdb) + + msg := sprintf("PodDisruptionBudget %s specifies minAvailable of %d, but matching ReplicaSet %s has %d replicas. minAvailable should be less than %d",[pdb.metadata.name,pdb.spec.minAvailable,rs.metadata.name,rs.spec.replicas,rs.spec.replicas]) + } + + valid_pdb_min_available(obj, pdb) { + # default to -1 if minAvailable is not set so valid_pdb_min_available is always true + # for objects with >= 0 replicas. If minAvailable defaults to >= 0, objects with + # replicas field might violate this constraint if they are equal to the default set here + min_available := object.get(pdb.spec, "minAvailable", -1) + obj.spec.replicas > min_available + } + + valid_pdb_max_unavailable(pdb) { + # default to 1 if maxUnavailable is not set so valid_pdb_max_unavailable always returns true. + # If maxUnavailable defaults to 0, it violates this constraint because all pods needs to be + # available and no pods can be evicted voluntarily + max_unavailable := object.get(pdb.spec, "maxUnavailable", 1) + max_unavailable > 0 + } \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/parameters/BUILD.bazel b/pkg/safeguards/lib/v1.0.0/parameters/BUILD.bazel new file mode 100644 index 00000000..683b3dc5 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/parameters/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "parameters", + srcs = ["map.go"], + importpath = "go.goms.io/aks/rp/guardrails/server/lib/v1.0.0/parameters", + visibility = ["//visibility:public"], + deps = ["@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_resources_armpolicy//:armpolicy"], +) diff --git a/pkg/safeguards/lib/v1.0.0/parameters/map.go b/pkg/safeguards/lib/v1.0.0/parameters/map.go new file mode 100644 index 00000000..e8ca1c93 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/parameters/map.go @@ -0,0 +1,38 @@ +package v100 + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy" +) + +var Params = map[string]*armpolicy.ParameterValuesValue{ + "allowedUsers": { + Value: []string{"nodeclient", "system:serviceaccount:kube-system:aci-connector-linux", "system:serviceaccount:kube-system:node-controller", "acsService", "aksService", "system:serviceaccount:kube-system:cloud-node-manager"}, + }, + "allowedGroups": { + Value: []string{"system:node"}, + }, + "cpuLimit": { + Value: "200m", + }, + "memoryLimit": { + Value: "1Gi", + }, + "excludedContainers": { + Value: []string{}, + }, + "excludedImages": { + Value: []string{}, + }, + "labels": { + Value: []string{"kubernetes.azure.com"}, + }, + "allowedContainerImagesRegex": { + Value: ".*", + }, + "reservedTaints": { + Value: []string{"CriticalAddonsOnly"}, + }, + "requiredProbes": { + Value: []string{"readinessProbe", "livenessProbe"}, + }, +} diff --git a/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/constraint.yaml b/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/constraint.yaml new file mode 100644 index 00000000..a2e037dc --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/constraint.yaml @@ -0,0 +1,9 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sAzureV1AntiAffinityRules +metadata: + name: v1-multiple-replicas-need-anti-affinity +spec: + match: + kinds: + - apiGroups: ["apps"] + kinds: ["Deployment","StatefulSet","ReplicationController","ReplicaSet"] \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/template.yaml b/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/template.yaml new file mode 100644 index 00000000..6e654588 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/pod-enforce-antiaffinity/template.yaml @@ -0,0 +1,25 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8sazurev1antiaffinityrules + annotations: + description: Requires deployments with multiple replicas have pod anti affinity rules +spec: + crd: + spec: + names: + kind: K8sAzureV1AntiAffinityRules + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sazurev1antiaffinityrules + + missing_affinity(obj) { + not obj.affinity.podAntiAffinity + } + + violation[{"msg": msg}] { + input.review.object.spec.replicas > 1 + missing_affinity(input.review.object.spec.template.spec) + msg := sprintf("%s with %d replicas should have pod anti-affinity rules set to avoid disruptions due to nodes crashing", [input.review.kind.kind, input.review.object.spec.replicas]) + } \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml b/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml new file mode 100644 index 00000000..e795ccd8 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sAzureV1ReservedTaints +metadata: + name: v1-system-reserved-taints +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Node"] + parameters: + reservedTaints: {{ .Values.reservedTaints }} \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/restricted-taints/template.yaml b/pkg/safeguards/lib/v1.0.0/restricted-taints/template.yaml new file mode 100644 index 00000000..ee17e48e --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/restricted-taints/template.yaml @@ -0,0 +1,43 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8sazurev1reservedtaints + annotations: + description: Restricts the CriticalAddonsOnly taint to just the system pool +spec: + crd: + spec: + names: + kind: K8sAzureV1ReservedTaints + validation: + openAPIV3Schema: + type: object + properties: + reservedTaints: + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sazurev1reservedtaints + + is_system_pool(node) { + node.metadata.labels["kubernetes.azure.com/mode"] == "system" + } + + is_system_pool(node) { + node.metadata.labels["kubernetes.azure.com/mode"] == "System" + } + + violation[{"msg": msg}] { + node := input.review.object + # did the customer try to add a taint with key "CriticalAddonsOnly" to a non-system pool? + taints := {x | x = node.spec.taints[_].key} + not is_system_pool(node) + taint := taints[_] + restrictedTaint := input.parameters.reservedTaints[_] + regex.match(restrictedTaint,taint) + + msg := sprintf("Taint with key <%s> is reserved for the system pool only",[taint]) + } \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/unique-service-selectors/constraint.yaml b/pkg/safeguards/lib/v1.0.0/unique-service-selectors/constraint.yaml new file mode 100644 index 00000000..695fde13 --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/unique-service-selectors/constraint.yaml @@ -0,0 +1,9 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sAzureV1UniqueServiceSelector +metadata: + name: v1-unique-service-selector +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Service"] \ No newline at end of file diff --git a/pkg/safeguards/lib/v1.0.0/unique-service-selectors/template.yaml b/pkg/safeguards/lib/v1.0.0/unique-service-selectors/template.yaml new file mode 100644 index 00000000..9a97ac5e --- /dev/null +++ b/pkg/safeguards/lib/v1.0.0/unique-service-selectors/template.yaml @@ -0,0 +1,70 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8sazurev1uniqueserviceselector + annotations: + metadata.gatekeeper.sh/title: "Unique Service Selectors" + metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/requires-sync-data: | + "[ + [ + { + "groups": [""], + "versions": ["v1"], + "kinds": ["Service"] + } + ] + ]" + description: >- + Requires Services to have unique selectors within a namespace. + Selectors are considered the same if they have identical keys and values. + Selectors may share a key/value pair so long as there is at least one + distinct key/value pair between them. + + https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service +spec: + crd: + spec: + names: + kind: K8sAzureV1UniqueServiceSelector + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sazurev1uniqueserviceselector + + make_apiversion(kind) = apiVersion { + g := kind.group + v := kind.version + g != "" + apiVersion = sprintf("%s/%s", [g, v]) + } + + make_apiversion(kind) = apiVersion { + kind.group == "" + apiVersion = kind.version + } + + identical(obj, review) { + obj.metadata.namespace == review.object.metadata.namespace + obj.metadata.name == review.object.metadata.name + obj.kind == review.kind.kind + obj.apiVersion == make_apiversion(review.kind) + } + + flatten_selector(obj) = flattened { + selectors := [s | s = concat(":", [key, val]); val = obj.spec.selector[key]] + flattened := concat(",", sort(selectors)) + } + + violation[{"msg": msg}] { + input.review.kind.kind == "Service" + input.review.kind.version == "v1" + input.review.kind.group == "" + input_namespace := input.review.object.metadata.namespace + input_selector := flatten_selector(input.review.object) + other := data.inventory.namespace[input_namespace]["v1"].Service[_] + not identical(other, input.review) + other_selector := flatten_selector(other) + input_selector == other_selector + msg := sprintf("same selector as service <%s> in namespace <%s>", [other.metadata.name, input_namespace]) + } \ No newline at end of file