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:
parent
f11a05a652
commit
f788f0e526
5 changed files with 370 additions and 43 deletions
6
main.go
6
main.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
162
pkg/policystore/policystore_test.go
Normal file
162
pkg/policystore/policystore_test.go
Normal 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()
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue