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

implement #387 Generate clusterpolicyviolation when policy action set to "enforce"

This commit is contained in:
Shuting Zhao 2019-10-15 20:56:41 -07:00
parent 81f202752c
commit 2ff6eb6e78
7 changed files with 126 additions and 34 deletions

View file

@ -59,6 +59,13 @@ func Validate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured)
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
}
}
// set PatchedResource with orgin resource if empty
// in order to create policy violation
if reflect.DeepEqual(response.PatchedResource, unstructured.Unstructured{}) {
response.PatchedResource = resource
}
return response
}

View file

@ -53,7 +53,7 @@ func initRecorder(client *client.Client) record.EventRecorder {
return nil
}
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartLogging(glog.V(4).Infof)
eventInterface, err := client.GetEventsInterface()
if err != nil {
glog.Error(err) // TODO: add more specific error

View file

@ -21,7 +21,7 @@ func (nsc *NamespaceController) report(engineResponses []engine.EngineResponse)
// failure - policy/rule failed to apply on the resource
}
// generate policy violation
policyviolation.CreatePV(nsc.pvLister, nsc.kyvernoClient, engineResponses)
policyviolation.CreatePV(nsc.pvLister, nsc.kyvernoClient, nil, engineResponses, false)
}
//reportEvents generates events for the failed resources

View file

@ -22,7 +22,7 @@ func (pc *PolicyController) report(engineResponses []engine.EngineResponse) {
}
// generate policy violation
policyviolation.CreatePV(pc.pvLister, pc.kyvernoClient, engineResponses)
policyviolation.CreatePV(pc.pvLister, pc.kyvernoClient, nil, engineResponses, false)
}
//reportEvents generates events for the failed resources

View file

@ -8,11 +8,19 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/tools/cache"
)
type pvResourceOwner struct {
kind string
namespace string
name string
}
//BuildPolicyViolation returns an value of type PolicyViolation
func BuildPolicyViolation(policy string, resource kyverno.ResourceSpec, fRules []kyverno.ViolatedRule) kyverno.ClusterPolicyViolation {
pv := kyverno.ClusterPolicyViolation{
@ -28,49 +36,37 @@ func BuildPolicyViolation(policy string, resource kyverno.ResourceSpec, fRules [
return pv
}
func buildPVForPolicy(er engine.EngineResponse) kyverno.ClusterPolicyViolation {
var violatedRules []kyverno.ViolatedRule
glog.V(4).Infof("building policy violation for engine response %v", er)
for _, r := range er.PolicyResponse.Rules {
// filter failed/violated rules
if !r.Success {
vrule := kyverno.ViolatedRule{
Name: r.Name,
Message: r.Message,
Type: r.Type,
}
violatedRules = append(violatedRules, vrule)
}
}
pv := BuildPolicyViolation(er.PolicyResponse.Policy,
kyverno.ResourceSpec{
Kind: er.PolicyResponse.Resource.Kind,
Namespace: er.PolicyResponse.Resource.Namespace,
Name: er.PolicyResponse.Resource.Name,
},
violatedRules,
)
return pv
}
//CreatePV creates policy violation resource based on the engine responses
func CreatePV(pvLister kyvernolister.ClusterPolicyViolationLister, client *kyvernoclient.Clientset, engineResponses []engine.EngineResponse) {
func CreatePV(pvLister kyvernolister.ClusterPolicyViolationLister, client *kyvernoclient.Clientset,
dclient *dclient.Client, engineResponses []engine.EngineResponse, requestBlocked bool) {
var pvs []kyverno.ClusterPolicyViolation
for _, er := range engineResponses {
if requestBlocked {
glog.V(4).Infof("Building policy violation for denied admission request, engineResponse: %v", er)
if pvList := buildPVWithOwner(dclient, er); len(pvList) != 0 {
pvs = append(pvs, pvList...)
}
continue
}
// ignore creation of PV for resoruces that are yet to be assigned a name
if er.PolicyResponse.Resource.Name == "" {
glog.V(4).Infof("resource %v, has not been assigned a name. not creating a policy violation for it", er.PolicyResponse.Resource)
continue
}
if !er.IsSuccesful() {
glog.V(4).Infof("Building policy violation for engine response %v", er)
if pv := buildPVForPolicy(er); !reflect.DeepEqual(pv, kyverno.ClusterPolicyViolation{}) {
pvs = append(pvs, pv)
}
}
}
if len(pvs) == 0 {
return
}
for _, newPv := range pvs {
glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name)
// check if there was a previous policy voilation for policy & resource combination
@ -91,7 +87,8 @@ func CreatePV(pvLister kyvernolister.ClusterPolicyViolationLister, client *kyver
// compare the policyviolation spec for existing resource if present else
if reflect.DeepEqual(curPv.Spec, newPv.Spec) {
// if they are equal there has been no change so dont update the polivy violation
glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec)
glog.Infof("policy violation '%s/%s/%s' spec did not change so not updating it", newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name)
glog.V(4).Infof("policy violation spec %v did not change so not updating it", newPv.Spec)
continue
}
// spec changed so update the policyviolation
@ -106,6 +103,47 @@ func CreatePV(pvLister kyvernolister.ClusterPolicyViolationLister, client *kyver
}
}
func buildPVForPolicy(er engine.EngineResponse) kyverno.ClusterPolicyViolation {
pvResourceSpec := kyverno.ResourceSpec{
Kind: er.PolicyResponse.Resource.Kind,
Namespace: er.PolicyResponse.Resource.Namespace,
Name: er.PolicyResponse.Resource.Name,
}
violatedRules := newViolatedRules(er, "")
return BuildPolicyViolation(er.PolicyResponse.Policy, pvResourceSpec, violatedRules)
}
func buildPVWithOwner(dclient *dclient.Client, er engine.EngineResponse) (pvs []kyverno.ClusterPolicyViolation) {
msg := fmt.Sprintf("Request Blocked for resource %s/%s; ", er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Name)
violatedRules := newViolatedRules(er, msg)
// create violation on resource owner (if exist) when action is set to enforce
owners := getOwners(dclient, er.PatchedResource)
// standaloneresource, set pvResourceSpec with resource itself
if len(owners) == 0 {
pvResourceSpec := kyverno.ResourceSpec{
Namespace: er.PolicyResponse.Resource.Namespace,
Kind: er.PolicyResponse.Resource.Kind,
Name: er.PolicyResponse.Resource.Name,
}
return append(pvs, BuildPolicyViolation(er.PolicyResponse.Policy, pvResourceSpec, violatedRules))
}
for _, owner := range owners {
// resource has owner, set pvResourceSpec with owner info
pvResourceSpec := kyverno.ResourceSpec{
Namespace: owner.namespace,
Kind: owner.kind,
Name: owner.name,
}
pvs = append(pvs, BuildPolicyViolation(er.PolicyResponse.Policy, pvResourceSpec, violatedRules))
}
return
}
//TODO: change the name
func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvLister kyvernolister.ClusterPolicyViolationLister, newPv kyverno.ClusterPolicyViolation) (*kyverno.ClusterPolicyViolation, error) {
// TODO: check for existing ov using label selectors on resource and policy
@ -148,3 +186,42 @@ func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvList
}
return pvs[0], nil
}
func getOwners(dclient *dclient.Client, unstr unstructured.Unstructured) []pvResourceOwner {
resourceOwners := unstr.GetOwnerReferences()
if len(resourceOwners) == 0 {
return []pvResourceOwner{pvResourceOwner{
kind: unstr.GetKind(),
namespace: unstr.GetNamespace(),
name: unstr.GetName(),
}}
}
var owners []pvResourceOwner
for _, resourceOwner := range resourceOwners {
// if name is empty then GetResource panic as it returns a list
unstrParent, err := dclient.GetResource(resourceOwner.Kind, unstr.GetNamespace(), resourceOwner.Name)
if err != nil {
glog.Errorf("Failed to get resource owner for %s/%s/%s, err: %v", resourceOwner.Kind, unstr.GetNamespace(), resourceOwner.Name, err)
return nil
}
owners = append(owners, getOwners(dclient, *unstrParent)...)
}
return owners
}
func newViolatedRules(er engine.EngineResponse, msg string) (violatedRules []kyverno.ViolatedRule) {
for _, r := range er.PolicyResponse.Rules {
// filter failed/violated rules
if !r.Success {
vrule := kyverno.ViolatedRule{
Name: r.Name,
Type: r.Type,
Message: msg + r.Message,
}
violatedRules = append(violatedRules, vrule)
}
}
return
}

View file

@ -143,6 +143,7 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
// MUTATION
ok, patches, msg := ws.HandleMutation(request)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
@ -158,6 +159,7 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
// VALIDATION
ok, msg = ws.HandleValidation(request, patchedResource)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{

View file

@ -64,8 +64,12 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
//TODO: check if the name is also passed right in the resource?
// all the patches to be applied on the resource
// explictly set resource namespace with request namespace
// resource namespace is empty for the first CREATE operation
resource.SetNamespace(request.Namespace)
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
@ -102,17 +106,19 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat
ws.eventGen.Add(events...)
// If Validation fails then reject the request
// violations are created if "audit" flag is set
// violations are created with resource owner(if exist) on "enforce"
// and if there are any then we dont block the resource creation
// Even if one the policy being applied
if !isResponseSuccesful(engineResponses) && toBlockResource(engineResponses) {
resource.GetOwnerReferences()
policyviolation.CreatePV(ws.pvLister, ws.kyvernoClient, ws.client, engineResponses, true)
sendStat(true)
return false, getErrorMsg(engineResponses)
}
// ADD POLICY VIOLATIONS
policyviolation.CreatePV(ws.pvLister, ws.kyvernoClient, engineResponses)
// violations are created with resource on "audit"
policyviolation.CreatePV(ws.pvLister, ws.kyvernoClient, ws.client, engineResponses, false)
sendStat(false)
return true, ""
}