mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-17 17:56:33 +00:00
Merge pull request #573 from nirmata/518_pod_controller
518 pod controller
This commit is contained in:
commit
db4d3d2c8c
14 changed files with 882 additions and 61 deletions
|
@ -73,9 +73,9 @@ type Rule struct {
|
|||
Name string `json:"name"`
|
||||
MatchResources MatchResources `json:"match"`
|
||||
ExcludeResources ExcludeResources `json:"exclude,omitempty"`
|
||||
Mutation Mutation `json:"mutate"`
|
||||
Validation Validation `json:"validate"`
|
||||
Generation Generation `json:"generate"`
|
||||
Mutation Mutation `json:"mutate,omitempty"`
|
||||
Validation Validation `json:"validate,omitempty"`
|
||||
Generation Generation `json:"generate,omitempty"`
|
||||
}
|
||||
|
||||
//MatchResources contains resource description of the resources that the rule is to apply on
|
||||
|
@ -92,23 +92,23 @@ type ExcludeResources struct {
|
|||
|
||||
// UserInfo filter based on users
|
||||
type UserInfo struct {
|
||||
Roles []string `json:"roles"`
|
||||
ClusterRoles []string `json:"clusterRoles"`
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
ClusterRoles []string `json:"clusterRoles,omitempty"`
|
||||
Subjects []rbacv1.Subject `json:"subjects,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceDescription describes the resource to which the PolicyRule will be applied.
|
||||
type ResourceDescription struct {
|
||||
Kinds []string `json:"kinds"`
|
||||
Name string `json:"name"`
|
||||
Kinds []string `json:"kinds,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
Selector *metav1.LabelSelector `json:"selector"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
}
|
||||
|
||||
// Mutation describes the way how Mutating Webhook will react on resource creation
|
||||
type Mutation struct {
|
||||
Overlay interface{} `json:"overlay"`
|
||||
Patches []Patch `json:"patches"`
|
||||
Overlay interface{} `json:"overlay,omitempty"`
|
||||
Patches []Patch `json:"patches,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=false
|
||||
|
@ -122,24 +122,24 @@ type Patch struct {
|
|||
|
||||
// Validation describes the way how Validating Webhook will check the resource on creation
|
||||
type Validation struct {
|
||||
Message string `json:"message"`
|
||||
Pattern interface{} `json:"pattern"`
|
||||
AnyPattern []interface{} `json:"anyPattern"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Pattern interface{} `json:"pattern,omitempty"`
|
||||
AnyPattern []interface{} `json:"anyPattern,omitempty"`
|
||||
}
|
||||
|
||||
// Generation describes which resources will be created when other resource is created
|
||||
type Generation struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Data interface{} `json:"data"`
|
||||
Clone CloneFrom `json:"clone"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Clone CloneFrom `json:"clone,omitempty"`
|
||||
}
|
||||
|
||||
// CloneFrom - location of a Secret or a ConfigMap
|
||||
// which will be used as source when applying 'generate'
|
||||
type CloneFrom struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
//PolicyStatus provides status for violations
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
||||
)
|
||||
|
||||
const (
|
||||
PodControllers = "DaemonSet,Deployment,Job,StatefulSet"
|
||||
PodControllersAnnotation = "pod-policies.kyverno.io/autogen-controllers"
|
||||
PodTemplateAnnotation = "pod-policies.kyverno.io/autogen-applied"
|
||||
)
|
||||
|
||||
// Mutate performs mutation. Overlay first and then mutation patches
|
||||
func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||
startTime := time.Now()
|
||||
|
@ -39,7 +48,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
|||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
//TODO: to be checked before calling the resources as well
|
||||
if !rule.HasMutate() {
|
||||
if !rule.HasMutate() && !strings.Contains(PodControllers, resource.GetKind()) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -83,8 +92,47 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
|||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
||||
incrementAppliedRuleCount()
|
||||
}
|
||||
|
||||
// insert annotation to podtemplate if resource is pod controller
|
||||
// skip inserting on existing resource
|
||||
if reflect.DeepEqual(policyContext.AdmissionInfo, RequestInfo{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(PodControllers, resource.GetKind()) {
|
||||
var ruleResponse response.RuleResponse
|
||||
ruleResponse, patchedResource = processOverlay(ctx, podTemplateRule, patchedResource)
|
||||
if !ruleResponse.Success {
|
||||
glog.Errorf("Failed to insert annotation to podTemplate of %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), ruleResponse.Message)
|
||||
continue
|
||||
}
|
||||
|
||||
if ruleResponse.Success && ruleResponse.Patches != nil {
|
||||
glog.V(2).Infof("Inserted annotation to podTemplate of %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), ruleResponse.Message)
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
// send the patched resource
|
||||
resp.PatchedResource = patchedResource
|
||||
return resp
|
||||
}
|
||||
|
||||
// podTemplateRule mutate pod template with annotation
|
||||
// pod-policies.kyverno.io/autogen-applied=true
|
||||
var podTemplateRule = kyverno.Rule{
|
||||
Name: "autogen-annotate-podtemplate",
|
||||
Mutation: kyverno.Mutation{
|
||||
Overlay: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"pod-policies.kyverno.io/autogen-applied": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -376,9 +376,10 @@ func preparePath(path string) string {
|
|||
}
|
||||
|
||||
annPath := "/metadata/annotations/"
|
||||
idx := strings.Index(path, annPath)
|
||||
// escape slash in annotation patch
|
||||
if strings.Contains(path, annPath) {
|
||||
p := path[len(annPath):]
|
||||
p := path[idx+len(annPath):]
|
||||
path = annPath + strings.ReplaceAll(p, "/", "~1")
|
||||
}
|
||||
return path
|
||||
|
|
|
@ -50,7 +50,7 @@ func (pc *PolicyController) cleanUpPolicyViolation(pResponse response.PolicyResp
|
|||
if reflect.DeepEqual(pv, kyverno.PolicyViolation{}) {
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("cleanup namespaced violation %s on %s", pv.Name, pv.Spec.ResourceSpec.ToKey())
|
||||
glog.V(4).Infof("cleanup namespaced violation %s on %s.%s", pv.Name, pResponse.Resource.Namespace, pv.Spec.ResourceSpec.ToKey())
|
||||
if err := pc.pvControl.DeleteNamespacedPolicyViolation(pv.Namespace, pv.Name); err != nil {
|
||||
glog.Errorf("failed to delete namespaced policy violation %s on %s: %v", pv.Name, pv.Spec.ResourceSpec.ToKey(), err)
|
||||
continue
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/config"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -30,6 +31,12 @@ func (pc *PolicyController) processExistingResources(policy kyverno.ClusterPolic
|
|||
glog.V(4).Infof("policy %s with resource version %s already processed on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
|
||||
continue
|
||||
}
|
||||
|
||||
// skip reporting violation on pod which has annotation pod-policies.kyverno.io/autogen-applied
|
||||
if skipPodApplication(resource) {
|
||||
continue
|
||||
}
|
||||
|
||||
// apply the policy on each
|
||||
glog.V(4).Infof("apply policy %s with resource version %s on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
|
||||
engineResponse := applyPolicy(policy, resource, pc.statusAggregator)
|
||||
|
@ -356,3 +363,17 @@ func (rm *ResourceManager) ProcessResource(policy, pv, kind, ns, name, rv string
|
|||
func buildKey(policy, pv, kind, ns, name, rv string) string {
|
||||
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
|
||||
}
|
||||
|
||||
func skipPodApplication(resource unstructured.Unstructured) bool {
|
||||
if resource.GetKind() != "Pod" {
|
||||
return false
|
||||
}
|
||||
|
||||
annotation := resource.GetAnnotations()
|
||||
if _, ok := annotation[engine.PodTemplateAnnotation]; ok {
|
||||
glog.V(4).Infof("Policies already processed on pod controllers, skip processing policy on Pod/%s/%s", resource.GetNamespace(), resource.GetName())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -3,10 +3,14 @@ package webhooks
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -27,7 +31,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest)
|
|||
}
|
||||
}
|
||||
// Generate JSON Patches for defaults
|
||||
patches, updateMsgs := generateJSONPatchesForDefaults(policy)
|
||||
patches, updateMsgs := generateJSONPatchesForDefaults(policy, request.Operation)
|
||||
if patches != nil {
|
||||
patchType := v1beta1.PatchTypeJSONPatch
|
||||
glog.V(4).Infof("defaulted values %v policy %s", updateMsgs, policy.Name)
|
||||
|
@ -46,7 +50,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest)
|
|||
}
|
||||
}
|
||||
|
||||
func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy) ([]byte, []string) {
|
||||
func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1beta1.Operation) ([]byte, []string) {
|
||||
var patches [][]byte
|
||||
var updateMsgs []string
|
||||
|
||||
|
@ -56,6 +60,20 @@ func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy) ([]byte, []st
|
|||
updateMsgs = append(updateMsgs, updateMsg)
|
||||
}
|
||||
|
||||
// TODO(shuting): enable this feature on policy UPDATE
|
||||
if operation == v1beta1.Create {
|
||||
patch, errs := generatePodControllerRule(*policy)
|
||||
if len(errs) > 0 {
|
||||
var errMsgs []string
|
||||
for _, err := range errs {
|
||||
errMsgs = append(errMsgs, err.Error())
|
||||
}
|
||||
glog.Errorf("failed auto generatig rule for pod controllers: %s", errMsgs)
|
||||
updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";"))
|
||||
}
|
||||
|
||||
patches = append(patches, patch...)
|
||||
}
|
||||
return utils.JoinPatches(patches), updateMsgs
|
||||
}
|
||||
|
||||
|
@ -82,3 +100,219 @@ func defaultvalidationFailureAction(policy *kyverno.ClusterPolicy) ([]byte, stri
|
|||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// podControllersKey annotation could be:
|
||||
// scenario A: not exist, set default to "all", which generates on all pod controllers
|
||||
// - if name / selector exist in resource description -> skip
|
||||
// as these fields may not be applicable to pod controllers
|
||||
// scenario B: "null", user explicitely disable this feature -> skip
|
||||
// scenario C: some certain controllers that user set -> generate on defined controllers
|
||||
// copy entrie match / exclude block, it's users' responsibility to
|
||||
// make sure all fields are applicable to pod cotrollers
|
||||
|
||||
// generatePodControllerRule returns two patches: rulePatches and annotation patch(if necessary)
|
||||
func generatePodControllerRule(policy kyverno.ClusterPolicy) (patches [][]byte, errs []error) {
|
||||
ann := policy.GetAnnotations()
|
||||
controllers, ok := ann[engine.PodControllersAnnotation]
|
||||
|
||||
// scenario A
|
||||
if !ok {
|
||||
controllers = "all"
|
||||
annPatch, err := defaultPodControllerAnnotation(ann)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to generate pod controller annotation for policy '%s': %v", policy.Name, err))
|
||||
} else {
|
||||
patches = append(patches, annPatch)
|
||||
}
|
||||
}
|
||||
|
||||
// scenario B
|
||||
if controllers == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
glog.V(3).Infof("Auto generating rule for pod controller: %s", controllers)
|
||||
|
||||
p, err := generateRulePatches(policy, controllers)
|
||||
patches = append(patches, p...)
|
||||
errs = append(errs, err...)
|
||||
return
|
||||
}
|
||||
|
||||
// generateRulePatches generates rule for podControllers based on scenario A and C
|
||||
func generateRulePatches(policy kyverno.ClusterPolicy, controllers string) (rulePatches [][]byte, errs []error) {
|
||||
var genRule kyvernoRule
|
||||
insertIdx := len(policy.Spec.Rules)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
genRule = generateRuleForControllers(rule, controllers)
|
||||
if reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
// generate patch bytes
|
||||
jsonPatch := struct {
|
||||
Path string `json:"path"`
|
||||
Op string `json:"op"`
|
||||
Value interface{} `json:"value"`
|
||||
}{
|
||||
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(insertIdx)),
|
||||
"add",
|
||||
genRule,
|
||||
}
|
||||
pbytes, err := json.Marshal(jsonPatch)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// check the patch
|
||||
if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
rulePatches = append(rulePatches, pbytes)
|
||||
insertIdx++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// the kyvernoRule holds the temporary kyverno rule struct
|
||||
// each field is a pointer to the the actual object
|
||||
// when serilizing data, we would expect to drop the omitempty key
|
||||
// otherwise (without the pointer), it will be set to empty value
|
||||
// - an empty struct in this case, some may fail the schema validation
|
||||
// TODO(shuting) may related to:
|
||||
// https://github.com/nirmata/kyverno/pull/549#discussion_r360088556
|
||||
// https://github.com/nirmata/kyverno/issues/568
|
||||
|
||||
type kyvernoRule struct {
|
||||
Name string `json:"name"`
|
||||
MatchResources *kyverno.MatchResources `json:"match"`
|
||||
ExcludeResources *kyverno.ExcludeResources `json:"exclude,omitempty"`
|
||||
Mutation *kyverno.Mutation `json:"mutate,omitempty"`
|
||||
Validation *kyverno.Validation `json:"validate,omitempty"`
|
||||
}
|
||||
|
||||
func generateRuleForControllers(rule kyverno.Rule, controllers string) kyvernoRule {
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
if !utils.ContainsString(match.ResourceDescription.Kinds, "Pod") ||
|
||||
(len(exclude.ResourceDescription.Kinds) != 0 && !utils.ContainsString(exclude.ResourceDescription.Kinds, "Pod")) {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
if rule.Mutation.Overlay == nil && !rule.HasValidate() {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
// scenario A
|
||||
if controllers == "all" {
|
||||
if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil ||
|
||||
exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil {
|
||||
glog.Warningf("Rule '%s' skip generating rule on pod controllers: Name / Selector in resource decription may not be applicable.", rule.Name)
|
||||
return kyvernoRule{}
|
||||
}
|
||||
controllers = engine.PodControllers
|
||||
}
|
||||
|
||||
controllerRule := &kyvernoRule{
|
||||
Name: fmt.Sprintf("autogen-%s", rule.Name),
|
||||
MatchResources: match.DeepCopy(),
|
||||
}
|
||||
|
||||
// overwrite Kinds by pod controllers defined in the annotation
|
||||
controllerRule.MatchResources.Kinds = strings.Split(controllers, ",")
|
||||
if len(exclude.Kinds) != 0 {
|
||||
controllerRule.ExcludeResources = exclude.DeepCopy()
|
||||
controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
|
||||
if rule.Mutation.Overlay != nil {
|
||||
newMutation := &kyverno.Mutation{
|
||||
Overlay: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Mutation.Overlay,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
controllerRule.Mutation = newMutation.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.Pattern != nil {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: rule.Validation.Message,
|
||||
Pattern: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Validation.Pattern,
|
||||
},
|
||||
},
|
||||
}
|
||||
controllerRule.Validation = newValidate.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if len(rule.Validation.AnyPattern) != 0 {
|
||||
var patterns []interface{}
|
||||
for _, pattern := range rule.Validation.AnyPattern {
|
||||
newPattern := map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": pattern,
|
||||
},
|
||||
}
|
||||
|
||||
patterns = append(patterns, newPattern)
|
||||
}
|
||||
|
||||
controllerRule.Validation = &kyverno.Validation{
|
||||
Message: rule.Validation.Message,
|
||||
AnyPattern: patterns,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
// defaultPodControllerAnnotation generates annotation "pod-policies.kyverno.io/autogen-controllers=all"
|
||||
// ann passes in the annotation of the policy
|
||||
func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) {
|
||||
if ann == nil {
|
||||
ann = make(map[string]string)
|
||||
ann[engine.PodControllersAnnotation] = "all"
|
||||
jsonPatch := struct {
|
||||
Path string `json:"path"`
|
||||
Op string `json:"op"`
|
||||
Value interface{} `json:"value"`
|
||||
}{
|
||||
"/metadata/annotations",
|
||||
"add",
|
||||
ann,
|
||||
}
|
||||
|
||||
patchByte, err := json.Marshal(jsonPatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return patchByte, nil
|
||||
}
|
||||
|
||||
jsonPatch := struct {
|
||||
Path string `json:"path"`
|
||||
Op string `json:"op"`
|
||||
Value interface{} `json:"value"`
|
||||
}{
|
||||
"/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers",
|
||||
"add",
|
||||
"all",
|
||||
}
|
||||
|
||||
patchByte, err := json.Marshal(jsonPatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return patchByte, nil
|
||||
}
|
||||
|
|
513
pkg/webhooks/policymutation_test.go
Normal file
513
pkg/webhooks/policymutation_test.go
Normal file
|
@ -0,0 +1,513 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func compareJSONAsMap(t *testing.T, expected, actual []byte) {
|
||||
var expectedMap, actualMap map[string]interface{}
|
||||
assert.NilError(t, json.Unmarshal(expected, &expectedMap))
|
||||
assert.NilError(t, json.Unmarshal(actual, &actualMap))
|
||||
assert.Assert(t, reflect.DeepEqual(expectedMap, actualMap))
|
||||
}
|
||||
|
||||
func TestGeneratePodControllerRule_NilAnnotation(t *testing.T) {
|
||||
policyRaw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict"
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.Assert(t, json.Unmarshal(policyRaw, &policy))
|
||||
patches, errs := generatePodControllerRule(policy)
|
||||
assert.Assert(t, len(errs) == 0)
|
||||
|
||||
p, err := engine.ApplyPatches(policyRaw, patches)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expectedPolicy := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict",
|
||||
"annotations": {
|
||||
"pod-policies.kyverno.io/autogen-controllers": "all"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
compareJSONAsMap(t, p, expectedPolicy)
|
||||
}
|
||||
|
||||
func TestGeneratePodControllerRule_PredefinedAnnotation(t *testing.T) {
|
||||
policyRaw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict",
|
||||
"annotations": {
|
||||
"pod-policies.kyverno.io/autogen-controllers": "StatefulSet,Pod"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.Assert(t, json.Unmarshal(policyRaw, &policy))
|
||||
patches, errs := generatePodControllerRule(policy)
|
||||
assert.Assert(t, len(errs) == 0)
|
||||
assert.Assert(t, len(patches) == 0)
|
||||
}
|
||||
|
||||
func TestGeneratePodControllerRule_ExistOtherAnnotation(t *testing.T) {
|
||||
policyRaw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict",
|
||||
"annotations": {
|
||||
"test": "annotation"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.Assert(t, json.Unmarshal(policyRaw, &policy))
|
||||
patches, errs := generatePodControllerRule(policy)
|
||||
assert.Assert(t, len(errs) == 0)
|
||||
|
||||
p, err := engine.ApplyPatches(policyRaw, patches)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expectedPolicy := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict",
|
||||
"annotations": {
|
||||
"pod-policies.kyverno.io/autogen-controllers": "all",
|
||||
"test": "annotation"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
compareJSONAsMap(t, p, expectedPolicy)
|
||||
}
|
||||
|
||||
func TestGeneratePodControllerRule(t *testing.T) {
|
||||
policyRaw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "add-safe-to-evict",
|
||||
"annotations": {
|
||||
"a": "b"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "annotate-empty-dir",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(emptyDir)": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "annotate-host-path",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(hostPath)": {
|
||||
"path": "*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validate-runAsNonRoot",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Running as root user is not allowed. Set runAsNonRoot to true",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "*",
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validate-docker-sock-mount",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Use of the Docker Unix socket is not allowed",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"=(volumes)": [
|
||||
{
|
||||
"=(hostPath)": {
|
||||
"path": "!/var/run/docker.sock"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.Assert(t, json.Unmarshal(policyRaw, &policy))
|
||||
patches, errs := generatePodControllerRule(policy)
|
||||
assert.Assert(t, len(errs) == 0)
|
||||
|
||||
p, err := engine.ApplyPatches(policyRaw, patches)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expectPolicy := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"a": "b",
|
||||
"pod-policies.kyverno.io/autogen-controllers": "all"
|
||||
},
|
||||
"name": "add-safe-to-evict"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "annotate-empty-dir",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(emptyDir)": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "annotate-host-path",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(hostPath)": {
|
||||
"path": "*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validate-runAsNonRoot",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Running as root user is not allowed. Set runAsNonRoot to true",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "*",
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validate-docker-sock-mount",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Use of the Docker Unix socket is not allowed",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"=(volumes)": [
|
||||
{
|
||||
"=(hostPath)": {
|
||||
"path": "!/var/run/docker.sock"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "autogen-annotate-empty-dir",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Job",
|
||||
"StatefulSet"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(emptyDir)": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "autogen-annotate-host-path",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Job",
|
||||
"StatefulSet"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": "true"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"(hostPath)": {
|
||||
"path": "*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "autogen-validate-runAsNonRoot",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Job",
|
||||
"StatefulSet"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Running as root user is not allowed. Set runAsNonRoot to true",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "*",
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "autogen-validate-docker-sock-mount",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Job",
|
||||
"StatefulSet"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Use of the Docker Unix socket is not allowed",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"=(volumes)": [
|
||||
{
|
||||
"=(hostPath)": {
|
||||
"path": "!/var/run/docker.sock"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
t.Log(string(expectPolicy))
|
||||
t.Log(string(p))
|
||||
compareJSONAsMap(t, expectPolicy, p)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/config"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"github.com/nirmata/kyverno/pkg/event"
|
||||
"github.com/nirmata/kyverno/pkg/policy"
|
||||
"github.com/nirmata/kyverno/pkg/policystore"
|
||||
|
@ -185,6 +187,18 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||
if request.Kind.Kind == "Pod" {
|
||||
if bytes.Contains(request.Object.Raw, []byte(engine.PodTemplateAnnotation)) {
|
||||
glog.V(4).Infof("Policies already processed on pod controllers, skip processing policy on Pod UID=%s", request.UID)
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
Result: &metav1.Status{
|
||||
Status: "Success",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
policies, err := ws.pMetaStore.LookUp(request.Kind.Kind, request.Namespace)
|
||||
if err != nil {
|
||||
// Unable to connect to policy Lister to access policies
|
||||
|
|
|
@ -22,6 +22,6 @@ spec:
|
|||
message: "Host path volumes are not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
volumes:
|
||||
=(volumes):
|
||||
- X(hostPath): null
|
||||
````
|
||||
|
|
|
@ -22,15 +22,13 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Privileged mode is not allowed. Set privileged to false"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
privileged: false
|
||||
- spec:
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
privileged: false
|
||||
- =(securityContext):
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L5707-L5711
|
||||
# k8s default to false
|
||||
=(privileged): false
|
||||
- name: validate-allowPrivilegeEscalation
|
||||
match:
|
||||
resources:
|
||||
|
@ -38,13 +36,10 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Privileged mode is not allowed. Set allowPrivilegeEscalation to false"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
- spec:
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
- securityContext:
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L5754
|
||||
allowPrivilegeEscalation: false
|
||||
````
|
||||
|
|
|
@ -30,7 +30,6 @@ spec:
|
|||
runAsNonRoot: true
|
||||
- spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
- securityContext:
|
||||
runAsNonRoot: true
|
||||
````
|
||||
|
|
|
@ -22,5 +22,5 @@ spec:
|
|||
message: "Host path volumes are not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
volumes:
|
||||
=(volumes):
|
||||
- X(hostPath): null
|
||||
|
|
|
@ -18,15 +18,13 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Privileged mode is not allowed. Set privileged to false"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
privileged: false
|
||||
- spec:
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
privileged: false
|
||||
- =(securityContext):
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L5707-L5711
|
||||
# k8s default to false
|
||||
=(privileged): false
|
||||
- name: validate-allowPrivilegeEscalation
|
||||
match:
|
||||
resources:
|
||||
|
@ -34,13 +32,10 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Privileged mode is not allowed. Set allowPrivilegeEscalation to false"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
- spec:
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
- securityContext:
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L5754
|
||||
allowPrivilegeEscalation: false
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ spec:
|
|||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L3165
|
||||
runAsNonRoot: true
|
||||
- spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
securityContext:
|
||||
- securityContext:
|
||||
# https://github.com/kubernetes/api/blob/7dc09db16fb8ff2eee16c65dc066c85ab3abb7ce/core/v1/types.go#L5742
|
||||
runAsNonRoot: true
|
Loading…
Add table
Reference in a new issue