2019-10-18 17:45:24 -07:00
package policy
2019-09-27 16:31:27 -07:00
import (
2020-03-24 23:12:45 +05:30
"encoding/json"
2019-09-27 16:31:27 -07:00
"fmt"
2021-02-04 02:39:42 +05:30
"reflect"
2021-09-06 02:52:51 -07:00
"regexp"
2022-03-16 00:50:33 -04:00
"sort"
2021-02-04 02:39:42 +05:30
"strings"
2022-01-17 04:06:44 +00:00
"github.com/distribution/distribution/reference"
2021-11-03 11:16:55 -07:00
"github.com/kyverno/kyverno/pkg/engine/context"
2021-09-06 02:52:51 -07:00
jsonpatch "github.com/evanphx/json-patch/v5"
2021-02-01 12:59:13 -08:00
"github.com/jmespath/go-jmespath"
2021-10-29 18:13:20 +02:00
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
2021-10-05 00:30:57 +05:30
comn "github.com/kyverno/kyverno/pkg/common"
dclient "github.com/kyverno/kyverno/pkg/dclient"
2021-02-01 12:59:13 -08:00
"github.com/kyverno/kyverno/pkg/engine"
2021-03-19 20:07:54 +01:00
"github.com/kyverno/kyverno/pkg/engine/variables"
2021-02-01 12:59:13 -08:00
"github.com/kyverno/kyverno/pkg/kyverno/common"
2020-11-13 16:25:51 -08:00
"github.com/kyverno/kyverno/pkg/openapi"
2020-12-15 15:21:39 -08:00
"github.com/kyverno/kyverno/pkg/utils"
2021-06-10 07:46:26 +05:30
"github.com/minio/pkg/wildcard"
2021-10-29 18:13:20 +02:00
"github.com/pkg/errors"
2022-03-16 00:50:33 -04:00
v1beta1 "k8s.io/api/admission/v1beta1"
2021-03-02 10:01:06 +05:30
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2019-09-27 16:31:27 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022-03-15 09:48:58 +01:00
"k8s.io/apimachinery/pkg/util/validation/field"
2021-09-06 02:52:51 -07:00
"k8s.io/apimachinery/pkg/util/yaml"
2021-10-29 18:13:20 +02:00
"sigs.k8s.io/controller-runtime/pkg/log"
2019-09-27 16:31:27 -07:00
)
2022-01-04 17:36:33 -08:00
var allowedVariables = regexp . MustCompile ( ` request\.|serviceAccountName|serviceAccountNamespace|element\.|elementIndex|@|images\.|([a-z_0-9]+\()[^ { }] ` )
2021-11-03 11:16:55 -07:00
2022-01-04 17:36:33 -08:00
var allowedVariablesBackground = regexp . MustCompile ( ` request\.|element\.|elementIndex|@|images\.|([a-z_0-9]+\()[^ { }] ` )
2021-11-03 11:16:55 -07:00
// wildCardAllowedVariables represents regex for the allowed fields in wildcards
var wildCardAllowedVariables = regexp . MustCompile ( ` \ { \ { \s*(request\.|serviceAccountName|serviceAccountNamespace)[^ { }]*\}\} ` )
2021-11-30 18:14:58 +01:00
var errOperationForbidden = errors . New ( "variables are forbidden in the path of a JSONPatch" )
2021-09-06 02:52:51 -07:00
// validateJSONPatchPathForForwardSlash checks for forward slash
func validateJSONPatchPathForForwardSlash ( patch string ) error {
2021-11-30 18:14:58 +01:00
// Replace all variables in PatchesJSON6902, all variable checks should have happened already.
// This prevents further checks from failing unexpectedly.
patch = variables . ReplaceAllVars ( patch , func ( s string ) string { return "kyvernojsonpatchvariable" } )
2021-09-06 02:52:51 -07:00
re , err := regexp . Compile ( "^/" )
if err != nil {
return err
}
jsonPatch , err := yaml . ToJSON ( [ ] byte ( patch ) )
if err != nil {
return err
}
decodedPatch , err := jsonpatch . DecodePatch ( jsonPatch )
if err != nil {
return err
}
for _ , operation := range decodedPatch {
path , err := operation . Path ( )
if err != nil {
return err
}
val := re . MatchString ( path )
if ! val {
return fmt . Errorf ( "%s" , path )
}
}
return nil
}
2021-11-04 23:26:22 -07:00
// Validate checks the policy and rules declarations for required configurations
2022-03-16 00:50:33 -04:00
func Validate ( policy * kyverno . ClusterPolicy , client * dclient . Client , mock bool , openAPIController * openapi . Controller ) ( * v1beta1 . AdmissionResponse , error ) {
2022-03-21 09:53:12 +01:00
namespaced := policy . GetNamespace ( ) != ""
2021-11-03 11:16:55 -07:00
background := policy . Spec . Background == nil || * policy . Spec . Background
2022-03-21 09:53:12 +01:00
var errs field . ErrorList
specPath := field . NewPath ( "spec" )
2020-08-24 03:41:03 +05:30
2022-03-21 09:53:12 +01:00
if errs := policy . Validate ( namespaced ) ; len ( errs ) != 0 {
2022-03-17 04:36:21 +01:00
return nil , errs . ToAggregate ( )
2019-10-03 16:49:41 -07:00
}
2022-03-21 09:53:12 +01:00
err := ValidateVariables ( policy , background )
if err != nil {
return nil , err
2021-10-22 01:48:22 +05:30
}
var res [ ] * metav1 . APIResourceList
2022-03-21 09:53:12 +01:00
clusterResources := make ( [ ] string , 0 )
2021-11-03 11:16:55 -07:00
if ! mock && namespaced {
2021-10-22 01:48:22 +05:30
var Empty struct { }
clusterResourcesMap := make ( map [ string ] * struct { } )
2022-01-21 18:06:44 +05:30
2022-03-21 09:53:12 +01:00
// Get all the cluster type kind supported by cluster
2021-10-22 01:48:22 +05:30
res , err := client . DiscoveryClient . DiscoveryCache ( ) . ServerPreferredResources ( )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , err
2021-10-22 01:48:22 +05:30
}
for _ , resList := range res {
for _ , r := range resList . APIResources {
if ! r . Namespaced {
if _ , ok := clusterResourcesMap [ r . Kind ] ; ! ok {
clusterResourcesMap [ r . Kind ] = & Empty
}
}
}
}
for k := range clusterResourcesMap {
clusterResources = append ( clusterResources , k )
}
}
2022-03-18 16:18:32 +01:00
rules := policy . GetRules ( )
2022-03-15 09:48:58 +01:00
rulesPath := specPath . Child ( "rules" )
2022-03-09 16:28:31 +01:00
for i , rule := range rules {
2022-03-15 09:48:58 +01:00
rulePath := rulesPath . Index ( i )
2021-09-06 02:52:51 -07:00
//check for forward slash
if err := validateJSONPatchPathForForwardSlash ( rule . Mutation . PatchesJSON6902 ) ; err != nil {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "path must begin with a forward slash: spec.rules[%d]: %s" , i , err )
2021-09-06 02:52:51 -07:00
}
2020-12-09 15:37:45 -08:00
if jsonPatchOnPod ( rule ) {
2022-03-16 00:50:33 -04:00
log . Log . V ( 1 ) . Info ( "Pods managed by workload controllers cannot be mutated using policies. Use the autogen feature or write policies that match Pod controllers." )
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Warnings : [ ] string { "Pods managed by workload controllers cannot be mutated using policies. Use the autogen feature or write policies that match Pod controllers." } ,
} , nil
2020-12-09 15:37:45 -08:00
}
2022-03-16 00:50:33 -04:00
2019-10-21 14:22:31 -07:00
// validate resource description
2022-03-16 17:15:46 +01:00
if path , err := validateResources ( rulePath , rule ) ; err != nil {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "path: spec.rules[%d].%s: %v" , i , path , err )
2019-10-21 14:22:31 -07:00
}
2020-10-14 17:39:45 -07:00
2021-10-14 22:44:11 +05:30
err := validateElementInForEach ( rule )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , err
2021-10-14 22:44:11 +05:30
}
2020-10-14 17:39:45 -07:00
if err := validateRuleContext ( rule ) ; err != nil {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "path: spec.rules[%d]: %v" , i , err )
2020-10-14 17:39:45 -07:00
}
2020-11-13 16:25:51 -08:00
// validate Cluster Resources in namespaced policy
// For namespaced policy, ClusterResource type field and values are not allowed in match and exclude
2021-11-03 11:16:55 -07:00
if namespaced {
2022-03-16 00:50:33 -04:00
return nil , checkClusterResourceInMatchAndExclude ( rule , clusterResources , mock , res )
2020-09-01 21:42:05 +05:30
}
2019-09-27 16:31:27 -07:00
2020-10-14 17:39:45 -07:00
if doMatchAndExcludeConflict ( rule ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "path: spec.rules[%v]: rule is matching an empty set" , rule . Name )
2020-04-27 22:01:33 +05:30
}
2020-03-20 20:23:34 +05:30
2020-03-11 18:14:23 -07:00
// validate rule actions
// - Mutate
// - Validate
// - Generate
2022-03-09 16:28:31 +01:00
if err := validateActions ( i , & rules [ i ] , client , mock ) ; err != nil {
2022-03-16 00:50:33 -04:00
return nil , err
2019-09-27 19:03:55 -07:00
}
2020-02-26 16:08:56 +05:30
2021-07-29 01:29:53 +05:30
// If a rule's match block does not match any kind,
// we should only allow it to have metadata in its overlay
if len ( rule . MatchResources . Any ) > 0 {
for _ , rmr := range rule . MatchResources . Any {
if len ( rmr . Kinds ) == 0 {
2022-03-16 00:50:33 -04:00
return nil , validateMatchKindHelper ( rule )
2021-07-29 01:29:53 +05:30
}
}
} else if len ( rule . MatchResources . All ) > 0 {
for _ , rmr := range rule . MatchResources . All {
if len ( rmr . Kinds ) == 0 {
2022-03-16 00:50:33 -04:00
return nil , validateMatchKindHelper ( rule )
2021-07-29 01:29:53 +05:30
}
}
} else {
if len ( rule . MatchResources . Kinds ) == 0 {
2022-03-16 00:50:33 -04:00
return nil , validateMatchKindHelper ( rule )
2020-02-26 16:08:56 +05:30
}
2021-07-14 23:49:15 +05:30
}
2021-11-03 11:16:55 -07:00
if utils . ContainsString ( rule . MatchResources . Kinds , "*" ) && ( policy . Spec . Background == nil || * policy . Spec . Background ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "wildcard policy not allowed in background mode. Set spec.background=false to disable background mode for this policy rule " )
2021-10-14 12:45:32 +05:30
}
if ( utils . ContainsString ( rule . MatchResources . Kinds , "*" ) && len ( rule . MatchResources . Kinds ) > 1 ) || ( utils . ContainsString ( rule . ExcludeResources . Kinds , "*" ) && len ( rule . ExcludeResources . Kinds ) > 1 ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "wildard policy can not deal more than one kind" )
2021-10-14 12:45:32 +05:30
}
2021-07-14 23:49:15 +05:30
if utils . ContainsString ( rule . MatchResources . Kinds , "*" ) || utils . ContainsString ( rule . ExcludeResources . Kinds , "*" ) {
2021-10-14 12:45:32 +05:30
if rule . HasGenerate ( ) || rule . HasVerifyImages ( ) || rule . Validation . ForEachValidation != nil {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "wildcard policy does not support rule type" )
2021-10-14 12:45:32 +05:30
}
if rule . HasValidate ( ) {
2022-03-06 20:07:51 +01:00
if rule . Validation . GetPattern ( ) != nil || rule . Validation . GetAnyPattern ( ) != nil {
2021-10-14 12:45:32 +05:30
if ! ruleOnlyDealsWithResourceMetaData ( rule ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
2021-10-14 12:45:32 +05:30
" the rule does not match any kind" )
}
}
if rule . Validation . Deny != nil {
2022-03-06 20:07:51 +01:00
kyvernoConditions , _ := utils . ApiextensionsJsonToKyvernoConditions ( rule . Validation . Deny . GetAnyAllConditions ( ) )
2021-10-14 12:45:32 +05:30
switch typedConditions := kyvernoConditions . ( type ) {
case [ ] kyverno . Condition : // backwards compatibility
for _ , condition := range typedConditions {
2022-03-06 20:07:51 +01:00
key := condition . GetKey ( )
if ! strings . Contains ( key . ( string ) , "request.object.metadata." ) && ( ! wildCardAllowedVariables . MatchString ( key . ( string ) ) || strings . Contains ( key . ( string ) , "request.object.spec" ) ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
2021-10-14 12:45:32 +05:30
" the rule does not match any kind" )
}
}
}
}
}
2021-11-04 23:26:22 -07:00
2021-10-14 12:45:32 +05:30
if rule . HasMutate ( ) {
if ! ruleOnlyDealsWithResourceMetaData ( rule ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
2021-10-14 12:45:32 +05:30
" the rule does not match any kind" )
}
}
2021-11-04 23:26:22 -07:00
if rule . HasVerifyImages ( ) {
2022-03-15 09:48:58 +01:00
verifyImagePath := rulePath . Child ( "verifyImages" )
for index , i := range rule . VerifyImages {
errs = append ( errs , i . Validate ( verifyImagePath . Index ( index ) ) ... )
2021-11-04 23:26:22 -07:00
}
}
2022-03-15 09:48:58 +01:00
if len ( errs ) != 0 {
2022-03-16 00:50:33 -04:00
return nil , errs . ToAggregate ( )
2022-03-15 09:48:58 +01:00
}
2020-02-26 16:08:56 +05:30
}
2020-08-29 06:52:22 +05:30
2022-03-16 00:50:33 -04:00
var podOnlyMap = make ( map [ string ] bool ) //Validate that Kind is only Pod
podOnlyMap [ "Pod" ] = true
if reflect . DeepEqual ( common . GetKindsFromRule ( rule ) , podOnlyMap ) && podControllerAutoGenExclusion ( policy ) {
log . Log . V ( 4 ) . Info ( "Pod controllers excluded from autogen require adding of preconditions to also exclude the desired controller(s)." )
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Warnings : [ ] string { "Pod controllers excluded from autogen require adding of preconditions to also exclude the desired controller(s)." } ,
} , nil
}
2021-10-14 12:45:32 +05:30
//Validate Kind with match resource kinds
2021-10-05 00:30:57 +05:30
match := rule . MatchResources
exclude := rule . ExcludeResources
for _ , value := range match . Any {
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , * policy )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "the kind defined in the any match resource is invalid" )
2022-03-08 18:29:33 +05:30
}
2021-10-05 00:30:57 +05:30
}
}
for _ , value := range match . All {
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , * policy )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "the kind defined in the all match resource is invalid" )
2022-03-08 18:29:33 +05:30
}
2021-08-21 03:58:49 +05:30
}
}
2021-10-05 00:30:57 +05:30
for _ , value := range exclude . Any {
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , * policy )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "the kind defined in the any exclude resource is invalid" )
2022-03-08 18:29:33 +05:30
}
2021-10-05 00:30:57 +05:30
}
}
for _ , value := range exclude . All {
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , * policy )
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "the kind defined in the all exclude resource is invalid" )
2022-03-08 18:29:33 +05:30
}
2021-10-05 00:30:57 +05:30
}
}
2021-10-14 12:45:32 +05:30
if ! utils . ContainsString ( rule . MatchResources . Kinds , "*" ) {
2021-11-03 11:16:55 -07:00
err := validateKinds ( rule . MatchResources . Kinds , mock , client , * policy )
2021-10-14 12:45:32 +05:30
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "match resource kind is invalid" )
2021-10-14 12:45:32 +05:30
}
2021-11-03 11:16:55 -07:00
err = validateKinds ( rule . ExcludeResources . Kinds , mock , client , * policy )
2021-10-14 12:45:32 +05:30
if err != nil {
2022-03-16 00:50:33 -04:00
return nil , errors . Wrapf ( err , "exclude resource kind is invalid" )
2021-10-14 12:45:32 +05:30
}
2021-10-05 00:30:57 +05:30
}
2021-08-21 03:58:49 +05:30
2020-08-29 06:52:22 +05:30
// Validate string values in labels
if ! isLabelAndAnnotationsString ( rule ) {
2022-03-16 00:50:33 -04:00
return nil , fmt . Errorf ( "labels and annotations supports only string values, \"use double quotes around the non string values\"" )
2020-08-29 06:52:22 +05:30
}
2020-12-30 12:10:41 +05:30
// add label to source mentioned in policy
if ! mock && rule . Generation . Clone . Name != "" {
obj , err := client . GetResource ( "" , rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name )
if err != nil {
log . Log . Error ( err , fmt . Sprintf ( "source resource %s/%s/%s not found." , rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name ) )
continue
}
updateSource := true
label := obj . GetLabels ( )
if len ( label ) == 0 {
label = make ( map [ string ] string )
2021-11-03 11:16:55 -07:00
label [ "generate.kyverno.io/clone-policy-name" ] = policy . GetName ( )
2020-12-30 12:10:41 +05:30
} else {
if label [ "generate.kyverno.io/clone-policy-name" ] != "" {
policyNames := label [ "generate.kyverno.io/clone-policy-name" ]
2021-11-03 11:16:55 -07:00
if ! strings . Contains ( policyNames , policy . GetName ( ) ) {
policyNames = policyNames + "," + policy . GetName ( )
2020-12-30 12:10:41 +05:30
label [ "generate.kyverno.io/clone-policy-name" ] = policyNames
} else {
updateSource = false
}
} else {
2021-11-03 11:16:55 -07:00
label [ "generate.kyverno.io/clone-policy-name" ] = policy . GetName ( )
2020-12-30 12:10:41 +05:30
}
}
if updateSource {
log . Log . V ( 4 ) . Info ( "updating existing clone source" )
obj . SetLabels ( label )
_ , err = client . UpdateResource ( obj . GetAPIVersion ( ) , rule . Generation . Kind , rule . Generation . Clone . Namespace , obj , false )
if err != nil {
2021-04-08 12:10:30 -07:00
log . Log . Error ( err , "failed to update source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
2020-12-30 12:10:41 +05:30
continue
}
2021-04-08 12:10:30 -07:00
log . Log . V ( 4 ) . Info ( "updated source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
2020-12-30 12:10:41 +05:30
}
}
2019-09-27 19:03:55 -07:00
}
2020-01-25 14:53:12 +05:30
2021-11-30 18:14:58 +01:00
if policy . Spec . SchemaValidation == nil || * policy . Spec . SchemaValidation {
2021-11-03 11:16:55 -07:00
if err := openAPIController . ValidatePolicyMutation ( * policy ) ; err != nil {
2022-03-16 00:50:33 -04:00
return nil , err
2021-11-03 11:16:55 -07:00
}
}
2022-03-16 00:50:33 -04:00
return nil , nil
2021-11-03 11:16:55 -07:00
}
func ValidateVariables ( p * kyverno . ClusterPolicy , backgroundMode bool ) error {
vars := hasVariables ( p )
if len ( vars ) == 0 {
return nil
}
if err := hasInvalidVariables ( p , backgroundMode ) ; err != nil {
return fmt . Errorf ( "policy contains invalid variables: %s" , err . Error ( ) )
}
if backgroundMode {
if err := containsUserVariables ( p , vars ) ; err != nil {
return fmt . Errorf ( "only select variables are allowed in background mode. Set spec.background=false to disable background mode for this policy rule: %s " , err )
}
}
return nil
}
// hasInvalidVariables - checks for unexpected variables in the policy
func hasInvalidVariables ( policy * kyverno . ClusterPolicy , background bool ) error {
2022-03-18 16:18:32 +01:00
for _ , r := range policy . GetRules ( ) {
2021-11-03 11:16:55 -07:00
ruleCopy := r . DeepCopy ( )
if err := ruleForbiddenSectionsHaveVariables ( ruleCopy ) ; err != nil {
2020-04-01 19:06:13 +05:30
return err
}
2021-11-03 11:16:55 -07:00
// skip variable checks on verifyImages.attestations, as variables in attestations are dynamic
for _ , vi := range ruleCopy . VerifyImages {
for _ , a := range vi . Attestations {
a . Conditions = nil
}
}
ctx := buildContext ( ruleCopy , background )
if _ , err := variables . SubstituteAllInRule ( log . Log , ctx , * ruleCopy ) ; ! checkNotFoundErr ( err ) {
return fmt . Errorf ( "variable substitution failed for rule %s: %s" , ruleCopy . Name , err . Error ( ) )
}
2020-03-04 19:16:26 +05:30
}
2019-09-27 19:03:55 -07:00
return nil
}
2021-11-03 11:16:55 -07:00
// for now forbidden sections are match, exclude and
func ruleForbiddenSectionsHaveVariables ( rule * kyverno . Rule ) error {
var err error
err = jsonPatchPathHasVariables ( rule . Mutation . PatchesJSON6902 )
2021-11-30 18:14:58 +01:00
if err != nil && errors . Is ( errOperationForbidden , err ) {
2021-11-03 11:16:55 -07:00
return fmt . Errorf ( "rule \"%s\" should not have variables in patchesJSON6902 path section" , rule . Name )
}
err = objectHasVariables ( rule . ExcludeResources )
if err != nil {
return fmt . Errorf ( "rule \"%s\" should not have variables in exclude section" , rule . Name )
}
err = objectHasVariables ( rule . MatchResources )
if err != nil {
return fmt . Errorf ( "rule \"%s\" should not have variables in match section" , rule . Name )
}
return nil
}
// hasVariables - check for variables in the policy
func hasVariables ( policy * kyverno . ClusterPolicy ) [ ] [ ] string {
policyRaw , _ := json . Marshal ( policy )
matches := variables . RegexVariables . FindAllStringSubmatch ( string ( policyRaw ) , - 1 )
return matches
}
func jsonPatchPathHasVariables ( patch string ) error {
jsonPatch , err := yaml . ToJSON ( [ ] byte ( patch ) )
if err != nil {
return err
}
decodedPatch , err := jsonpatch . DecodePatch ( jsonPatch )
if err != nil {
return err
}
for _ , operation := range decodedPatch {
path , err := operation . Path ( )
if err != nil {
return err
}
vars := variables . RegexVariables . FindAllString ( path , - 1 )
if len ( vars ) > 0 {
2021-11-30 18:14:58 +01:00
return errOperationForbidden
2021-11-03 11:16:55 -07:00
}
}
return nil
}
func objectHasVariables ( object interface { } ) error {
var err error
objectJSON , err := json . Marshal ( object )
if err != nil {
return err
}
if len ( common . RegexVariables . FindAllStringSubmatch ( string ( objectJSON ) , - 1 ) ) > 0 {
return fmt . Errorf ( "invalid variables" )
}
return nil
}
func buildContext ( rule * kyverno . Rule , background bool ) * context . MockContext {
re := getAllowedVariables ( background )
ctx := context . NewMockContext ( re )
addContextVariables ( rule . Context , ctx )
for _ , fe := range rule . Validation . ForEachValidation {
addContextVariables ( fe . Context , ctx )
}
for _ , fe := range rule . Mutation . ForEachMutation {
addContextVariables ( fe . Context , ctx )
}
return ctx
}
func getAllowedVariables ( background bool ) * regexp . Regexp {
if background {
return allowedVariablesBackground
}
return allowedVariables
}
func addContextVariables ( entries [ ] kyverno . ContextEntry , ctx * context . MockContext ) {
for _ , contextEntry := range entries {
2022-01-17 04:06:44 +00:00
if contextEntry . APICall != nil || contextEntry . ImageRegistry != nil {
2021-11-03 11:16:55 -07:00
ctx . AddVariable ( contextEntry . Name + "*" )
}
if contextEntry . ConfigMap != nil {
ctx . AddVariable ( contextEntry . Name + ".data.*" )
}
}
}
func checkNotFoundErr ( err error ) bool {
if err != nil {
switch err . ( type ) {
case jmespath . NotFoundError :
return true
case context . InvalidVariableErr :
return false
default :
return false
}
}
return true
}
2021-10-14 22:44:11 +05:30
func validateElementInForEach ( document apiextensions . JSON ) error {
jsonByte , err := json . Marshal ( document )
if err != nil {
return err
}
var jsonInterface interface { }
err = json . Unmarshal ( jsonByte , & jsonInterface )
if err != nil {
return err
}
_ , err = variables . ValidateElementInForEach ( log . Log , jsonInterface )
return err
}
2021-07-29 01:29:53 +05:30
func validateMatchKindHelper ( rule kyverno . Rule ) error {
if ! ruleOnlyDealsWithResourceMetaData ( rule ) {
return fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
2021-10-14 12:45:32 +05:30
" the rule does not match any kind" )
2021-07-29 01:29:53 +05:30
}
2021-11-03 11:16:55 -07:00
2021-10-12 23:29:20 +02:00
return fmt . Errorf ( "at least one element must be specified in a kind block, the kind attribute is mandatory when working with the resources element" )
2021-07-29 01:29:53 +05:30
}
2020-10-14 17:39:45 -07:00
// doMatchAndExcludeConflict checks if the resultant
2020-04-04 12:46:51 +05:30
// of match and exclude block is not an empty set
2021-06-29 11:01:22 +05:30
// returns true if it is an empty set
2020-10-14 17:39:45 -07:00
func doMatchAndExcludeConflict ( rule kyverno . Rule ) bool {
2020-03-20 20:23:34 +05:30
2021-07-29 01:29:53 +05:30
if len ( rule . ExcludeResources . All ) > 0 || len ( rule . MatchResources . All ) > 0 {
return false
}
// if both have any then no resource should be common
if len ( rule . MatchResources . Any ) > 0 && len ( rule . ExcludeResources . Any ) > 0 {
for _ , rmr := range rule . MatchResources . Any {
for _ , rer := range rule . ExcludeResources . Any {
if reflect . DeepEqual ( rmr , rer ) {
return true
}
}
}
return false
}
2020-04-04 16:18:36 +05:30
if reflect . DeepEqual ( rule . ExcludeResources , kyverno . ExcludeResources { } ) {
return false
}
2020-03-20 20:23:34 +05:30
excludeRoles := make ( map [ string ] bool )
for _ , role := range rule . ExcludeResources . UserInfo . Roles {
excludeRoles [ role ] = true
}
excludeClusterRoles := make ( map [ string ] bool )
for _ , clusterRoles := range rule . ExcludeResources . UserInfo . ClusterRoles {
excludeClusterRoles [ clusterRoles ] = true
}
excludeSubjects := make ( map [ string ] bool )
for _ , subject := range rule . ExcludeResources . UserInfo . Subjects {
subjectRaw , _ := json . Marshal ( subject )
excludeSubjects [ string ( subjectRaw ) ] = true
}
excludeKinds := make ( map [ string ] bool )
for _ , kind := range rule . ExcludeResources . ResourceDescription . Kinds {
excludeKinds [ kind ] = true
}
excludeNamespaces := make ( map [ string ] bool )
for _ , namespace := range rule . ExcludeResources . ResourceDescription . Namespaces {
excludeNamespaces [ namespace ] = true
}
2021-02-04 02:39:42 +05:30
excludeSelectorMatchExpressions := make ( map [ string ] bool )
2020-03-20 20:23:34 +05:30
if rule . ExcludeResources . ResourceDescription . Selector != nil {
for _ , matchExpression := range rule . ExcludeResources . ResourceDescription . Selector . MatchExpressions {
matchExpressionRaw , _ := json . Marshal ( matchExpression )
2021-02-04 02:39:42 +05:30
excludeSelectorMatchExpressions [ string ( matchExpressionRaw ) ] = true
}
}
excludeNamespaceSelectorMatchExpressions := make ( map [ string ] bool )
if rule . ExcludeResources . ResourceDescription . NamespaceSelector != nil {
for _ , matchExpression := range rule . ExcludeResources . ResourceDescription . NamespaceSelector . MatchExpressions {
matchExpressionRaw , _ := json . Marshal ( matchExpression )
excludeNamespaceSelectorMatchExpressions [ string ( matchExpressionRaw ) ] = true
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 12:46:51 +05:30
if len ( excludeRoles ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . UserInfo . Roles ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , role := range rule . MatchResources . UserInfo . Roles {
if ! excludeRoles [ role ] {
return false
}
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 12:46:51 +05:30
if len ( excludeClusterRoles ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . UserInfo . ClusterRoles ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , clusterRole := range rule . MatchResources . UserInfo . ClusterRoles {
if ! excludeClusterRoles [ clusterRole ] {
return false
}
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 12:46:51 +05:30
if len ( excludeSubjects ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . UserInfo . Subjects ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , subject := range rule . MatchResources . UserInfo . Subjects {
subjectRaw , _ := json . Marshal ( subject )
if ! excludeSubjects [ string ( subjectRaw ) ] {
return false
}
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 12:46:51 +05:30
if rule . ExcludeResources . ResourceDescription . Name != "" {
if ! wildcard . Match ( rule . ExcludeResources . ResourceDescription . Name , rule . MatchResources . ResourceDescription . Name ) {
return false
2020-03-20 20:23:34 +05:30
}
}
2021-06-29 11:01:22 +05:30
if len ( rule . ExcludeResources . ResourceDescription . Names ) > 0 {
excludeSlice := rule . ExcludeResources . ResourceDescription . Names
matchSlice := rule . MatchResources . ResourceDescription . Names
// if exclude block has something and match doesn't it means we
// have a non empty set
if len ( rule . MatchResources . ResourceDescription . Names ) == 0 {
return false
}
// if *any* name in match and exclude conflicts
// we want user to fix that
for _ , matchName := range matchSlice {
for _ , excludeName := range excludeSlice {
if wildcard . Match ( excludeName , matchName ) {
return true
}
}
}
return false
}
2020-04-04 14:49:50 +05:30
if len ( excludeNamespaces ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . ResourceDescription . Namespaces ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , namespace := range rule . MatchResources . ResourceDescription . Namespaces {
if ! excludeNamespaces [ namespace ] {
return false
}
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 14:49:50 +05:30
if len ( excludeKinds ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . ResourceDescription . Kinds ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , kind := range rule . MatchResources . ResourceDescription . Kinds {
if ! excludeKinds [ kind ] {
return false
}
2020-03-20 20:23:34 +05:30
}
}
if rule . MatchResources . ResourceDescription . Selector != nil && rule . ExcludeResources . ResourceDescription . Selector != nil {
2021-02-04 02:39:42 +05:30
if len ( excludeSelectorMatchExpressions ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . ResourceDescription . Selector . MatchExpressions ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for _ , matchExpression := range rule . MatchResources . ResourceDescription . Selector . MatchExpressions {
matchExpressionRaw , _ := json . Marshal ( matchExpression )
2021-02-04 02:39:42 +05:30
if ! excludeSelectorMatchExpressions [ string ( matchExpressionRaw ) ] {
2020-04-04 12:46:51 +05:30
return false
}
2020-03-20 20:23:34 +05:30
}
}
2020-04-04 14:49:50 +05:30
if len ( rule . ExcludeResources . ResourceDescription . Selector . MatchLabels ) > 0 {
2020-04-27 15:05:10 +05:30
if len ( rule . MatchResources . ResourceDescription . Selector . MatchLabels ) == 0 {
return false
}
2020-04-04 12:46:51 +05:30
for label , value := range rule . MatchResources . ResourceDescription . Selector . MatchLabels {
if rule . ExcludeResources . ResourceDescription . Selector . MatchLabels [ label ] != value {
return false
}
2020-03-20 20:23:34 +05:30
}
}
}
2021-02-04 02:39:42 +05:30
if rule . MatchResources . ResourceDescription . NamespaceSelector != nil && rule . ExcludeResources . ResourceDescription . NamespaceSelector != nil {
if len ( excludeNamespaceSelectorMatchExpressions ) > 0 {
if len ( rule . MatchResources . ResourceDescription . NamespaceSelector . MatchExpressions ) == 0 {
return false
}
for _ , matchExpression := range rule . MatchResources . ResourceDescription . NamespaceSelector . MatchExpressions {
matchExpressionRaw , _ := json . Marshal ( matchExpression )
if ! excludeNamespaceSelectorMatchExpressions [ string ( matchExpressionRaw ) ] {
return false
}
}
}
if len ( rule . ExcludeResources . ResourceDescription . NamespaceSelector . MatchLabels ) > 0 {
if len ( rule . MatchResources . ResourceDescription . NamespaceSelector . MatchLabels ) == 0 {
return false
}
for label , value := range rule . MatchResources . ResourceDescription . NamespaceSelector . MatchLabels {
if rule . ExcludeResources . ResourceDescription . NamespaceSelector . MatchLabels [ label ] != value {
return false
}
}
}
}
2020-10-07 02:57:40 +05:30
if ( rule . MatchResources . ResourceDescription . Selector == nil && rule . ExcludeResources . ResourceDescription . Selector != nil ) ||
( rule . MatchResources . ResourceDescription . Selector != nil && rule . ExcludeResources . ResourceDescription . Selector == nil ) {
return false
}
2021-02-04 02:39:42 +05:30
if ( rule . MatchResources . ResourceDescription . NamespaceSelector == nil && rule . ExcludeResources . ResourceDescription . NamespaceSelector != nil ) ||
( rule . MatchResources . ResourceDescription . NamespaceSelector != nil && rule . ExcludeResources . ResourceDescription . NamespaceSelector == nil ) {
return false
}
2020-11-25 11:50:53 +05:30
if rule . MatchResources . Annotations != nil && rule . ExcludeResources . Annotations != nil {
if ! ( reflect . DeepEqual ( rule . MatchResources . Annotations , rule . ExcludeResources . Annotations ) ) {
return false
}
}
if ( rule . MatchResources . Annotations == nil && rule . ExcludeResources . Annotations != nil ) ||
( rule . MatchResources . Annotations != nil && rule . ExcludeResources . Annotations == nil ) {
return false
}
2020-04-04 12:46:51 +05:30
return true
2020-03-20 20:23:34 +05:30
}
2020-08-29 06:52:22 +05:30
// isLabelAndAnnotationsString :- Validate if labels and annotations contains only string values
func isLabelAndAnnotationsString ( rule kyverno . Rule ) bool {
// checkMetadata - Verify if the labels and annotations contains string value inside metadata
checkMetadata := func ( patternMap map [ string ] interface { } ) bool {
for k := range patternMap {
if k == "metadata" {
metaKey , ok := patternMap [ k ] . ( map [ string ] interface { } )
if ok {
// range over metadata
for mk := range metaKey {
if mk == "labels" {
labelKey , ok := metaKey [ mk ] . ( map [ string ] interface { } )
if ok {
// range over labels
for _ , val := range labelKey {
if reflect . TypeOf ( val ) . String ( ) != "string" {
return false
}
}
}
} else if mk == "annotations" {
annotationKey , ok := metaKey [ mk ] . ( map [ string ] interface { } )
if ok {
// range over annotations
for _ , val := range annotationKey {
if reflect . TypeOf ( val ) . String ( ) != "string" {
return false
}
}
}
}
}
}
}
}
return true
}
2022-03-06 20:07:51 +01:00
patternMap , ok := rule . Validation . GetPattern ( ) . ( map [ string ] interface { } )
2020-08-29 06:52:22 +05:30
if ok {
return checkMetadata ( patternMap )
2022-03-06 20:07:51 +01:00
} else if rule . Validation . GetAnyPattern ( ) != nil {
2020-11-13 16:25:51 -08:00
anyPatterns , err := rule . Validation . DeserializeAnyPattern ( )
if err != nil {
2020-12-09 15:37:45 -08:00
log . Log . Error ( err , "failed to deserialize anyPattern, expect type array" )
2020-11-13 16:25:51 -08:00
return false
}
2020-08-29 06:52:22 +05:30
for _ , pattern := range anyPatterns {
patternMap , ok := pattern . ( map [ string ] interface { } )
if ok {
ret := checkMetadata ( patternMap )
if ret == false {
return ret
}
}
}
}
return true
}
2020-02-26 16:08:56 +05:30
func ruleOnlyDealsWithResourceMetaData ( rule kyverno . Rule ) bool {
2022-03-06 20:07:51 +01:00
patches , _ := rule . Mutation . GetPatchStrategicMerge ( ) . ( map [ string ] interface { } )
2022-01-04 17:36:33 -08:00
for k := range patches {
2020-02-26 16:08:56 +05:30
if k != "metadata" {
return false
}
}
2022-01-04 17:36:33 -08:00
if rule . Mutation . PatchesJSON6902 != "" {
bytes := [ ] byte ( rule . Mutation . PatchesJSON6902 )
jp , _ := jsonpatch . DecodePatch ( bytes )
for _ , o := range jp {
path , _ := o . Path ( )
if ! strings . HasPrefix ( path , "/metadata" ) {
return false
}
2021-10-14 12:45:32 +05:30
}
}
2022-03-06 20:07:51 +01:00
patternMap , _ := rule . Validation . GetPattern ( ) . ( map [ string ] interface { } )
2020-02-26 16:08:56 +05:30
for k := range patternMap {
if k != "metadata" {
return false
}
}
2020-11-13 16:25:51 -08:00
anyPatterns , err := rule . Validation . DeserializeAnyPattern ( )
if err != nil {
2020-12-09 15:37:45 -08:00
log . Log . Error ( err , "failed to deserialize anyPattern, expect type array" )
2020-11-13 16:25:51 -08:00
return false
}
for _ , pattern := range anyPatterns {
2020-02-26 16:08:56 +05:30
patternMap , _ := pattern . ( map [ string ] interface { } )
for k := range patternMap {
if k != "metadata" {
return false
}
}
}
return true
}
2022-03-16 17:15:46 +01:00
func validateResources ( path * field . Path , rule kyverno . Rule ) ( string , error ) {
2019-12-04 18:50:51 -08:00
// validate userInfo in match and exclude
2022-03-16 17:15:46 +01:00
if errs := rule . MatchResources . UserInfo . Validate ( path . Child ( "match" ) ) ; len ( errs ) != 0 {
return "match" , errs . ToAggregate ( )
}
if errs := rule . ExcludeResources . UserInfo . Validate ( path . Child ( "exclude" ) ) ; len ( errs ) != 0 {
return "exclude" , errs . ToAggregate ( )
2019-12-04 18:50:51 -08:00
}
2021-07-29 01:29:53 +05:30
if ( len ( rule . MatchResources . Any ) > 0 || len ( rule . MatchResources . All ) > 0 ) && ! reflect . DeepEqual ( rule . MatchResources . ResourceDescription , kyverno . ResourceDescription { } ) {
2021-10-12 23:29:20 +02:00
return "match." , fmt . Errorf ( "can't specify any/all together with match resources" )
2021-07-29 01:29:53 +05:30
}
if ( len ( rule . ExcludeResources . Any ) > 0 || len ( rule . ExcludeResources . All ) > 0 ) && ! reflect . DeepEqual ( rule . ExcludeResources . ResourceDescription , kyverno . ResourceDescription { } ) {
2021-10-12 23:29:20 +02:00
return "exclude." , fmt . Errorf ( "can't specify any/all together with exclude resources" )
2019-09-27 16:31:27 -07:00
}
2021-07-29 01:29:53 +05:30
if len ( rule . MatchResources . Any ) > 0 && len ( rule . MatchResources . All ) > 0 {
2021-10-12 23:29:20 +02:00
return "match." , fmt . Errorf ( "can't specify any and all together" )
2021-07-29 01:29:53 +05:30
}
if len ( rule . ExcludeResources . Any ) > 0 && len ( rule . ExcludeResources . All ) > 0 {
2021-10-12 23:29:20 +02:00
return "match." , fmt . Errorf ( "can't specify any and all together" )
2021-07-29 01:29:53 +05:30
}
if len ( rule . MatchResources . Any ) > 0 {
for _ , rmr := range rule . MatchResources . Any {
// matched resources
if path , err := validateMatchedResourceDescription ( rmr . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "match.resources.%s" , path ) , err
}
}
} else if len ( rule . MatchResources . All ) > 0 {
for _ , rmr := range rule . MatchResources . All {
// matched resources
if path , err := validateMatchedResourceDescription ( rmr . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "match.resources.%s" , path ) , err
}
}
} else {
// matched resources
if path , err := validateMatchedResourceDescription ( rule . MatchResources . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "match.resources.%s" , path ) , err
}
}
if len ( rule . ExcludeResources . Any ) > 0 {
for _ , rmr := range rule . ExcludeResources . Any {
// exclude resources
if path , err := validateExcludeResourceDescription ( rmr . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "exclude.resources.%s" , path ) , err
}
}
} else if len ( rule . ExcludeResources . All ) > 0 {
for _ , rmr := range rule . ExcludeResources . All {
// exclude resources
if path , err := validateExcludeResourceDescription ( rmr . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "exclude.resources.%s" , path ) , err
}
}
} else {
// exclude resources
if path , err := validateExcludeResourceDescription ( rule . ExcludeResources . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "exclude.resources.%s" , path ) , err
}
2021-02-02 02:57:16 +05:30
}
2021-03-02 10:01:06 +05:30
//validating the values present under validate.preconditions, if they exist
2022-03-06 20:07:51 +01:00
if target := rule . GetAnyAllConditions ( ) ; target != nil {
if path , err := validateConditions ( target , "preconditions" ) ; err != nil {
2021-02-02 02:57:16 +05:30
return fmt . Sprintf ( "validate.%s" , path ) , err
}
}
2021-03-02 10:01:06 +05:30
//validating the values present under validate.conditions, if they exist
2022-03-06 20:07:51 +01:00
if rule . Validation . Deny != nil {
if target := rule . Validation . Deny . GetAnyAllConditions ( ) ; target != nil {
if path , err := validateConditions ( target , "conditions" ) ; err != nil {
return fmt . Sprintf ( "validate.deny.%s" , path ) , err
}
2021-02-02 02:57:16 +05:30
}
}
return "" , nil
}
// validateConditions validates all the 'conditions' or 'preconditions' of a rule depending on the corresponding 'condition.key'.
// As of now, it is validating the 'value' field whether it contains the only allowed set of values or not when 'condition.key' is {{request.operation}}
2021-03-02 10:01:06 +05:30
// this is backwards compatible i.e. conditions can be provided in the old manner as well i.e. without 'any' or 'all'
func validateConditions ( conditions apiextensions . JSON , schemaKey string ) ( string , error ) {
// Conditions can only exist under some specific keys of the policy schema
allowedSchemaKeys := map [ string ] bool {
"preconditions" : true ,
"conditions" : true ,
}
if ! allowedSchemaKeys [ schemaKey ] {
return fmt . Sprintf ( schemaKey ) , fmt . Errorf ( "wrong schema key found for validating the conditions. Conditions can only occur under one of ['preconditions', 'conditions'] keys in the policy schema" )
}
// conditions are currently in the form of []interface{}
kyvernoConditions , err := utils . ApiextensionsJsonToKyvernoConditions ( conditions )
if err != nil {
return fmt . Sprintf ( "%s" , schemaKey ) , err
}
switch typedConditions := kyvernoConditions . ( type ) {
case kyverno . AnyAllConditions :
// validating the conditions under 'any', if there are any
if ! reflect . DeepEqual ( typedConditions , kyverno . AnyAllConditions { } ) && typedConditions . AnyConditions != nil {
for i , condition := range typedConditions . AnyConditions {
if path , err := validateConditionValues ( condition ) ; err != nil {
return fmt . Sprintf ( "%s.any[%d].%s" , schemaKey , i , path ) , err
}
}
}
// validating the conditions under 'all', if there are any
if ! reflect . DeepEqual ( typedConditions , kyverno . AnyAllConditions { } ) && typedConditions . AllConditions != nil {
for i , condition := range typedConditions . AllConditions {
if path , err := validateConditionValues ( condition ) ; err != nil {
return fmt . Sprintf ( "%s.all[%d].%s" , schemaKey , i , path ) , err
}
}
}
case [ ] kyverno . Condition : // backwards compatibility
for i , condition := range typedConditions {
if path , err := validateConditionValues ( condition ) ; err != nil {
return fmt . Sprintf ( "%s[%d].%s" , schemaKey , i , path ) , err
}
2021-02-02 02:57:16 +05:30
}
}
return "" , nil
}
// validateConditionValues validates whether all the values under the 'value' field of a 'conditions' field
// are apt with respect to the provided 'condition.key'
func validateConditionValues ( c kyverno . Condition ) ( string , error ) {
2022-03-06 20:07:51 +01:00
k := c . GetKey ( )
v := c . GetValue ( )
if k == nil || v == nil || c . Operator == "" {
2021-11-11 22:27:18 +05:30
return "" , fmt . Errorf ( "entered value of `key`, `value` or `operator` is missing or misspelled" )
}
2022-03-06 20:07:51 +01:00
switch reflect . TypeOf ( k ) . Kind ( ) {
2022-02-03 20:16:58 +05:30
case reflect . String :
value , err := validateValuesKeyRequest ( c )
return value , err
default :
return "" , nil
}
}
func validateValuesKeyRequest ( c kyverno . Condition ) ( string , error ) {
2022-03-06 20:07:51 +01:00
k := c . GetKey ( )
switch strings . ReplaceAll ( k . ( string ) , " " , "" ) {
2021-02-02 02:57:16 +05:30
case "{{request.operation}}" :
return validateConditionValuesKeyRequestOperation ( c )
default :
return "" , nil
}
}
// validateConditionValuesKeyRequestOperation validates whether all the values under the 'value' field of a 'conditions' field
// are one of ["CREATE", "UPDATE", "DELETE", "CONNECT"] when 'condition.key' is {{request.operation}}
func validateConditionValuesKeyRequestOperation ( c kyverno . Condition ) ( string , error ) {
valuesAllowed := map [ string ] bool {
"CREATE" : true ,
"UPDATE" : true ,
"DELETE" : true ,
"CONNECT" : true ,
}
2022-03-06 20:07:51 +01:00
v := c . GetValue ( )
switch reflect . TypeOf ( v ) . Kind ( ) {
2021-02-02 02:57:16 +05:30
case reflect . String :
2022-03-06 20:07:51 +01:00
valueStr := v . ( string )
2021-02-17 02:36:07 +05:30
// allow templatized values like {{ config-map.data.sample-key }}
// because they might be actually pointing to a rightful value in the provided config-map
if len ( valueStr ) >= 4 && valueStr [ : 2 ] == "{{" && valueStr [ len ( valueStr ) - 2 : ] == "}}" {
return "" , nil
}
if ! valuesAllowed [ valueStr ] {
2022-03-06 20:07:51 +01:00
return fmt . Sprintf ( "value: %s" , v . ( string ) ) , fmt . Errorf ( "unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]" , v . ( string ) )
2021-02-02 02:57:16 +05:30
}
case reflect . Slice :
2022-03-06 20:07:51 +01:00
values := reflect . ValueOf ( v )
2021-02-02 02:57:16 +05:30
for i := 0 ; i < values . Len ( ) ; i ++ {
value := values . Index ( i ) . Interface ( ) . ( string )
if ! valuesAllowed [ value ] {
return fmt . Sprintf ( "value[%d]" , i ) , fmt . Errorf ( "unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]" , value )
}
}
default :
2022-03-06 20:07:51 +01:00
return "value" , fmt . Errorf ( "'value' field found to be of the type %v. The provided value/values are expected to be either in the form of a string or list" , reflect . TypeOf ( v ) . Kind ( ) )
2019-09-27 16:31:27 -07:00
}
2019-10-21 14:22:31 -07:00
return "" , nil
}
2019-09-27 16:31:27 -07:00
2020-11-09 11:26:12 -08:00
func validateRuleContext ( rule kyverno . Rule ) error {
2020-10-14 17:39:45 -07:00
if rule . Context == nil || len ( rule . Context ) == 0 {
return nil
}
2021-08-27 13:50:30 +05:30
contextNames := make ( [ ] string , 0 )
2020-10-14 17:39:45 -07:00
for _ , entry := range rule . Context {
2020-11-09 11:26:12 -08:00
if entry . Name == "" {
2020-10-14 17:39:45 -07:00
return fmt . Errorf ( "a name is required for context entries" )
}
2021-08-27 13:50:30 +05:30
contextNames = append ( contextNames , entry . Name )
2020-10-19 12:36:55 -07:00
2021-02-01 12:59:13 -08:00
var err error
2020-10-14 17:39:45 -07:00
if entry . ConfigMap != nil {
2021-02-01 12:59:13 -08:00
err = validateConfigMap ( entry )
} else if entry . APICall != nil {
err = validateAPICall ( entry )
2022-01-17 04:06:44 +00:00
} else if entry . ImageRegistry != nil {
err = validateImageRegistry ( entry )
2021-02-01 12:59:13 -08:00
} else {
2022-01-17 04:06:44 +00:00
return fmt . Errorf ( "a configMap or apiCall or imageRegistry is required for context entries" )
2021-02-01 12:59:13 -08:00
}
2020-10-14 17:39:45 -07:00
2021-02-01 12:59:13 -08:00
if err != nil {
return err
}
}
2021-08-27 13:50:30 +05:30
ruleBytes , _ := json . Marshal ( rule )
for _ , contextName := range contextNames {
2022-01-31 18:23:59 +00:00
contextRegex , err := regexp . Compile ( fmt . Sprintf ( ` {{ . * \ b % s \ b . * }} ` , contextName ) )
if err != nil {
return fmt . Errorf ( "unable to validate context variable `%s`, %w" , contextName , err )
}
if ! contextRegex . Match ( ruleBytes ) {
2021-08-27 13:50:30 +05:30
return fmt . Errorf ( "context variable `%s` is not used in the policy" , contextName )
}
}
2021-02-01 12:59:13 -08:00
return nil
}
func validateConfigMap ( entry kyverno . ContextEntry ) error {
if entry . ConfigMap == nil {
return fmt . Errorf ( "configMap is empty" )
}
if entry . APICall != nil {
return fmt . Errorf ( "both configMap and apiCall are not allowed in a context entry" )
}
2022-01-17 04:06:44 +00:00
if entry . ImageRegistry != nil {
return fmt . Errorf ( "both imageRegistry and configMap are not allowed in a context entry" )
}
2021-02-01 12:59:13 -08:00
if entry . ConfigMap . Name == "" {
return fmt . Errorf ( "a name is required for configMap context entry" )
}
if entry . ConfigMap . Namespace == "" {
return fmt . Errorf ( "a namespace is required for configMap context entry" )
}
return nil
}
func validateAPICall ( entry kyverno . ContextEntry ) error {
if entry . APICall == nil {
return fmt . Errorf ( "apiCall is empty" )
}
if entry . ConfigMap != nil {
return fmt . Errorf ( "both configMap and apiCall are not allowed in a context entry" )
}
2022-01-17 04:06:44 +00:00
if entry . ImageRegistry != nil {
return fmt . Errorf ( "both imageRegistry and apiCall are not allowed in a context entry" )
}
2021-03-19 20:07:54 +01:00
// Replace all variables to prevent validation failing on variable keys.
2021-03-23 18:17:47 +01:00
urlPath := variables . ReplaceAllVars ( entry . APICall . URLPath , func ( s string ) string { return "kyvernoapicallvariable" } )
2021-03-19 20:07:54 +01:00
if _ , err := engine . NewAPIPath ( urlPath ) ; err != nil {
2021-02-01 12:59:13 -08:00
return err
}
2021-05-04 18:28:30 +02:00
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
// will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables . ReplaceAllVars ( entry . APICall . JMESPath , func ( s string ) string { return "kyvernojmespathvariable" } )
if ! strings . Contains ( jmesPath , "kyvernojmespathvariable" ) && entry . APICall . JMESPath != "" {
2021-02-01 12:59:13 -08:00
if _ , err := jmespath . NewParser ( ) . Parse ( entry . APICall . JMESPath ) ; err != nil {
return fmt . Errorf ( "failed to parse JMESPath %s: %v" , entry . APICall . JMESPath , err )
2020-10-14 17:39:45 -07:00
}
}
return nil
}
2022-01-17 04:06:44 +00:00
func validateImageRegistry ( entry kyverno . ContextEntry ) error {
if entry . ImageRegistry == nil {
return fmt . Errorf ( "imageRegistry is empty" )
}
if entry . ConfigMap != nil {
return fmt . Errorf ( "both configMap and imageRegistry are not allowed in a context entry" )
}
if entry . APICall != nil {
return fmt . Errorf ( "both configMap and apiCall are not allowed in a context entry" )
}
if entry . ImageRegistry . Reference == "" {
return fmt . Errorf ( "a ref is required for imageRegistry context entry" )
}
// Replace all variables to prevent validation failing on variable keys.
ref := variables . ReplaceAllVars ( entry . ImageRegistry . Reference , func ( s string ) string { return "kyvernoimageref" } )
// it's no use validating a refernce that contains a variable
if ! strings . Contains ( ref , "kyvernoimageref" ) {
_ , err := reference . Parse ( ref )
if err != nil {
return errors . Wrapf ( err , "bad image: %s" , ref )
}
}
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
// will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables . ReplaceAllVars ( entry . ImageRegistry . JMESPath , func ( s string ) string { return "kyvernojmespathvariable" } )
if ! strings . Contains ( jmesPath , "kyvernojmespathvariable" ) && entry . ImageRegistry . JMESPath != "" {
if _ , err := jmespath . NewParser ( ) . Parse ( entry . ImageRegistry . JMESPath ) ; err != nil {
return fmt . Errorf ( "failed to parse JMESPath %s: %v" , entry . ImageRegistry . JMESPath , err )
}
}
return nil
}
2020-12-09 15:37:45 -08:00
// validateResourceDescription checks if all necessary fields are present and have values. Also checks a Selector.
2019-09-27 16:31:27 -07:00
// field type is checked through openapi
// Returns error if
2019-10-01 15:01:24 -07:00
// - kinds is empty array in matched resource block, i.e. kinds: []
2019-09-27 16:31:27 -07:00
// - selector is invalid
2019-10-21 14:22:31 -07:00
func validateMatchedResourceDescription ( rd kyverno . ResourceDescription ) ( string , error ) {
2019-10-18 17:45:24 -07:00
if reflect . DeepEqual ( rd , kyverno . ResourceDescription { } ) {
2019-10-21 14:22:31 -07:00
return "" , fmt . Errorf ( "match resources not specified" )
2019-09-27 16:31:27 -07:00
}
2021-06-29 11:01:22 +05:30
if rd . Name != "" && len ( rd . Names ) > 0 {
return "" , fmt . Errorf ( "both name and names can not be specified together" )
}
2019-10-21 14:22:31 -07:00
if err := validateResourceDescription ( rd ) ; err != nil {
return "match" , err
}
return "" , nil
2019-12-04 18:50:51 -08:00
}
2019-10-21 14:22:31 -07:00
func validateExcludeResourceDescription ( rd kyverno . ResourceDescription ) ( string , error ) {
if reflect . DeepEqual ( rd , kyverno . ResourceDescription { } ) {
// exclude is not mandatory
return "" , nil
}
2021-06-29 11:01:22 +05:30
if rd . Name != "" && len ( rd . Names ) > 0 {
return "" , fmt . Errorf ( "both name and names can not be specified together" )
}
2019-10-21 14:22:31 -07:00
if err := validateResourceDescription ( rd ) ; err != nil {
return "exclude" , err
}
return "" , nil
2019-10-03 18:19:47 -07:00
}
// validateResourceDescription returns error if selector is invalid
// field type is checked through openapi
2019-10-18 17:45:24 -07:00
func validateResourceDescription ( rd kyverno . ResourceDescription ) error {
2019-09-27 16:31:27 -07:00
if rd . Selector != nil {
2021-12-28 12:29:38 +05:30
if labelSelectorContainsWildcard ( rd . Selector ) {
return nil
}
2019-09-27 16:31:27 -07:00
selector , err := metav1 . LabelSelectorAsSelector ( rd . Selector )
if err != nil {
return err
}
requirements , _ := selector . Requirements ( )
if len ( requirements ) == 0 {
return errors . New ( "the requirements are not specified in selector" )
}
}
return nil
}
2020-08-19 21:37:23 +05:30
2021-12-28 12:29:38 +05:30
func labelSelectorContainsWildcard ( v * metav1 . LabelSelector ) bool {
for k , v := range v . MatchLabels {
if isWildcardPresent ( k ) {
return true
}
if isWildcardPresent ( v ) {
return true
}
}
return false
}
func isWildcardPresent ( v string ) bool {
if strings . Contains ( v , "*" ) || strings . Contains ( v , "?" ) {
return true
}
return false
}
2020-08-19 21:37:23 +05:30
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
// Match and Exclude block
2021-10-22 01:48:22 +05:30
func checkClusterResourceInMatchAndExclude ( rule kyverno . Rule , clusterResources [ ] string , mock bool , res [ ] * metav1 . APIResourceList ) error {
2020-08-19 21:37:23 +05:30
// Contains Namespaces in Match->ResourceDescription
if len ( rule . MatchResources . ResourceDescription . Namespaces ) > 0 {
return fmt . Errorf ( "namespaced cluster policy : field namespaces not allowed in match.resources" )
}
// Contains Namespaces in Exclude->ResourceDescription
if len ( rule . ExcludeResources . ResourceDescription . Namespaces ) > 0 {
return fmt . Errorf ( "namespaced cluster policy : field namespaces not allowed in exclude.resources" )
}
2021-10-22 01:48:22 +05:30
if ! mock {
// Contains "Cluster Wide Resources" in Match->ResourceDescription->Kinds
for _ , kind := range rule . MatchResources . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in match.resources.kinds" , kind )
}
2020-08-19 21:37:23 +05:30
}
}
2021-10-22 01:48:22 +05:30
// Contains "Cluster Wide Resources" in Match->All->ResourceFilter->ResourceDescription->Kinds
for _ , allResourceFilter := range rule . MatchResources . All {
fmt . Println ( allResourceFilter . ResourceDescription )
for _ , kind := range allResourceFilter . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in match.resources.kinds" , kind )
}
}
}
}
// Contains "Cluster Wide Resources" in Match->Any->ResourceFilter->ResourceDescription->Kinds
for _ , allResourceFilter := range rule . MatchResources . Any {
fmt . Println ( allResourceFilter . ResourceDescription )
for _ , kind := range allResourceFilter . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in match.resources.kinds" , kind )
}
}
}
}
// Contains "Cluster Wide Resources" in Exclude->ResourceDescription->Kinds
for _ , kind := range rule . ExcludeResources . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in exclude.resources.kinds" , kind )
}
}
}
// Contains "Cluster Wide Resources" in Exclude->All->ResourceFilter->ResourceDescription->Kinds
for _ , allResourceFilter := range rule . ExcludeResources . All {
fmt . Println ( allResourceFilter . ResourceDescription )
for _ , kind := range allResourceFilter . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in match.resources.kinds" , kind )
}
}
}
}
// Contains "Cluster Wide Resources" in Exclude->Any->ResourceFilter->ResourceDescription->Kinds
for _ , allResourceFilter := range rule . ExcludeResources . Any {
fmt . Println ( allResourceFilter . ResourceDescription )
for _ , kind := range allResourceFilter . ResourceDescription . Kinds {
for _ , k := range clusterResources {
if kind == k {
return fmt . Errorf ( "namespaced policy : cluster-wide resource '%s' not allowed in match.resources.kinds" , kind )
}
}
}
}
// Check for generate policy
// - if resource to be generated is namespaced resource then the namespace field
// should be mentioned
// - if resource to be generated is non namespaced resource then the namespace field
// should not be mentioned
if rule . HasGenerate ( ) {
generateResourceKind := rule . Generation . Kind
2022-01-20 15:18:52 +08:00
generateResourceAPIVersion := rule . Generation . APIVersion
2021-10-22 01:48:22 +05:30
for _ , resList := range res {
for _ , r := range resList . APIResources {
2022-01-20 15:18:52 +08:00
if r . Kind == generateResourceKind && ( len ( generateResourceAPIVersion ) == 0 || r . Version == generateResourceAPIVersion ) {
2021-10-22 01:48:22 +05:30
if r . Namespaced {
if rule . Generation . Namespace == "" {
return fmt . Errorf ( "path: spec.rules[%v]: please mention the namespace to generate a namespaced resource" , rule . Name )
}
} else {
if rule . Generation . Namespace != "" {
return fmt . Errorf ( "path: spec.rules[%v]: do not mention the namespace to generate a non namespaced resource" , rule . Name )
}
}
}
}
2020-08-19 21:37:23 +05:30
}
}
}
return nil
}
2020-12-09 15:37:45 -08:00
// jsonPatchOnPod checks if a rule applies JSON patches to Pod
func jsonPatchOnPod ( rule kyverno . Rule ) bool {
if ! rule . HasMutate ( ) {
return false
}
2020-12-15 15:21:39 -08:00
if utils . ContainsString ( rule . MatchResources . Kinds , "Pod" ) && rule . Mutation . PatchesJSON6902 != "" {
2020-12-09 15:37:45 -08:00
return true
}
return false
}
2021-10-05 00:30:57 +05:30
2022-03-16 00:50:33 -04:00
func podControllerAutoGenExclusion ( policy * kyverno . ClusterPolicy ) bool {
annotations := policy . GetAnnotations ( )
val , ok := annotations [ "pod-policies.kyverno.io/autogen-controllers" ]
reorderVal := strings . Split ( strings . ToLower ( val ) , "," )
sort . Slice ( reorderVal , func ( i , j int ) bool { return reorderVal [ i ] < reorderVal [ j ] } )
if ok && strings . ToLower ( val ) == "none" || reflect . DeepEqual ( reorderVal , [ ] string { "cronjob" , "daemonset" , "deployment" , "job" , "statefulset" } ) == false {
return true
}
return false
}
2022-02-23 22:27:18 +05:30
// validateKinds verifies if an API resource that matches 'kind' is valid kind
// and found in the cache, returns error if not found
2021-10-05 00:30:57 +05:30
func validateKinds ( kinds [ ] string , mock bool , client * dclient . Client , p kyverno . ClusterPolicy ) error {
for _ , kind := range kinds {
2022-02-23 22:27:18 +05:30
gv , k := comn . GetKindFromGVK ( kind )
2021-10-05 00:30:57 +05:30
if k == p . Kind {
return fmt . Errorf ( "kind and match resource kind should not be the same" )
}
2022-02-23 22:27:18 +05:30
2022-03-11 15:09:32 +05:30
if ! mock && ! utils . SkipSubResources ( k ) {
2022-02-23 22:27:18 +05:30
_ , _ , err := client . DiscoveryClient . FindResource ( gv , k )
if err != nil {
return fmt . Errorf ( "unable to convert GVK to GVR, %s, err: %s" , kinds , err )
}
}
2021-10-05 00:30:57 +05:30
}
return nil
}