1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 18:15:48 +00:00

Merge branch 'master' into 196_anchor_selection_on_peer

This commit is contained in:
Shuting Zhao 2019-07-23 17:55:24 -07:00
commit 7d2abc5df3
16 changed files with 217 additions and 73 deletions

View file

@ -160,7 +160,7 @@ Here are some the major features we plan on completing before a 1.0 release:
## Getting help
* For feature requests and bugs, file an [issue](https://github.com/nirmata/kyverno/issues).
* For discussions or questions, join the [mailing list](https://groups.google.com/forum/#!forum/kyverno)
* For discussions or questions, join our [Kubernetes Slack channel #kyverno](https://app.slack.com/client/T09NY5SBT/CLGR9BJU9) or the [mailing list](https://groups.google.com/forum/#!forum/kyverno)
## Contributing

View file

@ -26,8 +26,8 @@ spec:
validationFailureAction:
type: string
enum:
- block
- report
- enforce # blocks the resorce api-reques if a rule fails. Default behavior
- audit # allows resource creationg and reports the failed validation rules as violations
rules:
type: array
items:

View file

@ -51,7 +51,7 @@ func main() {
eventController,
annotationsController)
genControler := gencontroller.NewGenController(client, eventController, policyInformerFactory, violationBuilder, kubeInformer.Core().V1().Namespaces())
genControler := gencontroller.NewGenController(client, eventController, policyInformerFactory, violationBuilder, kubeInformer.Core().V1().Namespaces(), annotationsController)
tlsPair, err := initTLSPemPair(clientConfig, client)
if err != nil {
glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)

View file

@ -23,7 +23,7 @@ type Policy struct {
type Rule struct {
Status string `json:"status"`
Changes string `json:"changes,omitempty"` // TODO for mutation changes
Error string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}
func (p *Policy) getOverAllStatus() string {
@ -61,7 +61,7 @@ func getRules(rules []*pinfo.RuleInfo, ruleType pinfo.RuleType) map[string]Rule
rule := Rule{Status: getStatus(r.IsSuccessful())}
if !r.IsSuccessful() {
rule.Error = r.GetErrorString()
rule.Message = r.GetErrorString()
}
annrules[r.Name] = rule
}

View file

@ -12,5 +12,5 @@ func getStatus(status bool) string {
}
func BuildKey(policyName string) string {
return "policies.kyverno.io." + policyName
return "policies.kyverno.io/" + policyName
}

View file

@ -225,7 +225,14 @@ func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) {
if err != nil {
glog.Error(err)
}
if mpatch == nil && vpatch == nil {
// Generation rules
ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Validation)
if err != nil {
glog.Error(err)
}
if mpatch == nil && vpatch == nil && gpatch == nil {
//nothing to patch
continue
}
@ -237,11 +244,21 @@ func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) {
continue
}
}
if mpatch == nil {
patch = vpatch
} else {
patch = mpatch
}
// generation
if gpatch != nil {
patch, err = jsonpatch.MergePatch(patch, gpatch)
if err != nil {
glog.Error(err)
continue
}
}
// add the anotation to the resource
_, err = pc.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, patch)
if err != nil {

View file

@ -10,22 +10,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// As the logic to process the policies in stateless, we do not need to define struct and implement behaviors for it
// Instead we expose them as standalone functions passing the required atrributes
// The each function returns the changes that need to be applied on the resource
// the caller is responsible to apply the changes to the resource
// ProcessExisting checks for mutation and validation violations of existing resources
func ProcessExisting(client *client.Client, policy *types.Policy) []*info.PolicyInfo {
glog.Infof("Applying policy %s on existing resources", policy.Name)
// key uid
resourceMap := map[string]*resourceInfo{}
resourceMap := map[string]resourceInfo{}
for _, rule := range policy.Spec.Rules {
for _, k := range rule.Kinds {
if k == "Namespace" {
// REWORK: will be handeled by namespace controller
continue
}
// kind -> resource
gvr := client.DiscoveryClient.GetGVRFromKind(k)
// label selectors
@ -34,6 +26,10 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy
if rule.ResourceDescription.Namespace != nil {
namespace = *rule.ResourceDescription.Namespace
}
if k == "Namespace" {
namespace = ""
}
list, err := client.ListResource(k, namespace, rule.ResourceDescription.Selector)
if err != nil {
glog.Errorf("unable to list resource for %s with label selector %s", gvr.Resource, rule.Selector.String())
@ -49,7 +45,7 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy
continue
}
}
ri := &resourceInfo{resource: &res, gvk: &metav1.GroupVersionKind{Group: gvk.Group,
ri := resourceInfo{resource: res, gvk: &metav1.GroupVersionKind{Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind}}
@ -74,7 +70,7 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy
return policyInfos
}
func applyPolicy(client *client.Client, policy *types.Policy, res *resourceInfo) (*info.PolicyInfo, error) {
func applyPolicy(client *client.Client, policy *types.Policy, res resourceInfo) (*info.PolicyInfo, error) {
policyInfo := info.NewPolicyInfo(policy.Name, res.gvk.Kind, res.resource.GetName(), res.resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
rawResource, err := res.resource.MarshalJSON()
@ -93,7 +89,12 @@ func applyPolicy(client *client.Client, policy *types.Policy, res *resourceInfo)
if err != nil {
return nil, err
}
// Generate rule managed by generation controller
if res.gvk.Kind == "Namespace" {
// Generation
gruleInfos := GenerateNew(client, policy, res.resource)
policyInfo.AddRuleInfos(gruleInfos)
}
return policyInfo, nil
}
@ -104,6 +105,16 @@ func mutation(p *types.Policy, rawResource []byte, gvk *metav1.GroupVersionKind)
// no rules were processed
return nil, nil
}
// if there are any errors return
for _, r := range ruleInfos {
if !r.IsSuccessful() {
return ruleInfos, nil
}
}
// if there are no patches // for overlay
if len(patches) == 0 {
return ruleInfos, nil
}
// option 2: (original Resource + patch) compare with (original resource)
mergePatches := JoinPatches(patches)
// merge the patches

View file

@ -9,13 +9,12 @@ import (
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
//GenerateNew apply generation rules on a resource
func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Namespace) []*info.RuleInfo {
func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns unstructured.Unstructured) []*info.RuleInfo {
ris := []*info.RuleInfo{}
for _, rule := range policy.Spec.Rules {
if rule.Generation == nil {
@ -35,14 +34,14 @@ func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Name
return ris
}
func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1alpha1.Generation) error {
func applyRuleGeneratorNew(client *client.Client, ns unstructured.Unstructured, gen *v1alpha1.Generation) error {
var err error
resource := &unstructured.Unstructured{}
var rdata map[string]interface{}
if gen.Data != nil {
// 1> Check if resource exists
obj, err := client.GetResource(gen.Kind, ns.Name, gen.Name)
obj, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name)
if err == nil {
// 2> If already exsists, then verify the content is contained
// found the resource
@ -64,7 +63,7 @@ func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1a
}
if gen.Clone != nil {
// 1> Check if resource exists
_, err := client.GetResource(gen.Kind, ns.Name, gen.Name)
_, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name)
if err == nil {
return nil
}
@ -77,11 +76,11 @@ func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1a
}
resource.SetUnstructuredContent(rdata)
resource.SetName(gen.Name)
resource.SetNamespace(ns.Name)
resource.SetNamespace(ns.GetName())
// Reset resource version
resource.SetResourceVersion("")
_, err = client.CreateResource(gen.Kind, ns.Name, resource, false)
_, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false)
if err != nil {
return err
}

View file

@ -9,7 +9,7 @@ import (
"gotest.tools/assert"
)
func compareJsonAsMap(t *testing.T, expected, actual []byte) {
func compareJSONAsMap(t *testing.T, expected, actual []byte) {
var expectedMap, actualMap map[string]interface{}
assert.NilError(t, json.Unmarshal(expected, &expectedMap))
assert.NilError(t, json.Unmarshal(actual, &actualMap))
@ -106,7 +106,7 @@ func TestApplyOverlay_NestedListWithAnchor(t *testing.T) {
]
}`)
compareJsonAsMap(t, expectedResult, patched)
compareJSONAsMap(t, expectedResult, patched)
}
func TestApplyOverlay_InsertIntoArray(t *testing.T) {
@ -223,7 +223,7 @@ func TestApplyOverlay_InsertIntoArray(t *testing.T) {
]
}`)
compareJsonAsMap(t, expectedResult, patched)
compareJSONAsMap(t, expectedResult, patched)
}
func TestApplyOverlay_TestInsertToArray(t *testing.T) {
@ -428,7 +428,7 @@ func TestApplyOverlay_ImagePullPolicy(t *testing.T) {
}
}`)
compareJsonAsMap(t, expectedResult, doc)
compareJSONAsMap(t, expectedResult, doc)
}
func TestApplyOverlay_AddingAnchor(t *testing.T) {
@ -471,7 +471,7 @@ func TestApplyOverlay_AddingAnchor(t *testing.T) {
}
}`)
compareJsonAsMap(t, expectedResult, doc)
compareJSONAsMap(t, expectedResult, doc)
}
func TestApplyOverlay_AddingAnchorInsideListElement(t *testing.T) {
@ -591,5 +591,5 @@ func TestApplyOverlay_AddingAnchorInsideListElement(t *testing.T) {
}
}
}`)
compareJsonAsMap(t, expectedResult, doc)
compareJSONAsMap(t, expectedResult, doc)
}

View file

@ -253,6 +253,6 @@ func convertToFloat(value interface{}) (float64, error) {
}
type resourceInfo struct {
resource *unstructured.Unstructured
resource unstructured.Unstructured
gvk *metav1.GroupVersionKind
}

View file

@ -197,6 +197,7 @@ func valFromReferenceToString(value interface{}, operator string) (string, error
}
}
//FormAbsolutePath returns absolute path
func FormAbsolutePath(referencePath, absolutePath string) string {
if filepath.IsAbs(referencePath) {
return referencePath

View file

@ -7,6 +7,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/annotations"
policyLister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
@ -22,13 +23,14 @@ import (
//Controller watches the 'Namespace' resource creation/update and applied the generation rules on them
type Controller struct {
client *client.Client
namespaceLister v1CoreLister.NamespaceLister
namespaceSynced cache.InformerSynced
policyLister policyLister.PolicyLister
eventController event.Generator
violationBuilder violation.Generator
workqueue workqueue.RateLimitingInterface
client *client.Client
namespaceLister v1CoreLister.NamespaceLister
namespaceSynced cache.InformerSynced
policyLister policyLister.PolicyLister
eventController event.Generator
violationBuilder violation.Generator
annotationsController annotations.Controller
workqueue workqueue.RateLimitingInterface
}
//NewGenController returns a new Controller to manage generation rules
@ -36,17 +38,19 @@ func NewGenController(client *client.Client,
eventController event.Generator,
policyInformer policySharedInformer.PolicyInformer,
violationBuilder violation.Generator,
namespaceInformer v1Informer.NamespaceInformer) *Controller {
namespaceInformer v1Informer.NamespaceInformer,
annotationsController annotations.Controller) *Controller {
// create the controller
controller := &Controller{
client: client,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
policyLister: policyInformer.GetLister(),
eventController: eventController,
violationBuilder: violationBuilder,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), wqNamespace),
client: client,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
policyLister: policyInformer.GetLister(),
eventController: eventController,
violationBuilder: violationBuilder,
annotationsController: annotationsController,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), wqNamespace),
}
namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.createNamespaceHandler,
@ -77,7 +81,7 @@ func (c *Controller) enqueueNamespace(obj interface{}) {
func (c *Controller) Run(stopCh <-chan struct{}) error {
if ok := cache.WaitForCacheSync(stopCh, c.namespaceSynced); !ok {
return fmt.Errorf("faield to wait for caches to sync")
return fmt.Errorf("failed to wait for caches to sync")
}
for i := 0; i < workerCount; i++ {

View file

@ -4,7 +4,10 @@ import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/annotations"
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine"
event "github.com/nirmata/kyverno/pkg/event"
@ -12,6 +15,7 @@ import (
violation "github.com/nirmata/kyverno/pkg/violation"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
func (c *Controller) processNamespace(ns *corev1.Namespace) error {
@ -62,9 +66,19 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) {
"",
p.Spec.ValidationFailureAction) // Namespace has no namespace..WOW
ruleInfos := engine.GenerateNew(c.client, p, ns)
// convert to unstructured
unstrMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns)
if err != nil {
glog.Error(err)
return
}
unstObj := unstructured.Unstructured{Object: unstrMap}
ruleInfos := engine.GenerateNew(c.client, p, unstObj)
policyInfo.AddRuleInfos(ruleInfos)
// generate annotations
c.createAnnotations(policyInfo)
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s %s", p.Name, ns.Kind, ns.Name)
for _, r := range ruleInfos {
@ -78,9 +92,11 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) {
if onViolation {
glog.Infof("Adding violation for generation rule of policy %s\n", policyInfo.Name)
// Policy Violation
v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), policyInfo.GetFailedRules())
c.violationBuilder.Add(v)
} else {
// Event
eventInfo = event.NewEvent(policyKind, "", policyInfo.Name, event.RequestBlocked,
event.FPolicyApplyBlockCreate, policyInfo.RName, policyInfo.GetRuleNames(false))
@ -100,3 +116,31 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) {
c.eventController.Add(eventInfo)
}
func (c *Controller) createAnnotations(pi *info.PolicyInfo) {
//get resource
obj, err := c.client.GetResource(pi.RKind, pi.RNamespace, pi.RName)
if err != nil {
glog.Error(err)
return
}
// add annotation for policy application
ann := obj.GetAnnotations()
// Generation rules
ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Mutation)
if err != nil {
glog.Error(err)
return
}
if gpatch == nil {
// nothing to patch
return
}
// add the anotation to the resource
_, err = c.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, gpatch)
if err != nil {
glog.Error(err)
return
}
}

View file

@ -67,12 +67,11 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
glog.Warningf("%s: %s\n", r.Name, r.Msgs)
}
} else {
// TODO
// // CleanUp Violations if exists
// err := ws.violationBuilder.RemoveInactiveViolation(policy.Name, request.Kind.Kind, rns, rname, info.Validation)
// if err != nil {
// glog.Info(err)
// }
// CleanUp Violations if exists
err := ws.violationBuilder.RemoveInactiveViolation(policy.Name, request.Kind.Kind, rns, rname, info.Mutation)
if err != nil {
glog.Info(err)
}
allPatches = append(allPatches, policyPatches...)
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns)
}

View file

@ -1,6 +1,7 @@
package webhooks
import (
"encoding/json"
"fmt"
"reflect"
"strings"
@ -54,6 +55,7 @@ func parseKinds(list []string) []string {
return kinds
}
//ArrayFlags to store filterkinds
type ArrayFlags []string
func (i *ArrayFlags) String() string {
@ -64,6 +66,7 @@ func (i *ArrayFlags) String() string {
return sb.String()
}
//Set setter for array flags
func (i *ArrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
@ -89,8 +92,8 @@ func getApplicableKindsForPolicy(p *v1alpha1.Policy) []string {
// Policy Reporting Modes
const (
BlockChanges = "block"
ReportViolation = "report"
BlockChanges = "enforce"
ReportViolation = "audit"
)
// returns true -> if there is even one policy that blocks resource requst
@ -105,31 +108,97 @@ func toBlock(pis []*info.PolicyInfo) bool {
}
func checkIfOnlyAnnotationsUpdate(request *v1beta1.AdmissionRequest) bool {
var err error
// process only if its for existing resources
if request.Operation != v1beta1.Update {
return false
}
// updated resoruce
obj := request.Object
objUnstr := unstructured.Unstructured{}
err := objUnstr.UnmarshalJSON(obj.Raw)
// approach : we only compare if the addition contains annotations the are added with prefix "policies.kyverno.io"
// get annotations for the old resource
oldObj := request.OldObject
oldObjUnstr := unstructured.Unstructured{}
// need to set kind as some request dont contain kind meta-data raw resource but in the api request
oldObj.Raw = setKindForObject(oldObj.Raw, request.Kind.Kind)
err = oldObjUnstr.UnmarshalJSON(oldObj.Raw)
if err != nil {
glog.Error(err)
return false
}
objUnstr.SetAnnotations(nil)
objUnstr.SetGeneration(0)
oldobj := request.OldObject
oldobjUnstr := unstructured.Unstructured{}
err = oldobjUnstr.UnmarshalJSON(oldobj.Raw)
oldAnn := oldObjUnstr.GetAnnotations()
// get annotations for the new resource
newObj := request.Object
newObjUnstr := unstructured.Unstructured{}
// need to set kind as some request dont contain kind meta-data raw resource but in the api request
newObj.Raw = setKindForObject(newObj.Raw, request.Kind.Kind)
err = newObjUnstr.UnmarshalJSON(newObj.Raw)
if err != nil {
glog.Error(err)
return false
}
oldobjUnstr.SetAnnotations(nil)
oldobjUnstr.SetGeneration(0)
if reflect.DeepEqual(objUnstr, oldobjUnstr) {
newAnn := newObjUnstr.GetAnnotations()
policiesAppliedNew := 0
newAnnPolicy := map[string]string{}
// check if annotations changed
// assuming that we only add an annotation with the given prefix
for k, v := range newAnn {
// check prefix
policyName := strings.Split(k, "/")
if len(policyName) == 1 {
continue
}
if policyName[0] == "policies.kyverno.io" {
newAnnPolicy[policyName[1]] = v
policiesAppliedNew++
}
}
oldAnnPolicy := map[string]string{}
policiesAppliedOld := 0
// check if annotations changed
// assuming that we only add an annotation with the given prefix
for k, v := range oldAnn {
// check prefix
policyName := strings.Split(k, "/")
if len(policyName) == 1 {
continue
}
if policyName[0] == "policies.kyverno.io" {
oldAnnPolicy[policyName[1]] = v
policiesAppliedOld++
}
}
diffCount := policiesAppliedNew - policiesAppliedOld
switch diffCount {
case 1: // policy applied
return true
case -1: // policy removed
return true
case 0: // no new policy added or remove
// need to check if the policy was updated
if !reflect.DeepEqual(newAnnPolicy, oldAnnPolicy) {
return true
}
}
//TODO: Hack if there an update on self link then we ignore
if oldObjUnstr.GetSelfLink() != newObjUnstr.GetSelfLink() {
return true
}
// then there is some other change and we should process it
return false
}
func setKindForObject(bytes []byte, kind string) []byte {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
objectJSON["kind"] = kind
data, err := json.Marshal(objectJSON)
if err != nil {
glog.Error(err)
glog.Error("unable to marshall, not setting the kind")
return bytes
}
return data
}

View file

@ -102,7 +102,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
eventsInfo, violations := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
// If the validationFailureAction flag is set "report",
// If the validationFailureAction flag is set "audit",
// then we dont block the request and report the violations
ws.violationBuilder.Add(violations...)
ws.eventController.Add(eventsInfo...)
@ -113,7 +113,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
}
// If Validation fails then reject the request
ok, msg := isAdmSuccesful(policyInfos)
// violations are created if "report" flag is set
// violations are created if "audit" flag is set
// and if there are any then we dont bock the resource creation
// Even if one the policy being applied