1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-30 19:35:06 +00:00

introduce policy store

This commit is contained in:
shivkumar dudhani 2019-11-11 11:10:25 -08:00
parent f11a05a652
commit f788f0e526
5 changed files with 370 additions and 43 deletions

View file

@ -12,6 +12,7 @@ import (
event "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/namespace"
"github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/policyviolation"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
@ -79,6 +80,9 @@ func main() {
glog.Fatalf("Unable to register admission webhooks on cluster: %v\n", err)
}
// Policy meta-data store
policyMetaStore := policystore.NewPolicyStore()
// KYVERNO CRD INFORMER
// watches CRD resources:
// - Policy
@ -106,7 +110,7 @@ func main() {
// - process policy on existing resources
// - status aggregator: recieves stats when a policy is applied
// & updates the policy status
pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations(), webhookRegistrationClient, configData)
pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations(), webhookRegistrationClient, configData, policyMetaStore)
if err != nil {
glog.Fatalf("error creating policy controller: %v\n", err)
}

View file

@ -16,6 +16,7 @@ import (
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/webhookconfig"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@ -77,12 +78,15 @@ type PolicyController struct {
configHandler config.Interface
// recieves stats and aggregates details
statusAggregator *PolicyStatusAggregator
// store to hold policy meta data for faster lookup
pMetaStore policystore.Interface
}
// NewPolicyController create a new PolicyController
func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.Client, pInformer kyvernoinformer.ClusterPolicyInformer, pvInformer kyvernoinformer.ClusterPolicyViolationInformer,
eventGen event.Interface, webhookInformer webhookinformer.MutatingWebhookConfigurationInformer, webhookRegistrationClient *webhookconfig.WebhookRegistrationClient,
configHandler config.Interface) (*PolicyController, error) {
configHandler config.Interface,
pMetaStore policystore.Interface) (*PolicyController, error) {
// Event broad caster
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
@ -100,6 +104,7 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "policy"),
webhookRegistrationClient: webhookRegistrationClient,
configHandler: configHandler,
pMetaStore: pMetaStore,
}
pc.pvControl = RealPVControl{Client: kyvernoClient, Recorder: pc.eventRecorder}
@ -141,6 +146,8 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.
func (pc *PolicyController) addPolicy(obj interface{}) {
p := obj.(*kyverno.ClusterPolicy)
glog.V(4).Infof("Adding Policy %s", p.Name)
// register with policy meta-store
pc.pMetaStore.Register(*p)
pc.enqueuePolicy(p)
}
@ -148,6 +155,10 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
oldP := old.(*kyverno.ClusterPolicy)
curP := cur.(*kyverno.ClusterPolicy)
glog.V(4).Infof("Updating Policy %s", oldP.Name)
// TODO: optimize this : policy meta-store
// Update policy-> (remove,add)
pc.pMetaStore.UnRegister(*oldP)
pc.pMetaStore.Register(*curP)
pc.enqueuePolicy(curP)
}
@ -166,6 +177,8 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
}
}
glog.V(4).Infof("Deleting Policy %s", p.Name)
// Unregister from policy meta-store
pc.pMetaStore.UnRegister(*p)
pc.enqueuePolicy(p)
}

View file

@ -4,60 +4,203 @@ import (
"sync"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PolicyElement struct {
Name string
Rule string
}
//Operation defines the operation that a rule is performing
// we can only have a single operation per rule
type Operation string
const (
//Mutation : mutation rules
Mutation Operation = "Mutation"
//Validation : validation rules
Validation Operation = "Validation"
//Generation : generation rules
Generation Operation = "Generation"
)
type policyMap map[PolicyElement]interface{}
//PolicyStore Store the meta-data information to faster lookup policies
type PolicyStore struct {
data map[Operation]map[string]map[string]policyMap
mu sync.RWMutex
}
type Interface interface {
Register(policy *kyverno.Policy) error
UnRegister(policy *kyverno.Policy) error // check if the controller can see the policy spec for details?
LookUp(kind, namespace, name string, ls *metav1.LabelSelector) // returns a list of policies and rules that apply
// Register a new policy
Register(policy kyverno.ClusterPolicy)
// Remove policy information
UnRegister(policy kyverno.ClusterPolicy) error
// Lookup based on kind and namespaces
LookUp(operation Operation, kind, namespace string) []PolicyElement
}
type Store struct {
data map[string]string
mux sync.RWMutex
}
func NewStore() *Store {
s := Store{
data: make(map[string]string), //key: kind, value is the name of the policy
// NewPolicyStore returns a new policy store
func NewPolicyStore() *PolicyStore {
ps := PolicyStore{
data: make(map[Operation]map[string]map[string]policyMap),
}
return &s
return &ps
}
var empty struct{}
func (s *Store) Register(policy *kyverno.Policy) error {
// check if this policy is already registered for this resource kind
kinds := map[string]string{}
// get kinds from the rules
for _, r := range policy.Spec.Rules {
rkinds := map[string]string{}
// matching resources
for _, k := range r.MatchResources.Kinds {
rkinds[k] = policy.Name
}
for _, k := range r.ExcludeResources.Kinds {
delete(rkinds, k)
}
// merge the result
mergeMap(kinds, rkinds)
func operation(rule kyverno.Rule) Operation {
if rule.HasMutate() {
return Mutation
} else if rule.HasValidate() {
return Validation
} else {
return Generation
}
}
// have all the kinds that the policy has rule on
s.mux.Lock()
defer s.mux.Unlock()
// merge kinds
mergeMap(s.data, kinds)
//Register a new policy
func (ps *PolicyStore) Register(policy kyverno.ClusterPolicy) {
ps.mu.Lock()
defer ps.mu.Unlock()
var pmap policyMap
// add an entry for each rule in policy
for _, rule := range policy.Spec.Rules {
// get operation
operation := operation(rule)
operationMap := ps.addOperation(operation)
// rule.MatchResources.Kinds - List - mandatory - atleast on entry
for _, kind := range rule.MatchResources.Kinds {
kindMap := addKind(operationMap, kind)
// namespaces
if len(rule.MatchResources.Namespaces) == 0 {
// all namespaces - *
pmap = addNamespace(kindMap, "*")
} else {
for _, ns := range rule.MatchResources.Namespaces {
pmap = addNamespace(kindMap, ns)
}
}
// add policy to the pmap
addPolicyElement(pmap, policy.Name, rule.Name)
}
}
}
//UnRegister Remove policy information
func (ps *PolicyStore) UnRegister(policy kyverno.ClusterPolicy) error {
ps.mu.Lock()
defer ps.mu.Unlock()
for _, rule := range policy.Spec.Rules {
// get operation
operation := operation(rule)
operationMap := ps.getOperation(operation)
for _, kind := range rule.MatchResources.Kinds {
// get kind Map
kindMap := getKind(operationMap, kind)
if kindMap == nil {
// kind does not exist
return nil
}
if len(rule.MatchResources.Namespaces) == 0 {
namespace := "*"
pmap := getNamespace(kindMap, namespace)
// remove element
delete(pmap, PolicyElement{Name: policy.Name, Rule: rule.Name})
} else {
for _, ns := range rule.MatchResources.Namespaces {
pmap := getNamespace(kindMap, ns)
// remove element
delete(pmap, PolicyElement{Name: policy.Name, Rule: rule.Name})
}
}
}
}
return nil
}
// merge m2 into m2
func mergeMap(m1, m2 map[string]string) {
for k, v := range m2 {
m1[k] = v
//LookUp lookups up the policies for kind and namespace
// returns a list of <policy, rule> that statisfy the filters
func (ps *PolicyStore) LookUp(operation Operation, kind, namespace string) []PolicyElement {
ps.mu.RLock()
defer ps.mu.RUnlock()
var policyMap policyMap
var ret []PolicyElement
// operation
operationMap := ps.getOperation(operation)
if operationMap == nil {
return []PolicyElement{}
}
// kind
kindMap := getKind(operationMap, kind)
if kindMap == nil {
return []PolicyElement{}
}
// get namespace specific policies
policyMap = kindMap[namespace]
ret = append(ret, transform(policyMap)...)
// get policies on all namespaces
policyMap = kindMap["*"]
ret = append(ret, transform(policyMap)...)
return ret
}
// generates a copy
func transform(pmap policyMap) []PolicyElement {
ret := []PolicyElement{}
for k := range pmap {
ret = append(ret, k)
}
return ret
}
func (ps *PolicyStore) addOperation(operation Operation) map[string]map[string]policyMap {
operationMap, ok := ps.data[operation]
if ok {
return operationMap
}
ps.data[operation] = make(map[string]map[string]policyMap)
return ps.data[operation]
}
func (ps *PolicyStore) getOperation(operation Operation) map[string]map[string]policyMap {
return ps.data[operation]
}
func addKind(operationMap map[string]map[string]policyMap, kind string) map[string]policyMap {
val, ok := operationMap[kind]
if ok {
return val
}
operationMap[kind] = make(map[string]policyMap)
return operationMap[kind]
}
func getKind(operationMap map[string]map[string]policyMap, kind string) map[string]policyMap {
return operationMap[kind]
}
func addNamespace(kindMap map[string]policyMap, namespace string) policyMap {
val, ok := kindMap[namespace]
if ok {
return val
}
kindMap[namespace] = make(policyMap)
return kindMap[namespace]
}
func getNamespace(kindMap map[string]policyMap, namespace string) policyMap {
return kindMap[namespace]
}
func addPolicyElement(pmap policyMap, name, rule string) {
var emptyInterface interface{}
key := PolicyElement{
Name: name,
Rule: rule,
}
if _, ok := pmap[key]; !ok {
pmap[key] = emptyInterface
}
}

View file

@ -0,0 +1,162 @@
package policystore
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
)
func Test_Add(t *testing.T) {
rawPolicy1 := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "test-policy"
},
"spec": {
"rules": [
{
"name": "r1",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"overlay": "temp"
}
},
{
"name": "r2",
"match": {
"resources": {
"kinds": [
"Pod",
"Deployment"
]
}
},
"mutate": {
"overlay": "temp"
}
},
{
"name": "r3",
"match": {
"resources": {
"kinds": [
"Pod",
"Deployment"
],
"namespaces": [
"n1"
]
}
},
"mutate": {
"overlay": "temp"
}
},
{
"name": "r4",
"match": {
"resources": {
"kinds": [
"Pod",
"Deployment"
],
"namespaces": [
"n1",
"n2"
]
}
},
"validate": {
"pattern": "temp"
}
}
]
}
}
`)
rawPolicy2 := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "test-policy1"
},
"spec": {
"rules": [
{
"name": "r1",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"overlay": "temp"
}
},
{
"name": "r2",
"match": {
"resources": {
"kinds": [
"Pod"
],
"namespaces": [
"n4"
]
}
},
"mutate": {
"overlay": "temp"
}
},
{
"name": "r2",
"match": {
"resources": {
"kinds": [
"Pod"
],
"namespaces": [
"n4",
"n5",
"n6"
]
}
},
"validate": {
"pattern": "temp"
}
}
]
}
}`)
var policy1 kyverno.ClusterPolicy
json.Unmarshal(rawPolicy1, &policy1)
var policy2 kyverno.ClusterPolicy
json.Unmarshal(rawPolicy2, &policy2)
var store Interface
store = NewPolicyStore()
// Add
store.Register(policy1)
store.Register(policy2)
t.Log(store.LookUp(Mutation, "Pod", ""))
store.UnRegister(policy1)
t.Log(store.LookUp(Mutation, "Pod", ""))
store.Register(policy1)
t.Log(store.LookUp(Mutation, "Pod", ""))
t.Fail()
}

View file

@ -19,6 +19,7 @@ import (
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/policystore"
tlsutils "github.com/nirmata/kyverno/pkg/tls"
"github.com/nirmata/kyverno/pkg/webhookconfig"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -46,6 +47,8 @@ type WebhookServer struct {
cleanUp chan<- struct{}
// last request time
lastReqTime *checker.LastReqTime
// store to hold policy meta data for faster lookup
pMetaStore policystore.Interface
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
@ -60,6 +63,7 @@ func NewWebhookServer(
webhookRegistrationClient *webhookconfig.WebhookRegistrationClient,
policyStatus policy.PolicyStatusInterface,
configHandler config.Interface,
pMetaStore policystore.Interface,
cleanUp chan<- struct{}) (*WebhookServer, error) {
if tlsPair == nil {
@ -87,6 +91,7 @@ func NewWebhookServer(
configHandler: configHandler,
cleanUp: cleanUp,
lastReqTime: checker.NewLastReqTime(),
pMetaStore: pMetaStore,
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)