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:
parent
7d76a45667
commit
2451756651
13 changed files with 146 additions and 94 deletions
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
26
pkg/engine/deny.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue