2019-08-13 09:37:02 -07:00
package policy
import (
2019-09-03 19:31:42 -07:00
"reflect"
2019-08-13 09:37:02 -07:00
"sync"
"time"
"github.com/golang/glog"
"github.com/minio/minio/pkg/wildcard"
2019-11-13 13:41:08 -08:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2019-10-18 17:38:46 -07:00
"github.com/nirmata/kyverno/pkg/config"
2019-08-13 09:37:02 -07:00
client "github.com/nirmata/kyverno/pkg/dclient"
2019-08-26 13:34:42 -07:00
"github.com/nirmata/kyverno/pkg/engine"
2019-08-13 09:37:02 -07:00
"github.com/nirmata/kyverno/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-09-03 19:31:42 -07:00
"k8s.io/apimachinery/pkg/labels"
2019-08-13 09:37:02 -07:00
)
2019-10-08 10:57:24 -07:00
func ( pc * PolicyController ) processExistingResources ( policy kyverno . ClusterPolicy ) [ ] engine . EngineResponse {
2019-08-13 09:37:02 -07:00
// Parse through all the resources
// drops the cache after configured rebuild time
pc . rm . Drop ( )
2019-10-08 10:57:24 -07:00
var engineResponses [ ] engine . EngineResponse
2019-08-13 09:37:02 -07:00
// get resource that are satisfy the resource description defined in the rules
2019-10-18 17:38:46 -07:00
resourceMap := listResources ( pc . client , policy , pc . configHandler )
2019-08-13 09:37:02 -07:00
for _ , resource := range resourceMap {
// pre-processing, check if the policy and resource version has been processed before
if ! pc . rm . ProcessResource ( policy . Name , policy . ResourceVersion , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , resource . GetResourceVersion ( ) ) {
2019-08-13 11:32:12 -07:00
glog . V ( 4 ) . Infof ( "policy %s with resource version %s already processed on resource %s/%s/%s with resource version %s" , policy . Name , policy . ResourceVersion , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , resource . GetResourceVersion ( ) )
2019-08-13 09:37:02 -07:00
continue
}
// apply the policy on each
glog . V ( 4 ) . Infof ( "apply policy %s with resource version %s on resource %s/%s/%s with resource version %s" , policy . Name , policy . ResourceVersion , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , resource . GetResourceVersion ( ) )
2019-08-26 13:34:42 -07:00
engineResponse := applyPolicy ( policy , resource , pc . statusAggregator )
// get engine response for mutation & validation indipendently
engineResponses = append ( engineResponses , engineResponse ... )
2019-08-13 09:37:02 -07:00
// post-processing, register the resource as processed
pc . rm . RegisterResource ( policy . GetName ( ) , policy . GetResourceVersion ( ) , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , resource . GetResourceVersion ( ) )
}
2019-08-26 13:34:42 -07:00
return engineResponses
2019-08-13 11:32:12 -07:00
}
2019-10-18 17:38:46 -07:00
func listResources ( client * client . Client , policy kyverno . ClusterPolicy , configHandler config . Interface ) map [ string ] unstructured . Unstructured {
2019-08-13 09:37:02 -07:00
// key uid
resourceMap := map [ string ] unstructured . Unstructured { }
for _ , rule := range policy . Spec . Rules {
// resources that match
for _ , k := range rule . MatchResources . Kinds {
2019-09-03 19:31:42 -07:00
// if kindIsExcluded(k, rule.ExcludeResources.Kinds) {
// glog.V(4).Infof("processing policy %s rule %s: kind %s is exluded", policy.Name, rule.Name, k)
// continue
// }
2019-08-13 09:37:02 -07:00
var namespaces [ ] string
if k == "Namespace" {
// TODO
// this is handled by generator controller
glog . V ( 4 ) . Infof ( "skipping processing policy %s rule %s for kind Namespace" , policy . Name , rule . Name )
continue
}
2019-08-17 09:45:57 -07:00
if len ( rule . MatchResources . Namespaces ) > 0 {
namespaces = append ( namespaces , rule . MatchResources . Namespaces ... )
2019-08-19 11:52:48 -07:00
glog . V ( 4 ) . Infof ( "namespaces specified for inclusion: %v" , rule . MatchResources . Namespaces )
2019-08-13 09:37:02 -07:00
} else {
glog . V ( 4 ) . Infof ( "processing policy %s rule %s, namespace not defined, getting all namespaces " , policy . Name , rule . Name )
// get all namespaces
namespaces = getAllNamespaces ( client )
}
// get resources in the namespaces
for _ , ns := range namespaces {
2019-10-18 17:38:46 -07:00
rMap := getResourcesPerNamespace ( k , client , ns , rule , configHandler )
2019-08-13 09:37:02 -07:00
mergeresources ( resourceMap , rMap )
}
}
}
return resourceMap
}
2019-10-18 17:38:46 -07:00
func getResourcesPerNamespace ( kind string , client * client . Client , namespace string , rule kyverno . Rule , configHandler config . Interface ) map [ string ] unstructured . Unstructured {
2019-08-13 09:37:02 -07:00
resourceMap := map [ string ] unstructured . Unstructured { }
// merge include and exclude label selector values
2019-09-03 19:31:42 -07:00
ls := rule . MatchResources . Selector
// ls := mergeLabelSectors(rule.MatchResources.Selector, rule.ExcludeResources.Selector)
2019-08-13 09:37:02 -07:00
// list resources
glog . V ( 4 ) . Infof ( "get resources for kind %s, namespace %s, selector %v" , kind , namespace , rule . MatchResources . Selector )
list , err := client . ListResource ( kind , namespace , ls )
if err != nil {
glog . Infof ( "unable to get resources: err %v" , err )
return nil
}
// filter based on name
for _ , r := range list . Items {
// match name
if rule . MatchResources . Name != "" {
if ! wildcard . Match ( rule . MatchResources . Name , r . GetName ( ) ) {
glog . V ( 4 ) . Infof ( "skipping resource %s/%s due to include condition name=%s mistatch" , r . GetNamespace ( ) , r . GetName ( ) , rule . MatchResources . Name )
continue
}
}
// Skip the filtered resources
2019-10-18 17:38:46 -07:00
if configHandler . ToFilter ( r . GetKind ( ) , r . GetNamespace ( ) , r . GetName ( ) ) {
2019-08-13 09:37:02 -07:00
continue
}
//TODO check if the group version kind is present or not
resourceMap [ string ( r . GetUID ( ) ) ] = r
}
2019-09-03 19:31:42 -07:00
2019-09-12 15:04:35 -07:00
// exclude the resources
// skip resources to be filtered
2019-10-18 17:38:46 -07:00
excludeResources ( resourceMap , rule . ExcludeResources . ResourceDescription , configHandler )
2019-09-12 15:04:35 -07:00
// glog.V(4).Infof("resource map: %v", resourceMap)
2019-08-13 09:37:02 -07:00
return resourceMap
}
2019-10-18 17:38:46 -07:00
func excludeResources ( included map [ string ] unstructured . Unstructured , exclude kyverno . ResourceDescription , configHandler config . Interface ) {
2019-09-03 19:31:42 -07:00
if reflect . DeepEqual ( exclude , ( kyverno . ResourceDescription { } ) ) {
return
}
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
}
selector , err := metav1 . LabelSelectorAsSelector ( exclude . Selector )
// if the label selector is incorrect, should be fail or
if err != nil {
glog . Error ( err )
return Skip
}
if selector . Matches ( labels . Set ( labelsMap ) ) {
return Skip
}
return Process
}
findKind := func ( kind string , kinds [ ] string ) bool {
for _ , k := range kinds {
if k == kind {
return true
}
}
return false
}
excludeKind := func ( kind string ) Condition {
if len ( exclude . Kinds ) == 0 {
return NotEvaluate
}
if findKind ( kind , exclude . Kinds ) {
return Skip
}
return Process
}
// check exclude condition for each resource
for uid , resource := range included {
// 0 -> dont check
// 1 -> is not to be exclude
// 2 -> to be exclude
excludeEval := [ ] Condition { }
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 )
}
2019-09-12 15:04:35 -07:00
// exclude the filtered resources
2019-10-18 17:38:46 -07:00
if configHandler . ToFilter ( resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) ) {
2019-09-12 15:04:35 -07:00
//TODO: improve the text
glog . V ( 4 ) . Infof ( "excluding resource %s/%s/%s as its satisfies the filtered resources" , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) )
delete ( included , uid )
continue
}
2019-09-03 19:31:42 -07:00
func ( ) bool {
for _ , ret := range excludeEval {
if ret == Process {
// Process the resources
continue
}
}
// Skip the resource from processing
delete ( included , uid )
return false
} ( )
}
}
type Condition int
const (
NotEvaluate Condition = 0
Process Condition = 1
Skip Condition = 2
)
2019-08-13 09:37:02 -07:00
// merge b into a map
func mergeresources ( a , b map [ string ] unstructured . Unstructured ) {
for k , v := range b {
a [ k ] = v
}
}
func mergeLabelSectors ( include , exclude * metav1 . LabelSelector ) * metav1 . LabelSelector {
if exclude == nil {
return include
}
// negate the exclude information
// copy the label selector
//TODO: support exclude expressions in exclude
ls := include . DeepCopy ( )
for k , v := range exclude . MatchLabels {
lsreq := metav1 . LabelSelectorRequirement {
Key : k ,
Operator : metav1 . LabelSelectorOpNotIn ,
Values : [ ] string { v } ,
}
ls . MatchExpressions = append ( ls . MatchExpressions , lsreq )
}
return ls
}
func kindIsExcluded ( kind string , list [ ] string ) bool {
for _ , b := range list {
if b == kind {
return true
}
}
return false
}
2019-08-17 09:45:57 -07:00
func excludeNamespaces ( namespaces , excludeNs [ ] string ) [ ] string {
if len ( excludeNs ) == 0 {
2019-08-13 09:37:02 -07:00
return namespaces
}
filteredNamespaces := [ ] string { }
for _ , n := range namespaces {
2019-09-12 17:11:55 -07:00
if utils . ContainsNamepace ( excludeNs , n ) {
2019-08-13 09:37:02 -07:00
continue
}
filteredNamespaces = append ( filteredNamespaces , n )
}
return filteredNamespaces
}
func getAllNamespaces ( client * client . Client ) [ ] string {
var 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
}
2019-08-14 10:01:47 -07:00
//NewResourceManager returns a new ResourceManager
2019-08-13 09:37:02 -07:00
func NewResourceManager ( rebuildTime int64 ) * ResourceManager {
rm := ResourceManager {
data : make ( map [ string ] interface { } ) ,
time : time . Now ( ) ,
rebuildTime : rebuildTime ,
}
// set time it was built
return & rm
}
2019-08-14 10:01:47 -07:00
// ResourceManager stores the details on already processed resources for caching
2019-08-13 09:37:02 -07:00
type ResourceManager struct {
// we drop and re-build the cache
// based on the memory consumer of by the map
data map [ string ] interface { }
mux sync . RWMutex
time time . Time
rebuildTime int64 // after how many seconds should we rebuild the cache
}
type resourceManager interface {
ProcessResource ( policy , pv , kind , ns , name , rv string ) bool
//TODO removeResource(kind, ns, name string) error
RegisterResource ( policy , pv , kind , ns , name , rv string )
// reload
Drop ( )
}
//Drop drop the cache after every rebuild interval mins
//TODO: or drop based on the size
func ( rm * ResourceManager ) Drop ( ) {
2019-08-13 10:03:00 -07:00
timeSince := time . Since ( rm . time )
glog . V ( 4 ) . Infof ( "time since last cache reset time %v is %v" , rm . time , timeSince )
glog . V ( 4 ) . Infof ( "cache rebuild time %v" , time . Duration ( rm . rebuildTime ) * time . Second )
if timeSince > time . Duration ( rm . rebuildTime ) * time . Second {
2019-08-13 09:37:02 -07:00
rm . mux . Lock ( )
defer rm . mux . Unlock ( )
rm . data = map [ string ] interface { } { }
2019-08-13 10:03:00 -07:00
rm . time = time . Now ( )
glog . V ( 4 ) . Infof ( "dropping cache at time %v" , rm . time )
2019-08-13 09:37:02 -07:00
}
}
var empty struct { }
//RegisterResource stores if the policy is processed on this resource version
func ( rm * ResourceManager ) RegisterResource ( policy , pv , kind , ns , name , rv string ) {
rm . mux . Lock ( )
defer rm . mux . Unlock ( )
// add the resource
key := buildKey ( policy , pv , kind , ns , name , rv )
rm . data [ key ] = empty
}
//ProcessResource returns true if the policy was not applied on the resource
func ( rm * ResourceManager ) ProcessResource ( policy , pv , kind , ns , name , rv string ) bool {
rm . mux . RLock ( )
defer rm . mux . RUnlock ( )
key := buildKey ( policy , pv , kind , ns , name , rv )
_ , ok := rm . data [ key ]
return ok == false
}
func buildKey ( policy , pv , kind , ns , name , rv string ) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}