mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 15:37:19 +00:00
initial redesign
This commit is contained in:
parent
3191713e76
commit
b062d70e29
12 changed files with 687 additions and 150 deletions
|
@ -59,6 +59,7 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response
|
||||||
|
|
||||||
// Process Overlay
|
// Process Overlay
|
||||||
if rule.Mutation.Overlay != nil {
|
if rule.Mutation.Overlay != nil {
|
||||||
|
// ruleRespone := processOverlay(rule, res)
|
||||||
rulePatches, err = processOverlay(rule, patchedDocument)
|
rulePatches, err = processOverlay(rule, patchedDocument)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(rulePatches) == 0 {
|
if len(rulePatches) == 0 {
|
||||||
|
@ -123,3 +124,67 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response
|
||||||
response.RuleInfos = ris
|
response.RuleInfos = ris
|
||||||
return response
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
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
|
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) {
|
func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) {
|
||||||
|
|
||||||
if !meetConditions(resource, overlay) {
|
if !meetConditions(resource, overlay) {
|
||||||
|
@ -65,6 +121,7 @@ func applyOverlay(resource, overlay interface{}, path string) ([][]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
appliedPatches = append(appliedPatches, patch)
|
appliedPatches = append(appliedPatches, patch)
|
||||||
|
//TODO : check if return is needed ?
|
||||||
}
|
}
|
||||||
return applyOverlayForSameTypes(resource, overlay, path)
|
return applyOverlayForSameTypes(resource, overlay, path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,13 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||||
|
@ -84,3 +88,88 @@ func ApplyPatches(resource []byte, patches [][]byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return patchedDocument, err
|
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
96
pkg/engine/response.go
Normal 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)
|
||||||
|
}
|
|
@ -21,12 +21,34 @@ import (
|
||||||
|
|
||||||
//EngineResponse provides the response to the application of a policy rule set on a resource
|
//EngineResponse provides the response to the application of a policy rule set on a resource
|
||||||
type EngineResponse struct {
|
type EngineResponse struct {
|
||||||
Patches [][]byte
|
// JSON patches for mutation rules
|
||||||
|
Patches [][]byte
|
||||||
|
// Resource patched with the policy changes
|
||||||
PatchedResource unstructured.Unstructured
|
PatchedResource unstructured.Unstructured
|
||||||
RuleInfos []info.RuleInfo
|
// Rule details
|
||||||
|
RuleInfos []info.RuleInfo
|
||||||
|
// PolicyS
|
||||||
EngineStats
|
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
|
//EngineStats stores in the statistics for a single application of resource
|
||||||
type EngineStats struct {
|
type EngineStats struct {
|
||||||
// average time required to process the policy rules on a resource
|
// 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
|
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]
|
||||||
|
}
|
||||||
|
|
|
@ -61,53 +61,67 @@ func Validate(policy kyverno.Policy, resource unstructured.Unstructured) (respon
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleInfo := validatePatterns(resource, rule.Validation, rule.Name)
|
// ruleInfo := validatePatterns(resource, rule)
|
||||||
incrementAppliedRuleCount()
|
incrementAppliedRuleCount()
|
||||||
ruleInfos = append(ruleInfos, ruleInfo)
|
// ruleInfos = append(ruleInfos, ruleInfo)
|
||||||
}
|
}
|
||||||
response.RuleInfos = ruleInfos
|
response.RuleInfos = ruleInfos
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePatterns validate pattern and anyPattern
|
// validatePatterns validate pattern and anyPattern
|
||||||
func validatePatterns(resource unstructured.Unstructured, validation kyverno.Validation, ruleName string) info.RuleInfo {
|
func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (response RuleResponse) {
|
||||||
var errs []error
|
startTime := time.Now()
|
||||||
ruleInfo := info.NewRuleInfo(ruleName, info.Validation)
|
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 {
|
// either pattern or anyPattern can be specified in Validation rule
|
||||||
err := validateResourceWithPattern(resource.Object, validation.Pattern)
|
if rule.Validation.Pattern != nil {
|
||||||
|
err := validateResourceWithPattern(resource.Object, rule.Validation.Pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ruleInfo.Fail()
|
// rule application failed
|
||||||
ruleInfo.Addf("Failed to apply pattern: %v", err)
|
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)
|
||||||
} else {
|
response.Success = false
|
||||||
ruleInfo.Add("Pattern succesfully validated")
|
response.Message = fmt.Sprintf("failed to apply pattern: %v", err)
|
||||||
glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
|
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 {
|
//TODO: add comments to explain the flow
|
||||||
for _, pattern := range validation.AnyPattern {
|
if rule.Validation.AnyPattern != nil {
|
||||||
|
var errs []error
|
||||||
|
for _, pattern := range rule.Validation.AnyPattern {
|
||||||
if err := validateResourceWithPattern(resource.Object, pattern); err != nil {
|
if err := validateResourceWithPattern(resource.Object, pattern); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
failedPattern := len(errs)
|
||||||
|
patterns := len(rule.Validation.AnyPattern)
|
||||||
failedPattern := len(errs)
|
// all patterns fail
|
||||||
patterns := len(validation.AnyPattern)
|
if failedPattern == patterns {
|
||||||
|
// any Pattern application failed
|
||||||
// all pattern fail
|
glog.V(4).Infof("none of anyPattern were processed: %v", errs)
|
||||||
if failedPattern == patterns {
|
response.Success = false
|
||||||
ruleInfo.Fail()
|
response.Message = fmt.Sprintf("None of anyPattern succeed: %v", errs)
|
||||||
ruleInfo.Addf("None of anyPattern succeed: %v", errs)
|
return response
|
||||||
} else {
|
}
|
||||||
ruleInfo.Addf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns)
|
// any Pattern application succesful
|
||||||
glog.V(4).Infof("%d/%d patterns validated succesfully on resource %s/%s", patterns-failedPattern, patterns, resource.GetNamespace(), resource.GetName())
|
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 RuleResponse{}
|
||||||
return info.RuleInfo{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateResourceWithPattern is a start of element-by-element validation process
|
// 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)
|
handler := CreateAnchorHandler(anchor, pattern, path)
|
||||||
return handler.Handle(resourceMapArray, patternMap, originPattern)
|
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
17
pkg/policy/utils.go
Normal 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
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ package webhooks
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
@ -33,6 +35,42 @@ type response struct {
|
||||||
Value interface{} `json:"value"`
|
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 {
|
func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte {
|
||||||
annots := resource.GetAnnotations()
|
annots := resource.GetAnnotations()
|
||||||
if annots == nil {
|
if annots == nil {
|
||||||
|
@ -82,6 +120,34 @@ func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte {
|
||||||
return result
|
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 {
|
func annotationFromPolicy(policyInfo info.PolicyInfo) []rulePatch {
|
||||||
if !policyInfo.IsSuccessful() {
|
if !policyInfo.IsSuccessful() {
|
||||||
glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name)
|
glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package webhooks
|
||||||
import (
|
import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
engine "github.com/nirmata/kyverno/pkg/engine"
|
engine "github.com/nirmata/kyverno/pkg/engine"
|
||||||
"github.com/nirmata/kyverno/pkg/info"
|
|
||||||
policyctr "github.com/nirmata/kyverno/pkg/policy"
|
policyctr "github.com/nirmata/kyverno/pkg/policy"
|
||||||
"github.com/nirmata/kyverno/pkg/utils"
|
"github.com/nirmata/kyverno/pkg/utils"
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
@ -12,16 +11,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleMutation handles mutating webhook admission request
|
// 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 patches [][]byte
|
||||||
var policyInfos []info.PolicyInfo
|
|
||||||
var policyStats []policyctr.PolicyStat
|
var policyStats []policyctr.PolicyStat
|
||||||
|
|
||||||
// gather stats from the engine response
|
// gather stats from the engine response
|
||||||
gatherStat := func(policyName string, er engine.EngineResponse) {
|
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
|
||||||
ps := policyctr.PolicyStat{}
|
ps := policyctr.PolicyStat{}
|
||||||
ps.PolicyName = policyName
|
ps.PolicyName = policyName
|
||||||
ps.Stats.MutationExecutionTime = er.ExecutionTime
|
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
|
||||||
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
|
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
|
||||||
policyStats = append(policyStats, ps)
|
policyStats = append(policyStats, ps)
|
||||||
}
|
}
|
||||||
// send stats for aggregation
|
// send stats for aggregation
|
||||||
|
@ -32,85 +34,72 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool
|
||||||
ws.policyStatus.SendStat(stat)
|
ws.policyStatus.SendStat(stat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// convert RAW to unstructured
|
||||||
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)
|
|
||||||
|
|
||||||
resource, err := engine.ConvertToUnstructured(request.Object.Raw)
|
resource, err := engine.ConvertToUnstructured(request.Object.Raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//TODO: skip applying the amiddions control ?
|
||||||
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
|
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 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
|
// 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})
|
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())
|
policies, err := ws.pLister.List(labels.NewSelector())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//TODO check if the CRD is created ?
|
//TODO check if the CRD is created ?
|
||||||
// Unable to connect to policy Lister to access policies
|
// 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.Errorln("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
|
||||||
glog.Warning(err)
|
glog.Warning(err)
|
||||||
return true, engineResponse
|
return true, nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var engineResponses []engine.EngineResponseNew
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
|
|
||||||
// check if policy has a rule for the admission request kind
|
// check if policy has a rule for the admission request kind
|
||||||
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
||||||
continue
|
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",
|
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)
|
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))
|
// glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||||
|
// TODO: this can be
|
||||||
engineResponse = engine.Mutate(*policy, *resource)
|
engineResponse := engine.MutateNew(*policy, *resource)
|
||||||
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
|
engineResponses = append(engineResponses, engineResponse)
|
||||||
// Gather policy application statistics
|
// Gather policy application statistics
|
||||||
gatherStat(policy.Name, engineResponse)
|
gatherStat(policy.Name, engineResponse.PolicyResponse)
|
||||||
|
if !engineResponse.IsSuccesful() {
|
||||||
// ps := policyctr.NewPolicyStat(policy.Name, engineResponse.ExecutionTime, nil, engineResponse.RulesAppliedCount)
|
|
||||||
|
|
||||||
if !policyInfo.IsSuccessful() {
|
|
||||||
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
// gather patches
|
||||||
patches = append(patches, engineResponse.Patches...)
|
patches = append(patches, engineResponse.GetPatches())
|
||||||
policyInfos = append(policyInfos, policyInfo)
|
// 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())
|
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
|
// ADD EVENTS
|
||||||
if len(patches) > 0 {
|
// if len(patches) > 0 {
|
||||||
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
|
// eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
|
||||||
ws.eventGen.Add(eventsInfo...)
|
// ws.eventGen.Add(eventsInfo...)
|
||||||
|
// }
|
||||||
annotation := prepareAnnotationPatches(resource, policyInfos)
|
if isResponseSuccesful(engineResponses) {
|
||||||
patches = append(patches, annotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, msg := isAdmSuccesful(policyInfos)
|
|
||||||
// Send policy engine Stats
|
|
||||||
if ok {
|
|
||||||
sendStat(false)
|
sendStat(false)
|
||||||
engineResponse.Patches = patches
|
patch := engine.JoinPatches(patches)
|
||||||
return true, engineResponse
|
return true, patch, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStat(true)
|
sendStat(true)
|
||||||
glog.Errorf("Failed to mutate the resource: %s\n", msg)
|
glog.Errorf("Failed to mutate the resource\n")
|
||||||
return false, engineResponse
|
return false, nil, getErrorMsg(engineResponses)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
|
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
|
||||||
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1"
|
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/utils"
|
||||||
"github.com/nirmata/kyverno/pkg/webhookconfig"
|
"github.com/nirmata/kyverno/pkg/webhookconfig"
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"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 {
|
func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||||
var response *v1beta1.AdmissionResponse
|
// MUTATION
|
||||||
|
ok, patches, msg := ws.HandleMutation(request)
|
||||||
allowed, engineResponse := ws.HandleMutation(request)
|
if !ok {
|
||||||
if !allowed {
|
|
||||||
// TODO: add failure message to response
|
|
||||||
return &v1beta1.AdmissionResponse{
|
return &v1beta1.AdmissionResponse{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Status: "Failure",
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = ws.HandleValidation(request, engineResponse.PatchedResource)
|
// patch the resource with patches before handling validation rules
|
||||||
if response.Allowed && len(engineResponse.Patches) > 0 {
|
patchedResource := processResourceWithPatches(patches, request.Object.Raw)
|
||||||
patchType := v1beta1.PatchTypeJSONPatch
|
|
||||||
response.Patch = engine.JoinPatches(engineResponse.Patches)
|
// VALIDATION
|
||||||
response.PatchType = &patchType
|
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
|
// RunAsync TLS server in separate thread and returns control immediately
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine"
|
||||||
"github.com/nirmata/kyverno/pkg/info"
|
"github.com/nirmata/kyverno/pkg/info"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,6 +27,43 @@ func isAdmSuccesful(policyInfos []info.PolicyInfo) (bool, string) {
|
||||||
return admSuccess, strings.Join(errMsgs, ";")
|
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
|
//ArrayFlags to store filterkinds
|
||||||
type ArrayFlags []string
|
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")
|
glog.V(3).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -8,23 +8,26 @@ import (
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
"github.com/nirmata/kyverno/pkg/policyviolation"
|
||||||
"github.com/nirmata/kyverno/pkg/utils"
|
"github.com/nirmata/kyverno/pkg/utils"
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
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/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleValidation handles validating webhook admission request
|
// HandleValidation handles validating webhook admission request
|
||||||
// If there are no errors in validating rule we apply generation rules
|
// 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 policyInfos []info.PolicyInfo
|
||||||
var policyStats []policyctr.PolicyStat
|
var policyStats []policyctr.PolicyStat
|
||||||
|
|
||||||
// gather stats from the engine response
|
// gather stats from the engine response
|
||||||
gatherStat := func(policyName string, er engine.EngineResponse) {
|
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
|
||||||
ps := policyctr.PolicyStat{}
|
ps := policyctr.PolicyStat{}
|
||||||
ps.PolicyName = policyName
|
ps.PolicyName = policyName
|
||||||
ps.Stats.ValidationExecutionTime = er.ExecutionTime
|
ps.Stats.ValidationExecutionTime = policyResponse.ProcessingTime
|
||||||
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
|
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
|
||||||
policyStats = append(policyStats, ps)
|
policyStats = append(policyStats, ps)
|
||||||
}
|
}
|
||||||
// send stats for aggregation
|
// 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",
|
resourceRaw := request.Object.Raw
|
||||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
|
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())
|
policies, err := ws.pLister.List(labels.NewSelector())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,81 +63,51 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, res
|
||||||
// Unable to connect to policy Lister to access policies
|
// 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.Error("Unable to connect to policy controller to access policies. Validation Rules are NOT being applied")
|
||||||
glog.Warning(err)
|
glog.Warning(err)
|
||||||
return &v1beta1.AdmissionResponse{
|
return true, ""
|
||||||
Allowed: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: check if resource gvk is available in raw resource,
|
var engineResponses []engine.EngineResponseNew
|
||||||
// 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
|
|
||||||
|
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
|
|
||||||
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
||||||
continue
|
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",
|
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)
|
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)
|
engineResponse := engine.ValidateNew(*policy, *resource)
|
||||||
if len(engineResponse.RuleInfos) == 0 {
|
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
|
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
|
// ADD EVENTS
|
||||||
if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
|
// if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
|
||||||
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
|
// eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
|
||||||
// If the validationFailureAction flag is set "audit",
|
// // If the validationFailureAction flag is set "audit",
|
||||||
// then we dont block the request and report the violations
|
// // then we dont block the request and report the violations
|
||||||
ws.eventGen.Add(eventsInfo...)
|
// ws.eventGen.Add(eventsInfo...)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If Validation fails then reject the request
|
// If Validation fails then reject the request
|
||||||
// violations are created if "audit" flag is set
|
// violations are created if "audit" flag is set
|
||||||
// and if there are any then we dont block the resource creation
|
// and if there are any then we dont block the resource creation
|
||||||
// Even if one the policy being applied
|
// Even if one the policy being applied
|
||||||
ok, msg := isAdmSuccesful(policyInfos)
|
if !isResponseSuccesful(engineResponses) && toBlockResource(engineResponses) {
|
||||||
if !ok && toBlock(policyInfos) {
|
|
||||||
sendStat(true)
|
sendStat(true)
|
||||||
return &v1beta1.AdmissionResponse{
|
return false, getErrorMsg(engineResponses)
|
||||||
Allowed: false,
|
|
||||||
Result: &metav1.Status{
|
|
||||||
Message: msg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADD POLICY VIOLATIONS
|
// ADD POLICY VIOLATIONS
|
||||||
policyviolation.GeneratePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos)
|
policyviolation.GeneratePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos)
|
||||||
|
|
||||||
sendStat(false)
|
sendStat(false)
|
||||||
return &v1beta1.AdmissionResponse{
|
return true, ""
|
||||||
Allowed: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue