1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

Merge pull request #860 from realshuting/744_deny_requests

[1.2.0] 744 deny requests
This commit is contained in:
Jim Bugwadia 2020-05-18 20:57:33 -07:00 committed by GitHub
commit 9536a0df22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 599 additions and 338 deletions

View file

@ -8,7 +8,7 @@ GIT_BRANCH := $(shell git branch | grep \* | cut -d ' ' -f2)
GIT_HASH := $(GIT_BRANCH)/$(shell git log -1 --pretty=format:"%H")
TIMESTAMP := $(shell date '+%Y-%m-%d_%I:%M:%S%p')
REGISTRY=index.docker.io
REGISTRY?=index.docker.io
REPO=$(REGISTRY)/nirmata/kyverno
IMAGE_TAG?=$(GIT_VERSION)
GOOS ?= $(shell go env GOOS)

View file

@ -62,7 +62,7 @@ func main() {
// Exit for unsupported version of kubernetes cluster
// https://github.com/nirmata/kyverno/issues/700
// - supported from v1.12.7+
if !utils.CompareKubernetesVersion(client, log.Log, 1, 12, 7) {
if !utils.HigherThanKubernetesVersion(client, log.Log, 1, 12, 7) {
os.Exit(1)
}

View file

@ -70,7 +70,7 @@ spec:
type: string
name:
type: string
Namespace:
namespace:
type: string
resources:
type: object
@ -133,7 +133,7 @@ spec:
type: string
name:
type: string
Namespace:
namespace:
type: string
resources:
type: object
@ -210,6 +210,28 @@ spec:
AnyValue: {}
anyPattern:
AnyValue: {}
deny:
properties:
conditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
properties:
operator:
type: string
enum:
- Equal
- Equals
- NotEqual
- NotEquals
key:
type: string
value:
type: string
generate:
type: object
required:

View file

@ -133,7 +133,7 @@ spec:
type: string
name:
type: string
Namespace:
namespace:
type: string
resources:
type: object
@ -210,6 +210,28 @@ spec:
AnyValue: {}
anyPattern:
AnyValue: {}
deny:
properties:
conditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
properties:
operator:
type: string
enum:
- Equal
- Equals
- NotEqual
- NotEquals
key:
type: string
value:
type: string
generate:
type: object
required:

View file

@ -155,9 +155,11 @@ type ConditionOperator string
const (
//Equal for Equal operator
Equal ConditionOperator = "Equal"
Equal ConditionOperator = "Equal"
Equals ConditionOperator = "Equals"
//NotEqual for NotEqual operator
NotEqual ConditionOperator = "NotEqual"
NotEqual ConditionOperator = "NotEqual"
NotEquals ConditionOperator = "NotEquals"
//In for In operator
In ConditionOperator = "In"
//NotIn for NotIn operator
@ -211,6 +213,11 @@ type Validation struct {
Message string `json:"message,omitempty"`
Pattern interface{} `json:"pattern,omitempty"`
AnyPattern []interface{} `json:"anyPattern,omitempty"`
Deny *Deny `json:"deny,omitempty"`
}
type Deny struct {
Conditions []Condition `json:"conditions,omitempty"`
}
// Generation describes which resources will be created when other resource is created

View file

@ -5,6 +5,8 @@ import (
"strings"
"sync"
"k8s.io/api/admission/v1beta1"
jsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -63,6 +65,21 @@ func (ctx *Context) AddJSON(dataRaw []byte) error {
return nil
}
func (ctx *Context) AddRequest(request *v1beta1.AdmissionRequest) error {
modifiedResource := struct {
Request interface{} `json:"request"`
}{
Request: request,
}
objRaw, err := json.Marshal(modifiedResource)
if err != nil {
ctx.log.Error(err, "failed to marshal the request")
return err
}
return ctx.AddJSON(objRaw)
}
//AddResource data at path: request.object
func (ctx *Context) AddResource(dataRaw []byte) error {

View file

@ -3,6 +3,7 @@ package context
import (
"encoding/json"
"fmt"
"strings"
jmespath "github.com/jmespath/go-jmespath"
)
@ -11,7 +12,7 @@ import (
func (ctx *Context) Query(query string) (interface{}, error) {
var emptyResult interface{}
// check for white-listed variables
if ctx.isWhiteListed(query) {
if !ctx.isWhiteListed(query) {
return emptyResult, fmt.Errorf("variable %s cannot be used", query)
}
@ -40,8 +41,11 @@ func (ctx *Context) Query(query string) (interface{}, error) {
}
func (ctx *Context) isWhiteListed(variable string) bool {
if len(ctx.whiteListVars) == 0 {
return true
}
for _, wVar := range ctx.whiteListVars {
if wVar == variable {
if strings.HasPrefix(variable, wVar) {
return true
}
}

View file

@ -159,7 +159,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
Context: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
expectedErrorStr := "[failed to resolve request.object.metadata.name1 at path /spec/name]"
expectedErrorStr := "could not find variable request.object.metadata.name1 at path /spec/name"
t.Log(er.PolicyResponse.Rules[0].Message)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
}

View file

@ -114,6 +114,14 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User
const SaPrefix = "system:serviceaccount:"
userGroups := append(userInfo.Groups, userInfo.Username)
// TODO: see issue https://github.com/nirmata/kyverno/issues/861
ruleSubjects = append(ruleSubjects,
rbacv1.Subject{Kind: "Group", Name: "system:serviceaccounts:kube-system"},
rbacv1.Subject{Kind: "Group", Name: "system:nodes"},
rbacv1.Subject{Kind: "Group", Name: "system:kube-scheduler"},
)
for _, subject := range ruleSubjects {
switch subject.Kind {
case "ServiceAccount":
@ -163,7 +171,8 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
}
// checking if resource matches the rule
if !reflect.DeepEqual(rule.MatchResources.ResourceDescription, kyverno.ResourceDescription{}) {
if !reflect.DeepEqual(rule.MatchResources.ResourceDescription, kyverno.ResourceDescription{}) ||
!reflect.DeepEqual(rule.MatchResources.UserInfo, kyverno.UserInfo{}) {
matchErrs := doesResourceMatchConditionBlock(rule.MatchResources.ResourceDescription, rule.MatchResources.UserInfo, admissionInfo, resource)
reasonsForFailure = append(reasonsForFailure, matchErrs...)
} else {
@ -171,7 +180,8 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
}
// checking if resource has been excluded
if !reflect.DeepEqual(rule.ExcludeResources.ResourceDescription, kyverno.ResourceDescription{}) {
if !reflect.DeepEqual(rule.ExcludeResources.ResourceDescription, kyverno.ResourceDescription{}) ||
!reflect.DeepEqual(rule.ExcludeResources.UserInfo, kyverno.UserInfo{}) {
excludeErrs := doesResourceMatchConditionBlock(rule.ExcludeResources.ResourceDescription, rule.ExcludeResources.UserInfo, admissionInfo, resource)
if excludeErrs == nil {
reasonsForFailure = append(reasonsForFailure, fmt.Errorf("resource has been excluded since it matches the exclude block"))

View file

@ -25,42 +25,51 @@ func Validate(policyContext PolicyContext) (resp response.EngineResponse) {
ctx := policyContext.Context
admissionInfo := policyContext.AdmissionInfo
logger := log.Log.WithName("Validate").WithValues("policy", policy.Name, "kind", newR.GetKind(), "namespace", newR.GetNamespace(), "name", newR.GetName())
// policy information
logger.V(4).Info("start processing", "startTime", startTime)
// Process new & old resource
if reflect.DeepEqual(oldR, unstructured.Unstructured{}) {
// Create Mode
// Operate on New Resource only
resp := validateResource(logger, ctx, policy, newR, admissionInfo)
startResultResponse(resp, policy, newR)
defer endResultResponse(logger, resp, startTime)
// set PatchedResource with origin resource if empty
// in order to create policy violation
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
resp.PatchedResource = newR
defer func() {
if reflect.DeepEqual(resp, response.EngineResponse{}) {
return
}
var resource unstructured.Unstructured
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
// for delete requests patched resource will be oldR since newR is empty
if reflect.DeepEqual(newR, unstructured.Unstructured{}) {
resource = oldR
} else {
resource = newR
}
}
for i := range resp.PolicyResponse.Rules {
messageInterface, err := variables.SubstituteVars(logger, ctx, resp.PolicyResponse.Rules[i].Message)
if err != nil {
continue
}
resp.PolicyResponse.Rules[i].Message, _ = messageInterface.(string)
}
resp.PatchedResource = resource
startResultResponse(&resp, policy, resource)
endResultResponse(logger, &resp, startTime)
}()
// If request is delete, newR will be empty
if reflect.DeepEqual(newR, unstructured.Unstructured{}) {
return *isRequestDenied(logger, ctx, policy, oldR, admissionInfo)
} else {
if denyResp := isRequestDenied(logger, ctx, policy, newR, admissionInfo); !denyResp.IsSuccesful() {
return *denyResp
}
return *resp
}
// Update Mode
// Operate on New and Old Resource only
// New resource
if reflect.DeepEqual(oldR, unstructured.Unstructured{}) {
return *validateResource(logger, ctx, policy, newR, admissionInfo)
}
oldResponse := validateResource(logger, ctx, policy, oldR, admissionInfo)
newResponse := validateResource(logger, ctx, policy, newR, admissionInfo)
// if the old and new response is same then return empty response
if !isSameResponse(oldResponse, newResponse) {
// there are changes send response
startResultResponse(newResponse, policy, newR)
defer endResultResponse(logger, newResponse, startTime)
if reflect.DeepEqual(newResponse.PatchedResource, unstructured.Unstructured{}) {
newResponse.PatchedResource = newR
}
return *newResponse
}
// if there are no changes with old and new response then sent empty response
// skip processing
return response.EngineResponse{}
}
@ -85,6 +94,44 @@ func incrementAppliedCount(resp *response.EngineResponse) {
resp.PolicyResponse.RulesAppliedCount++
}
func isRequestDenied(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &response.EngineResponse{}
for _, rule := range policy.Spec.Rules {
if !rule.HasValidate() {
continue
}
if err := MatchesResourceDescription(resource, rule, admissionInfo); err != nil {
log.V(4).Info("resource fails the match description")
continue
}
preconditionsCopy := copyConditions(rule.Conditions)
if !variables.EvaluateConditions(log, ctx, preconditionsCopy) {
log.V(4).Info("resource fails the preconditions")
continue
}
if rule.Validation.Deny != nil {
denyConditionsCopy := copyConditions(rule.Validation.Deny.Conditions)
if len(rule.Validation.Deny.Conditions) == 0 || variables.EvaluateConditions(log, ctx, denyConditionsCopy) {
ruleResp := response.RuleResponse{
Name: rule.Name,
Type: utils.Validation.String(),
Message: rule.Validation.Message,
Success: false,
}
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResp)
}
continue
}
}
return resp
}
func validateResource(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &response.EngineResponse{}
for _, rule := range policy.Spec.Rules {
@ -96,15 +143,15 @@ func validateResource(log logr.Logger, ctx context.EvalInterface, policy kyverno
// 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
if err := MatchesResourceDescription(resource, rule, admissionInfo); err != nil {
log.V(4).Info("resource fails the match description")
log.V(4).Info("resource fails the match description", "reason", err.Error())
continue
}
// operate on the copy of the conditions, as we perform variable substitution
copyConditions := copyConditions(rule.Conditions)
preconditionsCopy := copyConditions(rule.Conditions)
// evaluate pre-conditions
// - handle variable subsitutions
if !variables.EvaluateConditions(log, ctx, copyConditions) {
if !variables.EvaluateConditions(log, ctx, preconditionsCopy) {
log.V(4).Info("resource fails the preconditions")
continue
}
@ -114,6 +161,7 @@ func validateResource(log logr.Logger, ctx context.EvalInterface, policy kyverno
incrementAppliedCount(resp)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
}
}
return resp
}

File diff suppressed because one or more lines are too long

View file

@ -26,6 +26,10 @@ func CreateOperatorHandler(log logr.Logger, ctx context.EvalInterface, op kyvern
return NewEqualHandler(log, ctx, subHandler)
case kyverno.NotEqual:
return NewNotEqualHandler(log, ctx, subHandler)
case kyverno.Equals:
return NewEqualHandler(log, ctx, subHandler)
case kyverno.NotEquals:
return NewNotEqualHandler(log, ctx, subHandler)
default:
log.Info("operator not supported", "operator", string(op))
}

View file

@ -11,23 +11,20 @@ import (
)
const (
variableRegex = `\{\{([^{}]*)\}\}`
singleVarRegex = `^\{\{([^{}]*)\}\}$`
variableRegex = `\{\{([^{}]*)\}\}`
)
//SubstituteVars replaces the variables with the values defined in the context
// - if any variable is invaid or has nil value, it is considered as a failed varable substitution
func SubstituteVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}) (interface{}, error) {
errs := []error{}
pattern = subVars(log, ctx, pattern, "", &errs)
if len(errs) == 0 {
// no error while parsing the pattern
return pattern, nil
pattern, err := subVars(log, ctx, pattern, "")
if err != nil {
return pattern, err
}
return pattern, fmt.Errorf("%v", errs)
return pattern, nil
}
func subVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
func subVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}, path string) (interface{}, error) {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
mapCopy := make(map[string]interface{})
@ -35,141 +32,85 @@ func subVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}, pa
mapCopy[k] = v
}
return subMap(log, ctx, mapCopy, path, errs)
return subMap(log, ctx, mapCopy, path)
case []interface{}:
sliceCopy := make([]interface{}, len(typedPattern))
copy(sliceCopy, typedPattern)
return subArray(log, ctx, sliceCopy, path, errs)
return subArray(log, ctx, sliceCopy, path)
case string:
return subValR(log, ctx, typedPattern, path, errs)
return subValR(ctx, typedPattern, path)
default:
return pattern
return pattern, nil
}
}
func subMap(log logr.Logger, ctx context.EvalInterface, patternMap map[string]interface{}, path string, errs *[]error) map[string]interface{} {
func subMap(log logr.Logger, ctx context.EvalInterface, patternMap map[string]interface{}, path string) (map[string]interface{}, error) {
for key, patternElement := range patternMap {
curPath := path + "/" + key
value := subVars(log, ctx, patternElement, curPath, errs)
value, err := subVars(log, ctx, patternElement, curPath)
if err != nil {
return nil, err
}
patternMap[key] = value
}
return patternMap
return patternMap, nil
}
func subArray(log logr.Logger, ctx context.EvalInterface, patternList []interface{}, path string, errs *[]error) []interface{} {
func subArray(log logr.Logger, ctx context.EvalInterface, patternList []interface{}, path string) ([]interface{}, error) {
for idx, patternElement := range patternList {
curPath := path + "/" + strconv.Itoa(idx)
value := subVars(log, ctx, patternElement, curPath, errs)
value, err := subVars(log, ctx, patternElement, curPath)
if err != nil {
return nil, err
}
patternList[idx] = value
}
return patternList
return patternList, nil
}
type NotFoundVariableErr struct {
variable string
path string
}
func (n NotFoundVariableErr) Error() string {
return fmt.Sprintf("could not find variable %v at path %v", n.variable, n.path)
}
// subValR resolves the variables if defined
func subValR(log logr.Logger, ctx context.EvalInterface, valuePattern string, path string, errs *[]error) interface{} {
func subValR(ctx context.EvalInterface, valuePattern string, path string) (interface{}, error) {
originalPattern := valuePattern
// variable values can be scalar values(string,int, float) or they can be obects(map,slice)
// - {{variable}}
// there is a single variable resolution so the value can be scalar or object
// - {{variable1--{{variable2}}}}}
// variable2 is evaluted first as an individual variable and can be have scalar or object values
// but resolving the outer variable, {{variable--<value>}}
// if <value> is scalar then it can replaced, but for object types its tricky
// as object cannot be directy replaced, if the object is stringyfied then it loses it structure.
// since this might be a potential place for error, required better error reporting and handling
// object values are only suported for single variable substitution
if ok, retVal := processIfSingleVariable(log, ctx, valuePattern, path, errs); ok {
return retVal
}
// var emptyInterface interface{}
var failedVars []string
// process type string
regex := regexp.MustCompile(`\{\{([^{}]*)\}\}`)
for {
valueStr := valuePattern
if len(failedVars) != 0 {
log.Info("failed to resolve variablesl short-circuiting")
if vars := regex.FindAllString(valuePattern, -1); len(vars) > 0 {
for _, variable := range vars {
underlyingVariable := strings.ReplaceAll(strings.ReplaceAll(variable, "}}", ""), "{{", "")
substitutedVar, err := ctx.Query(underlyingVariable)
if err != nil {
return nil, fmt.Errorf("failed to resolve %v at path %s", underlyingVariable, path)
}
if val, ok := substitutedVar.(string); ok {
valuePattern = strings.Replace(valuePattern, variable, val, -1)
} else {
if substitutedVar != nil {
if originalPattern == variable {
return substitutedVar, nil
}
return nil, fmt.Errorf("failed to resolve %v at path %s", underlyingVariable, path)
}
return nil, NotFoundVariableErr{
variable: underlyingVariable,
path: path,
}
}
}
} else {
break
}
// get variables at this level
validRegex := regexp.MustCompile(variableRegex)
groups := validRegex.FindAllStringSubmatch(valueStr, -1)
if len(groups) == 0 {
// there was no match
// not variable defined
break
}
subs := map[string]interface{}{}
for _, group := range groups {
if _, ok := subs[group[0]]; ok {
// value has already been substituted
continue
}
// here we do the querying of the variables from the context
variable, err := ctx.Query(group[1])
if err != nil {
// error while evaluating
failedVars = append(failedVars, group[1])
continue
}
// path not found in context and value stored in null/nill
if variable == nil {
failedVars = append(failedVars, group[1])
continue
}
// get values for each and replace
subs[group[0]] = variable
}
// perform substitutions
newVal := valueStr
for k, v := range subs {
// if value is of type string then cast else consider it as direct replacement
if val, ok := v.(string); ok {
newVal = strings.Replace(newVal, k, val, -1)
continue
}
// if type is not scalar then consider this as a failed variable
log.Info("variable resolves to non-scalar value. Non-Scalar values are not supported for nested variables", "variable", k, "value", v)
failedVars = append(failedVars, k)
}
valuePattern = newVal
}
// update errors if any
if len(failedVars) > 0 {
*errs = append(*errs, fmt.Errorf("failed to resolve %v at path %s", failedVars, path))
}
return valuePattern
}
// processIfSingleVariable will process the evaluation of single variables
// {{variable-{{variable}}}} -> compound/nested variables
// {{variable}}{{variable}} -> multiple variables
// {{variable}} -> single variable
// if the value can be evaluted return the value
// -> return value can be scalar or object type
// -> if the variable is not present in the context then add an error and dont process further
func processIfSingleVariable(log logr.Logger, ctx context.EvalInterface, valuePattern interface{}, path string, errs *[]error) (bool, interface{}) {
valueStr, ok := valuePattern.(string)
if !ok {
log.Info("failed to convert to string", "pattern", valuePattern)
return false, nil
}
// get variables at this level
validRegex := regexp.MustCompile(singleVarRegex)
groups := validRegex.FindAllStringSubmatch(valueStr, -1)
if len(groups) == 0 {
return false, nil
}
// as there will be exactly one variable based on the above regex
group := groups[0]
variable, err := ctx.Query(group[1])
if err != nil || variable == nil {
*errs = append(*errs, fmt.Errorf("failed to resolve %v at path %s", group[1], path))
// return the same value pattern, and add un-resolvable variable error
return true, valuePattern
}
return true, variable
return valuePattern, nil
}

View file

@ -152,6 +152,5 @@ func Test_SubvarRecursive(t *testing.T) {
ctx := context.NewContext()
assert.Assert(t, ctx.AddResource(resourceRaw))
errs := []error{}
subValR(log.Log, ctx, string(patternRaw), "/", &errs)
subValR(ctx, string(patternRaw), "/")
}

View file

@ -9,57 +9,76 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
)
//ContainsUserInfo returns error is userInfo is defined
func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
//ContainsVariablesOtherThanObject returns error if variable that does not start from request.object
func ContainsVariablesOtherThanObject(policy kyverno.ClusterPolicy) error {
var err error
// iterate of the policy rules to identify if userInfo is used
for idx, rule := range policy.Spec.Rules {
if path := userInfoDefined(rule.MatchResources.UserInfo); path != "" {
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/match/%s", idx, path)
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/%s", idx, path)
}
if path := userInfoDefined(rule.ExcludeResources.UserInfo); path != "" {
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/exclude/%s", idx, path)
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/%s", idx, path)
}
// variable defined with user information
// - condition.key
// - condition.value
// - mutate.overlay
// - validate.pattern
// - validate.anyPattern[*]
// variables to filter
// - request.userInfo*
// - serviceAccountName
// - serviceAccountNamespace
filterVars := []string{"request.userInfo*", "serviceAccountName", "serviceAccountNamespace"}
filterVars := []string{"request.object"}
ctx := context.NewContext(filterVars...)
for condIdx, condition := range rule.Conditions {
if condition.Key, err = variables.SubstituteVars(log.Log, ctx, condition.Key); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/key", idx, condIdx)
if condition.Key, err = variables.SubstituteVars(log.Log, ctx, condition.Key); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/condition[%d]/key", idx, condIdx)
}
if condition.Value, err = variables.SubstituteVars(log.Log, ctx, condition.Value); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/value", idx, condIdx)
if condition.Value, err = variables.SubstituteVars(log.Log, ctx, condition.Value); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/condition[%d]/value", idx, condIdx)
}
}
if rule.Mutation.Overlay, err = variables.SubstituteVars(log.Log, ctx, rule.Mutation.Overlay); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/mutate/overlay", idx)
if rule.Mutation.Overlay != nil {
if rule.Mutation.Overlay, err = variables.SubstituteVars(log.Log, ctx, rule.Mutation.Overlay); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/mutate/overlay", idx)
}
}
if rule.Validation.Pattern, err = variables.SubstituteVars(log.Log, ctx, rule.Validation.Pattern); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/pattern", idx)
if rule.Validation.Pattern != nil {
if rule.Validation.Pattern, err = variables.SubstituteVars(log.Log, ctx, rule.Validation.Pattern); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/validate/pattern", idx)
}
}
for idx2, pattern := range rule.Validation.AnyPattern {
if rule.Validation.AnyPattern[idx2], err = variables.SubstituteVars(log.Log, ctx, pattern); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/anyPattern[%d]", idx, idx2)
if rule.Validation.AnyPattern[idx2], err = variables.SubstituteVars(log.Log, ctx, pattern); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/validate/anyPattern[%d]", idx, idx2)
}
}
if _, err = variables.SubstituteVars(log.Log, ctx, rule.Validation.Message); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/validate/message", idx)
}
if rule.Validation.Deny != nil {
for i := range rule.Validation.Deny.Conditions {
if _, err = variables.SubstituteVars(log.Log, ctx, rule.Validation.Deny.Conditions[i].Key); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/validate/deny/conditions[%d]/key", idx, i)
}
if _, err = variables.SubstituteVars(log.Log, ctx, rule.Validation.Deny.Conditions[i].Value); !checkNotFoundErr(err) {
return fmt.Errorf("invalid variable used at spec/rules[%d]/validate/deny/conditions[%d]/value", idx, i)
}
}
}
}
return nil
}
func checkNotFoundErr(err error) bool {
if err != nil {
switch err.(type) {
case variables.NotFoundVariableErr:
return true
default:
return false
}
}
return true
}
func userInfoDefined(ui kyverno.UserInfo) string {
if len(ui.Roles) > 0 {
return "roles"

View file

@ -158,7 +158,7 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
if p.Spec.Background == nil {
// if userInfo is not defined in policy we process the policy
if err := ContainsUserInfo(*p); err != nil {
if err := ContainsVariablesOtherThanObject(*p); err != nil {
return
}
} else {
@ -167,7 +167,7 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
}
// If userInfo is used then skip the policy
// ideally this should be handled by background flag only
if err := ContainsUserInfo(*p); err != nil {
if err := ContainsVariablesOtherThanObject(*p); err != nil {
// contains userInfo used in policy
return
}
@ -193,7 +193,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
if curP.Spec.Background == nil {
// if userInfo is not defined in policy we process the policy
if err := ContainsUserInfo(*curP); err != nil {
if err := ContainsVariablesOtherThanObject(*curP); err != nil {
return
}
} else {
@ -202,7 +202,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
}
// If userInfo is used then skip the policy
// ideally this should be handled by background flag only
if err := ContainsUserInfo(*curP); err != nil {
if err := ContainsVariablesOtherThanObject(*curP); err != nil {
// contains userInfo used in policy
return
}

View file

@ -30,16 +30,9 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro
if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
}
if p.Spec.Background == nil {
//skipped policy mutation default -> skip validation -> will not be processed for background processing
return nil
}
if *p.Spec.Background {
if err := ContainsUserInfo(p); err != nil {
// policy.spec.background -> "true"
// - cannot use variables with request.userInfo
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
return fmt.Errorf("userInfo is not allowed in match or exclude when backgroud policy mode is true. Set spec.background=false to disable background mode for this policy rule. %s ", err)
if p.Spec.Background == nil || (p.Spec.Background != nil && *p.Spec.Background) {
if err := ContainsVariablesOtherThanObject(p); err != nil {
return fmt.Errorf("only variables referring request.object are allowed in background mode. Set spec.background=false to disable background mode for this policy rule. %s ", err)
}
}

View file

@ -49,8 +49,8 @@ func (v *Validate) Validate() (string, error) {
// validateOverlayPattern checks one of pattern/anyPattern must exist
func (v *Validate) validateOverlayPattern() error {
rule := v.rule
if rule.Pattern == nil && len(rule.AnyPattern) == 0 {
return fmt.Errorf("a pattern or anyPattern must be specified")
if rule.Pattern == nil && len(rule.AnyPattern) == 0 && rule.Deny == nil {
return fmt.Errorf("pattern, anyPattern or deny must be specified")
}
if rule.Pattern != nil && len(rule.AnyPattern) != 0 {

View file

@ -608,8 +608,8 @@ func Test_BackGroundUserInfo_match_roles(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/roles")
err = ContainsVariablesOtherThanObject(*policy)
assert.Equal(t, err.Error(), "invalid variable used at path: spec/rules[0]/match/roles")
}
func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
@ -640,9 +640,9 @@ func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/clusterRoles")
assert.Equal(t, err.Error(), "invalid variable used at path: spec/rules[0]/match/clusterRoles")
}
func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
@ -676,9 +676,9 @@ func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/subjects")
assert.Equal(t, err.Error(), "invalid variable used at path: spec/rules[0]/match/subjects")
}
func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
@ -708,9 +708,9 @@ func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
if err.Error() != "invalid variable used at spec/rules[0]/mutate/overlay" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -743,9 +743,9 @@ func Test_BackGroundUserInfo_mutate_overlay2(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
if err.Error() != "invalid variable used at spec/rules[0]/mutate/overlay" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -778,9 +778,9 @@ func Test_BackGroundUserInfo_validate_pattern(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/validate/pattern" {
if err.Error() != "invalid variable used at spec/rules[0]/validate/pattern" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -817,9 +817,9 @@ func Test_BackGroundUserInfo_validate_anyPattern(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
if err.Error() != "invalid variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -856,9 +856,9 @@ func Test_BackGroundUserInfo_validate_anyPattern_multiple_var(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
if err.Error() != "invalid variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -895,9 +895,9 @@ func Test_BackGroundUserInfo_validate_anyPattern_serviceAccount(t *testing.T) {
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
err = ContainsVariablesOtherThanObject(*policy)
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
if err.Error() != "invalid variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}

View file

@ -1,10 +1,15 @@
package utils
import (
"fmt"
"reflect"
"regexp"
"strconv"
engineutils "github.com/nirmata/kyverno/pkg/engine/utils"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/go-logr/logr"
"github.com/minio/minio/pkg/wildcard"
client "github.com/nirmata/kyverno/pkg/dclient"
@ -89,8 +94,51 @@ func CleanupOldCrd(client *dclient.Client, log logr.Logger) {
}
}
// CompareKubernetesVersion compare kuberneates client version to user given version
func CompareKubernetesVersion(client *client.Client, log logr.Logger, k8smajor, k8sminor, k8ssub int) bool {
// extracts the new and old resource as unstructured
func ExtractResources(newRaw []byte, request *v1beta1.AdmissionRequest) (unstructured.Unstructured, unstructured.Unstructured, error) {
var emptyResource unstructured.Unstructured
var newResource unstructured.Unstructured
var oldResource unstructured.Unstructured
var err error
// New Resource
if newRaw == nil {
newRaw = request.Object.Raw
}
if newRaw != nil {
newResource, err = ConvertResource(newRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
return emptyResource, emptyResource, fmt.Errorf("failed to convert new raw to unstructured: %v", err)
}
}
// Old Resource
oldRaw := request.OldObject.Raw
if oldRaw != nil {
oldResource, err = ConvertResource(oldRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
return emptyResource, emptyResource, fmt.Errorf("failed to convert old raw to unstructured: %v", err)
}
}
return newResource, oldResource, err
}
// convertResource converts raw bytes to an unstructured object
func ConvertResource(raw []byte, group, version, kind, namespace string) (unstructured.Unstructured, error) {
obj, err := engineutils.ConvertToUnstructured(raw)
if err != nil {
return unstructured.Unstructured{}, fmt.Errorf("failed to convert raw to unstructured: %v", err)
}
obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind})
obj.SetNamespace(namespace)
return *obj, nil
}
// HigherThanKubernetesVersion compare kuberneates client version to user given version
func HigherThanKubernetesVersion(client *client.Client, log logr.Logger, k8smajor, k8sminor, k8ssub int) bool {
logger := log.WithName("CompareKubernetesVersion")
serverVersion, err := client.DiscoveryClient.GetServerVersion()
if err != nil {

View file

@ -102,7 +102,7 @@ func (wrc *WebhookRegistrationClient) constructDebugValidatingWebhookConfig(caDa
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete},
),
},
}
@ -126,7 +126,7 @@ func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(caData []
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete},
),
},
}

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

@ -5,10 +5,10 @@ import (
"strings"
"github.com/go-logr/logr"
yamlv2 "gopkg.in/yaml.v2"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/response"
engineutils "github.com/nirmata/kyverno/pkg/engine/utils"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -167,3 +167,13 @@ func convertResource(raw []byte, group, version, kind, namespace string) (unstru
obj.SetNamespace(namespace)
return *obj, nil
}
func excludeKyvernoResources(kind string) bool {
switch kind {
case "ClusterPolicy", "ClusterPolicyViolation", "PolicyViolation", "GenerateRequest":
return true
default:
return false
}
}

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

@ -21,7 +21,9 @@ import (
func (ws *WebhookServer) HandleMutation(
request *v1beta1.AdmissionRequest,
resource unstructured.Unstructured,
policies []kyverno.ClusterPolicy, roles, clusterRoles []string) []byte {
policies []kyverno.ClusterPolicy,
ctx *context.Context,
userRequestInfo kyverno.RequestInfo) []byte {
resourceName := request.Kind.Kind + "/" + request.Name
if request.Namespace != "" {
@ -32,30 +34,6 @@ func (ws *WebhookServer) HandleMutation(
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,23 +10,24 @@ import (
"net/http"
"time"
"github.com/julienschmidt/httprouter"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/go-logr/logr"
"github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/checker"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/policystatus"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/policyviolation"
tlsutils "github.com/nirmata/kyverno/pkg/tls"
userinfo "github.com/nirmata/kyverno/pkg/userinfo"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
"github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -133,12 +134,13 @@ func NewWebhookServer(
}
mux := httprouter.New()
webhookTimeout := ws.webhookRegistrationClient.GetWebhookTimeOut()
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.resourceMutation, true), webhookTimeout))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.resourceValidation, true), webhookTimeout))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.policyMutation, true), webhookTimeout))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.policyValidation, true), webhookTimeout))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.verifyHandler, false), webhookTimeout))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleValidateAdmissionRequest, true), ws.webhookRegistrationClient.GetWebhookTimeOut()))
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleMutateAdmissionRequest, true), ws.webhookRegistrationClient.GetWebhookTimeOut()))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handlePolicyValidation, true), ws.webhookRegistrationClient.GetWebhookTimeOut()))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handlePolicyMutation, true), ws.webhookRegistrationClient.GetWebhookTimeOut()))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleVerifyRequest, false), ws.webhookRegistrationClient.GetWebhookTimeOut()))
ws.server = http.Server{
Addr: ":443", // Listen on port for HTTPS requests
TLSConfig: &tlsConfig,
@ -162,9 +164,6 @@ func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequ
}
logger := ws.log.WithValues("kind", admissionReview.Request.Kind, "namespace", admissionReview.Request.Namespace, "name", admissionReview.Request.Name)
defer func() {
logger.V(4).Info("request processed", "processingTime", time.Since(startTime))
}()
admissionReview.Response = &v1beta1.AdmissionResponse{
Allowed: true,
@ -180,6 +179,8 @@ func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequ
admissionReview.Response = handler(request)
writeResponse(rw, admissionReview)
logger.V(4).Info("request processed", "processingTime", time.Since(startTime).Milliseconds())
return
}
}
@ -197,8 +198,17 @@ func writeResponse(rw http.ResponseWriter, admissionReview *v1beta1.AdmissionRev
}
}
func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("handleMutateAdmissionRequest").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
if excludeKyvernoResources(request.Kind.Kind) {
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
}
}
logger := ws.log.WithName("resourceMutation").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 {
// Unable to connect to policy Lister to access policies
@ -206,9 +216,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 {
@ -218,7 +227,7 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
// convert RAW to unstructured
resource, err := convertResource(request.Object.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
resource, err := utils.ConvertResource(request.Object.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
logger.Error(err, "failed to convert RAW resource to unstructured format")
@ -240,22 +249,43 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
}
var patches, patchedResource []byte
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0)
// build context
ctx := context2.NewContext()
err = ctx.AddRequest(request)
if err != nil {
logger.Error(err, "failed to load incoming request in context")
}
if versionCheck {
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")
}
var patches []byte
patchedResource := request.Object.Raw
higherVersion := utils.HigherThanKubernetesVersion(ws.client, ws.log, 1, 14, 0)
if higherVersion {
// 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)
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{
@ -267,16 +297,14 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
}
}
} else {
// patch the resource with patches before handling validation rules
patchedResource = processResourceWithPatches(patches, request.Object.Raw, logger)
}
// GENERATE
// Only applied during resource creation
// 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{
@ -302,8 +330,17 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission
}
func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("handleValidateAdmissionRequest").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
if excludeKyvernoResources(request.Kind.Kind) {
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
}
}
logger := ws.log.WithName("resourceValidation").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 {
// Unable to connect to policy Lister to access policies
@ -322,7 +359,33 @@ func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.Admissi
}
}
resource, err := convertResource(request.Object.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo}
// build context
ctx := context2.NewContext()
err = ctx.AddRequest(request)
if err != nil {
logger.Error(err, "failed to load incoming request 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")
}
raw := request.Object.Raw
if request.Operation == v1beta1.Delete {
raw = request.OldObject.Raw
}
resource, err := convertResource(raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
logger.Error(err, "failed to convert RAW resource to unstructured format")
@ -344,11 +407,9 @@ func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.Admissi
}
}
versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0)
if !versionCheck {
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, nil, roles, clusterRoles)
higherVersion := utils.HigherThanKubernetesVersion(ws.client, ws.log, 1, 14, 0)
if higherVersion {
ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
@ -360,6 +421,7 @@ func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.Admissi
}
}
}
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{

View file

@ -5,6 +5,8 @@ import (
"sort"
"time"
"github.com/nirmata/kyverno/pkg/utils"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
@ -20,7 +22,9 @@ import (
func (ws *WebhookServer) HandleValidation(
request *v1beta1.AdmissionRequest,
policies []kyverno.ClusterPolicy,
patchedResource []byte, roles, clusterRoles []string) (bool, string) {
patchedResource []byte,
ctx *context.Context,
userRequestInfo kyverno.RequestInfo) (bool, string) {
resourceName := request.Kind.Kind + "/" + request.Name
if request.Namespace != "" {
@ -30,41 +34,20 @@ func (ws *WebhookServer) HandleValidation(
logger := ws.log.WithValues("action", "validate", "resource", resourceName, "operation", request.Operation)
// Get new and old resource
newR, oldR, err := extractResources(patchedResource, request)
newR, oldR, err := utils.ExtractResources(patchedResource, request)
if err != nil {
// as resource cannot be parsed, we skip processing
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,
OldResource: oldR,
Context: ctx,
AdmissionInfo: userRequestInfo,
}
var engineResponses []response.EngineResponse
for _, policy := range policies {
logger.V(3).Info("evaluating policy", "policy", policy.Name)