-
Notifications
You must be signed in to change notification settings - Fork 360
feat: add custom key for cel expression support #961
Changes from 8 commits
b6bb02f
82410b0
559eb29
8ea4d74
91f7629
31782b7
175987a
1aa323a
f4ce267
5e42045
1f64bd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)" | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,140 @@ | ||||||
// This file defines a custom key to implement the logic for rego rule: | ||||||
|
||||||
package jsonSchemaValidator | ||||||
|
||||||
import ( | ||||||
"encoding/json" | ||||||
"fmt" | ||||||
|
||||||
"github.com/google/cel-go/cel" | ||||||
"github.com/santhosh-tekuri/jsonschema/v5" | ||||||
"gopkg.in/yaml.v3" | ||||||
) | ||||||
|
||||||
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 object") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
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 | ||||||
dataValueObjectWrapper := make(map[string]interface{}) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. naming maybe |
||||||
dataValueObjectWrapper["object"] = dataValue | ||||||
|
||||||
// prepare CEL env inputs - in our case the only input is the resource that should be validated | ||||||
inputs, err := getCELEnvInputs(dataValueObjectWrapper) | ||||||
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(dataValueObjectWrapper) | ||||||
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(CustomKeyValidationErrorKeyPath, "cel expression failure message: %s", celExpression.Message) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "cel expression failure message:" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will show it to Adi |
||||||
} | ||||||
} | ||||||
|
||||||
return nil | ||||||
} | ||||||
|
||||||
type CELExpression struct { | ||||||
Expression string `json:"expression"` | ||||||
Message string `json:"message"` | ||||||
} | ||||||
|
||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok let's see, what is your suggestion? |
||||||
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) { | ||||||
var inputs map[string]any | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that it suppose to be any because it can be any type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to know:) |
||||||
dataValueBytes, _ := json.Marshal(dataValue) | ||||||
if err := yaml.Unmarshal(dataValueBytes, &inputs); err != nil { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's see if I understand this properly.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your comment made me understand it was redundant! thanks |
||||||
return nil, fmt.Errorf("failed to decode input: %s", err) | ||||||
} | ||||||
|
||||||
inputVars := make([]cel.EnvOption, 0, len(inputs)) | ||||||
for input := range inputs { | ||||||
inputVars = append(inputVars, cel.Variable(input, cel.DynType)) | ||||||
} | ||||||
return inputVars, nil | ||||||
} |
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" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"CELDefinition": [ | ||
{ | ||
"expression": 1, | ||
"message": "deployment labels should contain billing label" | ||
} | ||
] | ||
} |
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" | ||
} | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.