Skip to content

Commit

Permalink
Add cost to k8s and reenable in the UI
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Conner <[email protected]>
  • Loading branch information
knrc committed Apr 22, 2024
1 parent 23df193 commit 2be6077
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 60 deletions.
59 changes: 0 additions & 59 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (

"github.com/undistro/cel-playground/eval"
"github.com/undistro/cel-playground/k8s"
"gopkg.in/yaml.v2"
)

type execFunction func(mode string, argMap js.Value) (string, error)
Expand Down Expand Up @@ -66,8 +65,6 @@ var modeExecFns = map[string]execFunction{

func main() {
defer addFunction("eval", dynamicEvalWrapper).Release()
// defer addFunction("vapEval", validatingAdmissionPolicyWrapper).Release()
// defer addFunction("webhookEval", webhookWrapper).Release()
<-make(chan bool)
}

Expand Down Expand Up @@ -97,62 +94,6 @@ func dynamicEvalWrapper(_ js.Value, args []js.Value) any {
return response(output, nil)
}

// evalWrapper wraps the eval function with `syscall/js` parameters
func evalWrapper(_ js.Value, args []js.Value) any {
if len(args) < 2 {
return response("", errors.New("invalid arguments"))
}
exp := args[0].String()
is := args[1].String()

var input map[string]any
if err := yaml.Unmarshal([]byte(is), &input); err != nil {
return response("", fmt.Errorf("failed to decode input: %w", err))
}
output, err := eval.Eval(exp, input)
if err != nil {
return response("", err)
}
return response(output, nil)
}

// ValidatingAdmissionPolicy functionality
func validatingAdmissionPolicyWrapper(_ js.Value, args []js.Value) any {
if len(args) < 6 {
return response("", errors.New("invalid arguments"))
}
policy := []byte(args[0].String())
originalValue := []byte(args[1].String())
updatedValue := []byte(args[2].String())
namespace := []byte(args[3].String())
request := []byte(args[4].String())
authorizer := []byte(args[5].String())

output, err := k8s.EvalValidatingAdmissionPolicy(policy, originalValue, updatedValue, namespace, request, authorizer)
if err != nil {
return response("", err)
}
return response(output, nil)
}

// Webhook functionality
func webhookWrapper(_ js.Value, args []js.Value) any {
if len(args) < 5 {
return response("", errors.New("invalid arguments"))
}
policy := []byte(args[0].String())
originalValue := []byte(args[1].String())
updatedValue := []byte(args[2].String())
request := []byte(args[3].String())
authorizer := []byte(args[4].String())

output, err := k8s.EvalWebhook(policy, originalValue, updatedValue, request, authorizer)
if err != nil {
return response("", err)
}
return response(output, nil)
}

func response(out string, err error) any {
if err != nil {
out = err.Error()
Expand Down
36 changes: 36 additions & 0 deletions k8s/evals.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type EvalResponse struct {
Validations []*EvalResult `json:"validations,omitempty"`
AuditAnnotations []*EvalResult `json:"auditAnnotations,omitempty"`
WebhookMatchConditions [][]*EvalResult `json:"webhookMatchConditions,omitempty"`
Cost *uint64 `json:"cost, omitempty"`
}

func getResults(val *ref.Val) (any, *string) {
Expand Down Expand Up @@ -174,15 +175,50 @@ func generateEvalArrayResults(responses []evalResponses) [][]*EvalResult {
return evalsArray
}

func calculateLazyEvalCost(lazyEvals lazyEvalMap) uint64 {
var cost uint64
for _, lazyEval := range lazyEvals {
if lazyEval.val != nil {
cost += *lazyEval.val.details.ActualCost()
}
}
return cost
}

func calculateEvalResponsesCost(evals evalResponses) uint64 {
var cost uint64
for _, eval := range evals {
cost += *eval.details.ActualCost()
}
return cost
}

func calculateEvalResponsesArrayCost(evalsArray []evalResponses) uint64 {
var cost uint64
for _, evals := range evalsArray {
cost += calculateEvalResponsesCost(evals)
}
return cost
}

func generateEvalResponse(matchConditionsVariableNames []string, matchConditionsVariableLazyEvals lazyEvalMap, matchConditionsEvals evalResponses,
validationVariableNames []string, validationVariableLazyEvals lazyEvalMap, validationEvals evalResponses,
auditAnnotationEvals evalResponses, webhookMatchConditionsEvals []evalResponses) *EvalResponse {

cost := calculateLazyEvalCost(matchConditionsVariableLazyEvals)
cost += calculateEvalResponsesCost(matchConditionsEvals)
cost += calculateLazyEvalCost(validationVariableLazyEvals)
cost += calculateEvalResponsesCost(validationEvals)
cost += calculateEvalResponsesCost(auditAnnotationEvals)
cost += calculateEvalResponsesArrayCost(webhookMatchConditionsEvals)

return &EvalResponse{
MatchConditionsVariables: generateEvalVariables(matchConditionsVariableNames, matchConditionsVariableLazyEvals),
MatchConditions: generateEvalResults(matchConditionsEvals),
ValidationVariables: generateEvalVariables(validationVariableNames, validationVariableLazyEvals),
Validations: generateEvalResults(validationEvals),
AuditAnnotations: generateEvalResults(auditAnnotationEvals),
WebhookMatchConditions: generateEvalArrayResults(webhookMatchConditionsEvals),
Cost: &cost,
}
}
12 changes: 12 additions & 0 deletions k8s/validatingadmissionpolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func TestValidationEval(t *testing.T) {
updated: "updated1.yaml",
expected: k8s.EvalResponse{
Validations: []*k8s.EvalResult{{Message: "All production deployments should be HA with at least three replicas", Result: false, Cost: uint64ptr(4)}},
Cost: uint64ptr(4),
},
}, {
name: "test an expression which should succeed",
Expand All @@ -76,6 +77,7 @@ func TestValidationEval(t *testing.T) {
updated: "updated2.yaml",
expected: k8s.EvalResponse{
Validations: []*k8s.EvalResult{{Result: true, Cost: uint64ptr(4)}},
Cost: uint64ptr(4),
},
}, {
name: "test an expression with variables, expression should fail with no audit annotation",
Expand All @@ -89,6 +91,7 @@ func TestValidationEval(t *testing.T) {
Cost: uint64ptr(6),
}},
Validations: []*k8s.EvalResult{{Result: false, Cost: uint64ptr(2)}},
Cost: uint64ptr(8),
},
}, {
name: "test an expression with variables, expression should succeed with audit annotation",
Expand All @@ -110,6 +113,7 @@ func TestValidationEval(t *testing.T) {
Message: "Label for foo is set to bar",
Cost: uint64ptr(2),
}},
Cost: uint64ptr(15),
},
}, {
name: "test an expression with variables evaluating to a map, expression should succeed",
Expand All @@ -126,6 +130,7 @@ func TestValidationEval(t *testing.T) {
Cost: uint64ptr(5),
}},
Validations: []*k8s.EvalResult{{Result: true, Cost: uint64ptr(2)}},
Cost: uint64ptr(7),
},
}, {
name: "test an expression with variables evaluating to query parameters in a URL, expression should succeed",
Expand All @@ -141,6 +146,7 @@ func TestValidationEval(t *testing.T) {
Cost: uint64ptr(14),
}},
Validations: []*k8s.EvalResult{{Result: true, Cost: uint64ptr(2)}},
Cost: uint64ptr(16),
},
}, {
name: "test valid matchConditions, should see validations and auditAnnotations",
Expand All @@ -164,6 +170,7 @@ func TestValidationEval(t *testing.T) {
Message: "Name is kubernetes-bootcamp, namespace is default",
Cost: uint64ptr(9),
}},
Cost: uint64ptr(24),
},
}, {
name: "test invalid matchConditions, should not see validations and auditAnnotations",
Expand All @@ -186,6 +193,7 @@ func TestValidationEval(t *testing.T) {
Result: false,
Cost: uint64ptr(5),
}},
Cost: uint64ptr(11),
},
}, {
name: "test an expression using namespace attributes",
Expand Down Expand Up @@ -233,6 +241,7 @@ func TestValidationEval(t *testing.T) {
Result: true,
Cost: uint64ptr(11),
}},
Cost: uint64ptr(50),
},
}, {
name: "test an expression using request attributes",
Expand All @@ -242,6 +251,7 @@ func TestValidationEval(t *testing.T) {
request: "request1 request.yaml",
expected: k8s.EvalResponse{
Validations: []*k8s.EvalResult{{Result: true, Cost: uint64ptr(12)}},
Cost: uint64ptr(12),
},
}, {
name: "test an expression using allowed authorizer checks",
Expand Down Expand Up @@ -269,6 +279,7 @@ func TestValidationEval(t *testing.T) {
Message: "Deployment is allowed in namespace default",
Cost: uint64ptr(4),
}},
Cost: uint64ptr(23),
},
}, {
name: "test an expression using disallowed authorizer checks",
Expand All @@ -291,6 +302,7 @@ func TestValidationEval(t *testing.T) {
Result: false,
Cost: uint64ptr(10),
}},
Cost: uint64ptr(19),
},
}}
for _, tt := range tests {
Expand Down
6 changes: 5 additions & 1 deletion web/assets/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ function run() {
output.style.color = "red";
} else {
const obj = JSON.parse(resultOutput);

const resultCost = obj?.cost
delete obj.cost

const objValues = Object.values(obj);
const hasSomeChildrenArray = objValues.some((values) =>
Array.isArray(values)
Expand All @@ -79,7 +83,7 @@ function run() {
output.style.color = "white";
}

setCost(obj?.cost);
setCost(resultCost);
}
}

Expand Down
Binary file modified web/assets/main.wasm.gz
Binary file not shown.

0 comments on commit 2be6077

Please sign in to comment.