2019-08-13 09:37:02 -07:00
package policy
import (
2019-09-03 19:31:42 -07:00
"reflect"
2020-05-26 10:36:56 -07:00
"strings"
2019-08-13 09:37:02 -07:00
"sync"
"time"
2020-05-26 22:26:07 -07:00
listerv1 "k8s.io/client-go/listers/core/v1"
2020-03-17 11:05:20 -07:00
"github.com/go-logr/logr"
2019-08-13 09:37:02 -07:00
"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-12-26 18:41:14 -08:00
"github.com/nirmata/kyverno/pkg/engine"
2019-12-12 15:02:59 -08:00
"github.com/nirmata/kyverno/pkg/engine/response"
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
)
2020-06-25 09:52:54 -07:00
func ( pc * PolicyController ) processExistingResources ( policy * kyverno . ClusterPolicy ) [ ] response . EngineResponse {
2020-03-17 11:05:20 -07:00
logger := pc . log . WithValues ( "policy" , policy . Name )
2019-08-13 09:37:02 -07:00
// Parse through all the resources
// drops the cache after configured rebuild time
pc . rm . Drop ( )
2019-12-12 15:02:59 -08:00
var engineResponses [ ] response . EngineResponse
2019-08-13 09:37:02 -07:00
// get resource that are satisfy the resource description defined in the rules
2020-05-26 10:36:56 -07:00
resourceMap := pc . listResources ( policy )
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 ( ) ) {
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "policy and resource already processed" , "policyResourceVersion" , policy . ResourceVersion , "resourceResourceVersion" , resource . GetResourceVersion ( ) , "kind" , resource . GetKind ( ) , "namespace" , resource . GetNamespace ( ) , "name" , resource . GetName ( ) )
2019-08-13 09:37:02 -07:00
continue
}
2019-12-26 18:41:14 -08:00
// skip reporting violation on pod which has annotation pod-policies.kyverno.io/autogen-applied
2020-05-17 18:51:56 -07:00
ann := policy . GetAnnotations ( )
2020-05-20 13:42:23 -07:00
if annValue , ok := ann [ engine . PodControllersAnnotation ] ; ok {
if annValue != "none" {
2020-05-17 18:51:56 -07:00
if skipPodApplication ( resource , logger ) {
continue
}
}
2019-12-26 18:41:14 -08:00
}
2019-08-13 09:37:02 -07:00
// apply the policy on each
2020-08-14 12:21:06 -07:00
engineResponse := applyPolicy ( * policy , resource , logger , pc . configHandler . GetExcludeGroupRole ( ) )
2020-01-24 12:05:53 -08:00
// get engine response for mutation & validation independently
2019-08-26 13:34:42 -07:00
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
}
2020-06-25 09:52:54 -07:00
func ( pc * PolicyController ) listResources ( policy * kyverno . ClusterPolicy ) map [ string ] unstructured . Unstructured {
pc . log . V ( 4 ) . Info ( "list resources to be processed" )
2019-08-13 09:37:02 -07:00
// key uid
resourceMap := map [ string ] unstructured . Unstructured { }
for _ , rule := range policy . Spec . Rules {
for _ , k := range rule . MatchResources . Kinds {
2020-05-17 09:51:18 -07:00
2020-08-07 09:47:33 +05:30
resourceSchema , _ , err := pc . client . DiscoveryClient . FindResource ( "" , k )
2020-05-17 09:51:18 -07:00
if err != nil {
2020-05-26 10:36:56 -07:00
pc . log . Error ( err , "failed to find resource" , "kind" , k )
2020-05-17 09:51:18 -07:00
continue
2019-08-13 09:37:02 -07:00
}
2020-05-17 09:51:18 -07:00
if ! resourceSchema . Namespaced {
2020-05-26 10:36:56 -07:00
rMap := getResourcesPerNamespace ( k , pc . client , "" , rule , pc . configHandler , pc . log )
mergeResources ( resourceMap , rMap )
2020-05-17 09:51:18 -07:00
} else {
2020-05-26 22:26:07 -07:00
namespaces := getNamespacesForRule ( & rule , pc . nsLister , pc . log )
2020-05-17 09:51:18 -07:00
for _ , ns := range namespaces {
2020-05-26 10:36:56 -07:00
rMap := getResourcesPerNamespace ( k , pc . client , ns , rule , pc . configHandler , pc . log )
mergeResources ( resourceMap , rMap )
2020-05-17 09:51:18 -07:00
}
}
2019-08-13 09:37:02 -07:00
}
}
2020-05-17 09:51:18 -07:00
2020-06-25 09:52:54 -07:00
if policy . HasAutoGenAnnotation ( ) {
return excludePod ( resourceMap , pc . log )
}
return resourceMap
}
// excludePod filter out the pods with ownerReference
func excludePod ( resourceMap map [ string ] unstructured . Unstructured , log logr . Logger ) map [ string ] unstructured . Unstructured {
for uid , r := range resourceMap {
if r . GetKind ( ) != "Pod" {
continue
}
if len ( r . GetOwnerReferences ( ) ) > 0 {
log . V ( 4 ) . Info ( "exclude Pod" , "namespace" , r . GetNamespace ( ) , "name" , r . GetName ( ) )
delete ( resourceMap , uid )
}
}
2019-08-13 09:37:02 -07:00
return resourceMap
}
2020-05-26 22:26:07 -07:00
func getNamespacesForRule ( rule * kyverno . Rule , nslister listerv1 . NamespaceLister , log logr . Logger ) [ ] string {
if len ( rule . MatchResources . Namespaces ) == 0 {
return getAllNamespaces ( nslister , log )
2020-05-26 10:36:56 -07:00
}
var wildcards [ ] string
var results [ ] string
for _ , nsName := range rule . MatchResources . Namespaces {
if hasWildcard ( nsName ) {
wildcards = append ( wildcards , nsName )
}
results = append ( results , nsName )
}
if len ( wildcards ) > 0 {
2020-05-26 22:26:07 -07:00
wildcardMatches := getMatchingNamespaces ( wildcards , nslister , log )
results = append ( results , wildcardMatches ... )
2020-05-26 10:36:56 -07:00
}
return results
}
func hasWildcard ( s string ) bool {
if s == "" {
return false
}
return strings . Contains ( s , "*" ) || strings . Contains ( s , "?" )
}
2020-05-26 22:26:07 -07:00
func getMatchingNamespaces ( wildcards [ ] string , nslister listerv1 . NamespaceLister , log logr . Logger ) [ ] string {
all := getAllNamespaces ( nslister , log )
2020-05-26 10:36:56 -07:00
if len ( all ) == 0 {
return all
}
var results [ ] string
for _ , wc := range wildcards {
for _ , ns := range all {
if wildcard . Match ( wc , ns ) {
results = append ( results , ns )
}
}
}
return results
}
2020-05-26 22:26:07 -07:00
func getAllNamespaces ( nslister listerv1 . NamespaceLister , log logr . Logger ) [ ] string {
2020-05-26 10:36:56 -07:00
var results [ ] string
2020-05-26 22:26:07 -07:00
namespaces , err := nslister . List ( labels . NewSelector ( ) )
2020-05-26 10:36:56 -07:00
if err != nil {
log . Error ( err , "Failed to list namespaces" )
}
for _ , n := range namespaces {
name := n . GetName ( )
results = append ( results , name )
}
return results
}
2020-03-17 11:05:20 -07:00
func getResourcesPerNamespace ( kind string , client * client . Client , namespace string , rule kyverno . Rule , configHandler config . Interface , log logr . Logger ) map [ string ] unstructured . Unstructured {
2019-08-13 09:37:02 -07:00
resourceMap := map [ string ] unstructured . Unstructured { }
2019-09-03 19:31:42 -07:00
ls := rule . MatchResources . Selector
2020-05-15 18:51:45 -07:00
if kind == "Namespace" {
namespace = ""
}
2020-08-07 09:47:33 +05:30
list , err := client . ListResource ( "" , kind , namespace , ls )
2019-08-13 09:37:02 -07:00
if err != nil {
2020-05-15 18:51:45 -07:00
log . Error ( err , "failed to list resources" , "kind" , kind , "namespace" , namespace )
2019-08-13 09:37:02 -07:00
return nil
}
// filter based on name
for _ , r := range list . Items {
2020-05-26 13:56:07 -07:00
if r . GetDeletionTimestamp ( ) != nil {
continue
}
2020-06-25 09:52:27 -07:00
if r . GetKind ( ) == "Pod" {
if ! isRunningPod ( r ) {
continue
}
}
2019-08-13 09:37:02 -07:00
// match name
if rule . MatchResources . Name != "" {
if ! wildcard . Match ( rule . MatchResources . Name , r . GetName ( ) ) {
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
2020-03-17 11:05:20 -07:00
excludeResources ( resourceMap , rule . ExcludeResources . ResourceDescription , configHandler , log )
2019-08-13 09:37:02 -07:00
return resourceMap
}
2020-03-17 11:05:20 -07:00
func excludeResources ( included map [ string ] unstructured . Unstructured , exclude kyverno . ResourceDescription , configHandler config . Interface , log logr . Logger ) {
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 {
2020-03-17 11:05:20 -07:00
log . Error ( err , "failed to build label selector" )
2019-09-03 19:31:42 -07:00
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
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
} ( )
}
}
2020-01-24 12:05:53 -08:00
//Condition defines condition type
2019-09-03 19:31:42 -07:00
type Condition int
const (
2020-01-24 12:05:53 -08:00
//NotEvaluate to not evaluate condition
2019-09-03 19:31:42 -07:00
NotEvaluate Condition = 0
2020-01-24 12:05:53 -08:00
// Process to evaluate condition
Process Condition = 1
// Skip to ignore/skip the condition
Skip Condition = 2
2019-09-03 19:31:42 -07:00
)
2019-08-13 09:37:02 -07:00
// merge b into a map
2020-05-26 10:36:56 -07:00
func mergeResources ( a , b map [ string ] unstructured . Unstructured ) {
2019-08-13 09:37:02 -07:00
for k , v := range b {
a [ k ] = v
}
}
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 )
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 ( )
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 ]
2020-01-27 08:58:53 -08:00
return ! ok
2019-08-13 09:37:02 -07:00
}
func buildKey ( policy , pv , kind , ns , name , rv string ) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}
2019-12-26 18:41:14 -08:00
2020-03-17 11:05:20 -07:00
func skipPodApplication ( resource unstructured . Unstructured , log logr . Logger ) bool {
2019-12-26 18:41:14 -08:00
if resource . GetKind ( ) != "Pod" {
return false
}
annotation := resource . GetAnnotations ( )
if _ , ok := annotation [ engine . PodTemplateAnnotation ] ; ok {
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "Policies already processed on pod controllers, skip processing policy on Pod" , "kind" , resource . GetKind ( ) , "namespace" , resource . GetNamespace ( ) , "name" , resource . GetName ( ) )
2019-12-26 18:41:14 -08:00
return true
}
return false
}