2019-08-13 16:37:02 +00:00
package policy
import (
2019-09-04 02:31:42 +00:00
"reflect"
2019-08-13 16:37:02 +00:00
"sync"
"time"
"github.com/golang/glog"
"github.com/minio/minio/pkg/wildcard"
2019-11-13 21:41:08 +00:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2019-10-19 00:38:46 +00:00
"github.com/nirmata/kyverno/pkg/config"
2019-08-13 16:37:02 +00:00
client "github.com/nirmata/kyverno/pkg/dclient"
2019-12-27 02:41:14 +00:00
"github.com/nirmata/kyverno/pkg/engine"
2019-12-12 23:02:59 +00:00
"github.com/nirmata/kyverno/pkg/engine/response"
2019-08-13 16:37:02 +00: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-04 02:31:42 +00:00
"k8s.io/apimachinery/pkg/labels"
2019-08-13 16:37:02 +00:00
)
2019-12-12 23:02:59 +00:00
func ( pc * PolicyController ) processExistingResources ( policy kyverno . ClusterPolicy ) [ ] response . EngineResponse {
2019-08-13 16:37:02 +00:00
// Parse through all the resources
// drops the cache after configured rebuild time
pc . rm . Drop ( )
2019-12-12 23:02:59 +00:00
var engineResponses [ ] response . EngineResponse
2019-08-13 16:37:02 +00:00
// get resource that are satisfy the resource description defined in the rules
2019-10-19 00:38:46 +00:00
resourceMap := listResources ( pc . client , policy , pc . configHandler )
2019-08-13 16:37:02 +00: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 18:32:12 +00: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 16:37:02 +00:00
continue
}
2019-12-27 02:41:14 +00:00
// skip reporting violation on pod which has annotation pod-policies.kyverno.io/autogen-applied
if skipPodApplication ( resource ) {
continue
}
2019-08-13 16:37:02 +00:00
// 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 ( ) )
2020-02-22 18:05:02 +00:00
engineResponse := applyPolicy ( policy , resource )
2020-01-24 20:05:53 +00:00
// get engine response for mutation & validation independently
2019-08-26 20:34:42 +00:00
engineResponses = append ( engineResponses , engineResponse ... )
2019-08-13 16:37:02 +00: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 20:34:42 +00:00
return engineResponses
2019-08-13 18:32:12 +00:00
}
2019-10-19 00:38:46 +00:00
func listResources ( client * client . Client , policy kyverno . ClusterPolicy , configHandler config . Interface ) map [ string ] unstructured . Unstructured {
2019-08-13 16:37:02 +00: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-04 02:31:42 +00: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 16:37:02 +00: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 16:45:57 +00:00
if len ( rule . MatchResources . Namespaces ) > 0 {
namespaces = append ( namespaces , rule . MatchResources . Namespaces ... )
2019-08-19 18:52:48 +00:00
glog . V ( 4 ) . Infof ( "namespaces specified for inclusion: %v" , rule . MatchResources . Namespaces )
2019-08-13 16:37:02 +00: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-19 00:38:46 +00:00
rMap := getResourcesPerNamespace ( k , client , ns , rule , configHandler )
2019-08-13 16:37:02 +00:00
mergeresources ( resourceMap , rMap )
}
}
}
return resourceMap
}
2019-10-19 00:38:46 +00:00
func getResourcesPerNamespace ( kind string , client * client . Client , namespace string , rule kyverno . Rule , configHandler config . Interface ) map [ string ] unstructured . Unstructured {
2019-08-13 16:37:02 +00:00
resourceMap := map [ string ] unstructured . Unstructured { }
// merge include and exclude label selector values
2019-09-04 02:31:42 +00:00
ls := rule . MatchResources . Selector
// ls := mergeLabelSectors(rule.MatchResources.Selector, rule.ExcludeResources.Selector)
2019-08-13 16:37:02 +00: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-19 00:38:46 +00:00
if configHandler . ToFilter ( r . GetKind ( ) , r . GetNamespace ( ) , r . GetName ( ) ) {
2019-08-13 16:37:02 +00:00
continue
}
//TODO check if the group version kind is present or not
resourceMap [ string ( r . GetUID ( ) ) ] = r
}
2019-09-04 02:31:42 +00:00
2019-09-12 22:04:35 +00:00
// exclude the resources
// skip resources to be filtered
2019-10-19 00:38:46 +00:00
excludeResources ( resourceMap , rule . ExcludeResources . ResourceDescription , configHandler )
2019-09-12 22:04:35 +00:00
// glog.V(4).Infof("resource map: %v", resourceMap)
2019-08-13 16:37:02 +00:00
return resourceMap
}
2019-10-19 00:38:46 +00:00
func excludeResources ( included map [ string ] unstructured . Unstructured , exclude kyverno . ResourceDescription , configHandler config . Interface ) {
2019-09-04 02:31:42 +00: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-13 00:11:55 +00:00
if utils . ContainsNamepace ( exclude . Namespaces , namespace ) {
2019-09-04 02:31:42 +00: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 22:04:35 +00:00
// exclude the filtered resources
2019-10-19 00:38:46 +00:00
if configHandler . ToFilter ( resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) ) {
2019-09-12 22:04:35 +00: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-04 02:31:42 +00:00
func ( ) bool {
for _ , ret := range excludeEval {
if ret == Process {
// Process the resources
continue
}
}
// Skip the resource from processing
delete ( included , uid )
return false
} ( )
}
}
2020-01-24 20:05:53 +00:00
//Condition defines condition type
2019-09-04 02:31:42 +00:00
type Condition int
const (
2020-01-24 20:05:53 +00:00
//NotEvaluate to not evaluate condition
2019-09-04 02:31:42 +00:00
NotEvaluate Condition = 0
2020-01-24 20:05:53 +00:00
// Process to evaluate condition
Process Condition = 1
// Skip to ignore/skip the condition
Skip Condition = 2
2019-09-04 02:31:42 +00:00
)
2019-08-13 16:37:02 +00:00
// merge b into a map
func mergeresources ( a , b map [ string ] unstructured . Unstructured ) {
for k , v := range b {
a [ k ] = v
}
}
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 17:01:47 +00:00
//NewResourceManager returns a new ResourceManager
2019-08-13 16:37:02 +00: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 17:01:47 +00:00
// ResourceManager stores the details on already processed resources for caching
2019-08-13 16:37:02 +00: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 17:03:00 +00: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 16:37:02 +00:00
rm . mux . Lock ( )
defer rm . mux . Unlock ( )
rm . data = map [ string ] interface { } { }
2019-08-13 17:03:00 +00:00
rm . time = time . Now ( )
glog . V ( 4 ) . Infof ( "dropping cache at time %v" , rm . time )
2019-08-13 16:37:02 +00: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 ]
2020-01-27 16:58:53 +00:00
return ! ok
2019-08-13 16:37:02 +00:00
}
func buildKey ( policy , pv , kind , ns , name , rv string ) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}
2019-12-27 02:41:14 +00:00
func skipPodApplication ( resource unstructured . Unstructured ) bool {
if resource . GetKind ( ) != "Pod" {
return false
}
annotation := resource . GetAnnotations ( )
if _ , ok := annotation [ engine . PodTemplateAnnotation ] ; ok {
glog . V ( 4 ) . Infof ( "Policies already processed on pod controllers, skip processing policy on Pod/%s/%s" , resource . GetNamespace ( ) , resource . GetName ( ) )
return true
}
return false
}