-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.rego
185 lines (147 loc) · 4.74 KB
/
main.rego
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package system
# This is copied from https://raw.githubusercontent.com/open-policy-agent/library/master/kubernetes/mutating-admission/main.rego
###########################################################################
# Implementation of the k8s admission control external webhook interface,
# combining validating and mutating admission controllers
###########################################################################
main = {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": response,
}
default response = {"allowed": true}
# non-patch response i.e. validation response
response = x {
count(patch) == 0
x := {
"allowed": false,
"status": {"reason": reason},
}
reason = concat(", ", deny)
reason != ""
}
# patch response i.e. mutating respone
response = x {
count(patch) > 0
# if there are missing leaves e.g. trying to add a label to something that doesn't
# yet have any, we need to create the leaf nodes as well
fullPatches := ensureParentPathsExist(cast_array(patch))
x := {
"allowed": true,
"patchType": "JSONPatch",
"patch": base64.encode(json.marshal(fullPatches)),
}
}
isValidRequest {
# not sure if this might be a race condition, it might get called before
# all the validation rules have been run
count(deny) == 0
}
isCreateOrUpdate {
isCreate
}
isCreateOrUpdate {
isUpdate
}
isCreate {
input.request.operation == "CREATE"
}
isUpdate {
input.request.operation == "UPDATE"
}
###########################################################################
# PATCH helpers
# Note: These rules assume that the input is an object
# not an AdmissionRequest, because labels and annotations
# can apply to various sub-objects within a request
# So from the context of an AdmissionRequest they need to
# be called like
# hasLabelValue("foo", "bar") with input as input.request.object
# or
# hasLabelValue("foo", "bar") with input as input.request.oldObject
###########################################################################
hasLabels(obj) {
obj.metadata.labels
}
hasLabel(obj, label) {
obj.metadata.labels[label]
}
hasLabelValue(obj, key, val) {
obj.metadata.labels[key] = val
}
hasAnnotations(obj) {
obj.metadata.annotations
}
hasAnnotation(obj, annotation) {
obj.metadata.annotations[annotation]
}
hasAnnotationValue(obj, key, val) {
obj.metadata.annotations[key] = val
}
###########################################################################
# makeLabelPatch creates a label patch
# Labels can exist on numerous child objects e.g. Deployment.template.metadata
# Use pathPrefix to specify a lower level object, or pass "" to select the
# top level object
# Note: pathPrefix should have a leading '/' but no trailing '/'
###########################################################################
makeLabelPatch(op, key, value, pathPrefix) = patchCode {
patchCode = {
"op": op,
"path": concat("/", [pathPrefix, "metadata/labels", replace(key, "/", "~1")]),
"value": value,
}
}
makeAnnotationPatch(op, key, value, pathPrefix) = patchCode {
patchCode = {
"op": op,
"path": concat("/", [pathPrefix, "metadata/annotations", replace(key, "/", "~1")]),
"value": value,
}
}
# Given array of JSON patches create and prepend new patches that create missing paths.
ensureParentPathsExist(patches) = result {
# Convert patches to a set
paths := {p.path | p := patches[_]}
# Compute all missing subpaths.
# Iterate over all paths and over all subpaths
# If subpath doesn't exist, add it to the set after making it a string
missingPaths := {sprintf("/%s", [concat("/", prefixPath)]) |
paths[path]
pathArray := split(path, "/")
pathArray[i] # walk over path
i > 0 # skip initial element
# array of all elements in path up to i
prefixPath := [pathArray[j] | pathArray[j]; j < i; j > 0] # j > 0: skip initial element
walkPath := [toWalkElement(x) | x := prefixPath[_]]
not inputPathExists(walkPath) with input as input.request.object
}
# Sort paths, to ensure they apply in correct order
ordered_paths := sort(missingPaths)
# Return new patches prepended to original patches.
# Don't forget to prepend all paths with a /
new_patches := [{"op": "add", "path": p, "value": {}} |
p := ordered_paths[_]
]
result := array.concat(new_patches, patches)
}
# Check that the given @path exists as part of the input object.
inputPathExists(path) {
walk(input, [path, _])
}
toWalkElement(str) = str {
not re_match("^[0-9]+$", str)
}
toWalkElement(str) = x {
re_match("^[0-9]+$", str)
x := to_number(str)
}
# Top-level deny and patch rules that delegate to kubernetes.admission package.
# If you want to override the deny and patch behaviour of kubernetes.admission package,
# do so here.
deny[msg] {
data.kubernetes.admission.deny[msg]
}
patch[p] {
data.kubernetes.admission.patch[p]
}