1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 15:37:19 +00:00

NK-10: Refactored policy types, used patch operation struct from there instead of internal struct.

Implemented checking of incoming request to correspond the policy rule, added tests.
Implemented generation of JSON patches according to patches in policy object, added tests.
Implemented base version of Mutate function as a wrapper for all mutation functions.
This commit is contained in:
belyshevdenis 2019-02-22 18:12:14 +02:00
parent ea9491a105
commit eb5c486ae1
7 changed files with 299 additions and 52 deletions

View file

@ -1,4 +1,3 @@
package v1alpha1
import (
@ -25,8 +24,9 @@ type PolicySpec struct {
// PolicyRule is policy rule that will be applied to resource
type PolicyRule struct {
Resource PolicyResource `json:"resource"`
Patches []PolicyPatch `json:"patches"`
Generators []PolicyConfigGenerator `json:"generator"`
Patches *[]PolicyPatch `json:"patches"`
ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator"`
SecretGenerator *PolicyConfigGenerator `json:"secretGenerator"`
}
// PolicyResource describes the resource rule applied to
@ -39,8 +39,8 @@ type PolicyResource struct {
// PolicyPatch is TODO
type PolicyPatch struct {
Path string `json:"path"`
Operation string `json:"operation"`
Value int `json:"value"`
Operation string `json:"op"`
Value string `json:"value"`
}
// PolicyConfigGenerator is TODO

View file

@ -25,16 +25,6 @@ type WebhookServer struct {
mutationWebhook *webhooks.MutationWebhook
}
type patchOperations struct {
patches []patchOperation
}
type patchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
// NewWebhookServer creates new instance of WebhookServer and configures it
func NewWebhookServer(certFile string, keyFile string, controller *controller.PolicyController, logger *log.Logger) *WebhookServer {
if logger == nil {

View file

@ -1,6 +1,9 @@
package webhooks
import "k8s.io/api/admission/v1beta1"
import (
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"k8s.io/api/admission/v1beta1"
)
var supportedKinds = [...]string{
"ConfigMap",
@ -36,3 +39,17 @@ func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool {
// Here you can make additional hardcoded checks
return kindIsSupported(request.Kind.Kind)
}
func IsRuleApplicableToRequest(rule types.PolicyRule, request *v1beta1.AdmissionRequest) bool {
return IsRuleResourceFitsRequest(rule.Resource, request)
}
func IsRuleResourceFitsRequest(resource types.PolicyResource, request *v1beta1.AdmissionRequest) bool {
if resource.Kind != request.Kind.Kind {
return false
}
if resource.Name != nil && *resource.Name != request.Name {
return false
}
return true
}

View file

@ -0,0 +1,87 @@
package webhooks_test
import (
"testing"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kube-policy/webhooks"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestAdmissionIsRequired(t *testing.T) {
var request v1beta1.AdmissionRequest
request.Kind.Kind = "ConfigMap"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "CronJob"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "DaemonSet"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Deployment"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Endpoint"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "HorizontalPodAutoscaler"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Ingress"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Job"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "LimitRange"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Namespace"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "NetworkPolicy"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "PersistentVolumeClaim"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "PodDisruptionBudget"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "PodTemplate"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "ResourceQuota"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Secret"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "Service"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
request.Kind.Kind = "StatefulSet"
assertEq(t, true, webhooks.AdmissionIsRequired(&request))
}
func TestIsRuleResourceFitsRequest_Kind(t *testing.T) {
resource := types.PolicyResource{
Kind: "ConfigMap",
}
request := v1beta1.AdmissionRequest{
Kind: metav1.GroupVersionKind{Kind: "ConfigMap"},
}
assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request))
resource.Kind = "Deployment"
assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request))
}
func TestIsRuleResourceFitsRequest_Name(t *testing.T) {
resourceName := "test-config-map"
resource := types.PolicyResource{
Kind: "ConfigMap",
Name: &resourceName,
}
request := v1beta1.AdmissionRequest{
Kind: metav1.GroupVersionKind{Kind: "ConfigMap"},
Name: "test-config-map",
}
assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request))
resourceName = "test-config-map-new"
assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request))
request.Name = "test-config-map-new"
assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request))
request.Name = ""
assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request))
}
func TestIsRuleApplicableToRequest(t *testing.T) {
// TODO
}

View file

@ -8,7 +8,6 @@ import (
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
coreTypes "k8s.io/kubernetes/pkg/apis/core"
)
type MutationWebhook struct {
@ -30,23 +29,44 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []
return nil
}
var configMap coreTypes.ConfigMap
if err := json.Unmarshal(request.Object.Raw, &configMap); err != nil {
mw.logger.Printf("Could not unmarshal raw object: %v", err)
return errorToResponse(err)
var allPatches []types.PolicyPatch
for _, policy := range policies {
var stopOnError bool = true
if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" {
stopOnError = false
}
for ruleIdx, rule := range policy.Spec.Rules {
if IsRuleApplicableToRequest(rule, request) {
mw.logger.Printf("Applying policy %v, rule index = %v", policy.ObjectMeta.Name, ruleIdx)
rulePatches, err := mw.applyPolicyRule(request, rule)
/*
* If at least one error is detected in the rule, the entire rule will not be applied.
* This may be changed in the future by varying the policy.Spec.FailurePolicy values.
*/
if err != nil {
mw.logger.Printf("Error occurred while applying the policy: %v", err)
if stopOnError {
mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\")
return errorToResponse(err, false)
}
} else {
mw.logger.Printf("Prepared %v patches", len(rulePatches))
allPatches = append(allPatches, rulePatches...)
}
}
}
}
patchesBytes, err := SerializePatches(allPatches)
if err != nil {
mw.logger.Printf("Error occerred while serializing JSONPathch: %v", err)
return errorToResponse(err, true)
}
/*patch := patchOperation{
Path: "/labels",
Op: "add",
Value: map[string]string{
"is-mutated": "true",
},
}*/
patch := `[ {"op":"add","path":"/metadata/labels","value":{"is-mutated":"true"}} ]`
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(patch),
Patch: patchesBytes,
PatchType: func() *v1beta1.PatchType {
pt := v1beta1.PatchTypeJSONPatch
return &pt
@ -54,10 +74,57 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []
}
}
func errorToResponse(err error) *v1beta1.AdmissionResponse {
// Applies all possible patches in a rule
func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) {
var allPatches []types.PolicyPatch
if rule.Patches == nil && rule.ConfigMapGenerator == nil && rule.SecretGenerator == nil {
return nil, errors.New("The rule is empty!")
}
if rule.Patches != nil {
for _, patch := range *rule.Patches {
allPatches = append(allPatches, patch)
}
}
if rule.ConfigMapGenerator != nil {
// TODO: Make patches from configMapGenerator and add them to returned array
}
if rule.SecretGenerator != nil {
// TODO: Make patches from secretGenerator and add them to returned array
}
return allPatches, nil
}
func SerializePatches(patches []types.PolicyPatch) ([]byte, error) {
var result []byte
result = append(result, []byte("[\n")...)
for index, patch := range patches {
if patch.Operation == "" || patch.Path == "" {
return nil, errors.New("JSONPatch doesn't contain mandatory fields 'path' or 'op'")
}
patchBytes, err := json.Marshal(patch)
if err != nil {
return nil, err
}
result = append(result, patchBytes...)
if index != (len(patches) - 1) {
result = append(result, []byte(",\n")...)
}
}
result = append(result, []byte("\n]")...)
return result, nil
}
func errorToResponse(err error, allowed bool) *v1beta1.AdmissionResponse {
return &v1beta1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
Allowed: allowed,
}
}

48
webhooks/mutation_test.go Normal file
View file

@ -0,0 +1,48 @@
package webhooks_test
import (
"testing"
"github.com/nirmata/kube-policy/webhooks"
//v1beta1 "k8s.io/api/admission/v1beta1"
//metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
)
func TestSerializePatches_Empty(t *testing.T) {
var patches []types.PolicyPatch
bytes, err := webhooks.SerializePatches(patches)
assertEq(t, nil, err)
assertEqStringAndData(t, "[\n\n]", bytes)
}
func TestSerializePatches_SingleValid(t *testing.T) {
patch := types.PolicyPatch{
Path: "/metadata/labels/is-mutated",
Operation: "add",
Value: "true",
}
patches := []types.PolicyPatch{patch}
bytes, err := webhooks.SerializePatches(patches)
assertEq(t, nil, err)
assertEqStringAndData(t, `[
{"path":"/metadata/labels/is-mutated","op":"add","value":"true"}
]`, bytes)
}
func TestSerializePatches_SingleInvalid(t *testing.T) {
patch := types.PolicyPatch{
Path: "/metadata/labels/is-mutated",
Value: "true",
}
patches := []types.PolicyPatch{patch}
_, err := webhooks.SerializePatches(patches)
assertNe(t, nil, err)
patches[0].Path = ""
patches[0].Operation = "delete"
_, err = webhooks.SerializePatches(patches)
assertNe(t, nil, err)
}
// patch := `[ {"op":"add","path":"/metadata/labels","value":{"is-mutated":"true"}} ]`

38
webhooks/utils_test.go Normal file
View file

@ -0,0 +1,38 @@
package webhooks_test
import (
"testing"
)
func assertEq(t *testing.T, expected interface{}, actual interface{}) {
if expected != actual {
t.Errorf("%s != %s", expected, actual)
}
}
func assertNe(t *testing.T, expected interface{}, actual interface{}) {
if expected == actual {
t.Errorf("%s != %s", expected, actual)
}
}
func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) {
if len(expected) != len(actual) {
t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual)
return
}
for idx, val := range actual {
if val != expected[idx] {
t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual)
}
}
}
func assertEqData(t *testing.T, expected, actual []byte) {
assertEqDataImpl(t, expected, actual, "%x")
}
func assertEqStringAndData(t *testing.T, str string, data []byte) {
assertEqDataImpl(t, []byte(str), data, "%s")
}