2019-12-31 01:08:50 +00:00
package validate
import (
"fmt"
2022-10-20 11:36:46 +00:00
"sort"
2019-12-31 01:08:50 +00:00
"strconv"
2020-03-17 18:05:20 +00:00
"github.com/go-logr/logr"
2020-10-07 18:12:31 +00:00
"github.com/kyverno/kyverno/pkg/engine/anchor"
2023-01-10 13:07:26 +00:00
"github.com/kyverno/kyverno/pkg/engine/pattern"
2022-05-17 05:56:48 +00:00
"github.com/kyverno/kyverno/pkg/engine/wildcards"
2022-08-02 14:54:02 +00:00
"go.uber.org/multierr"
2019-12-31 01:08:50 +00:00
)
2021-10-01 06:34:04 +00:00
type PatternError struct {
2021-10-02 23:57:40 +00:00
Err error
2021-10-01 06:34:04 +00:00
Path string
Skip bool
}
func ( e * PatternError ) Error ( ) string {
if e . Err == nil {
return ""
}
return e . Err . Error ( )
}
2021-09-26 09:12:31 +00:00
// MatchPattern is a start of element-by-element pattern validation process.
2020-02-13 21:57:48 +00:00
// It assumes that validation is started from root, so "/" is passed
2021-10-01 06:34:04 +00:00
func MatchPattern ( logger logr . Logger , resource , pattern interface { } ) error {
2020-08-29 01:22:22 +00:00
// newAnchorMap - to check anchor key has values
2022-01-05 01:36:33 +00:00
ac := anchor . NewAnchorMap ( )
2021-07-23 17:53:37 +00:00
elemPath , err := validateResourceElement ( logger , resource , pattern , pattern , "/" , ac )
2020-02-13 21:57:48 +00:00
if err != nil {
2022-01-05 01:36:33 +00:00
if skip ( err ) {
logger . V ( 2 ) . Info ( "resource skipped" , "reason" , ac . AnchorError . Error ( ) )
2021-10-02 23:53:02 +00:00
return & PatternError { err , "" , true }
2020-12-09 06:52:37 +00:00
}
2022-01-19 10:09:07 +00:00
if fail ( err ) {
logger . V ( 2 ) . Info ( "failed to apply rule on resource" , "msg" , ac . AnchorError . Error ( ) )
return & PatternError { err , elemPath , false }
}
2021-09-27 21:28:55 +00:00
// check if an anchor defined in the policy rule is missing in the resource
2023-01-30 12:17:19 +00:00
if ac . KeysAreMissing ( ) {
2021-09-26 09:12:31 +00:00
logger . V ( 3 ) . Info ( "missing anchor in resource" )
2021-10-01 06:34:04 +00:00
return & PatternError { err , "" , false }
2020-08-29 01:22:22 +00:00
}
2021-09-26 09:12:31 +00:00
2021-10-01 06:34:04 +00:00
return & PatternError { err , elemPath , false }
2020-02-13 21:57:48 +00:00
}
2021-10-02 23:53:02 +00:00
return nil
2020-02-13 21:57:48 +00:00
}
2022-01-05 01:36:33 +00:00
func skip ( err error ) bool {
// if conditional or global anchors report errors, the rule does not apply to the resource
2023-01-30 12:17:19 +00:00
return anchor . IsConditionalAnchorError ( err ) || anchor . IsGlobalAnchorError ( err )
2022-01-05 01:36:33 +00:00
}
2022-01-19 10:09:07 +00:00
func fail ( err error ) bool {
// if negation anchors report errors, the rule will fail
2023-01-30 12:17:19 +00:00
return anchor . IsNegationAnchorError ( err )
2022-01-19 10:09:07 +00:00
}
2019-12-31 01:08:50 +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
2023-01-30 12:17:19 +00:00
func validateResourceElement ( log logr . Logger , resourceElement , patternElement , originPattern interface { } , path string , ac * anchor . AnchorMap ) ( string , error ) {
2019-12-31 01:08:50 +00:00
switch typedPatternElement := patternElement . ( type ) {
// map
case map [ string ] interface { } :
typedResourceElement , ok := resourceElement . ( map [ string ] interface { } )
if ! ok {
2020-03-17 18:05:20 +00:00
log . V ( 4 ) . Info ( "Pattern and resource have different structures." , "path" , path , "expected" , fmt . Sprintf ( "%T" , patternElement ) , "current" , fmt . Sprintf ( "%T" , resourceElement ) )
2021-10-12 21:29:20 +00:00
return path , fmt . Errorf ( "pattern and resource have different structures. Path: %s. Expected %T, found %T" , path , patternElement , resourceElement )
2019-12-31 01:08:50 +00:00
}
2021-09-28 06:40:05 +00:00
// CheckAnchorInResource - check anchor key exists in resource and update the AnchorKey fields.
2020-08-29 01:22:22 +00:00
ac . CheckAnchorInResource ( typedPatternElement , typedResourceElement )
return validateMap ( log , typedResourceElement , typedPatternElement , originPattern , path , ac )
2019-12-31 01:08:50 +00:00
// array
case [ ] interface { } :
typedResourceElement , ok := resourceElement . ( [ ] interface { } )
if ! ok {
2020-03-17 18:05:20 +00:00
log . V ( 4 ) . Info ( "Pattern and resource have different structures." , "path" , path , "expected" , fmt . Sprintf ( "%T" , patternElement ) , "current" , fmt . Sprintf ( "%T" , resourceElement ) )
2022-05-14 21:04:35 +00:00
return path , fmt . Errorf ( "validation rule failed at path %s, resource does not satisfy the expected overlay pattern" , path )
2019-12-31 01:08:50 +00:00
}
2020-08-29 01:22:22 +00:00
return validateArray ( log , typedResourceElement , typedPatternElement , originPattern , path , ac )
2019-12-31 01:08:50 +00:00
// elementary values
case string , float64 , int , int64 , bool , nil :
/*Analyze pattern */
2020-12-04 17:28:30 +00:00
2021-04-28 20:31:55 +00:00
switch resource := resourceElement . ( type ) {
case [ ] interface { } :
for _ , res := range resource {
2023-01-10 13:07:26 +00:00
if ! pattern . Validate ( log , res , patternElement ) {
2021-10-03 10:15:22 +00:00
return path , fmt . Errorf ( "resource value '%v' does not match '%v' at path %s" , resourceElement , patternElement , path )
2021-04-28 20:31:55 +00:00
}
}
return "" , nil
default :
2023-01-10 13:07:26 +00:00
if ! pattern . Validate ( log , resourceElement , patternElement ) {
2021-10-03 10:15:22 +00:00
return path , fmt . Errorf ( "resource value '%v' does not match '%v' at path %s" , resourceElement , patternElement , path )
2021-04-28 20:31:55 +00:00
}
2019-12-31 01:08:50 +00:00
}
default :
2020-03-17 18:05:20 +00:00
log . V ( 4 ) . Info ( "Pattern contains unknown type" , "path" , path , "current" , fmt . Sprintf ( "%T" , patternElement ) )
2021-10-03 10:15:22 +00:00
return path , fmt . Errorf ( "failed at '%s', pattern contains unknown type" , path )
2019-12-31 01:08:50 +00:00
}
return "" , nil
}
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
2023-01-30 12:17:19 +00:00
func validateMap ( log logr . Logger , resourceMap , patternMap map [ string ] interface { } , origPattern interface { } , path string , ac * anchor . AnchorMap ) ( string , error ) {
2020-12-04 17:28:30 +00:00
patternMap = wildcards . ExpandInMetadata ( patternMap , resourceMap )
2019-12-31 01:08:50 +00:00
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors
// Phase 2 : Evaluate non-anchors
anchors , resources := anchor . GetAnchorsResourcesFromMap ( patternMap )
2022-10-20 11:36:46 +00:00
keys := make ( [ ] string , 0 , len ( anchors ) )
for k := range anchors {
keys = append ( keys , k )
}
sort . Strings ( keys )
2019-12-31 01:08:50 +00:00
// Evaluate anchors
2024-01-29 13:06:39 +00:00
var skipErrors [ ] error
var applyCount int
2022-10-20 11:36:46 +00:00
for _ , key := range keys {
patternElement := anchors [ key ]
2019-12-31 01:08:50 +00:00
// get handler for each pattern in the pattern
// - Conditional
2020-01-24 20:05:53 +00:00
// - Existence
2019-12-31 01:08:50 +00:00
// - Equality
handler := anchor . CreateElementHandler ( key , patternElement , path )
2020-08-29 01:22:22 +00:00
handlerPath , err := handler . Handle ( validateResourceElement , resourceMap , origPattern , ac )
2019-12-31 01:08:50 +00:00
if err != nil {
2024-01-29 13:06:39 +00:00
if skip ( err ) {
skipErrors = append ( skipErrors , err )
continue
}
2024-05-22 09:04:14 +00:00
skipSiblingExists := false
for _ , skipError := range skipErrors {
if _ , ok := skipError . ( * PatternError ) ; ! ok {
skipSiblingExists = true
break
}
}
if ! skipSiblingExists {
return handlerPath , err
} else {
continue
}
2019-12-31 01:08:50 +00:00
}
2024-01-29 13:06:39 +00:00
applyCount ++
}
2024-05-22 09:04:14 +00:00
if len ( skipErrors ) > 0 {
if applyCount == 0 {
return path , & PatternError {
Err : multierr . Combine ( skipErrors ... ) ,
Path : path ,
Skip : true ,
}
} else {
skipSiblingExists := false
for _ , skipError := range skipErrors {
if _ , ok := skipError . ( * PatternError ) ; ! ok {
skipSiblingExists = true
break
}
}
if skipSiblingExists {
return path , & PatternError {
Err : multierr . Combine ( skipErrors ... ) ,
Path : path ,
Skip : true ,
}
}
2024-01-29 13:06:39 +00:00
}
2019-12-31 01:08:50 +00:00
}
2020-12-04 17:28:30 +00:00
2019-12-31 01:08:50 +00:00
// Evaluate resources
2020-08-29 01:22:22 +00:00
// getSortedNestedAnchorResource - keeps the anchor key to start of the list
sortedResourceKeys := getSortedNestedAnchorResource ( resources )
for e := sortedResourceKeys . Front ( ) ; e != nil ; e = e . Next ( ) {
key := e . Value . ( string )
handler := anchor . CreateElementHandler ( key , resources [ key ] , path )
handlerPath , err := handler . Handle ( validateResourceElement , resourceMap , origPattern , ac )
2019-12-31 01:08:50 +00:00
if err != nil {
return handlerPath , err
}
}
2022-01-05 01:36:33 +00:00
2019-12-31 01:08:50 +00:00
return "" , nil
}
2023-01-30 12:17:19 +00:00
func validateArray ( log logr . Logger , resourceArray , patternArray [ ] interface { } , originPattern interface { } , path string , ac * anchor . AnchorMap ) ( string , error ) {
2021-10-13 09:29:45 +00:00
if len ( patternArray ) == 0 {
2021-10-12 21:29:20 +00:00
return path , fmt . Errorf ( "pattern Array empty" )
2019-12-31 01:08:50 +00:00
}
switch typedPatternElement := patternArray [ 0 ] . ( type ) {
case map [ string ] interface { } :
// This is special case, because maps in arrays can have anchors that must be
// processed with the special way affecting the entire array
2020-12-04 17:28:30 +00:00
elemPath , err := validateArrayOfMaps ( log , resourceArray , typedPatternElement , originPattern , path , ac )
2019-12-31 01:08:50 +00:00
if err != nil {
2020-12-04 17:28:30 +00:00
return elemPath , err
2019-12-31 01:08:50 +00:00
}
2021-04-28 20:31:55 +00:00
case string , float64 , int , int64 , bool , nil :
elemPath , err := validateResourceElement ( log , resourceArray , typedPatternElement , originPattern , path , ac )
if err != nil {
return elemPath , err
}
2019-12-31 01:08:50 +00:00
default :
// In all other cases - detect type and handle each array element with validateResourceElement
2022-01-05 01:36:33 +00:00
if len ( resourceArray ) < len ( patternArray ) {
return "" , fmt . Errorf ( "validate Array failed, array length mismatch, resource Array len is %d and pattern Array len is %d" , len ( resourceArray ) , len ( patternArray ) )
}
var applyCount int
var skipErrors [ ] error
for i , patternElement := range patternArray {
currentPath := path + strconv . Itoa ( i ) + "/"
elemPath , err := validateResourceElement ( log , resourceArray [ i ] , patternElement , originPattern , currentPath , ac )
if err != nil {
if skip ( err ) {
skipErrors = append ( skipErrors , err )
continue
2020-07-09 18:50:05 +00:00
}
2022-01-05 01:36:33 +00:00
return elemPath , err
}
applyCount ++
}
if applyCount == 0 && len ( skipErrors ) > 0 {
return path , & PatternError {
2022-08-02 14:54:02 +00:00
Err : multierr . Combine ( skipErrors ... ) ,
2022-01-05 01:36:33 +00:00
Path : path ,
Skip : true ,
2019-12-31 01:08:50 +00:00
}
}
}
2022-01-05 01:36:33 +00:00
2019-12-31 01:08:50 +00:00
return "" , nil
}
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
2023-01-30 12:17:19 +00:00
func validateArrayOfMaps ( log logr . Logger , resourceMapArray [ ] interface { } , patternMap map [ string ] interface { } , originPattern interface { } , path string , ac * anchor . AnchorMap ) ( string , error ) {
2022-01-05 01:36:33 +00:00
applyCount := 0
skipErrors := make ( [ ] error , 0 )
2019-12-31 01:08:50 +00:00
for i , resourceElement := range resourceMapArray {
// check the types of resource element
2022-01-05 01:36:33 +00:00
// expect it to be a map, but can be anything ?:(
2019-12-31 01:08:50 +00:00
currentPath := path + strconv . Itoa ( i ) + "/"
2022-01-05 01:36:33 +00:00
returnPath , err := validateResourceElement ( log , resourceElement , patternMap , originPattern , currentPath , ac )
2019-12-31 01:08:50 +00:00
if err != nil {
2022-01-05 01:36:33 +00:00
if skip ( err ) {
skipErrors = append ( skipErrors , err )
2020-12-09 06:52:37 +00:00
continue
}
2022-01-05 01:36:33 +00:00
return returnPath , err
}
applyCount ++
}
if applyCount == 0 && len ( skipErrors ) > 0 {
return path , & PatternError {
2022-08-02 14:54:02 +00:00
Err : multierr . Combine ( skipErrors ... ) ,
2022-01-05 01:36:33 +00:00
Path : path ,
Skip : true ,
2019-12-31 01:08:50 +00:00
}
}
2022-01-05 01:36:33 +00:00
2019-12-31 01:08:50 +00:00
return "" , nil
}