Skip to content

Commit 18e463d

Browse files
committed
Integrate ValidatingAdmissionPolicy and Webhooks backends with new UI
Signed-off-by: Kevin Conner <[email protected]>
1 parent b4d1dc9 commit 18e463d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+4052
-80
lines changed

Makefile

+7-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ serve: ## Serve static files.
3131
#go run cmd/server/main.go --dir web/
3232

3333
.PHONY: update-data
34-
update-data: ## Update the web/assets/data.json file.
35-
yq -ojson '.' examples.yaml > web/assets/data.json
36-
yq -ojson -i '.versions.cel-go = "$(CEL_GO_VERSION)"' web/assets/data.json
34+
update-data: ## Update the example files
35+
yq -ojson '.' examples.yaml > web/assets/examples/cel.json
36+
yq -ojson -i '.versions.cel-go = "$(CEL_GO_VERSION)"' web/assets/examples/cel.json
37+
yq -ojson '.' validating_examples.yaml > web/assets/examples/vap.json
38+
yq -ojson -i '.versions.cel-go = "$(CEL_GO_VERSION)"' web/assets/examples/vap.json
39+
yq -ojson '.' webhooks_examples.yaml > web/assets/examples/webhooks.json
40+
yq -ojson -i '.versions.cel-go = "$(CEL_GO_VERSION)"' web/assets/examples/webhooks.json
3741

3842
.PHONY: addlicense
3943
addlicense: ## Add copyright license headers in source code files.

cmd/wasm/main.go

+106-5
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,82 @@ import (
2121
"fmt"
2222
"syscall/js"
2323

24-
"gopkg.in/yaml.v3"
25-
2624
"github.com/undistro/cel-playground/eval"
25+
"github.com/undistro/cel-playground/k8s"
26+
"gopkg.in/yaml.v2"
2727
)
2828

29+
type execFunction func(mode string, argMap js.Value) (string, error)
30+
31+
func getArg(value js.Value, name string) []byte {
32+
arg := value.Get(name)
33+
if arg.Type() == js.TypeString {
34+
return []byte(arg.String())
35+
}
36+
return []byte{}
37+
}
38+
39+
var modeExecFns = map[string]execFunction{
40+
"cel": func(mode string, argMap js.Value) (string, error) {
41+
return eval.CelEval(
42+
getArg(argMap, "cel"),
43+
getArg(argMap, "dataInput"),
44+
)
45+
},
46+
"vap": func(mode string, argMap js.Value) (string, error) {
47+
return k8s.EvalValidatingAdmissionPolicy(
48+
getArg(argMap, "vap"),
49+
getArg(argMap, "dataOriginal"),
50+
getArg(argMap, "dataUpdated"),
51+
getArg(argMap, "dataNamespace"),
52+
getArg(argMap, "dataRequest"),
53+
getArg(argMap, "dataAuthorizer"),
54+
)
55+
},
56+
"webhooks": func(mode string, argMap js.Value) (string, error) {
57+
return k8s.EvalWebhook(
58+
getArg(argMap, "webhooks"),
59+
getArg(argMap, "dataOriginal"),
60+
getArg(argMap, "dataUpdated"),
61+
getArg(argMap, "dataRequest"),
62+
getArg(argMap, "dataAuthorizer"),
63+
)
64+
},
65+
}
66+
2967
func main() {
30-
evalFunc := js.FuncOf(evalWrapper)
31-
js.Global().Set("eval", evalFunc)
32-
defer evalFunc.Release()
68+
defer addFunction("eval", dynamicEvalWrapper).Release()
69+
// defer addFunction("vapEval", validatingAdmissionPolicyWrapper).Release()
70+
// defer addFunction("webhookEval", webhookWrapper).Release()
3371
<-make(chan bool)
3472
}
3573

74+
func addFunction(name string, fn func(js.Value, []js.Value) any) js.Func {
75+
function := js.FuncOf(fn)
76+
js.Global().Set(name, function)
77+
return function
78+
}
79+
80+
func dynamicEvalWrapper(_ js.Value, args []js.Value) any {
81+
if len(args) < 2 {
82+
return response("", errors.New("invalid arguments"))
83+
}
84+
if args[0].Type() != js.TypeString || args[1].Type() != js.TypeObject {
85+
return response("", errors.New("invalid argument types, expecting string and object"))
86+
}
87+
mode := args[0].String()
88+
fn, ok := modeExecFns[mode]
89+
if !ok {
90+
return response("", fmt.Errorf("unknown mode %s", mode))
91+
}
92+
93+
output, err := fn(mode, args[1])
94+
if err != nil {
95+
return response("", err)
96+
}
97+
return response(output, nil)
98+
}
99+
36100
// evalWrapper wraps the eval function with `syscall/js` parameters
37101
func evalWrapper(_ js.Value, args []js.Value) any {
38102
if len(args) < 2 {
@@ -52,6 +116,43 @@ func evalWrapper(_ js.Value, args []js.Value) any {
52116
return response(output, nil)
53117
}
54118

119+
// ValidatingAdmissionPolicy functionality
120+
func validatingAdmissionPolicyWrapper(_ js.Value, args []js.Value) any {
121+
if len(args) < 6 {
122+
return response("", errors.New("invalid arguments"))
123+
}
124+
policy := []byte(args[0].String())
125+
originalValue := []byte(args[1].String())
126+
updatedValue := []byte(args[2].String())
127+
namespace := []byte(args[3].String())
128+
request := []byte(args[4].String())
129+
authorizer := []byte(args[5].String())
130+
131+
output, err := k8s.EvalValidatingAdmissionPolicy(policy, originalValue, updatedValue, namespace, request, authorizer)
132+
if err != nil {
133+
return response("", err)
134+
}
135+
return response(output, nil)
136+
}
137+
138+
// Webhook functionality
139+
func webhookWrapper(_ js.Value, args []js.Value) any {
140+
if len(args) < 5 {
141+
return response("", errors.New("invalid arguments"))
142+
}
143+
policy := []byte(args[0].String())
144+
originalValue := []byte(args[1].String())
145+
updatedValue := []byte(args[2].String())
146+
request := []byte(args[3].String())
147+
authorizer := []byte(args[4].String())
148+
149+
output, err := k8s.EvalWebhook(policy, originalValue, updatedValue, request, authorizer)
150+
if err != nil {
151+
return response("", err)
152+
}
153+
return response(output, nil)
154+
}
155+
55156
func response(out string, err error) any {
56157
if err != nil {
57158
out = err.Error()

eval/eval.go

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/google/cel-go/ext"
2626
"github.com/google/cel-go/interpreter"
2727
"google.golang.org/protobuf/types/known/structpb"
28+
"gopkg.in/yaml.v2"
2829
k8s "k8s.io/apiserver/pkg/cel/library"
2930
)
3031

@@ -76,6 +77,14 @@ var celProgramOptions = []cel.ProgramOption{
7677
cel.CostTrackerOptions(interpreter.PresenceTestHasCost(false)),
7778
}
7879

80+
func CelEval(exp []byte, input []byte) (string, error) {
81+
var inputMap map[string]any
82+
if err := yaml.Unmarshal(input, &inputMap); err != nil {
83+
return "", fmt.Errorf("failed to decode input: %w", err)
84+
}
85+
return Eval(string(exp), inputMap)
86+
}
87+
7988
// Eval evaluates the cel expression against the given input
8089
func Eval(exp string, input map[string]any) (string, error) {
8190
inputVars := make([]cel.EnvOption, 0, len(input))

examples.yaml

+14-14
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ examples:
2727
|| (account.overdraftProtection
2828
&& account.overdraftLimit >= transaction.withdrawal - account.balance)
2929
30-
data: |
30+
dataInput: |
3131
# Here is the input data in YAML or JSON format.
3232
3333
account:
@@ -46,7 +46,7 @@ examples:
4646
container.image.startsWith(registry)
4747
)
4848
)
49-
data: |
49+
dataInput: |
5050
params:
5151
allowedRegistries:
5252
- myregistry.com
@@ -83,7 +83,7 @@ examples:
8383
port.hostPort == 0
8484
)
8585
)
86-
data: |
86+
dataInput: |
8787
object:
8888
apiVersion: apps/v1
8989
kind: Deployment
@@ -129,7 +129,7 @@ examples:
129129
!has(container.securityContext) || !has(container.securityContext.runAsNonRoot) || container.securityContext.runAsNonRoot != false
130130
)
131131
)
132-
data: |
132+
dataInput: |
133133
object:
134134
apiVersion: apps/v1
135135
kind: Deployment
@@ -175,7 +175,7 @@ examples:
175175
!has(container.securityContext.capabilities.add) ||
176176
container.securityContext.capabilities.add.all(cap, cap in params.allowedCapabilities)
177177
)
178-
data: |
178+
dataInput: |
179179
params:
180180
allowedCapabilities: [NET_BIND_SERVICE]
181181
object:
@@ -214,7 +214,7 @@ examples:
214214
// the regex above is suggested by semver.org: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
215215
// allowing the "v" prefix
216216
)
217-
data: |
217+
dataInput: |
218218
object:
219219
apiVersion: v1
220220
kind: Pod
@@ -244,7 +244,7 @@ examples:
244244
&& url(object.href).getPort() == '80'
245245
&& url(object.href).getEscapedPath() == '/path'
246246
&& url(object.href).getQuery().size() == 1
247-
data: |
247+
dataInput: |
248248
{
249249
"object": {
250250
"href": "https://user:[email protected]:80/path?query=val#fragment"
@@ -266,7 +266,7 @@ examples:
266266
.filter(c, c.startsWith('group'))
267267
.all(c, jwt.extra_claims[c]
268268
.all(g, g.endsWith('@acme.co')))
269-
data: |
269+
dataInput: |
270270
jwt: {
271271
"iss": "auth.acme.com:12350",
272272
"sub": "serviceAccount:[email protected]",
@@ -286,15 +286,15 @@ examples:
286286

287287
- name: "Optional"
288288
cel: 'object.?foo.orValue("fallback")'
289-
data: "object: {}"
289+
dataInput: "object: {}"
290290
category: "General"
291291

292292
- name: "Duration and timestamp"
293293
cel: |
294294
// Validate that 'expired' date is after a 'created' date plus a 'ttl' duration
295295
has(object.expired) &&
296296
timestamp(object.created) + duration(object.ttl) < timestamp(object.expired)
297-
data: |
297+
dataInput: |
298298
object:
299299
created: "2023-06-14T02:00:14+00:00"
300300
ttl: "5m"
@@ -310,7 +310,7 @@ examples:
310310
.add(quantity("700M"))
311311
.sub(1) // test without this subtraction
312312
.isLessThan(quantity(object.limit))
313-
data: |
313+
dataInput: |
314314
object:
315315
memory: 1.3G
316316
limit: 2G
@@ -334,7 +334,7 @@ examples:
334334
// expression: "response.code >= 400 || xds.cluster_name == 'BlackHoleCluster' || xds.cluster_name == 'PassthroughCluster' "
335335
336336
response.code >= 400 || (xds.cluster_name == 'BlackHoleCluster' || xds.cluster_name == 'PassthroughCluster')
337-
data: |
337+
dataInput: |
338338
# The following configuration is true access logs only when the response code is greater or equal to 400
339339
# or the request went to the BlackHoleCluster or the PassthroughCluster
340340
request:
@@ -404,7 +404,7 @@ examples:
404404
// value: "request.host" # <--- CEL
405405
406406
has(request.host) ? request.host : "unknown"
407-
data: |
407+
dataInput: |
408408
request:
409409
duration: "4.144461ms"
410410
headers:
@@ -474,5 +474,5 @@ examples:
474474

475475
- name: "Blank"
476476
cel: ""
477-
data: ""
477+
dataInput: ""
478478
category: "Blank"

go.mod

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
module github.com/undistro/cel-playground
22

3-
go 1.21
3+
go 1.22.0
44

55
require (
66
github.com/google/cel-go v0.17.8
7-
google.golang.org/protobuf v1.31.0
7+
google.golang.org/protobuf v1.33.0
88
gopkg.in/yaml.v3 v3.0.1
9-
k8s.io/apiserver v0.28.4
9+
k8s.io/api v0.29.2
10+
k8s.io/apimachinery v0.29.2
11+
k8s.io/apiserver v0.29.2
12+
k8s.io/client-go v0.29.2
1013
)
1114

1215
require (
1316
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
1417
github.com/davecgh/go-spew v1.1.1 // indirect
1518
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
16-
github.com/go-logr/logr v1.3.0 // indirect
19+
github.com/go-logr/logr v1.4.1 // indirect
1720
github.com/go-openapi/jsonpointer v0.19.6 // indirect
1821
github.com/go-openapi/jsonreference v0.20.2 // indirect
1922
github.com/go-openapi/swag v0.22.3 // indirect
2023
github.com/gogo/protobuf v1.3.2 // indirect
21-
github.com/golang/protobuf v1.5.3 // indirect
24+
github.com/golang/protobuf v1.5.4 // indirect
2225
github.com/google/gnostic-models v0.6.8 // indirect
2326
github.com/google/gofuzz v1.2.0 // indirect
2427
github.com/google/uuid v1.3.0 // indirect
@@ -29,27 +32,29 @@ require (
2932
github.com/modern-go/reflect2 v1.0.2 // indirect
3033
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
3134
github.com/stoewer/go-strcase v1.2.0 // indirect
32-
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
33-
golang.org/x/net v0.17.0 // indirect
35+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
36+
golang.org/x/net v0.21.0 // indirect
3437
golang.org/x/oauth2 v0.10.0 // indirect
35-
golang.org/x/sys v0.13.0 // indirect
36-
golang.org/x/term v0.13.0 // indirect
37-
golang.org/x/text v0.13.0 // indirect
38+
golang.org/x/sys v0.17.0 // indirect
39+
golang.org/x/term v0.17.0 // indirect
40+
golang.org/x/text v0.14.0 // indirect
3841
golang.org/x/time v0.3.0 // indirect
3942
google.golang.org/appengine v1.6.7 // indirect
4043
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
4144
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
4245
gopkg.in/inf.v0 v0.9.1 // indirect
4346
gopkg.in/yaml.v2 v2.4.0 // indirect
44-
k8s.io/api v0.28.4 // indirect
45-
k8s.io/apimachinery v0.28.4 // indirect
46-
k8s.io/client-go v0.28.4 // indirect
47-
k8s.io/klog/v2 v2.110.1 // indirect
48-
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
47+
k8s.io/klog/v2 v2.120.1 // indirect
48+
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
4949
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
5050
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
5151
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
5252
sigs.k8s.io/yaml v1.3.0 // indirect
5353
)
5454

55-
replace k8s.io/apiserver v0.28.4 => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20231128103858-022d50fe3a1b
55+
replace (
56+
k8s.io/api v0.29.2 => github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20240306180723-5f2c9e73c01f
57+
k8s.io/apimachinery v0.29.2 => github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20240306180723-5f2c9e73c01f
58+
k8s.io/apiserver v0.29.2 => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20240306180723-5f2c9e73c01f
59+
k8s.io/client-go v0.29.2 => github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20240306180723-5f2c9e73c01f
60+
)

0 commit comments

Comments
 (0)