1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

initial redesign

This commit is contained in:
shivkumar dudhani 2019-08-23 18:34:23 -07:00
parent 3191713e76
commit b062d70e29
12 changed files with 687 additions and 150 deletions

View file

@ -59,6 +59,7 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response
// Process Overlay
if rule.Mutation.Overlay != nil {
// ruleRespone := processOverlay(rule, res)
rulePatches, err = processOverlay(rule, patchedDocument)
if err == nil {
if len(rulePatches) == 0 {
@ -123,3 +124,67 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response
response.RuleInfos = ris
return response
}
//MutateNew ...
func MutateNew(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponseNew) {
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = resource.GetName()
response.PolicyResponse.Resource.Namespace = resource.GetNamespace()
response.PolicyResponse.Resource.Kind = resource.GetKind()
response.PolicyResponse.Resource.APIVersion = resource.GetAPIVersion()
}()
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying mutation rules policy %v (%v)", policy.Name, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Mutation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
}
var patchedResource unstructured.Unstructured
for _, rule := range policy.Spec.Rules {
//TODO: to be checked before calling the resources as well
if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resource, rule)
if !ok {
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
// Process Overlay
if rule.Mutation.Overlay != nil {
var ruleResponse RuleResponse
ruleResponse, patchedResource = processOverlayNew(rule, resource)
if reflect.DeepEqual(ruleResponse, (RuleResponse{})) {
// overlay pattern does not match the resource conditions
continue
}
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
// Process Patches
if rule.Mutation.Patches != nil {
var ruleResponse RuleResponse
ruleResponse, patchedResource = processPatchesNew(rule, resource)
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
}
// send the patched resource
response.PatchedResource = patchedResource
return response
}

View file

@ -7,8 +7,10 @@ import (
"reflect"
"strconv"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
@ -38,6 +40,60 @@ func processOverlay(rule kyverno.Rule, rawResource []byte) ([][]byte, error) {
return patches, err
}
// rawResource handles validating admission request
// Checks the target resources for rules defined in the policy
// TODO: pass in the unstructured object in stead of raw byte?
func processOverlayNew(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
startTime := time.Now()
glog.V(4).Infof("started applying overlay rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Mutation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying overlay rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
patches, err := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay)
// resource does not satisfy the overlay pattern, we dont apply this rule
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.V(4).Infof("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
//TODO: send zero response and not consider this as applied?
return RuleResponse{}, resource
}
if err != nil {
// rule application failed
response.Success = false
response.Message = fmt.Sprintf("failed to process overlay: %v", err)
return response, resource
}
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
joinedPatches := JoinPatches(patches)
var patchResource []byte
patchResource, err = ApplyPatchNew(resourceRaw, joinedPatches)
err = patchedResource.UnmarshalJSON(patchResource)
if err != nil {
glog.Infof("failed to unmarshall resource to undstructured: %v", err)
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
// rule application succesfuly
response.Success = true
response.Message = fmt.Sprintf("succesfully process overlay")
response.Patches = patches
// apply the patches to the resource
return response, patchedResource
}
func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) {
if !meetConditions(resource, overlay) {
@ -65,6 +121,7 @@ func applyOverlay(resource, overlay interface{}, path string) ([][]byte, error)
}
appliedPatches = append(appliedPatches, patch)
//TODO : check if return is needed ?
}
return applyOverlayForSameTypes(resource, overlay, path)
}

View file

@ -3,9 +3,13 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
@ -84,3 +88,88 @@ func ApplyPatches(resource []byte, patches [][]byte) ([]byte, error) {
}
return patchedDocument, err
}
//ApplyPatchNew ...
func ApplyPatchNew(resource, patch []byte) ([]byte, error) {
jsonpatch, err := jsonpatch.DecodePatch(patch)
if err != nil {
return nil, err
}
patchedResource, err := jsonpatch.Apply(resource)
if err != nil {
return nil, err
}
return patchedResource, err
}
func processPatchesNew(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
startTime := time.Now()
glog.V(4).Infof("started JSON patch rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Mutation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished JSON patch rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
var errs []error
var patches [][]byte
for _, patch := range rule.Mutation.Patches {
// JSON patch
patchRaw, err := json.Marshal(patch)
if err != nil {
glog.V(4).Infof("failed to marshall JSON patch %v: %v", patch, err)
errs = append(errs, err)
continue
}
patchResource, err := ApplyPatchNew(resourceRaw, patchRaw)
// TODO: continue on error if one of the patches fails, will add the failure event in such case
if err != nil && patch.Operation == "remove" {
glog.Info(err)
continue
}
if err != nil {
errs = append(errs, err)
continue
}
resourceRaw = patchResource
patches = append(patches, patchRaw)
}
// error while processing JSON patches
if len(errs) > 0 {
response.Success = false
response.Message = fmt.Sprint("failed to process JSON patches: %v", func() string {
var str []string
for _, err := range errs {
str = append(str, err.Error())
}
return strings.Join(str, ";")
}())
return response, resource
}
err = patchedResource.UnmarshalJSON(resourceRaw)
if err != nil {
glog.Info("failed to unmarshall resource to undstructured: %v", err)
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
// JSON patches processed succesfully
response.Success = true
response.Message = fmt.Sprintf("succesfully process JSON patches")
response.Patches = patches
return response, patchedResource
}

96
pkg/engine/response.go Normal file
View file

@ -0,0 +1,96 @@
package engine
import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
//EngineResponseNew engine response to the action
type EngineResponseNew struct {
// Resource patched with the engine action changes
PatchedResource unstructured.Unstructured
// Policy Response
PolicyResponse PolicyResponse
}
//PolicyResponse policy application response
type PolicyResponse struct {
// policy name
Policy string
// resource details
Resource ResourceSpec
// policy statistics
PolicyStats
// rule response
Rules []RuleResponse
// ValidationFailureAction: audit,enforce(default)
ValidationFailureAction string
}
//ResourceSpec resource action applied on
type ResourceSpec struct {
//TODO: support ApiVersion
Kind string
APIVersion string
Namespace string
Name string
}
//PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// time required to process the policy rules on a resource
ProcessingTime time.Duration
// Count of rules that were applied succesfully
RulesAppliedCount int
}
//RuleResponse details for each rule applicatino
type RuleResponse struct {
// rule name specified in policy
Name string
// rule type (Mutation,Generation,Validation) for Kyverno Policy
Type string
// message response from the rule application
Message string
// JSON patches, for mutation rules
Patches [][]byte
// success/fail
Success bool
// statistics
RuleStats
}
//ToString ...
func (rr RuleResponse) ToString() string {
return fmt.Sprintf("rule %s (%s): %v", rr.Name, rr.Type, rr.Message)
}
//RuleStats stores the statisctis for the single rule application
type RuleStats struct {
// time required to appliy the rule on the resource
ProcessingTime time.Duration
}
//IsSuccesful checks if any rule has failed or not
func (er EngineResponseNew) IsSuccesful() bool {
for _, r := range er.PolicyResponse.Rules {
if !r.Success {
return false
}
}
return true
}
//GetPatches returns all the patches joined
func (er EngineResponseNew) GetPatches() []byte {
var patches [][]byte
for _, r := range er.PolicyResponse.Rules {
if r.Patches != nil {
patches = append(patches, r.Patches...)
}
}
// join patches
return JoinPatches(patches)
}

View file

@ -21,12 +21,34 @@ import (
//EngineResponse provides the response to the application of a policy rule set on a resource
type EngineResponse struct {
Patches [][]byte
// JSON patches for mutation rules
Patches [][]byte
// Resource patched with the policy changes
PatchedResource unstructured.Unstructured
RuleInfos []info.RuleInfo
// Rule details
RuleInfos []info.RuleInfo
// PolicyS
EngineStats
}
// type EngineResponseNew struct {
// // error while processing engine action
// Err error
// // Resource patched with the engine action changes
// PatchedResource unstructured.Unstructured
// // Policy Response
// PolicyRespone PolicyResponse
// }
// type PolicyResponse struct {
// // policy name
// Policy string
// // resource details
// Resource kyverno.ResourceSpec
// }
// type PolicyStatus
//EngineStats stores in the statistics for a single application of resource
type EngineStats struct {
// average time required to process the policy rules on a resource
@ -518,3 +540,21 @@ func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
}
return resource, nil
}
type RuleType int
const (
Mutation RuleType = iota
Validation
Generation
All
)
func (ri RuleType) String() string {
return [...]string{
"Mutation",
"Validation",
"Generation",
"All",
}[ri]
}

View file

@ -61,53 +61,67 @@ func Validate(policy kyverno.Policy, resource unstructured.Unstructured) (respon
continue
}
ruleInfo := validatePatterns(resource, rule.Validation, rule.Name)
// ruleInfo := validatePatterns(resource, rule)
incrementAppliedRuleCount()
ruleInfos = append(ruleInfos, ruleInfo)
// ruleInfos = append(ruleInfos, ruleInfo)
}
response.RuleInfos = ruleInfos
return response
}
// validatePatterns validate pattern and anyPattern
func validatePatterns(resource unstructured.Unstructured, validation kyverno.Validation, ruleName string) info.RuleInfo {
var errs []error
ruleInfo := info.NewRuleInfo(ruleName, info.Validation)
func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (response RuleResponse) {
startTime := time.Now()
glog.V(4).Infof("started applying validation rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Validation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying validation rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
if validation.Pattern != nil {
err := validateResourceWithPattern(resource.Object, validation.Pattern)
// either pattern or anyPattern can be specified in Validation rule
if rule.Validation.Pattern != nil {
err := validateResourceWithPattern(resource.Object, rule.Validation.Pattern)
if err != nil {
ruleInfo.Fail()
ruleInfo.Addf("Failed to apply pattern: %v", err)
} else {
ruleInfo.Add("Pattern succesfully validated")
glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
// rule application failed
glog.V(4).Infof("failed to apply validation for rule %s on resource %s/%s/%s, pattern %v ", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Pattern)
response.Success = false
response.Message = fmt.Sprintf("failed to apply pattern: %v", err)
return response
}
return ruleInfo
// rule application succesful
glog.V(4).Infof("rule %s pattern validated succesfully on resource %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("validation pattern succesfully validated")
return response
}
if validation.AnyPattern != nil {
for _, pattern := range validation.AnyPattern {
//TODO: add comments to explain the flow
if rule.Validation.AnyPattern != nil {
var errs []error
for _, pattern := range rule.Validation.AnyPattern {
if err := validateResourceWithPattern(resource.Object, pattern); err != nil {
errs = append(errs, err)
}
}
failedPattern := len(errs)
patterns := len(validation.AnyPattern)
// all pattern fail
if failedPattern == patterns {
ruleInfo.Fail()
ruleInfo.Addf("None of anyPattern succeed: %v", errs)
} else {
ruleInfo.Addf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns)
failedPattern := len(errs)
patterns := len(rule.Validation.AnyPattern)
// all patterns fail
if failedPattern == patterns {
// any Pattern application failed
glog.V(4).Infof("none of anyPattern were processed: %v", errs)
response.Success = false
response.Message = fmt.Sprintf("None of anyPattern succeed: %v", errs)
return response
}
// any Pattern application succesful
glog.V(4).Infof("%d/%d patterns validated succesfully on resource %s/%s", patterns-failedPattern, patterns, resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns)
return response
}
return ruleInfo
}
return info.RuleInfo{}
return RuleResponse{}
}
// validateResourceWithPattern is a start of element-by-element validation process
@ -328,3 +342,51 @@ func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]i
handler := CreateAnchorHandler(anchor, pattern, path)
return handler.Handle(resourceMapArray, patternMap, originPattern)
}
//ValidateNew ...
func ValidateNew(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponseNew) {
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = resource.GetName()
response.PolicyResponse.Resource.Namespace = resource.GetNamespace()
response.PolicyResponse.Resource.Kind = resource.GetKind()
response.PolicyResponse.Resource.APIVersion = resource.GetAPIVersion()
response.PolicyResponse.ValidationFailureAction = policy.Spec.ValidationFailureAction
}()
glog.V(4).Infof("started applying validation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("Finished applying validation rules policy %v (%v)", policy.Name, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
}
for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resource, rule)
if !ok {
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
if rule.Validation.Pattern != nil {
ruleResponse := validatePatterns(resource, rule)
incrementAppliedRuleCount()
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
}
}
return response
}

17
pkg/policy/utils.go Normal file
View file

@ -0,0 +1,17 @@
package policy
import kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// reEvaulatePolicy checks if the policy needs to be re-evaulated
// during re-evaulation we remove all the old policy violations and re-create new ones
// - Rule count changes
// - Rule resource description changes
// - Rule operation changes
// - Rule name changed
func reEvaulatePolicy(curP, oldP *kyverno.Policy) bool {
// count of rules changed
if len(curP.Spec.Rules) != len(curP.Spec.Rules) {
}
return true
}

View file

@ -3,6 +3,8 @@ package webhooks
import (
"encoding/json"
"github.com/nirmata/kyverno/pkg/engine"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
@ -33,6 +35,42 @@ type response struct {
Value interface{} `json:"value"`
}
func generateAnnotationPatches(annotations map[string]string, policyResponse engine.PolicyResponse) []byte {
if annotations == nil {
annotations = map[string]string{}
}
var patchResponse response
value := generateAnnotationsFromPolicyInfo(policyResponse)
if value == nil {
// no patches or error while processing patches
return nil
}
if _, ok := annotations[policyAnnotation]; ok {
// create update patch string
patchResponse = response{
Op: "replace",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
}
} else {
patchResponse = response{
Op: "add",
Path: "/metadata/annotations",
Value: map[string]string{policyAnnotation: string(value)},
}
}
patchByte, _ := json.Marshal(patchResponse)
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + string(patchByte) + "]"))
if err != nil {
glog.Errorf("Failed to make patch from annotation'%s', err: %v\n ", string(patchByte), err)
}
return patchByte
}
func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte {
annots := resource.GetAnnotations()
if annots == nil {
@ -82,6 +120,34 @@ func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte {
return result
}
func generateAnnotationsFromPolicyInfo(policyResponse engine.PolicyResponse) []byte {
var rulePatches []rulePatch
// generate annotation for each mutation JSON patch to be applied on the resource
for _, rule := range policyResponse.Rules {
var patchmap map[string]string
patch := engine.JoinPatches(rule.Patches)
if err := json.Unmarshal(patch, &patchmap); err != nil {
glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
continue
}
rp := rulePatch{
RuleName: rule.Name,
Op: patchmap["op"],
Path: patchmap["path"]}
rulePatches = append(rulePatches, rp)
glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
}
patch, err := json.Marshal(rulePatches)
if err != nil {
glog.Info("failed to marshall: %v", err)
return nil
}
return patch
}
func annotationFromPolicy(policyInfo info.PolicyInfo) []rulePatch {
if !policyInfo.IsSuccessful() {
glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name)

View file

@ -3,7 +3,6 @@ package webhooks
import (
"github.com/golang/glog"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -12,16 +11,19 @@ import (
)
// HandleMutation handles mutating webhook admission request
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, engine.EngineResponse) {
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, []byte, string) {
glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
var patches [][]byte
var policyInfos []info.PolicyInfo
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, er engine.EngineResponse) {
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
@ -32,85 +34,72 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool
ws.policyStatus.SendStat(stat)
}
}
glog.V(5).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
// convert RAW to unstructured
resource, err := engine.ConvertToUnstructured(request.Object.Raw)
if err != nil {
//TODO: skip applying the amiddions control ?
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
return true, nil, ""
}
//TODO: check if resource gvk is available in raw resource,
//TODO: check if the name and namespace is also passed right in the 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?
engineResponse := engine.EngineResponse{PatchedResource: *resource}
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
//TODO check if the CRD is created ?
// Unable to connect to policy Lister to access policies
glog.Errorln("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
glog.Warning(err)
return true, engineResponse
return true, nil, ""
}
var engineResponses []engine.EngineResponseNew
for _, policy := range policies {
// check if policy has a rule for the admission request kind
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
continue
}
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
// policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.V(4).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
engineResponse = engine.Mutate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
// TODO: this can be
engineResponse := engine.MutateNew(*policy, *resource)
engineResponses = append(engineResponses, engineResponse)
// Gather policy application statistics
gatherStat(policy.Name, engineResponse)
// ps := policyctr.NewPolicyStat(policy.Name, engineResponse.ExecutionTime, nil, engineResponse.RulesAppliedCount)
if !policyInfo.IsSuccessful() {
gatherStat(policy.Name, engineResponse.PolicyResponse)
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Info("Failed rule details")
for _, r := range engineResponse.RuleInfos {
glog.V(4).Infof("%s: %s\n", r.Name, r.Msgs)
}
continue
}
patches = append(patches, engineResponse.Patches...)
policyInfos = append(policyInfos, policyInfo)
// gather patches
patches = append(patches, engineResponse.GetPatches())
// generate annotations
if annPatches := generateAnnotationPatches(resource.GetAnnotations(), engineResponse.PolicyResponse); annPatches != nil {
patches = append(patches, annPatches)
}
glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
//TODO: check if there is an order to policy application on resource
// resource = &engineResponse.PatchedResource
}
// combine rule patches & annotations
// ADD ANNOTATIONS
// ADD EVENTS
if len(patches) > 0 {
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
ws.eventGen.Add(eventsInfo...)
annotation := prepareAnnotationPatches(resource, policyInfos)
patches = append(patches, annotation)
}
ok, msg := isAdmSuccesful(policyInfos)
// Send policy engine Stats
if ok {
// if len(patches) > 0 {
// eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
// ws.eventGen.Add(eventsInfo...)
// }
if isResponseSuccesful(engineResponses) {
sendStat(false)
engineResponse.Patches = patches
return true, engineResponse
patch := engine.JoinPatches(patches)
return true, patch, ""
}
sendStat(true)
glog.Errorf("Failed to mutate the resource: %s\n", msg)
return false, engineResponse
glog.Errorf("Failed to mutate the resource\n")
return false, nil, getErrorMsg(engineResponses)
}

View file

@ -10,8 +10,6 @@ import (
"net/http"
"time"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/golang/glog"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1"
@ -24,6 +22,7 @@ import (
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
@ -135,24 +134,43 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
}
func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var response *v1beta1.AdmissionResponse
allowed, engineResponse := ws.HandleMutation(request)
if !allowed {
// TODO: add failure message to response
// MUTATION
ok, patches, msg := ws.HandleMutation(request)
if !ok {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
response = ws.HandleValidation(request, engineResponse.PatchedResource)
if response.Allowed && len(engineResponse.Patches) > 0 {
patchType := v1beta1.PatchTypeJSONPatch
response.Patch = engine.JoinPatches(engineResponse.Patches)
response.PatchType = &patchType
// patch the resource with patches before handling validation rules
patchedResource := processResourceWithPatches(patches, request.Object.Raw)
// VALIDATION
ok, msg = ws.HandleValidation(request, patchedResource)
if !ok {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
return response
// Succesfful processing of mutation & validation rules in policy
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
Patch: patches,
PatchType: &patchType,
}
}
// RunAsync TLS server in separate thread and returns control immediately

View file

@ -6,6 +6,7 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
)
@ -26,6 +27,43 @@ func isAdmSuccesful(policyInfos []info.PolicyInfo) (bool, string) {
return admSuccess, strings.Join(errMsgs, ";")
}
func isResponseSuccesful(engineReponses []engine.EngineResponseNew) bool {
for _, er := range engineReponses {
if !er.IsSuccesful() {
return false
}
}
return true
}
// returns true -> if there is even one policy that blocks resource requst
// returns false -> if all the policies are meant to report only, we dont block resource request
func toBlockResource(engineReponses []engine.EngineResponseNew) bool {
for _, er := range engineReponses {
if er.PolicyResponse.ValidationFailureAction != ReportViolation {
glog.V(4).Infof("ValidationFailureAction set to enforce for policy %s , blocking resource ceation", er.PolicyResponse.Policy)
return true
}
}
glog.V(4).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
return false
}
func getErrorMsg(engineReponses []engine.EngineResponseNew) string {
var str []string
for _, er := range engineReponses {
if !er.IsSuccesful() {
str = append(str, fmt.Sprintf("failed policy %s"), er.PolicyResponse.Policy)
for _, rule := range er.PolicyResponse.Rules {
if !rule.Success {
str = append(str, rule.ToString())
}
}
}
}
return strings.Join(str, "\n")
}
//ArrayFlags to store filterkinds
type ArrayFlags []string
@ -87,3 +125,15 @@ func toBlock(pis []info.PolicyInfo) bool {
glog.V(3).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
return false
}
func processResourceWithPatches(patch []byte, resource []byte) []byte {
if patch == nil {
return nil
}
resource, err := engine.ApplyPatchNew(resource, patch)
if err != nil {
glog.Error("failed to patch resource: %v", err)
return nil
}
return resource
}

View file

@ -8,23 +8,26 @@ import (
"github.com/nirmata/kyverno/pkg/policyviolation"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured) *v1beta1.AdmissionResponse {
// patchedResource is the (resource + patches) after applying mutation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, patchedResource []byte) (bool, string) {
glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
var policyInfos []info.PolicyInfo
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, er engine.EngineResponse) {
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.ValidationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
ps.Stats.ValidationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
@ -36,8 +39,23 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, res
}
}
glog.V(5).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
resourceRaw := request.Object.Raw
if patchedResource != nil {
glog.V(4).Info("using patched resource from mutation to process validation rules")
resourceRaw = patchedResource
}
// convert RAW to unstructured
resource, err := engine.ConvertToUnstructured(resourceRaw)
if err != nil {
//TODO: skip applying the amiddions control ?
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
return true, ""
}
//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?
// all the patches to be applied on the resource
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
@ -45,81 +63,51 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, res
// Unable to connect to policy Lister to access policies
glog.Error("Unable to connect to policy controller to access policies. Validation Rules are NOT being applied")
glog.Warning(err)
return &v1beta1.AdmissionResponse{
Allowed: true,
}
return true, ""
}
//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?
// all the patches to be applied on the resource
var engineResponses []engine.EngineResponseNew
for _, policy := range policies {
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
continue
}
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.V(4).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
// glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
engineResponse := engine.Validate(*policy, resource)
if len(engineResponse.RuleInfos) == 0 {
engineResponse := engine.ValidateNew(*policy, *resource)
engineResponses = append(engineResponses, engineResponse)
// Gather policy application statistics
gatherStat(policy.Name, engineResponse.PolicyResponse)
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
continue
}
gatherStat(policy.Name, engineResponse)
if len(engineResponse.RuleInfos) > 0 {
glog.V(4).Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, resource.GetNamespace(), resource.GetName())
for _, r := range engineResponse.RuleInfos {
glog.Warningf("%s: %s\n", r.Name, r.Msgs)
}
}
policyInfos = append(policyInfos, policyInfo)
}
// ADD EVENTS
if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
// If the validationFailureAction flag is set "audit",
// then we dont block the request and report the violations
ws.eventGen.Add(eventsInfo...)
}
// if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
// eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
// // If the validationFailureAction flag is set "audit",
// // then we dont block the request and report the violations
// ws.eventGen.Add(eventsInfo...)
// }
// If Validation fails then reject the request
// violations are created if "audit" flag is set
// and if there are any then we dont block the resource creation
// Even if one the policy being applied
ok, msg := isAdmSuccesful(policyInfos)
if !ok && toBlock(policyInfos) {
if !isResponseSuccesful(engineResponses) && toBlockResource(engineResponses) {
sendStat(true)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: msg,
},
}
return false, getErrorMsg(engineResponses)
}
// ADD POLICY VIOLATIONS
policyviolation.GeneratePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos)
sendStat(false)
return &v1beta1.AdmissionResponse{
Allowed: true,
}
return true, ""
}