2019-05-14 01:17:28 +00:00
package engine
2019-05-14 15:10:25 +00:00
import (
2019-06-26 01:16:02 +00:00
"errors"
2019-06-20 15:21:55 +00:00
"fmt"
"path/filepath"
"reflect"
2019-06-05 10:43:07 +00:00
"strconv"
2019-06-20 15:21:55 +00:00
"strings"
2019-08-19 23:40:10 +00:00
"time"
2019-05-16 16:31:02 +00:00
2019-06-26 01:16:02 +00:00
"github.com/golang/glog"
2019-08-09 23:55:43 +00:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
2019-08-13 18:32:12 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-05-14 15:10:25 +00:00
)
2019-08-26 20:34:42 +00:00
// // Validate handles validating admission request
// // Checks the target resources for rules defined in the policy
// func Validate(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponse) {
// // var response EngineResponse
// startTime := time.Now()
// glog.V(4).Infof("started applying validation rules of policy %q (%v)", policy.Name, startTime)
// defer func() {
// response.ExecutionTime = time.Since(startTime)
// glog.V(4).Infof("Finished applying validation rules policy %v (%v)", policy.Name, response.ExecutionTime)
// glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.RulesAppliedCount, policy.Name)
// }()
// incrementAppliedRuleCount := func() {
// // rules applied succesfully count
// response.RulesAppliedCount++
// }
// resourceRaw, err := resource.MarshalJSON()
// if err != nil {
// glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err)
// response.PatchedResource = resource
// return response
// }
2019-05-14 15:10:25 +00:00
2019-08-26 20:34:42 +00:00
// var resourceInt interface{}
// if err := json.Unmarshal(resourceRaw, &resourceInt); err != nil {
// glog.V(4).Infof("unable to unmarshal resource : %v\n", err)
// response.PatchedResource = resource
// return response
// }
2019-06-05 10:43:07 +00:00
2019-08-26 20:34:42 +00:00
// var ruleInfos []info.RuleInfo
2019-08-09 19:59:37 +00:00
2019-08-26 20:34:42 +00:00
// for _, rule := range policy.Spec.Rules {
// if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
// continue
// }
2019-06-05 10:43:07 +00:00
2019-08-26 20:34:42 +00:00
// // check if the resource satisfies the filter conditions defined in the rule
// // TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// // dont statisfy a policy rule resource description
// ok := MatchesResourceDescription(resource, rule)
// if !ok {
// glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
// continue
// }
2019-05-14 15:10:25 +00:00
2019-08-26 20:34:42 +00:00
// // ruleInfo := validatePatterns(resource, rule)
// incrementAppliedRuleCount()
// // ruleInfos = append(ruleInfos, ruleInfo)
// }
// response.RuleInfos = ruleInfos
// return response
// }
2019-08-21 19:38:15 +00:00
// validatePatterns validate pattern and anyPattern
2019-08-24 01:34:23 +00:00
func validatePatterns ( resource unstructured . Unstructured , rule kyverno . Rule ) ( response RuleResponse ) {
startTime := time . Now ( )
glog . V ( 4 ) . Infof ( "started applying validation rule %q (%v)" , rule . Name , startTime )
response . Name = rule . Name
response . Type = Validation . String ( )
defer func ( ) {
response . RuleStats . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "finished applying validation rule %q (%v)" , response . Name , response . RuleStats . ProcessingTime )
} ( )
2019-08-09 19:59:37 +00:00
2019-08-24 01:34:23 +00:00
// either pattern or anyPattern can be specified in Validation rule
if rule . Validation . Pattern != nil {
err := validateResourceWithPattern ( resource . Object , rule . Validation . Pattern )
2019-06-26 01:16:02 +00:00
if err != nil {
2019-08-24 01:34:23 +00:00
// rule application failed
glog . V ( 4 ) . Infof ( "failed to apply validation for rule %s on resource %s/%s/%s, pattern %v " , rule . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , rule . Validation . Pattern )
response . Success = false
response . Message = fmt . Sprintf ( "failed to apply pattern: %v" , err )
return response
2019-06-26 01:16:02 +00:00
}
2019-08-24 01:34:23 +00:00
// rule application succesful
glog . V ( 4 ) . Infof ( "rule %s pattern validated succesfully on resource %s/%s/%s" , rule . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) )
response . Success = true
response . Message = fmt . Sprintf ( "validation pattern succesfully validated" )
return response
2019-05-14 15:10:25 +00:00
}
2019-08-24 01:34:23 +00:00
//TODO: add comments to explain the flow
if rule . Validation . AnyPattern != nil {
var errs [ ] error
for _ , pattern := range rule . Validation . AnyPattern {
2019-08-21 19:38:15 +00:00
if err := validateResourceWithPattern ( resource . Object , pattern ) ; err != nil {
errs = append ( errs , err )
}
2019-08-24 01:34:23 +00:00
failedPattern := len ( errs )
patterns := len ( rule . Validation . AnyPattern )
// all patterns fail
if failedPattern == patterns {
// any Pattern application failed
glog . V ( 4 ) . Infof ( "none of anyPattern were processed: %v" , errs )
response . Success = false
response . Message = fmt . Sprintf ( "None of anyPattern succeed: %v" , errs )
return response
}
// any Pattern application succesful
2019-08-21 19:38:15 +00:00
glog . V ( 4 ) . Infof ( "%d/%d patterns validated succesfully on resource %s/%s" , patterns - failedPattern , patterns , resource . GetNamespace ( ) , resource . GetName ( ) )
2019-08-24 01:34:23 +00:00
response . Success = true
response . Message = fmt . Sprintf ( "%d/%d patterns succesfully validated" , patterns - failedPattern , patterns )
return response
2019-08-21 19:38:15 +00:00
}
}
2019-08-24 01:34:23 +00:00
return RuleResponse { }
2019-05-14 15:10:25 +00:00
}
2019-06-10 14:06:31 +00:00
// validateResourceWithPattern is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
2019-06-26 01:16:02 +00:00
func validateResourceWithPattern ( resource , pattern interface { } ) error {
2019-06-20 15:21:55 +00:00
return validateResourceElement ( resource , pattern , pattern , "/" )
2019-06-05 10:43:07 +00:00
}
2019-06-10 14:06:31 +00:00
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
// and calls corresponding handler
// Pattern tree and resource tree can have different structure. In this case validation fails
2019-06-26 01:16:02 +00:00
func validateResourceElement ( resourceElement , patternElement , originPattern interface { } , path string ) error {
var err error
2019-06-10 14:06:31 +00:00
switch typedPatternElement := patternElement . ( type ) {
// map
2019-06-05 10:43:07 +00:00
case map [ string ] interface { } :
2019-06-10 14:06:31 +00:00
typedResourceElement , ok := resourceElement . ( map [ string ] interface { } )
2019-06-05 10:43:07 +00:00
if ! ok {
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Pattern and resource have different structures. Path: %s. Expected %T, found %T" , path , patternElement , resourceElement )
2019-06-05 10:43:07 +00:00
}
2019-05-17 11:51:54 +00:00
2019-06-20 15:21:55 +00:00
return validateMap ( typedResourceElement , typedPatternElement , originPattern , path )
2019-06-10 14:06:31 +00:00
// array
2019-06-05 10:43:07 +00:00
case [ ] interface { } :
2019-06-10 14:06:31 +00:00
typedResourceElement , ok := resourceElement . ( [ ] interface { } )
2019-06-05 10:43:07 +00:00
if ! ok {
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Pattern and resource have different structures. Path: %s. Expected %T, found %T" , path , patternElement , resourceElement )
2019-06-05 10:43:07 +00:00
}
2019-06-20 15:21:55 +00:00
return validateArray ( typedResourceElement , typedPatternElement , originPattern , path )
2019-06-10 14:06:31 +00:00
// elementary values
2019-06-05 14:35:34 +00:00
case string , float64 , int , int64 , bool , nil :
2019-06-20 15:21:55 +00:00
/*Analyze pattern */
if checkedPattern := reflect . ValueOf ( patternElement ) ; checkedPattern . Kind ( ) == reflect . String {
if isStringIsReference ( checkedPattern . String ( ) ) { //check for $ anchor
2019-06-26 01:16:02 +00:00
patternElement , err = actualizePattern ( originPattern , checkedPattern . String ( ) , path )
if err != nil {
return err
2019-06-20 15:21:55 +00:00
}
}
}
2019-06-10 14:06:31 +00:00
if ! ValidateValueWithPattern ( resourceElement , patternElement ) {
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Failed to validate value %v with pattern %v. Path: %s" , resourceElement , patternElement , path )
2019-06-05 10:43:07 +00:00
}
default :
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Pattern contains unknown type %T. Path: %s" , patternElement , path )
2019-05-15 16:25:49 +00:00
}
2019-06-26 01:16:02 +00:00
return nil
2019-06-05 10:43:07 +00:00
}
2019-05-15 16:25:49 +00:00
2019-06-10 14:06:31 +00:00
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
2019-06-12 09:21:52 +00:00
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
2019-06-26 01:16:02 +00:00
func validateMap ( resourceMap , patternMap map [ string ] interface { } , origPattern interface { } , path string ) error {
2019-06-05 10:43:07 +00:00
2019-06-10 14:06:31 +00:00
for key , patternElement := range patternMap {
key = removeAnchor ( key )
2019-05-16 18:36:30 +00:00
2019-06-10 14:06:31 +00:00
// The '*' pattern means that key exists and has value
if patternElement == "*" && resourceMap [ key ] != nil {
2019-06-04 14:33:21 +00:00
continue
2019-06-10 14:06:31 +00:00
} else if patternElement == "*" && resourceMap [ key ] == nil {
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Field %s is not present" , key )
2019-06-04 14:33:21 +00:00
} else {
2019-06-26 01:16:02 +00:00
err := validateResourceElement ( resourceMap [ key ] , patternElement , origPattern , path + key + "/" )
if err != nil {
return err
}
2019-05-15 16:25:49 +00:00
}
}
2019-06-26 01:16:02 +00:00
return nil
2019-05-15 16:25:49 +00:00
}
2019-06-26 01:16:02 +00:00
func validateArray ( resourceArray , patternArray [ ] interface { } , originPattern interface { } , path string ) error {
2019-05-17 11:51:54 +00:00
2019-06-05 10:43:07 +00:00
if 0 == len ( patternArray ) {
2019-06-26 01:16:02 +00:00
return fmt . Errorf ( "Pattern Array empty" )
2019-05-15 16:25:49 +00:00
}
2019-06-10 14:06:31 +00:00
switch typedPatternElement := patternArray [ 0 ] . ( type ) {
2019-05-15 16:25:49 +00:00
case map [ string ] interface { } :
2019-06-10 14:06:31 +00:00
// This is special case, because maps in arrays can have anchors that must be
// processed with the special way affecting the entire array
2019-06-26 01:16:02 +00:00
err := validateArrayOfMaps ( resourceArray , typedPatternElement , originPattern , path )
if err != nil {
return err
}
2019-06-10 14:06:31 +00:00
default :
// In all other cases - detect type and handle each array element with validateResourceElement
for i , patternElement := range patternArray {
2019-06-05 10:43:07 +00:00
currentPath := path + strconv . Itoa ( i ) + "/"
2019-06-26 01:16:02 +00:00
err := validateResourceElement ( resourceArray [ i ] , patternElement , originPattern , currentPath )
if err != nil {
return err
}
2019-06-10 14:06:31 +00:00
}
}
2019-05-16 14:37:05 +00:00
2019-06-26 01:16:02 +00:00
return nil
2019-06-10 14:06:31 +00:00
}
2019-05-16 14:37:05 +00:00
2019-06-26 01:16:02 +00:00
func actualizePattern ( origPattern interface { } , referencePattern , absolutePath string ) ( interface { } , error ) {
2019-06-20 15:21:55 +00:00
var foundValue interface { }
referencePattern = strings . Trim ( referencePattern , "$()" )
operator := getOperatorFromStringPattern ( referencePattern )
referencePattern = referencePattern [ len ( operator ) : ]
if len ( referencePattern ) == 0 {
2019-06-26 01:16:02 +00:00
return nil , errors . New ( "Expected path. Found empty reference" )
2019-06-20 15:21:55 +00:00
}
actualPath := FormAbsolutePath ( referencePattern , absolutePath )
2019-06-26 01:16:02 +00:00
valFromReference , err := getValueFromReference ( origPattern , actualPath )
if err != nil {
return err , nil
2019-06-20 15:21:55 +00:00
}
2019-06-26 01:16:02 +00:00
//TODO validate this
2019-06-20 15:21:55 +00:00
if operator == Equal { //if operator does not exist return raw value
2019-06-26 01:16:02 +00:00
return valFromReference , nil
2019-06-20 15:21:55 +00:00
}
2019-06-26 01:16:02 +00:00
foundValue , err = valFromReferenceToString ( valFromReference , string ( operator ) )
if err != nil {
return "" , err
}
return string ( operator ) + foundValue . ( string ) , nil
2019-06-20 15:21:55 +00:00
}
//Parse value to string
2019-06-26 01:16:02 +00:00
func valFromReferenceToString ( value interface { } , operator string ) ( string , error ) {
2019-06-20 15:21:55 +00:00
switch typed := value . ( type ) {
case string :
2019-06-26 01:16:02 +00:00
return typed , nil
2019-06-20 15:21:55 +00:00
case int , int64 :
2019-06-26 01:16:02 +00:00
return fmt . Sprintf ( "%d" , value ) , nil
2019-06-20 15:21:55 +00:00
case float64 :
2019-06-26 01:16:02 +00:00
return fmt . Sprintf ( "%f" , value ) , nil
2019-06-20 15:21:55 +00:00
default :
2019-06-26 01:16:02 +00:00
return "" , fmt . Errorf ( "Incorrect expression. Operator %s does not match with value: %v" , operator , value )
2019-06-20 15:21:55 +00:00
}
}
2019-07-23 07:07:11 +00:00
//FormAbsolutePath returns absolute path
2019-06-20 15:21:55 +00:00
func FormAbsolutePath ( referencePath , absolutePath string ) string {
if filepath . IsAbs ( referencePath ) {
return referencePath
}
return filepath . Join ( absolutePath , referencePath )
}
//Prepares original pattern, path to value, and call traverse function
2019-06-26 01:16:02 +00:00
func getValueFromReference ( origPattern interface { } , reference string ) ( interface { } , error ) {
2019-06-20 15:21:55 +00:00
originalPatternMap := origPattern . ( map [ string ] interface { } )
reference = reference [ 1 : len ( reference ) ]
statements := strings . Split ( reference , "/" )
return getValueFromPattern ( originalPatternMap , statements , 0 )
}
2019-06-26 01:16:02 +00:00
func getValueFromPattern ( patternMap map [ string ] interface { } , keys [ ] string , currentKeyIndex int ) ( interface { } , error ) {
2019-06-20 15:21:55 +00:00
for key , pattern := range patternMap {
rawKey := getRawKeyIfWrappedWithAttributes ( key )
if rawKey == keys [ len ( keys ) - 1 ] && currentKeyIndex == len ( keys ) - 1 {
2019-06-26 01:16:02 +00:00
return pattern , nil
2019-06-20 15:21:55 +00:00
} else if rawKey != keys [ currentKeyIndex ] && currentKeyIndex != len ( keys ) - 1 {
continue
}
switch typedPattern := pattern . ( type ) {
case [ ] interface { } :
if keys [ currentKeyIndex ] == rawKey {
for i , value := range typedPattern {
resourceMap , ok := value . ( map [ string ] interface { } )
if ! ok {
2019-06-26 01:16:02 +00:00
return nil , fmt . Errorf ( "Pattern and resource have different structures. Expected %T, found %T" , pattern , value )
2019-06-20 15:21:55 +00:00
}
if keys [ currentKeyIndex + 1 ] == strconv . Itoa ( i ) {
return getValueFromPattern ( resourceMap , keys , currentKeyIndex + 2 )
}
2019-06-26 01:16:02 +00:00
return nil , errors . New ( "Reference to non-existent place in the document" )
2019-06-20 15:21:55 +00:00
}
}
2019-06-26 01:16:02 +00:00
return nil , errors . New ( "Reference to non-existent place in the document" )
2019-06-20 15:21:55 +00:00
case map [ string ] interface { } :
if keys [ currentKeyIndex ] == rawKey {
return getValueFromPattern ( typedPattern , keys , currentKeyIndex + 1 )
}
2019-06-26 01:16:02 +00:00
return nil , errors . New ( "Reference to non-existent place in the document" )
2019-06-20 15:21:55 +00:00
case string , float64 , int , int64 , bool , nil :
continue
}
}
path := ""
/ * for i := len ( keys ) - 1 ; i >= 0 ; i -- {
path = keys [ i ] + path + "/"
} * /
for _ , elem := range keys {
path = "/" + elem + path
}
2019-06-26 01:16:02 +00:00
return nil , fmt . Errorf ( "No value found for specified reference: %s" , path )
2019-06-20 15:21:55 +00:00
}
2019-06-10 14:06:31 +00:00
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
2019-06-26 01:16:02 +00:00
func validateArrayOfMaps ( resourceMapArray [ ] interface { } , patternMap map [ string ] interface { } , originPattern interface { } , path string ) error {
2019-06-13 14:20:00 +00:00
anchor , pattern := getAnchorFromMap ( patternMap )
2019-06-10 14:06:31 +00:00
2019-06-13 14:20:00 +00:00
handler := CreateAnchorHandler ( anchor , pattern , path )
2019-06-20 15:21:55 +00:00
return handler . Handle ( resourceMapArray , patternMap , originPattern )
2019-05-15 16:25:49 +00:00
}
2019-08-24 01:34:23 +00:00
//ValidateNew ...
2019-09-03 21:51:51 +00:00
func ValidateNew ( policy kyverno . ClusterPolicy , resource unstructured . Unstructured ) ( response EngineResponseNew ) {
2019-08-24 01:34:23 +00:00
startTime := time . Now ( )
// policy information
func ( ) {
// set policy information
response . PolicyResponse . Policy = policy . Name
// resource details
response . PolicyResponse . Resource . Name = resource . GetName ( )
response . PolicyResponse . Resource . Namespace = resource . GetNamespace ( )
response . PolicyResponse . Resource . Kind = resource . GetKind ( )
response . PolicyResponse . Resource . APIVersion = resource . GetAPIVersion ( )
response . PolicyResponse . ValidationFailureAction = policy . Spec . ValidationFailureAction
} ( )
glog . V ( 4 ) . Infof ( "started applying validation rules of policy %q (%v)" , policy . Name , startTime )
defer func ( ) {
response . PolicyResponse . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "Finished applying validation rules policy %v (%v)" , policy . Name , response . PolicyResponse . ProcessingTime )
glog . V ( 4 ) . Infof ( "Validation Rules appplied succesfully count %v for policy %q" , response . PolicyResponse . RulesAppliedCount , policy . Name )
} ( )
incrementAppliedRuleCount := func ( ) {
// rules applied succesfully count
response . PolicyResponse . RulesAppliedCount ++
}
for _ , rule := range policy . Spec . Rules {
if reflect . DeepEqual ( rule . Validation , kyverno . Validation { } ) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription ( resource , rule )
if ! ok {
glog . V ( 4 ) . Infof ( "resource %s/%s does not satisfy the resource description for the rule " , resource . GetNamespace ( ) , resource . GetName ( ) )
continue
}
2019-08-30 01:48:58 +00:00
if rule . Validation . Pattern != nil || rule . Validation . AnyPattern != nil {
2019-08-24 01:34:23 +00:00
ruleResponse := validatePatterns ( resource , rule )
incrementAppliedRuleCount ( )
response . PolicyResponse . Rules = append ( response . PolicyResponse . Rules , ruleResponse )
}
}
return response
}