2019-05-15 14:25:32 +03:00
|
|
|
package engine
|
2019-03-06 13:01:17 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2019-06-10 17:32:26 +03:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
2019-04-30 18:54:08 -07:00
|
|
|
"strings"
|
2019-08-19 18:57:19 -07:00
|
|
|
"time"
|
2019-03-06 13:01:17 +02:00
|
|
|
|
2019-07-23 23:34:03 -04:00
|
|
|
"github.com/golang/glog"
|
|
|
|
|
2019-05-14 18:10:25 +03:00
|
|
|
"github.com/minio/minio/pkg/wildcard"
|
2019-08-09 16:55:43 -07:00
|
|
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
2019-07-31 17:43:46 -07:00
|
|
|
"github.com/nirmata/kyverno/pkg/utils"
|
2019-06-25 22:53:18 -07:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2019-05-15 14:25:32 +03:00
|
|
|
|
2019-05-14 18:10:25 +03:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2019-03-06 13:01:17 +02:00
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
)
|
|
|
|
|
2019-08-19 18:57:19 -07:00
|
|
|
//EngineStats stores in the statistics for a single application of resource
|
|
|
|
type EngineStats struct {
|
|
|
|
// average time required to process the policy rules on a resource
|
|
|
|
ExecutionTime time.Duration
|
|
|
|
// Count of rules that were applied succesfully
|
|
|
|
RulesAppliedCount int
|
2019-08-14 15:18:46 -07:00
|
|
|
}
|
|
|
|
|
2019-08-09 12:59:37 -07:00
|
|
|
//MatchesResourceDescription checks if the resource matches resource desription of the rule or not
|
2019-08-13 11:32:12 -07:00
|
|
|
func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno.Rule) bool {
|
2019-08-09 12:59:37 -07:00
|
|
|
matches := rule.MatchResources.ResourceDescription
|
|
|
|
exclude := rule.ExcludeResources.ResourceDescription
|
|
|
|
|
2019-08-13 11:32:12 -07:00
|
|
|
if !findKind(matches.Kinds, resource.GetKind()) {
|
2019-08-09 12:59:37 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
name := resource.GetName()
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-08-09 12:59:37 -07:00
|
|
|
namespace := resource.GetNamespace()
|
|
|
|
|
2019-08-09 16:55:43 -07:00
|
|
|
if matches.Name != "" {
|
2019-08-09 12:59:37 -07:00
|
|
|
// Matches
|
2019-08-09 16:55:43 -07:00
|
|
|
if !wildcard.Match(matches.Name, name) {
|
2019-08-09 12:59:37 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-08-09 12:59:37 -07:00
|
|
|
// Matches
|
2019-09-12 17:11:55 -07:00
|
|
|
// check if the resource namespace is defined in the list of namespace pattern
|
|
|
|
if len(matches.Namespaces) > 0 && !utils.ContainsNamepace(matches.Namespaces, namespace) {
|
2019-08-09 12:59:37 -07:00
|
|
|
return false
|
|
|
|
}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-08-09 12:59:37 -07:00
|
|
|
// Matches
|
|
|
|
if matches.Selector != nil {
|
|
|
|
selector, err := metav1.LabelSelectorAsSelector(matches.Selector)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !selector.Matches(labels.Set(resource.GetLabels())) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2019-09-03 19:31:42 -07:00
|
|
|
|
|
|
|
excludeName := func(name string) Condition {
|
|
|
|
if exclude.Name == "" {
|
|
|
|
return NotEvaluate
|
|
|
|
}
|
|
|
|
if wildcard.Match(exclude.Name, name) {
|
|
|
|
return Skip
|
|
|
|
}
|
|
|
|
return Process
|
|
|
|
}
|
|
|
|
|
|
|
|
excludeNamespace := func(namespace string) Condition {
|
|
|
|
if len(exclude.Namespaces) == 0 {
|
|
|
|
return NotEvaluate
|
|
|
|
}
|
2019-09-12 17:11:55 -07:00
|
|
|
if utils.ContainsNamepace(exclude.Namespaces, namespace) {
|
2019-09-03 19:31:42 -07:00
|
|
|
return Skip
|
|
|
|
}
|
|
|
|
return Process
|
|
|
|
}
|
|
|
|
|
|
|
|
excludeSelector := func(labelsMap map[string]string) Condition {
|
|
|
|
if exclude.Selector == nil {
|
|
|
|
return NotEvaluate
|
|
|
|
}
|
2019-08-09 12:59:37 -07:00
|
|
|
selector, err := metav1.LabelSelectorAsSelector(exclude.Selector)
|
|
|
|
// if the label selector is incorrect, should be fail or
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
2019-09-03 19:31:42 -07:00
|
|
|
return Skip
|
2019-08-09 12:59:37 -07:00
|
|
|
}
|
2019-09-03 19:31:42 -07:00
|
|
|
if selector.Matches(labels.Set(labelsMap)) {
|
|
|
|
return Skip
|
2019-08-09 12:59:37 -07:00
|
|
|
}
|
2019-09-03 19:31:42 -07:00
|
|
|
return Process
|
2019-08-09 12:59:37 -07:00
|
|
|
}
|
|
|
|
|
2019-09-03 19:31:42 -07:00
|
|
|
excludeKind := func(kind string) Condition {
|
|
|
|
if len(exclude.Kinds) == 0 {
|
|
|
|
return NotEvaluate
|
|
|
|
}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-09-03 19:31:42 -07:00
|
|
|
if findKind(exclude.Kinds, kind) {
|
|
|
|
return Skip
|
|
|
|
}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-09-03 19:31:42 -07:00
|
|
|
return Process
|
|
|
|
}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-09-03 19:31:42 -07:00
|
|
|
// 0 -> dont check
|
|
|
|
// 1 -> is not to be exclude
|
|
|
|
// 2 -> to be exclude
|
|
|
|
excludeEval := []Condition{}
|
2019-08-17 09:45:57 -07:00
|
|
|
|
2019-09-03 19:31:42 -07:00
|
|
|
if ret := excludeName(resource.GetName()); ret != NotEvaluate {
|
|
|
|
excludeEval = append(excludeEval, ret)
|
|
|
|
}
|
|
|
|
if ret := excludeNamespace(resource.GetNamespace()); ret != NotEvaluate {
|
|
|
|
excludeEval = append(excludeEval, ret)
|
|
|
|
}
|
|
|
|
if ret := excludeSelector(resource.GetLabels()); ret != NotEvaluate {
|
|
|
|
excludeEval = append(excludeEval, ret)
|
|
|
|
}
|
|
|
|
if ret := excludeKind(resource.GetKind()); ret != NotEvaluate {
|
|
|
|
excludeEval = append(excludeEval, ret)
|
|
|
|
}
|
|
|
|
// Filtered NotEvaluate
|
|
|
|
|
2019-09-04 09:56:44 -07:00
|
|
|
if len(excludeEval) == 0 {
|
|
|
|
// nothing to exclude
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return func() bool {
|
2019-09-03 19:31:42 -07:00
|
|
|
for _, ret := range excludeEval {
|
|
|
|
if ret == Process {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Condition int
|
|
|
|
|
|
|
|
const (
|
|
|
|
NotEvaluate Condition = 0
|
|
|
|
Process Condition = 1
|
|
|
|
Skip Condition = 2
|
|
|
|
)
|
2019-03-06 13:01:17 +02:00
|
|
|
|
2019-07-26 15:54:42 -07:00
|
|
|
// ParseResourceInfoFromObject get kind/namepace/name from resource
|
|
|
|
func ParseResourceInfoFromObject(rawResource []byte) string {
|
|
|
|
|
|
|
|
kind := ParseKindFromObject(rawResource)
|
|
|
|
namespace := ParseNamespaceFromObject(rawResource)
|
|
|
|
name := ParseNameFromObject(rawResource)
|
|
|
|
return strings.Join([]string{kind, namespace, name}, "/")
|
|
|
|
}
|
|
|
|
|
2019-06-26 12:19:11 -07:00
|
|
|
//ParseKindFromObject get kind from resource
|
|
|
|
func ParseKindFromObject(bytes []byte) string {
|
2019-05-01 14:48:50 -07:00
|
|
|
var objectJSON map[string]interface{}
|
|
|
|
json.Unmarshal(bytes, &objectJSON)
|
|
|
|
|
|
|
|
return objectJSON["kind"].(string)
|
|
|
|
}
|
|
|
|
|
2019-06-05 17:43:59 -07:00
|
|
|
//ParseNameFromObject extracts resource name from JSON obj
|
2019-05-07 16:50:39 -07:00
|
|
|
func ParseNameFromObject(bytes []byte) string {
|
2019-05-02 11:15:23 -07:00
|
|
|
var objectJSON map[string]interface{}
|
|
|
|
json.Unmarshal(bytes, &objectJSON)
|
2019-07-25 13:14:55 -04:00
|
|
|
meta, ok := objectJSON["metadata"]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
2019-05-02 11:15:23 -07:00
|
|
|
|
2019-07-25 13:14:55 -04:00
|
|
|
metaMap, ok := meta.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if name, ok := metaMap["name"].(string); ok {
|
2019-03-06 13:01:17 +02:00
|
|
|
return name
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2019-03-21 18:09:58 +02:00
|
|
|
|
2019-06-05 17:43:59 -07:00
|
|
|
// ParseNamespaceFromObject extracts the namespace from the JSON obj
|
2019-05-07 16:50:39 -07:00
|
|
|
func ParseNamespaceFromObject(bytes []byte) string {
|
2019-05-02 11:15:23 -07:00
|
|
|
var objectJSON map[string]interface{}
|
|
|
|
json.Unmarshal(bytes, &objectJSON)
|
2019-07-25 13:14:55 -04:00
|
|
|
meta, ok := objectJSON["metadata"]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
metaMap, ok := meta.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
2019-05-02 11:15:23 -07:00
|
|
|
|
2019-07-25 13:14:55 -04:00
|
|
|
if name, ok := metaMap["namespace"].(string); ok {
|
|
|
|
return name
|
2019-03-21 18:09:58 +02:00
|
|
|
}
|
2019-07-25 13:14:55 -04:00
|
|
|
|
2019-03-21 18:09:58 +02:00
|
|
|
return ""
|
|
|
|
}
|
2019-04-30 17:26:50 -07:00
|
|
|
|
2019-06-05 17:43:59 -07:00
|
|
|
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
|
2019-05-21 18:27:56 +03:00
|
|
|
result := make(map[string]interface{})
|
|
|
|
|
|
|
|
for key, value := range anchorsMap {
|
2019-06-13 17:20:00 +03:00
|
|
|
if isConditionAnchor(key) || isExistanceAnchor(key) {
|
2019-05-21 18:27:56 +03:00
|
|
|
result[key] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
2019-05-22 16:17:26 -07:00
|
|
|
|
2019-07-26 12:01:09 -07:00
|
|
|
func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
|
|
|
|
anchors := make(map[string]interface{})
|
|
|
|
elementsWithoutanchor := make(map[string]interface{})
|
|
|
|
for key, value := range anchorsMap {
|
|
|
|
if isConditionAnchor(key) || isExistanceAnchor(key) {
|
|
|
|
anchors[key] = value
|
|
|
|
} else if !isAddingAnchor(key) {
|
|
|
|
elementsWithoutanchor[key] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return anchors, elementsWithoutanchor
|
|
|
|
}
|
|
|
|
|
2019-06-13 17:20:00 +03:00
|
|
|
func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) {
|
|
|
|
for key, value := range anchorsMap {
|
|
|
|
if isConditionAnchor(key) || isExistanceAnchor(key) {
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2019-05-21 15:43:43 -07:00
|
|
|
func findKind(kinds []string, kindGVK string) bool {
|
|
|
|
for _, kind := range kinds {
|
|
|
|
if kind == kindGVK {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2019-06-05 13:43:07 +03:00
|
|
|
|
2019-06-13 17:20:00 +03:00
|
|
|
func isConditionAnchor(str string) bool {
|
2019-06-05 13:43:07 +03:00
|
|
|
if len(str) < 2 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return (str[0] == '(' && str[len(str)-1] == ')')
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:21:55 +03:00
|
|
|
func getRawKeyIfWrappedWithAttributes(str string) string {
|
|
|
|
if len(str) < 2 {
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
if str[0] == '(' && str[len(str)-1] == ')' {
|
|
|
|
return str[1 : len(str)-1]
|
2019-10-01 13:08:34 -07:00
|
|
|
} else if (str[0] == '$' || str[0] == '^' || str[0] == '+' || str[0] == '=') && (str[1] == '(' && str[len(str)-1] == ')') {
|
2019-06-20 18:21:55 +03:00
|
|
|
return str[2 : len(str)-1]
|
|
|
|
} else {
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isStringIsReference(str string) bool {
|
|
|
|
if len(str) < len(referenceSign) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')'
|
|
|
|
}
|
|
|
|
|
2019-06-13 17:20:00 +03:00
|
|
|
func isExistanceAnchor(str string) bool {
|
|
|
|
left := "^("
|
|
|
|
right := ")"
|
|
|
|
|
|
|
|
if len(str) < len(left)+len(right) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
|
|
|
|
}
|
|
|
|
|
2019-10-01 12:35:14 -07:00
|
|
|
func isEqualityAnchor(str string) bool {
|
2019-10-01 13:08:34 -07:00
|
|
|
left := "=("
|
2019-10-01 12:35:14 -07:00
|
|
|
right := ")"
|
|
|
|
if len(str) < len(left)+len(right) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
//TODO: trim spaces ?
|
|
|
|
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
|
|
|
|
}
|
|
|
|
|
2019-06-14 17:19:32 +03:00
|
|
|
func isAddingAnchor(key string) bool {
|
|
|
|
const left = "+("
|
|
|
|
const right = ")"
|
|
|
|
|
|
|
|
if len(key) < len(left)+len(right) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return left == key[:len(left)] && right == key[len(key)-len(right):]
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
// Checks if array object matches anchors. If not - skip - return true
|
|
|
|
func skipArrayObject(object, anchors map[string]interface{}) bool {
|
|
|
|
for key, pattern := range anchors {
|
|
|
|
key = key[1 : len(key)-1]
|
|
|
|
|
|
|
|
value, ok := object[key]
|
|
|
|
if !ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ValidateValueWithPattern(value, pattern) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
2019-06-10 17:06:31 +03:00
|
|
|
|
2019-06-10 17:32:26 +03:00
|
|
|
// removeAnchor remove special characters around anchored key
|
2019-06-10 17:06:31 +03:00
|
|
|
func removeAnchor(key string) string {
|
2019-06-13 17:20:00 +03:00
|
|
|
if isConditionAnchor(key) {
|
2019-06-10 17:06:31 +03:00
|
|
|
return key[1 : len(key)-1]
|
|
|
|
}
|
|
|
|
|
2019-10-01 12:35:14 -07:00
|
|
|
if isExistanceAnchor(key) || isAddingAnchor(key) || isEqualityAnchor(key) {
|
2019-06-13 17:20:00 +03:00
|
|
|
return key[2 : len(key)-1]
|
|
|
|
}
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
return key
|
|
|
|
}
|
2019-06-10 17:32:26 +03:00
|
|
|
|
|
|
|
// convertToFloat converts string and any other value to float64
|
|
|
|
func convertToFloat(value interface{}) (float64, error) {
|
|
|
|
switch typed := value.(type) {
|
|
|
|
case string:
|
|
|
|
var err error
|
|
|
|
floatValue, err := strconv.ParseFloat(typed, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return floatValue, nil
|
|
|
|
case float64:
|
|
|
|
return typed, nil
|
|
|
|
case int64:
|
|
|
|
return float64(typed), nil
|
|
|
|
case int:
|
|
|
|
return float64(typed), nil
|
|
|
|
default:
|
|
|
|
return 0, fmt.Errorf("Could not convert %T to float64", value)
|
|
|
|
}
|
|
|
|
}
|
2019-06-25 22:53:18 -07:00
|
|
|
|
|
|
|
type resourceInfo struct {
|
2019-07-23 23:34:03 -04:00
|
|
|
Resource unstructured.Unstructured
|
|
|
|
Gvk *metav1.GroupVersionKind
|
2019-06-25 22:53:18 -07:00
|
|
|
}
|
2019-08-14 19:00:37 -07:00
|
|
|
|
|
|
|
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
|
|
|
|
resource := &unstructured.Unstructured{}
|
|
|
|
err := resource.UnmarshalJSON(data)
|
|
|
|
if err != nil {
|
|
|
|
glog.V(4).Infof("failed to unmarshall resource: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resource, nil
|
|
|
|
}
|
2019-08-23 18:34:23 -07:00
|
|
|
|
|
|
|
type RuleType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
Mutation RuleType = iota
|
|
|
|
Validation
|
|
|
|
Generation
|
|
|
|
All
|
|
|
|
)
|
|
|
|
|
|
|
|
func (ri RuleType) String() string {
|
|
|
|
return [...]string{
|
|
|
|
"Mutation",
|
|
|
|
"Validation",
|
|
|
|
"Generation",
|
|
|
|
"All",
|
|
|
|
}[ri]
|
|
|
|
}
|