Skip to content

Instantly share code, notes, and snippets.

@sdickhoven
Last active October 28, 2021 15:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdickhoven/1de8e0f290d7cfcedba8ad142c6b8930 to your computer and use it in GitHub Desktop.
Save sdickhoven/1de8e0f290d7cfcedba8ad142c6b8930 to your computer and use it in GitHub Desktop.
Gatekeeper Forbidden Annotations Constraint Template
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: gatekeeperforbiddenannotations
spec:
crd:
spec:
names:
kind: GatekeeperForbiddenAnnotations
shortNames:
- gkfbanno
validation:
# Schema for the `parameters` field
openAPIV3Schema:
anyOf:
- required:
- annotations
- required:
- annotationValues
type: object
properties:
overridable:
type: boolean
reason:
type: string
annotations:
type: array
items:
type: string
annotationValues:
type: object
additionalProperties:
type: string
annotationValueMatch:
type: string
enum:
- exact
- substr
- glob
- regex
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package gatekeeperforbiddenannotations
override {
input.parameters["overridable"]
regex.match("\\bgatekeeperforbiddenannotations\\b", input.review.object.metadata.annotations["policy.my-company.com/override"])
}
inspect {
not override
}
forbidden_annotation_values = v {
v := object.get(input.parameters, "annotationValues", {})
}
forbidden_annotations = v {
v := cast_set(object.get(input.parameters, "annotations", []))
}
object_annotation_values = v {
v := object.get(input.review.object.metadata, "annotations", {})
}
object_annotations = v {
v := {annotation | object_annotation_values[annotation]}
}
match_type = v {
v := object.get(input.parameters, "annotationValueMatch", "exact")
}
is_match(object_annotation, forbidden_annotation) {
match_type == "exact"
object_annotation == forbidden_annotation
}
is_match(object_annotation, forbidden_annotation) {
match_type == "substr"
contains(object_annotation, forbidden_annotation)
}
is_match(object_annotation, forbidden_annotation) {
match_type == "glob"
glob.match(forbidden_annotation, [" "], object_annotation)
}
is_match(object_annotation, forbidden_annotation) {
match_type == "regex"
regex.match(forbidden_annotation, object_annotation)
}
violation[{"msg": msg}] {
inspect
detected := object_annotations & forbidden_annotations
count(detected) > 0
msg := output(input.review.kind.group, input.review.kind.kind, input.review.object.metadata.name, detected)
}
violation[{"msg": msg}] {
inspect
detected := {sprintf("%v=%v", [i, forbidden_annotation_values[i]]) | some i; is_match(object_annotation_values[i], forbidden_annotation_values[i])}
count(detected) > 0
msg := output(input.review.kind.group, input.review.kind.kind, input.review.object.metadata.name, detected)
}
output(group, kind, name, annotations) = v {
reason := object.get(input.parameters, "reason", "")
overridable := object.get(input.parameters, "overridable", false)
offending := concat(", ", annotations)
v := sprintf("Forbidden annotations: %v [ Group=%v | Kind=%v | Name=%v | Overridable=%v | Reason=%v ]", [offending, group, kind, name, overridable, reason])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GatekeeperForbiddenAnnotations
metadata:
name: ingress-class
spec:
match:
scope: Namespaced
kinds:
- apiGroups:
- extensions
- networking.k8s.io
kinds:
- Ingress
excludedNamespaces:
- kube-system
parameters:
reason: This ingress class is not supported.
annotationValues:
kubernetes.io/ingress.class: alb
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GatekeeperForbiddenAnnotations
metadata:
name: cloudflare-proxied
spec:
match:
scope: Namespaced
kinds:
- apiGroups:
- extensions
- networking.k8s.io
kinds:
- Ingress
excludedNamespaces:
- kube-system
parameters:
overridable: true
reason: You are either confused or you are using the wrong DNS name. Please consult with Cloud Engineering before proceeding.
annotations:
- external-dns.alpha.kubernetes.io/cloudflare-proxied
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GatekeeperForbiddenAnnotations
metadata:
name: cve-2021-25742
spec:
match:
scope: Namespaced
kinds:
- apiGroups:
- extensions
- networking.k8s.io
kinds:
- Ingress
excludedNamespaces:
- kube-system
parameters:
overridable: false
reason: It appears that you are trying to exploit CVE-2021-25742. The armed response team will be right with you.
annotationValueMatch: regex
annotationValues:
nginx.ingress.kubernetes.io/auth-snippet: '\blua_|_lua\b|_lua_|\bkubernetes\.io\b'
nginx.ingress.kubernetes.io/configuration-snippet: '\blua_|_lua\b|_lua_|\bkubernetes\.io\b'
nginx.ingress.kubernetes.io/modsecurity-snippet: '\blua_|_lua\b|_lua_|\bkubernetes\.io\b'
nginx.ingress.kubernetes.io/server-snippet: '\blua_|_lua\b|_lua_|\bkubernetes\.io\b'
@sdickhoven
Copy link
Author

sdickhoven commented Oct 27, 2021

for the cloudflare-proxied policy, users can exempt themselves by adding

metadata:
  annotations:
    policy.my-company.com/override: gatekeeperforbiddenannotations

to their Ingress.

so this is really just a stumbling block, but it makes for easy reporting:

$ kubectl get ing -A -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,OVERRIDE:.metadata.annotations.policy\.my-company\.com/override" | \
  grep -w gatekeeperforbiddenannotations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment