mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 15:37:19 +00:00
323 lines
9.3 KiB
Go
323 lines
9.3 KiB
Go
package pattern
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
"github.com/kyverno/kyverno/ext/wildcard"
|
|
"github.com/kyverno/kyverno/pkg/engine/operator"
|
|
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
|
)
|
|
|
|
type quantity int
|
|
|
|
const (
|
|
equal quantity = 0
|
|
lessThan quantity = -1
|
|
greaterThan quantity = 1
|
|
)
|
|
|
|
// Validate validates a value against a pattern
|
|
func Validate(log logr.Logger, value, pattern interface{}) bool {
|
|
switch typedPattern := pattern.(type) {
|
|
case bool:
|
|
return validateBoolPattern(log, value, typedPattern)
|
|
case int:
|
|
return validateIntPattern(log, value, int64(typedPattern))
|
|
case int64:
|
|
return validateIntPattern(log, value, typedPattern)
|
|
case float64:
|
|
return validateFloatPattern(log, value, typedPattern)
|
|
case nil:
|
|
return validateNilPattern(log, value)
|
|
case map[string]interface{}:
|
|
return validateMapPattern(log, value, typedPattern)
|
|
case string:
|
|
return validateStringPatterns(log, value, typedPattern)
|
|
case []interface{}:
|
|
log.V(2).Info("arrays are not supported as patterns")
|
|
return false
|
|
default:
|
|
log.V(2).Info("Unknown type", "type", fmt.Sprintf("%T", typedPattern), "value", typedPattern)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validateBoolPattern(log logr.Logger, value interface{}, pattern bool) bool {
|
|
switch typedValue := value.(type) {
|
|
case bool:
|
|
return pattern == typedValue
|
|
default:
|
|
log.V(4).Info("Expected type bool", "type", fmt.Sprintf("%T", value), "value", value)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validateIntPattern(log logr.Logger, value interface{}, pattern int64) bool {
|
|
switch typedValue := value.(type) {
|
|
case int:
|
|
return int64(typedValue) == pattern
|
|
case int64:
|
|
return typedValue == pattern
|
|
case float64:
|
|
// check that float has no fraction
|
|
if typedValue != math.Trunc(typedValue) {
|
|
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
|
return false
|
|
}
|
|
return int64(typedValue) == pattern
|
|
case string:
|
|
value, err := strconv.ParseInt(typedValue, 10, 64)
|
|
if err != nil {
|
|
log.Error(err, "Failed to parse int64 from string")
|
|
return false
|
|
}
|
|
return value == pattern
|
|
default:
|
|
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", value), "value", value)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validateFloatPattern(log logr.Logger, value interface{}, pattern float64) bool {
|
|
switch typedValue := value.(type) {
|
|
case int:
|
|
// check that float has no fraction
|
|
if pattern != math.Trunc(pattern) {
|
|
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
|
return false
|
|
}
|
|
return int(pattern) == value
|
|
case int64:
|
|
// check that float has no fraction
|
|
if pattern != math.Trunc(pattern) {
|
|
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
|
return false
|
|
}
|
|
return int64(pattern) == value
|
|
case float64:
|
|
return typedValue == pattern
|
|
case string:
|
|
value, err := strconv.ParseFloat(typedValue, 64)
|
|
if err != nil {
|
|
log.Error(err, "Failed to parse float64 from string")
|
|
return false
|
|
}
|
|
return value == pattern
|
|
default:
|
|
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", value), "value", value)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validateNilPattern(log logr.Logger, value interface{}) bool {
|
|
switch typedValue := value.(type) {
|
|
case float64:
|
|
return typedValue == 0.0
|
|
case int:
|
|
return typedValue == 0
|
|
case int64:
|
|
return typedValue == 0
|
|
case string:
|
|
return typedValue == ""
|
|
case bool:
|
|
return !typedValue
|
|
case nil:
|
|
return true
|
|
case map[string]interface{}, []interface{}:
|
|
log.V(2).Info("Maps and arrays could not be checked with nil pattern")
|
|
return false
|
|
default:
|
|
log.V(2).Info("Unknown type as value when checking for nil pattern", "type", fmt.Sprintf("%T", value), "value", value)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validateMapPattern(log logr.Logger, value interface{}, _ map[string]interface{}) bool {
|
|
// verify the type of the resource value is map[string]interface,
|
|
// we only check for existence of object, not the equality of content and value
|
|
_, ok := value.(map[string]interface{})
|
|
if !ok {
|
|
log.V(2).Info("Expected type map[string]interface{}", "type", fmt.Sprintf("%T", value), "value", value)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validateStringPatterns(log logr.Logger, value interface{}, pattern string) bool {
|
|
if value == pattern {
|
|
return true
|
|
}
|
|
for _, condition := range strings.Split(pattern, "|") {
|
|
condition = strings.Trim(condition, " ")
|
|
if checkForAndConditionsAndValidate(log, value, condition) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func checkForAndConditionsAndValidate(log logr.Logger, value interface{}, pattern string) bool {
|
|
for _, condition := range strings.Split(pattern, "&") {
|
|
condition = strings.Trim(condition, " ")
|
|
if !validateStringPattern(log, value, condition) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validateStringPattern(log logr.Logger, value interface{}, pattern string) bool {
|
|
op := operator.GetOperatorFromStringPattern(pattern)
|
|
if op == operator.InRange {
|
|
// Upon encountering InRange operator split the string by `-` and basically
|
|
// verify the result of (x >= leftEndpoint & x <= rightEndpoint)
|
|
if left, right, ok := split(pattern, operator.InRangeRegex); ok {
|
|
return validateStringPattern(log, value, fmt.Sprintf(">= %s", left)) &&
|
|
validateStringPattern(log, value, fmt.Sprintf("<= %s", right))
|
|
}
|
|
return false
|
|
} else if op == operator.NotInRange {
|
|
// Upon encountering NotInRange operator split the string by `!-` and basically
|
|
// verify the result of (x < leftEndpoint | x > rightEndpoint)
|
|
if left, right, ok := split(pattern, operator.NotInRangeRegex); ok {
|
|
return validateStringPattern(log, value, fmt.Sprintf("< %s", left)) ||
|
|
validateStringPattern(log, value, fmt.Sprintf("> %s", right))
|
|
}
|
|
return false
|
|
} else {
|
|
pattern := strings.TrimSpace(pattern[len(op):])
|
|
return validateString(log, value, pattern, op)
|
|
}
|
|
}
|
|
|
|
func split(pattern string, r *regexp.Regexp) (string, string, bool) {
|
|
match := r.FindStringSubmatch(pattern)
|
|
if len(match) == 0 {
|
|
return "", "", false
|
|
}
|
|
return match[1], match[2], true
|
|
}
|
|
|
|
func validateString(log logr.Logger, value interface{}, pattern string, op operator.Operator) bool {
|
|
if res, proc := compareDuration(log, value, pattern, op); proc {
|
|
return res
|
|
}
|
|
if res, proc := compareQuantity(log, value, pattern, op); proc {
|
|
return res
|
|
}
|
|
return compareString(log, value, pattern, op)
|
|
}
|
|
|
|
func compareDuration(_ logr.Logger, value interface{}, pattern string, op operator.Operator) (res bool, processed bool) {
|
|
if pattern, err := time.ParseDuration(pattern); err != nil {
|
|
return false, false
|
|
} else if value, err := convertNumberToString(value); err != nil {
|
|
return false, false
|
|
} else if value, err := time.ParseDuration(value); err != nil {
|
|
return false, false
|
|
} else {
|
|
switch op {
|
|
case operator.Equal:
|
|
return value == pattern, true
|
|
case operator.NotEqual:
|
|
return value != pattern, true
|
|
case operator.More:
|
|
return value > pattern, true
|
|
case operator.Less:
|
|
return value < pattern, true
|
|
case operator.MoreEqual:
|
|
return value >= pattern, true
|
|
case operator.LessEqual:
|
|
return value <= pattern, true
|
|
}
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
func compareQuantity(_ logr.Logger, value interface{}, pattern string, op operator.Operator) (res bool, processed bool) {
|
|
if pattern, err := apiresource.ParseQuantity(pattern); err != nil {
|
|
return false, false
|
|
} else if value, err := convertNumberToString(value); err != nil {
|
|
return false, false
|
|
} else if value, err := apiresource.ParseQuantity(value); err != nil {
|
|
return false, false
|
|
} else {
|
|
result := value.Cmp(pattern)
|
|
switch op {
|
|
case operator.Equal:
|
|
return result == int(equal), true
|
|
case operator.NotEqual:
|
|
return result != int(equal), true
|
|
case operator.More:
|
|
return result == int(greaterThan), true
|
|
case operator.Less:
|
|
return result == int(lessThan), true
|
|
case operator.MoreEqual:
|
|
return (result == int(equal)) || (result == int(greaterThan)), true
|
|
case operator.LessEqual:
|
|
return (result == int(equal)) || (result == int(lessThan)), true
|
|
}
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
func compareString(log logr.Logger, value interface{}, pattern string, operatorVariable operator.Operator) bool {
|
|
if operator.NotEqual == operatorVariable || operator.Equal == operatorVariable {
|
|
var strValue string
|
|
var ok bool = false
|
|
switch v := value.(type) {
|
|
case float64:
|
|
strValue = strconv.FormatFloat(v, 'E', -1, 64)
|
|
ok = true
|
|
case int:
|
|
strValue = strconv.FormatInt(int64(v), 10)
|
|
ok = true
|
|
case int64:
|
|
strValue = strconv.FormatInt(v, 10)
|
|
ok = true
|
|
case string:
|
|
strValue = v
|
|
ok = true
|
|
case bool:
|
|
strValue = strconv.FormatBool(v)
|
|
ok = true
|
|
case nil:
|
|
ok = false
|
|
}
|
|
if !ok {
|
|
log.V(4).Info("unexpected type", "got", value, "expect", pattern)
|
|
return false
|
|
}
|
|
wildcardResult := wildcard.Match(pattern, strValue)
|
|
if operator.NotEqual == operatorVariable {
|
|
return !wildcardResult
|
|
}
|
|
return wildcardResult
|
|
}
|
|
log.V(2).Info("Operators >, >=, <, <= are not applicable to strings")
|
|
return false
|
|
}
|
|
|
|
func convertNumberToString(value interface{}) (string, error) {
|
|
if value == nil {
|
|
return "0", nil
|
|
}
|
|
switch typed := value.(type) {
|
|
case string:
|
|
return typed, nil
|
|
case float64:
|
|
return fmt.Sprintf("%f", typed), nil
|
|
case int64:
|
|
return strconv.FormatInt(typed, 10), nil
|
|
case int:
|
|
return strconv.Itoa(typed), nil
|
|
default:
|
|
return "", fmt.Errorf("could not convert %v to string", typed)
|
|
}
|
|
}
|