This repository has been archived by the owner on Jun 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 361
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add custom key for cel expression support (#961)
* feat: add custom key for cel expression support --------- Co-authored-by: teselil <tzlil@datree.com>
- Loading branch information
1 parent
5d637d2
commit 68a198d
Showing
11 changed files
with
337 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
apiVersion: v1 | ||
policies: | ||
- name: CEL_policy | ||
isDefault: true | ||
rules: | ||
- identifier: CUSTOM_DEPLOYMENT_BILLING_LABEL_EXISTS | ||
messageOnFailure: "workloads labels should contain billing label" | ||
- identifier: CUSTOM_SECRET_ENVIRONMENT_LABEL_EXISTS | ||
messageOnFailure: "secret labels should contain environment label" | ||
|
||
|
||
customRules: | ||
- identifier: CUSTOM_WORKLOADS_BILLING_LABEL_EXISTS | ||
name: Ensure Workloads has billing label [CUSTOM RULE] | ||
defaultMessageOnFailure: workloads labels should contain billing label | ||
schema: | ||
# constraint schema | ||
if: | ||
properties: | ||
kind: | ||
type: string | ||
enum: | ||
- Deployment | ||
- Pod | ||
then: | ||
CELDefinition: | ||
- expression: "object.kind != 'Deployment' || (has(object.metadata.labels) && has(object.metadata.labels.billing))" | ||
message: "deployment labels should contain billing label" | ||
- expression: "object.kind != 'Pod' || (has(object.metadata.labels) && has(object.metadata.labels.billing))" | ||
message: "pod labels should contain billing label" | ||
- identifier: CUSTOM_SECRET_ENVIRONMENT_LABEL_EXISTS | ||
name: Ensure Secret has environment label [CUSTOM RULE] | ||
defaultMessageOnFailure: secret labels should contain environment label | ||
schema: | ||
# constraint schema | ||
if: | ||
properties: | ||
kind: | ||
type: string | ||
enum: | ||
- Secret | ||
then: | ||
CELDefinition: | ||
- expression: "has(object.metadata.labels) && has(object.metadata.labels.environment)" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
pkg/jsonSchemaValidator/extensions/customKeyCELDefinition.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// This file defines a custom key to implement the logic for cel rule: | ||
|
||
package jsonSchemaValidator | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/google/cel-go/cel" | ||
"github.com/santhosh-tekuri/jsonschema/v5" | ||
) | ||
|
||
const CELDefinitionCustomKey = "CELDefinition" | ||
|
||
type CustomKeyCELDefinitionCompiler struct{} | ||
|
||
type CustomKeyCELDefinitionSchema []interface{} | ||
|
||
var CustomKeyCELRule = jsonschema.MustCompileString("customKeyCELDefinition.json", `{ | ||
"properties" : { | ||
"CELDefinition": { | ||
"type": "array" | ||
} | ||
} | ||
}`) | ||
|
||
func (CustomKeyCELDefinitionCompiler) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) { | ||
if customKeyCELRule, ok := m[CELDefinitionCustomKey]; ok { | ||
customKeyCELRuleObj, validObject := customKeyCELRule.([]interface{}) | ||
if !validObject { | ||
return nil, fmt.Errorf("CELDefinition must be an array") | ||
} | ||
|
||
CELDefinitionSchema, err := convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(customKeyCELRuleObj) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(CELDefinitionSchema.CELExpressions) == 0 { | ||
return nil, fmt.Errorf("CELDefinition can't be empty") | ||
} | ||
|
||
return CustomKeyCELDefinitionSchema(customKeyCELRuleObj), nil | ||
} | ||
return nil, nil | ||
} | ||
|
||
func (customKeyCELDefinitionSchema CustomKeyCELDefinitionSchema) Validate(ctx jsonschema.ValidationContext, dataValue interface{}) error { | ||
CELDefinitionSchema, err := convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(customKeyCELDefinitionSchema) | ||
if err != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) | ||
} | ||
// wrap dataValue (the resource that should be validated) inside a struct with parent object key | ||
resourceWithParentKey := make(map[string]interface{}) | ||
resourceWithParentKey["object"] = dataValue | ||
|
||
// prepare CEL env inputs - in our case the only input is the resource that should be validated | ||
inputs, err := getCELEnvInputs(resourceWithParentKey) | ||
if err != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) | ||
} | ||
|
||
env, err := cel.NewEnv(inputs...) | ||
if err != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, err.Error()) | ||
} | ||
|
||
for _, celExpression := range CELDefinitionSchema.CELExpressions { | ||
ast, issues := env.Compile(celExpression.Expression) | ||
if issues != nil && issues.Err() != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression compile error: %s", issues.Err()) | ||
} | ||
|
||
prg, err := env.Program(ast) | ||
if err != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, "cel program construction error: %s", err) | ||
} | ||
|
||
res1, _, err := prg.Eval(resourceWithParentKey) | ||
if err != nil { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, "cel evaluation error: %s", err) | ||
} | ||
|
||
if res1.Type().TypeName() != "bool" { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression needs to return a boolean") | ||
} | ||
|
||
celReturnValue, ok := res1.Value().(bool) | ||
if !ok { | ||
return ctx.Error(CustomKeyValidationErrorKeyPath, "cel expression needs to return a boolean") | ||
} | ||
if !celReturnValue { | ||
return ctx.Error(CELDefinitionCustomKey, "values in data value %v do not match", dataValue) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type CELExpression struct { | ||
Expression string `json:"expression"` | ||
} | ||
|
||
type CELDefinition struct { | ||
CELExpressions []CELExpression | ||
} | ||
|
||
func convertCustomKeyCELDefinitionSchemaToCELDefinitionSchema(CELDefinitionSchema CustomKeyCELDefinitionSchema) (*CELDefinition, error) { | ||
var CELDefinition CELDefinition | ||
for _, CELExpressionFromSchema := range CELDefinitionSchema { | ||
var CELExpression CELExpression | ||
b, err := json.Marshal(CELExpressionFromSchema) | ||
if err != nil { | ||
return nil, fmt.Errorf("CELExpression failed to marshal to json, %s", err.Error()) | ||
} | ||
err = json.Unmarshal(b, &CELExpression) | ||
if err != nil { | ||
return nil, fmt.Errorf("CELExpression must be an object of type CELExpression %s", err.Error()) | ||
} | ||
CELDefinition.CELExpressions = append(CELDefinition.CELExpressions, CELExpression) | ||
} | ||
|
||
return &CELDefinition, nil | ||
} | ||
|
||
func getCELEnvInputs(dataValue map[string]interface{}) ([]cel.EnvOption, error) { | ||
inputVars := make([]cel.EnvOption, 0, len(dataValue)) | ||
for input := range dataValue { | ||
inputVars = append(inputVars, cel.Variable(input, cel.DynType)) | ||
} | ||
return inputVars, nil | ||
} |
8 changes: 8 additions & 0 deletions
8
pkg/jsonSchemaValidator/test_fixtures/invalid-cel-definition-expression.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"CELDefinition": [ | ||
{ | ||
"expression": "hassss(object.metadata.labels) && has(object.metadata.labels.billing)", | ||
"message": "deployment labels should contain billing label" | ||
} | ||
] | ||
} |
8 changes: 8 additions & 0 deletions
8
pkg/jsonSchemaValidator/test_fixtures/invalid-cel-definition.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"CELDefinition": [ | ||
{ | ||
"expression": 1, | ||
"message": "deployment labels should contain billing label" | ||
} | ||
] | ||
} |
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions
8
pkg/jsonSchemaValidator/test_fixtures/valid-cel-definition.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"CELDefinition": [ | ||
{ | ||
"expression": "has(object.metadata.labels) && has(object.metadata.labels.billing)", | ||
"message": "deployment labels should contain billing label" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.