1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

744 tested prototype

This commit is contained in:
shravan 2020-04-10 23:24:54 +05:30
parent 7d76a45667
commit 2451756651
13 changed files with 146 additions and 94 deletions

View file

@ -38,7 +38,6 @@ spec:
type: object
required:
- name
- match
properties:
name:
type: string
@ -177,6 +176,19 @@ spec:
- key # can be of any type
- operator # typed
- value # can be of any type
deny:
type: object
properties:
message:
type: string
conditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
mutate:
type: object
properties:

View file

@ -38,7 +38,6 @@ spec:
type: object
required:
- name
- match
properties:
name:
type: string
@ -177,6 +176,19 @@ spec:
- key # can be of any type
- operator # typed
- value # can be of any type
deny:
type: object
properties:
message:
type: string
conditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
mutate:
type: object
properties:

View file

@ -141,6 +141,12 @@ type Rule struct {
Mutation Mutation `json:"mutate,omitempty"`
Validation Validation `json:"validate,omitempty"`
Generation Generation `json:"generate,omitempty"`
Deny *Deny `json:"deny,omitempty"`
}
type Deny struct {
Message string `json:"message,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
}
//Condition defines the evaluation condition

View file

@ -27,6 +27,10 @@ func (r Rule) HasGenerate() bool {
return !reflect.DeepEqual(r.Generation, Generation{})
}
func (r Rule) HasDeny() bool {
return r.Deny != nil
}
// DeepCopyInto is declared because k8s:deepcopy-gen is
// not able to generate this method for interface{} member
func (in *Mutation) DeepCopyInto(out *Mutation) {

26
pkg/engine/deny.go Normal file
View file

@ -0,0 +1,26 @@
package engine
import (
"fmt"
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/engine/context"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/variables"
)
func Deny(logger logr.Logger, policy v1.ClusterPolicy, ctx *context.Context) error {
for _, rule := range policy.Spec.Rules {
if rule.Deny != nil {
sliceCopy := make([]v1.Condition, len(rule.Deny.Conditions))
copy(sliceCopy, rule.Deny.Conditions)
if !variables.EvaluateConditions(logger, ctx, sliceCopy) {
return fmt.Errorf("request has been denied by policy %s due to - %s", policy.Name, rule.Deny.Message)
}
}
}
return nil
}

View file

@ -51,12 +51,6 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro
if path, err := validateResources(rule); err != nil {
return fmt.Errorf("path: spec.rules[%d].%s: %v", i, path, err)
}
// validate rule types
// only one type of rule is allowed per rule
if err := validateRuleType(rule); err != nil {
// as there are more than 1 operation in rule, not need to evaluate it further
return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
// validate rule actions
// - Mutate
// - Validate
@ -122,6 +116,10 @@ func ruleOnlyDealsWithResourceMetaData(rule kyverno.Rule) bool {
}
func validateResources(rule kyverno.Rule) (string, error) {
if rule.HasDeny() {
return "", nil
}
// validate userInfo in match and exclude
if path, err := validateUserInfo(rule); err != nil {
return fmt.Sprintf("resources.%s", path), err
@ -153,7 +151,7 @@ func validateUniqueRuleName(p kyverno.ClusterPolicy) (string, error) {
// validateRuleType checks only one type of rule is defined per rule
func validateRuleType(r kyverno.Rule) error {
ruleTypes := []bool{r.HasMutate(), r.HasValidate(), r.HasGenerate()}
ruleTypes := []bool{r.HasMutate(), r.HasValidate(), r.HasGenerate(), r.HasDeny()}
operationCount := func() int {
count := 0

View file

@ -4,7 +4,7 @@ import (
"k8s.io/api/admission/v1beta1"
)
func (ws *WebhookServer) handleVerifyRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) verifyHandler(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("action", "verify", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
logger.V(4).Info("incoming request")
return &v1beta1.AdmissionResponse{

View file

@ -16,7 +16,7 @@ import (
)
//HandleGenerate handles admission-requests for policies with generate rules
func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, policies []kyverno.ClusterPolicy, patchedResource []byte, roles, clusterRoles []string) (bool, string) {
func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, policies []kyverno.ClusterPolicy, ctx *context.Context, userRequestInfo kyverno.RequestInfo) (bool, string) {
logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
logger.V(4).Info("incoming request")
var engineResponses []response.EngineResponse
@ -31,27 +31,6 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
// CREATE resources, do not have name, assigned in admission-request
userRequestInfo := kyverno.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context.NewContext()
// load incoming resource into the context
err = ctx.AddResource(request.Object.Raw)
if err != nil {
logger.Error(err, "failed to load incoming resource in context")
}
err = ctx.AddUserInfo(userRequestInfo)
if err != nil {
logger.Error(err, "failed to load userInfo in context")
}
// load service account in context
err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
if err != nil {
logger.Error(err, "failed to load service account in context")
}
policyContext := engine.PolicyContext{
NewResource: *resource,
AdmissionInfo: userRequestInfo,

View file

@ -18,36 +18,12 @@ import (
// HandleMutation handles mutating webhook admission request
// return value: generated patches
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured, policies []kyverno.ClusterPolicy, roles, clusterRoles []string) []byte {
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured, policies []kyverno.ClusterPolicy, ctx *context.Context, userRequestInfo kyverno.RequestInfo) []byte {
logger := ws.log.WithValues("action", "mutation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
logger.V(4).Info("incoming request")
var patches [][]byte
var engineResponses []response.EngineResponse
userRequestInfo := kyverno.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context.NewContext()
var err error
// load incoming resource into the context
err = ctx.AddResource(request.Object.Raw)
if err != nil {
logger.Error(err, "failed to load incoming resource in context")
}
err = ctx.AddUserInfo(userRequestInfo)
if err != nil {
logger.Error(err, "failed to load userInfo in context")
}
err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
if err != nil {
logger.Error(err, "failed to load service account in context")
}
policyContext := engine.PolicyContext{
NewResource: resource,
AdmissionInfo: userRequestInfo,

View file

@ -16,7 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) policyMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("action", "policymutation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
var policy *kyverno.ClusterPolicy
raw := request.Object.Raw

View file

@ -9,7 +9,7 @@ import (
)
//HandlePolicyValidation performs the validation check on policy resource
func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) policyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
//TODO: can this happen? wont this be picked by OpenAPI spec schema ?
if err := policyvalidate.Validate(request.Object.Raw, ws.client, false, ws.openAPIController); err != nil {
return &v1beta1.AdmissionResponse{

View file

@ -10,6 +10,11 @@ import (
"net/http"
"time"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/go-logr/logr"
@ -131,11 +136,11 @@ func NewWebhookServer(
openAPIController: openAPIController,
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.handlerFunc(ws.handleMutateAdmissionRequest, true))
mux.HandleFunc(config.ValidatingWebhookServicePath, ws.handlerFunc(ws.handleValidateAdmissionRequest, true))
mux.HandleFunc(config.PolicyMutatingWebhookServicePath, ws.handlerFunc(ws.handlePolicyMutation, true))
mux.HandleFunc(config.PolicyValidatingWebhookServicePath, ws.handlerFunc(ws.handlePolicyValidation, true))
mux.HandleFunc(config.VerifyMutatingWebhookServicePath, ws.handlerFunc(ws.handleVerifyRequest, false))
mux.HandleFunc(config.MutatingWebhookServicePath, ws.handlerFunc(ws.resourceMutation, true))
mux.HandleFunc(config.ValidatingWebhookServicePath, ws.handlerFunc(ws.resourceValidation, true))
mux.HandleFunc(config.PolicyMutatingWebhookServicePath, ws.handlerFunc(ws.policyMutation, true))
mux.HandleFunc(config.PolicyValidatingWebhookServicePath, ws.handlerFunc(ws.policyValidation, true))
mux.HandleFunc(config.VerifyMutatingWebhookServicePath, ws.handlerFunc(ws.verifyHandler, false))
ws.server = http.Server{
Addr: ":443", // Listen on port for HTTPS requests
TLSConfig: &tlsConfig,
@ -190,7 +195,7 @@ func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequ
}
}
func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
policies, err := ws.pMetaStore.ListAll()
if err != nil {
@ -199,9 +204,8 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
return &v1beta1.AdmissionResponse{Allowed: true}
}
var roles, clusterRoles []string
// getRoleRef only if policy has roles/clusterroles defined
var roles, clusterRoles []string
if containRBACinfo(policies) {
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request)
if err != nil {
@ -233,17 +237,51 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
}
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context2.NewContext()
// load incoming resource into the context
err = ctx.AddResource(request.Object.Raw)
if err != nil {
logger.Error(err, "failed to load incoming resource in context")
}
err = ctx.AddUserInfo(userRequestInfo)
if err != nil {
logger.Error(err, "failed to load userInfo in context")
}
err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
if err != nil {
logger.Error(err, "failed to load service account in context")
}
for _, policy := range policies {
if err := engine.Deny(logger, policy, ctx); err != nil {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: err.Error(),
},
}
}
}
// MUTATION
// mutation failure should not block the resource creation
// any mutation failure is reported as the violation
patches := ws.HandleMutation(request, resource, policies, roles, clusterRoles)
patches := ws.HandleMutation(request, resource, policies, ctx, userRequestInfo)
// patch the resource with patches before handling validation rules
patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger)
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" {
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles)
ok, msg := ws.HandleValidation(request, policies, patchedResource, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
@ -261,7 +299,7 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
// Success -> Generate Request CR created successsfully
// Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create {
ok, msg := ws.HandleGenerate(request, policies, patchedResource, roles, clusterRoles)
ok, msg := ws.HandleGenerate(request, policies, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
@ -285,7 +323,7 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
}
func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
policies, err := ws.pMetaStore.ListAll()
if err != nil {
@ -305,8 +343,30 @@ func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.Admissi
}
}
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context2.NewContext()
// load incoming resource into the context
err = ctx.AddResource(request.Object.Raw)
if err != nil {
logger.Error(err, "failed to load incoming resource in context")
}
err = ctx.AddUserInfo(userRequestInfo)
if err != nil {
logger.Error(err, "failed to load userInfo in context")
}
err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
if err != nil {
logger.Error(err, "failed to load service account in context")
}
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, nil, roles, clusterRoles)
ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{

View file

@ -17,7 +17,7 @@ import (
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
// patchedResource is the (resource + patches) after applying mutation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, policies []kyverno.ClusterPolicy, patchedResource []byte, roles, clusterRoles []string) (bool, string) {
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, policies []kyverno.ClusterPolicy, patchedResource []byte, ctx *context.Context, userRequestInfo kyverno.RequestInfo) (bool, string) {
logger := ws.log.WithValues("action", "validation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
logger.V(4).Info("incoming request")
@ -28,27 +28,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
logger.Error(err, "failed to extract resource")
return true, ""
}
userRequestInfo := kyverno.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context.NewContext()
// load incoming resource into the context
err = ctx.AddResource(request.Object.Raw)
if err != nil {
logger.Error(err, "failed to load incoming resource in context")
}
err = ctx.AddUserInfo(userRequestInfo)
if err != nil {
logger.Error(err, "failed to load userInfo in context")
}
err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
if err != nil {
logger.Error(err, "failed to load service account in context")
}
policyContext := engine.PolicyContext{
NewResource: newR,