2019-11-12 14:41:29 -08:00
package policyviolation
import (
"reflect"
2019-11-12 16:01:09 -08:00
"strconv"
"strings"
"sync"
2019-11-12 14:41:29 -08:00
"time"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
2019-11-12 16:04:00 -08:00
kyvernov1alpha1 "github.com/nirmata/kyverno/pkg/client/clientset/versioned/typed/kyverno/v1alpha1"
2019-11-12 14:41:29 -08:00
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
dclient "github.com/nirmata/kyverno/pkg/dclient"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-11-12 16:01:09 -08:00
"k8s.io/apimachinery/pkg/labels"
2019-11-12 14:41:29 -08:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/workqueue"
)
const workQueueName = "policy-violation-controller"
const workQueueRetryLimit = 3
//Generator creates PV
type Generator struct {
dclient * dclient . Client
2019-11-12 16:04:00 -08:00
pvInterface kyvernov1alpha1 . KyvernoV1alpha1Interface
2019-11-12 14:41:29 -08:00
pvLister kyvernolister . ClusterPolicyViolationLister
2019-11-12 16:04:00 -08:00
nspvLister kyvernolister . NamespacedPolicyViolationLister
2019-11-12 14:41:29 -08:00
queue workqueue . RateLimitingInterface
2019-11-12 16:01:09 -08:00
dataStore * dataStore
}
func NewDataStore ( ) * dataStore {
ds := dataStore {
data : make ( map [ string ] Info ) ,
}
return & ds
}
type dataStore struct {
data map [ string ] Info
mu sync . RWMutex
}
func ( ds * dataStore ) add ( keyHash string , info Info ) {
ds . mu . Lock ( )
defer ds . mu . Unlock ( )
// queue the key hash
ds . data [ keyHash ] = info
}
func ( ds * dataStore ) lookup ( keyHash string ) Info {
ds . mu . RLock ( )
defer ds . mu . RUnlock ( )
return ds . data [ keyHash ]
}
func ( ds * dataStore ) delete ( keyHash string ) {
ds . mu . Lock ( )
defer ds . mu . Unlock ( )
delete ( ds . data , keyHash )
2019-11-12 14:41:29 -08:00
}
//Info is a request to create PV
type Info struct {
Blocked bool
PolicyName string
Resource unstructured . Unstructured
Rules [ ] kyverno . ViolatedRule
}
2019-11-12 16:01:09 -08:00
func ( i Info ) toKey ( ) string {
keys := [ ] string {
strconv . FormatBool ( i . Blocked ) ,
i . PolicyName ,
i . Resource . GetKind ( ) ,
i . Resource . GetNamespace ( ) ,
i . Resource . GetName ( ) ,
strconv . Itoa ( len ( i . Rules ) ) ,
}
return strings . Join ( keys , "/" )
}
2019-11-12 14:41:29 -08:00
// make the struct hashable
//GeneratorInterface provides API to create PVs
type GeneratorInterface interface {
Add ( infos ... Info )
}
// NewPVGenerator returns a new instance of policy violation generator
2019-11-12 20:19:20 -08:00
func NewPVGenerator ( client * kyvernoclient . Clientset , dclient * client . Client ,
2019-11-12 16:04:00 -08:00
pvLister kyvernolister . ClusterPolicyViolationLister ,
nspvLister kyvernolister . NamespacedPolicyViolationLister ) * Generator {
2019-11-12 14:41:29 -08:00
gen := Generator {
2019-11-12 16:04:00 -08:00
pvInterface : client . KyvernoV1alpha1 ( ) ,
2019-11-12 20:19:20 -08:00
dclient : dclient ,
2019-11-12 14:41:29 -08:00
pvLister : pvLister ,
2019-11-12 16:04:00 -08:00
nspvLister : nspvLister ,
2019-11-12 14:41:29 -08:00
queue : workqueue . NewNamedRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) , workQueueName ) ,
2019-11-12 16:01:09 -08:00
dataStore : NewDataStore ( ) ,
2019-11-12 14:41:29 -08:00
}
return & gen
}
func ( gen * Generator ) enqueue ( info Info ) {
2019-11-12 16:01:09 -08:00
// add to data map
keyHash := info . toKey ( )
// add to
// queue the key hash
gen . dataStore . add ( keyHash , info )
gen . queue . Add ( keyHash )
2019-11-12 14:41:29 -08:00
}
//Add queues a policy violation create request
func ( gen * Generator ) Add ( infos ... Info ) {
for _ , info := range infos {
gen . enqueue ( info )
}
}
// Run starts the workers
func ( gen * Generator ) Run ( workers int , stopCh <- chan struct { } ) {
defer utilruntime . HandleCrash ( )
2019-11-12 18:25:50 -08:00
glog . Info ( "Start policy violation generator" )
defer glog . Info ( "Shutting down policy violation generator" )
2019-11-12 14:41:29 -08:00
for i := 0 ; i < workers ; i ++ {
go wait . Until ( gen . runWorker , time . Second , stopCh )
}
<- stopCh
}
func ( gen * Generator ) runWorker ( ) {
for gen . processNextWorkitem ( ) {
}
}
func ( gen * Generator ) handleErr ( err error , key interface { } ) {
if err == nil {
gen . queue . Forget ( key )
2019-11-12 16:01:09 -08:00
return
2019-11-12 14:41:29 -08:00
}
// retires requests if there is error
if gen . queue . NumRequeues ( key ) < workQueueRetryLimit {
glog . Warningf ( "Error syncing policy violation %v: %v" , key , err )
// Re-enqueue the key rate limited. Based on the rate limiter on the
// queue and the re-enqueue history, the key will be processed later again.
gen . queue . AddRateLimited ( key )
return
}
gen . queue . Forget ( key )
glog . Error ( err )
2019-11-12 16:01:09 -08:00
// remove from data store
if keyHash , ok := key . ( string ) ; ok {
gen . dataStore . delete ( keyHash )
}
2019-11-12 14:41:29 -08:00
glog . Warningf ( "Dropping the key out of the queue: %v" , err )
}
func ( gen * Generator ) processNextWorkitem ( ) bool {
obj , shutdown := gen . queue . Get ( )
if shutdown {
return false
}
err := func ( obj interface { } ) error {
defer gen . queue . Done ( obj )
2019-11-12 16:01:09 -08:00
var keyHash string
2019-11-12 14:41:29 -08:00
var ok bool
2019-11-12 16:01:09 -08:00
if keyHash , ok = obj . ( string ) ; ! ok {
2019-11-12 14:41:29 -08:00
gen . queue . Forget ( obj )
2019-11-12 19:01:48 -08:00
glog . Warningf ( "Expecting type string but got %v\n" , obj )
2019-11-12 14:41:29 -08:00
return nil
}
2019-11-12 16:01:09 -08:00
// lookup data store
info := gen . dataStore . lookup ( keyHash )
if reflect . DeepEqual ( info , Info { } ) {
// empty key
gen . queue . Forget ( obj )
glog . Warningf ( "Got empty key %v\n" , obj )
return nil
}
err := gen . syncHandler ( info )
2019-11-12 14:41:29 -08:00
gen . handleErr ( err , obj )
return nil
} ( obj )
if err != nil {
glog . Error ( err )
return true
}
return true
}
func ( gen * Generator ) syncHandler ( info Info ) error {
2019-11-12 18:25:50 -08:00
glog . V ( 4 ) . Infof ( "recieved info:%v" , info )
2019-11-12 16:04:00 -08:00
// cluster policy violations
if info . Resource . GetNamespace ( ) == "" {
var pvs [ ] kyverno . ClusterPolicyViolation
if ! info . Blocked {
pvs = append ( pvs , buildPV ( info ) )
} else {
// blocked
// get owners
pvs = buildPVWithOwners ( gen . dclient , info )
}
// create policy violation
2019-11-12 20:19:20 -08:00
if err := createPVS ( gen . dclient , pvs , gen . pvLister , gen . pvInterface ) ; err != nil {
return err
}
2019-11-12 19:01:48 -08:00
glog . V ( 3 ) . Infof ( "Created cluster policy violation policy=%s, resource=%s/%s/%s" ,
info . PolicyName , info . Resource . GetKind ( ) , info . Resource . GetNamespace ( ) , info . Resource . GetName ( ) )
2019-11-12 16:04:00 -08:00
return nil
}
// namespaced policy violations
var pvs [ ] kyverno . NamespacedPolicyViolation
2019-11-12 14:41:29 -08:00
if ! info . Blocked {
2019-11-12 16:04:00 -08:00
pvs = append ( pvs , buildNamespacedPV ( info ) )
2019-11-12 14:41:29 -08:00
} else {
2019-11-12 16:04:00 -08:00
pvs = buildNamespacedPVWithOwner ( gen . dclient , info )
2019-11-12 14:41:29 -08:00
}
2019-11-12 16:04:00 -08:00
2019-11-12 20:19:20 -08:00
if err := createNamespacedPV ( gen . dclient , gen . nspvLister , gen . pvInterface , pvs ) ; err != nil {
return err
}
2019-11-12 19:01:48 -08:00
glog . V ( 3 ) . Infof ( "Created namespaced policy violation policy=%s, resource=%s/%s/%s" ,
info . PolicyName , info . Resource . GetKind ( ) , info . Resource . GetNamespace ( ) , info . Resource . GetName ( ) )
2019-11-12 14:41:29 -08:00
return nil
}
2019-11-12 20:19:20 -08:00
func createPVS ( dclient * client . Client , pvs [ ] kyverno . ClusterPolicyViolation , pvLister kyvernolister . ClusterPolicyViolationLister , pvInterface kyvernov1alpha1 . KyvernoV1alpha1Interface ) error {
2019-11-12 14:41:29 -08:00
for _ , pv := range pvs {
2019-11-12 20:19:20 -08:00
if err := createPVNew ( dclient , pv , pvLister , pvInterface ) ; err != nil {
return err
}
2019-11-12 14:41:29 -08:00
}
2019-11-12 20:19:20 -08:00
return nil
2019-11-12 14:41:29 -08:00
}
2019-11-12 20:19:20 -08:00
func createPVNew ( dclient * client . Client , pv kyverno . ClusterPolicyViolation , pvLister kyvernolister . ClusterPolicyViolationLister , pvInterface kyvernov1alpha1 . KyvernoV1alpha1Interface ) error {
2019-11-12 14:41:29 -08:00
var err error
// PV already exists
ePV , err := getExistingPVIfAny ( pvLister , pv )
if err != nil {
glog . Error ( err )
return err
}
if ePV == nil {
// Create a New PV
glog . V ( 4 ) . Infof ( "creating new policy violation for policy %s & resource %s/%s/%s" , pv . Spec . Policy , pv . Spec . ResourceSpec . Kind , pv . Spec . ResourceSpec . Namespace , pv . Spec . ResourceSpec . Name )
2019-11-12 20:19:20 -08:00
err := retryGetResource ( dclient , pv . Spec . ResourceSpec )
if err != nil {
return err
}
2019-11-12 16:04:00 -08:00
_ , err = pvInterface . ClusterPolicyViolations ( ) . Create ( & pv )
2019-11-12 14:41:29 -08:00
if err != nil {
glog . Error ( err )
return err
}
glog . Infof ( "policy violation created for resource %v" , pv . Spec . ResourceSpec )
return nil
}
// Update existing PV if there any changes
if reflect . DeepEqual ( pv . Spec , ePV . Spec ) {
glog . V ( 4 ) . Infof ( "policy violation spec %v did not change so not updating it" , pv . Spec )
return nil
}
2019-11-12 16:04:00 -08:00
_ , err = pvInterface . ClusterPolicyViolations ( ) . Update ( & pv )
2019-11-12 14:41:29 -08:00
if err != nil {
glog . Error ( err )
return err
}
glog . Infof ( "policy violation updated for resource %v" , pv . Spec . ResourceSpec )
return nil
}
2019-11-12 16:01:09 -08:00
func getExistingPVIfAny ( pvLister kyvernolister . ClusterPolicyViolationLister , currpv kyverno . ClusterPolicyViolation ) ( * kyverno . ClusterPolicyViolation , error ) {
pvs , err := pvLister . List ( labels . Everything ( ) )
2019-11-12 14:41:29 -08:00
if err != nil {
2019-11-12 16:01:09 -08:00
glog . Errorf ( "unable to list policy violations : %v" , err )
2019-11-12 14:41:29 -08:00
return nil , err
}
2019-11-12 16:01:09 -08:00
for _ , pv := range pvs {
// find a policy on same resource and policy combination
if pv . Spec . Policy == currpv . Spec . Policy &&
pv . Spec . ResourceSpec . Kind == currpv . Spec . ResourceSpec . Kind &&
pv . Spec . ResourceSpec . Namespace == currpv . Spec . ResourceSpec . Namespace &&
pv . Spec . ResourceSpec . Name == currpv . Spec . ResourceSpec . Name {
return pv , nil
}
2019-11-12 14:41:29 -08:00
}
2019-11-12 16:01:09 -08:00
return nil , nil
2019-11-12 14:41:29 -08:00
}
// build PV without owners
func buildPV ( info Info ) kyverno . ClusterPolicyViolation {
pv := buildPVObj ( info . PolicyName , kyverno . ResourceSpec {
Kind : info . Resource . GetKind ( ) ,
Namespace : info . Resource . GetNamespace ( ) ,
Name : info . Resource . GetName ( ) ,
} , info . Rules ,
)
return pv
}
// build PV object
func buildPVObj ( policyName string , resourceSpec kyverno . ResourceSpec , rules [ ] kyverno . ViolatedRule ) kyverno . ClusterPolicyViolation {
pv := kyverno . ClusterPolicyViolation {
Spec : kyverno . PolicyViolationSpec {
Policy : policyName ,
ResourceSpec : resourceSpec ,
ViolatedRules : rules ,
} ,
}
2019-11-13 10:37:57 -08:00
labelMap := map [ string ] string {
"policy" : policyName ,
"resource" : resourceSpec . ToKey ( ) ,
}
pv . SetLabels ( labelMap )
2019-11-12 18:25:50 -08:00
pv . SetGenerateName ( "pv-" )
2019-11-12 14:41:29 -08:00
return pv
}
// build PV with owners
func buildPVWithOwners ( dclient * client . Client , info Info ) [ ] kyverno . ClusterPolicyViolation {
var pvs [ ] kyverno . ClusterPolicyViolation
// as its blocked resource, the violation is created on owner
ownerMap := map [ kyverno . ResourceSpec ] interface { } { }
2019-11-13 12:34:55 -08:00
GetOwner ( dclient , ownerMap , info . Resource )
2019-11-13 10:37:57 -08:00
// standaloneresource, set pvResourceSpec with resource itself
if len ( ownerMap ) == 0 {
pvResourceSpec := kyverno . ResourceSpec {
Namespace : info . Resource . GetNamespace ( ) ,
Kind : info . Resource . GetKind ( ) ,
Name : info . Resource . GetName ( ) ,
}
return append ( pvs , buildPVObj ( info . PolicyName , pvResourceSpec , info . Rules ) )
}
2019-11-12 14:41:29 -08:00
// Generate owner on all owners
for owner := range ownerMap {
pv := buildPVObj ( info . PolicyName , owner , info . Rules )
pvs = append ( pvs , pv )
}
return pvs
}
2019-11-13 12:34:55 -08:00
// GetOwner of a resource by iterating over ownerReferences
func GetOwner ( dclient * client . Client , ownerMap map [ kyverno . ResourceSpec ] interface { } , resource unstructured . Unstructured ) {
2019-11-12 14:41:29 -08:00
var emptyInterface interface { }
resourceSpec := kyverno . ResourceSpec {
Kind : resource . GetKind ( ) ,
Namespace : resource . GetNamespace ( ) ,
Name : resource . GetName ( ) ,
}
if _ , ok := ownerMap [ resourceSpec ] ; ok {
// owner seen before
// breaking loop
return
}
rOwners := resource . GetOwnerReferences ( )
// if there are no resource owners then its top level resource
if len ( rOwners ) == 0 {
// add resource to map
ownerMap [ resourceSpec ] = emptyInterface
return
}
for _ , rOwner := range rOwners {
// lookup resource via client
// owner has to be in same namespace
owner , err := dclient . GetResource ( rOwner . Kind , resource . GetNamespace ( ) , rOwner . Name )
if err != nil {
glog . Errorf ( "Failed to get resource owner for %s/%s/%s, err: %v" , rOwner . Kind , resource . GetNamespace ( ) , rOwner . Name , err )
// as we want to process other owners
continue
}
2019-11-13 12:34:55 -08:00
GetOwner ( dclient , ownerMap , * owner )
2019-11-12 14:41:29 -08:00
}
}