1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/utils.go
2019-08-19 18:57:19 -07:00

520 lines
13 KiB
Go

package engine
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/golang/glog"
"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
//EngineResponse provides the response to the application of a policy rule set on a resource
type EngineResponse struct {
Patches [][]byte
PatchedResource unstructured.Unstructured
RuleInfos []info.RuleInfo
EngineStats
}
//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
}
// //ListResourcesThatApplyToPolicy returns list of resources that are filtered by policy rules
// func ListResourcesThatApplyToPolicy(client *client.Client, policy *kyverno.Policy, filterK8Resources []utils.K8Resource) map[string]resourceInfo {
// // key uid
// resourceMap := map[string]resourceInfo{}
// for _, rule := range policy.Spec.Rules {
// // Match
// for _, k := range rule.MatchResources.Kinds {
// namespaces := []string{}
// if k == "Namespace" {
// namespaces = []string{""}
// } else {
// if rule.MatchResources.Namespace != "" {
// // if namespace is specified then we add the namespace
// namespaces = append(namespaces, rule.MatchResources.Namespace)
// } else {
// // no namespace specified, refer to all namespaces
// namespaces = getAllNamespaces(client)
// }
// // Check if exclude namespace is not clashing
// namespaces = excludeNamespaces(namespaces, rule.ExcludeResources.Namespace)
// }
// // If kind is namespace then namespace is "", override
// // Get resources in the namespace
// for _, ns := range namespaces {
// rMap := getResourcesPerNamespace(k, client, ns, rule, filterK8Resources)
// mergeresources(resourceMap, rMap)
// }
// }
// }
// return resourceMap
// }
// func getResourcesPerNamespace(kind string, client *client.Client, namespace string, rule kyverno.Rule, filterK8Resources []utils.K8Resource) map[string]resourceInfo {
// resourceMap := map[string]resourceInfo{}
// // List resources
// list, err := client.ListResource(kind, namespace, rule.MatchResources.Selector)
// if err != nil {
// glog.Errorf("unable to list resource for %s with label selector %s", kind, rule.MatchResources.Selector.String())
// return nil
// }
// var selector labels.Selector
// // exclude label selector
// if rule.ExcludeResources.Selector != nil {
// selector, err = v1helper.LabelSelectorAsSelector(rule.ExcludeResources.Selector)
// if err != nil {
// glog.Error(err)
// }
// }
// for _, res := range list.Items {
// // exclude label selectors
// if selector != nil {
// set := labels.Set(res.GetLabels())
// if selector.Matches(set) {
// // if matches
// continue
// }
// }
// var name string
// // match
// // name
// // wild card matching
// name = rule.MatchResources.Name
// if name != "" {
// // if does not match then we skip
// if !wildcard.Match(name, res.GetName()) {
// continue
// }
// }
// // exclude
// // name
// // wild card matching
// name = rule.ExcludeResources.Name
// if name != "nil" {
// // if matches then we skip
// if wildcard.Match(name, res.GetName()) {
// continue
// }
// }
// gvk := res.GroupVersionKind()
// ri := resourceInfo{Resource: res, Gvk: &metav1.GroupVersionKind{Group: gvk.Group,
// Version: gvk.Version,
// Kind: gvk.Kind}}
// // Skip the filtered resources
// if utils.SkipFilteredResources(gvk.Kind, res.GetNamespace(), res.GetName(), filterK8Resources) {
// continue
// }
// resourceMap[string(res.GetUID())] = ri
// }
// return resourceMap
// }
// // merge b into a map
// func mergeresources(a, b map[string]resourceInfo) {
// for k, v := range b {
// a[k] = v
// }
// }
// func getAllNamespaces(client *client.Client) []string {
// namespaces := []string{}
// // get all namespaces
// nsList, err := client.ListResource("Namespace", "", nil)
// if err != nil {
// glog.Error(err)
// return namespaces
// }
// for _, ns := range nsList.Items {
// namespaces = append(namespaces, ns.GetName())
// }
// return namespaces
// }
// func excludeNamespaces(namespaces []string, excludeNs string) []string {
// if excludeNs == "" {
// return namespaces
// }
// filteredNamespaces := []string{}
// for _, n := range namespaces {
// if n == excludeNs {
// continue
// }
// filteredNamespaces = append(filteredNamespaces, n)
// }
// return filteredNamespaces
// }
//MatchesResourceDescription checks if the resource matches resource desription of the rule or not
func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno.Rule) bool {
matches := rule.MatchResources.ResourceDescription
exclude := rule.ExcludeResources.ResourceDescription
if !findKind(matches.Kinds, resource.GetKind()) {
return false
}
name := resource.GetName()
namespace := resource.GetNamespace()
if matches.Name != "" {
// Matches
if !wildcard.Match(matches.Name, name) {
return false
}
}
// Exclude
// the resource name matches the exclude resource name then reject
if exclude.Name != "" {
if wildcard.Match(exclude.Name, name) {
return false
}
}
// Matches
// check if the resource namespace is defined in the list of namespaces for inclusion
if len(matches.Namespaces) > 0 && !utils.Contains(matches.Namespaces, namespace) {
return false
}
// Exclude
// check if the resource namespace is defined in the list of namespace for exclusion
if len(exclude.Namespaces) > 0 && utils.Contains(exclude.Namespaces, namespace) {
return false
}
// 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
}
}
// Exclude
if exclude.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(exclude.Selector)
// if the label selector is incorrect, should be fail or
if err != nil {
glog.Error(err)
return false
}
if selector.Matches(labels.Set(resource.GetLabels())) {
return false
}
}
return true
}
// // ResourceMeetsDescription checks requests kind, name and labels to fit the policy rule
// func ResourceMeetsDescription(resourceRaw []byte, matches kyverno.ResourceDescription, exclude kyverno.ResourceDescription, gvk metav1.GroupVersionKind) bool {
// if !findKind(matches.Kinds, gvk.Kind) {
// return false
// }
// if resourceRaw != nil {
// meta := parseMetadataFromObject(resourceRaw)
// name := ParseNameFromObject(resourceRaw)
// namespace := ParseNamespaceFromObject(resourceRaw)
// if matches.Name != "" {
// // Matches
// if !wildcard.Match(matches.Name, name) {
// return false
// }
// }
// // Exclude
// // the resource name matches the exclude resource name then reject
// if exclude.Name != "" {
// if wildcard.Match(exclude.Name, name) {
// return false
// }
// }
// // Matches
// // check if the resource namespace is defined in the list of namespaces for inclusion
// if len(matches.Namespaces) > 0 && !utils.Contains(matches.Namespaces, namespace) {
// return false
// }
// // Exclude
// // check if the resource namespace is defined in the list of namespace for exclusion
// if len(exclude.Namespaces) > 0 && utils.Contains(exclude.Namespaces, namespace) {
// return false
// }
// // Matches
// if matches.Selector != nil {
// selector, err := metav1.LabelSelectorAsSelector(matches.Selector)
// if err != nil {
// glog.Error(err)
// return false
// }
// if meta != nil {
// labelMap := parseLabelsFromMetadata(meta)
// if !selector.Matches(labelMap) {
// return false
// }
// }
// }
// // Exclude
// if exclude.Selector != nil {
// selector, err := metav1.LabelSelectorAsSelector(exclude.Selector)
// // if the label selector is incorrect, should be fail or
// if err != nil {
// glog.Error(err)
// return false
// }
// if meta != nil {
// labelMap := parseLabelsFromMetadata(meta)
// if selector.Matches(labelMap) {
// return false
// }
// }
// }
// }
// return true
// }
// 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}, "/")
}
//ParseKindFromObject get kind from resource
func ParseKindFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
return objectJSON["kind"].(string)
}
//ParseNameFromObject extracts resource name from JSON obj
func ParseNameFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
meta, ok := objectJSON["metadata"]
if !ok {
return ""
}
metaMap, ok := meta.(map[string]interface{})
if !ok {
return ""
}
if name, ok := metaMap["name"].(string); ok {
return name
}
return ""
}
// ParseNamespaceFromObject extracts the namespace from the JSON obj
func ParseNamespaceFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
meta, ok := objectJSON["metadata"]
if !ok {
return ""
}
metaMap, ok := meta.(map[string]interface{})
if !ok {
return ""
}
if name, ok := metaMap["namespace"].(string); ok {
return name
}
return ""
}
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
result[key] = value
}
}
return result
}
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
}
func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) {
for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
return key, value
}
}
return "", nil
}
func findKind(kinds []string, kindGVK string) bool {
for _, kind := range kinds {
if kind == kindGVK {
return true
}
}
return false
}
func isConditionAnchor(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}
func getRawKeyIfWrappedWithAttributes(str string) string {
if len(str) < 2 {
return str
}
if str[0] == '(' && str[len(str)-1] == ')' {
return str[1 : len(str)-1]
} else if (str[0] == '$' || str[0] == '^' || str[0] == '+') && (str[1] == '(' && str[len(str)-1] == ')') {
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] == ')'
}
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)
}
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):]
}
// 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
}
// removeAnchor remove special characters around anchored key
func removeAnchor(key string) string {
if isConditionAnchor(key) {
return key[1 : len(key)-1]
}
if isExistanceAnchor(key) || isAddingAnchor(key) {
return key[2 : len(key)-1]
}
return key
}
// 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)
}
}
type resourceInfo struct {
Resource unstructured.Unstructured
Gvk *metav1.GroupVersionKind
}
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
}