2019-08-14 14:56:53 -07:00
package namespace
import (
"sync"
"time"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
2019-08-17 09:58:14 -07:00
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
2019-08-21 01:07:32 -07:00
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
2019-08-26 13:34:42 -07:00
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
2019-08-14 14:56:53 -07:00
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
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 ( )
}
// ResourceManager stores the details on already processed resources for caching
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
}
//NewResourceManager returns a new ResourceManager
func NewResourceManager ( rebuildTime int64 ) * ResourceManager {
rm := ResourceManager {
data : make ( map [ string ] interface { } ) ,
time : time . Now ( ) ,
rebuildTime : rebuildTime ,
}
// set time it was built
return & rm
}
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
}
2019-08-14 15:06:38 -07:00
//Drop drop the cache after every rebuild interval mins
//TODO: or drop based on the size
func ( rm * ResourceManager ) Drop ( ) {
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 {
rm . mux . Lock ( )
defer rm . mux . Unlock ( )
rm . data = map [ string ] interface { } { }
rm . time = time . Now ( )
glog . V ( 4 ) . Infof ( "dropping cache at time %v" , rm . time )
}
}
2019-08-14 14:56:53 -07:00
func buildKey ( policy , pv , kind , ns , name , rv string ) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}
2019-10-08 10:57:24 -07:00
func ( nsc * NamespaceController ) processNamespace ( namespace corev1 . Namespace ) [ ] engine . EngineResponse {
2019-08-14 14:56:53 -07:00
// convert to unstructured
2019-08-14 18:40:33 -07:00
unstr , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( & namespace )
2019-08-14 14:56:53 -07:00
if err != nil {
glog . Infof ( "unable to convert to unstructured, not processing any policies: %v" , err )
2019-08-26 13:34:42 -07:00
return nil
2019-08-14 14:56:53 -07:00
}
2019-08-14 15:06:38 -07:00
nsc . rm . Drop ( )
2019-08-14 14:56:53 -07:00
ns := unstructured . Unstructured { Object : unstr }
2019-08-14 18:40:33 -07:00
// get all the policies that have a generate rule and resource description satifies the namespace
2019-08-14 14:56:53 -07:00
// apply policy on resource
policies := listpolicies ( ns , nsc . pLister )
2019-10-08 10:57:24 -07:00
var engineResponses [ ] engine . EngineResponse
2019-08-14 14:56:53 -07:00
for _ , policy := range policies {
2019-08-14 15:06:38 -07:00
// pre-processing, check if the policy and resource version has been processed before
if ! nsc . rm . ProcessResource ( policy . Name , policy . ResourceVersion , ns . GetKind ( ) , ns . GetNamespace ( ) , ns . GetName ( ) , ns . GetResourceVersion ( ) ) {
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 , ns . GetKind ( ) , ns . GetNamespace ( ) , ns . GetName ( ) , ns . GetResourceVersion ( ) )
continue
}
2019-08-26 13:34:42 -07:00
engineResponse := applyPolicy ( nsc . client , ns , * policy , nsc . policyStatus )
engineResponses = append ( engineResponses , engineResponse )
2019-08-14 15:06:38 -07:00
// post-processing, register the resource as processed
nsc . rm . RegisterResource ( policy . GetName ( ) , policy . GetResourceVersion ( ) , ns . GetKind ( ) , ns . GetNamespace ( ) , ns . GetName ( ) , ns . GetResourceVersion ( ) )
2019-08-14 14:56:53 -07:00
}
2019-08-26 13:34:42 -07:00
return engineResponses
2019-08-14 14:56:53 -07:00
}
2019-09-28 15:39:06 -07:00
func generateRuleExists ( policy * kyverno . ClusterPolicy ) bool {
for _ , rule := range policy . Spec . Rules {
if rule . Generation != ( kyverno . Generation { } ) {
return true
}
}
return false
}
func ( nsc * NamespaceController ) processPolicy ( policy * kyverno . ClusterPolicy ) {
filteredNamespaces := [ ] * corev1 . Namespace { }
// get namespaces that policy applies on
namespaces , err := nsc . nsLister . ListResources ( labels . NewSelector ( ) )
if err != nil {
glog . Errorf ( "failed to get list namespaces: %v" , err )
return
}
for _ , namespace := range namespaces {
// convert to unstructured
unstr , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( namespace )
if err != nil {
glog . Infof ( "unable to convert to unstructured, not processing any policies: %v" , err )
continue
}
ns := unstructured . Unstructured { Object : unstr }
for _ , rule := range policy . Spec . Rules {
if rule . Generation == ( kyverno . Generation { } ) {
continue
}
ok := engine . MatchesResourceDescription ( ns , rule )
if ! ok {
glog . V ( 4 ) . Infof ( "namespace %s does not satisfy the resource description for the policy %s rule %s" , ns . GetName ( ) , policy . Name , rule . Name )
continue
}
glog . V ( 4 ) . Infof ( "namespace %s satisfies resource description for policy %s rule %s" , ns . GetName ( ) , policy . Name , rule . Name )
filteredNamespaces = append ( filteredNamespaces , namespace )
}
}
// list of namespaces that the policy applies on
for _ , ns := range filteredNamespaces {
glog . V ( 4 ) . Infof ( "policy %s with generate rule: namespace %s to be processed " , policy . Name , ns . Name )
nsc . addNamespace ( ns )
}
}
2019-09-03 14:51:51 -07:00
func listpolicies ( ns unstructured . Unstructured , pLister kyvernolister . ClusterPolicyLister ) [ ] * kyverno . ClusterPolicy {
var filteredpolicies [ ] * kyverno . ClusterPolicy
2019-08-14 18:40:33 -07:00
glog . V ( 4 ) . Infof ( "listing policies for namespace %s" , ns . GetName ( ) )
2019-08-14 14:56:53 -07:00
policies , err := pLister . List ( labels . NewSelector ( ) )
if err != nil {
glog . Errorf ( "failed to get list policies: %v" , err )
return nil
}
for _ , policy := range policies {
for _ , rule := range policy . Spec . Rules {
if rule . Generation == ( kyverno . Generation { } ) {
continue
}
ok := engine . MatchesResourceDescription ( ns , rule )
if ! ok {
2019-08-14 18:40:33 -07:00
glog . V ( 4 ) . Infof ( "namespace %s does not satisfy the resource description for the policy %s rule %s" , ns . GetName ( ) , policy . Name , rule . Name )
2019-08-14 14:56:53 -07:00
continue
}
2019-08-14 18:40:33 -07:00
glog . V ( 4 ) . Infof ( "namespace %s satisfies resource description for policy %s rule %s" , ns . GetName ( ) , policy . Name , rule . Name )
2019-08-14 14:56:53 -07:00
filteredpolicies = append ( filteredpolicies , policy )
}
}
return filteredpolicies
}
2019-10-08 10:57:24 -07:00
func applyPolicy ( client * client . Client , resource unstructured . Unstructured , p kyverno . ClusterPolicy , policyStatus policyctr . PolicyStatusInterface ) engine . EngineResponse {
2019-08-26 13:34:42 -07:00
var policyStats [ ] policyctr . PolicyStat
// gather stats from the engine response
gatherStat := func ( policyName string , policyResponse engine . PolicyResponse ) {
ps := policyctr . PolicyStat { }
2019-08-20 17:35:40 -07:00
ps . PolicyName = policyName
2019-10-08 10:57:24 -07:00
ps . Stats . GenerationExecutionTime = policyResponse . ProcessingTime
2019-08-26 13:34:42 -07:00
ps . Stats . RulesAppliedCount = policyResponse . RulesAppliedCount
2019-09-03 17:43:36 -07:00
// capture rule level stats
for _ , rule := range policyResponse . Rules {
rs := policyctr . RuleStatinfo { }
rs . RuleName = rule . Name
rs . ExecutionTime = rule . RuleStats . ProcessingTime
if rule . Success {
rs . RuleAppliedCount ++
} else {
rs . RulesFailedCount ++
}
ps . Stats . Rules = append ( ps . Stats . Rules , rs )
}
2019-08-26 13:34:42 -07:00
policyStats = append ( policyStats , ps )
2019-08-20 17:35:40 -07:00
}
// send stats for aggregation
sendStat := func ( blocked bool ) {
2019-08-26 13:34:42 -07:00
for _ , stat := range policyStats {
stat . Stats . ResourceBlocked = utils . Btoi ( blocked )
//SEND
policyStatus . SendStat ( stat )
}
2019-08-20 17:35:40 -07:00
}
2019-08-14 14:56:53 -07:00
startTime := time . Now ( )
2019-08-21 01:07:32 -07:00
glog . V ( 4 ) . Infof ( "Started apply policy %s on resource %s/%s/%s (%v)" , p . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , startTime )
2019-08-14 14:56:53 -07:00
defer func ( ) {
2019-08-21 01:07:32 -07:00
glog . V ( 4 ) . Infof ( "Finished applying %s on resource %s/%s/%s (%v)" , p . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , time . Since ( startTime ) )
2019-08-14 14:56:53 -07:00
} ( )
2019-08-21 01:07:32 -07:00
engineResponse := engine . Generate ( client , p , resource )
2019-08-20 17:35:40 -07:00
// gather stats
2019-08-26 13:34:42 -07:00
gatherStat ( p . Name , engineResponse . PolicyResponse )
2019-08-20 17:35:40 -07:00
//send stats
sendStat ( false )
2019-08-14 14:56:53 -07:00
2019-08-26 13:34:42 -07:00
return engineResponse
2019-08-14 14:56:53 -07:00
}