2019-10-18 17:45:24 -07:00
package policy
2019-09-27 16:31:27 -07:00
import (
2022-09-30 13:56:47 +02:00
"context"
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-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"
2022-05-24 18:15:23 +05:30
"github.com/jmoiron/jsonq"
2022-05-17 13:12:43 +02:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2022-04-14 17:50:18 +05:30
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
2022-04-01 10:34:25 +02:00
"github.com/kyverno/kyverno/pkg/autogen"
2022-08-31 14:03:47 +08:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2022-10-12 18:54:16 +02:00
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
2022-09-30 13:56:47 +02:00
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
2021-03-19 20:07:54 +01:00
"github.com/kyverno/kyverno/pkg/engine/variables"
2022-10-03 12:55:39 +02:00
"github.com/kyverno/kyverno/pkg/logging"
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"
2022-04-01 10:34:25 +02:00
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
2021-10-29 18:13:20 +02:00
"github.com/pkg/errors"
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-11-12 01:00:54 +05:30
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2022-03-21 12:51:12 +01:00
"k8s.io/apimachinery/pkg/util/sets"
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"
2022-07-01 08:30:05 +05:30
"k8s.io/client-go/discovery"
2019-09-27 16:31:27 -07:00
)
2022-05-24 20:19:36 +08:00
var allowedVariables = regexp . MustCompile ( ` request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images\.|target\.|([a-z_0-9]+\()[^ { }] ` )
2021-11-03 11:16:55 -07:00
2022-05-24 20:19:36 +08:00
var allowedVariablesBackground = regexp . MustCompile ( ` request\.|element|elementIndex|@|images\.|target\.|([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
}
2022-10-17 20:55:03 +05:30
func validateJSONPatch ( patch string , ruleIdx int ) error {
patch = variables . ReplaceAllVars ( patch , func ( s string ) string { return "kyvernojsonpatchvariable" } )
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 {
op := operation . Kind ( )
if op != "add" && op != "remove" && op != "replace" {
return fmt . Errorf ( "Unexpected kind: spec.rules[%d]: %s" , ruleIdx , op )
}
v , _ := operation . ValueInterface ( )
if v != nil {
vs := fmt . Sprintf ( "%v" , v )
if strings . ContainsAny ( vs , ` " ` ) || strings . ContainsAny ( vs , ` ' ` ) {
return fmt . Errorf ( "missing quote around value: spec.rules[%d]: %s" , ruleIdx , vs )
}
}
}
return nil
}
2022-11-07 23:16:53 +01:00
func checkValidationFailureAction ( spec * kyvernov1 . Spec ) [ ] string {
msg := "Validation failure actions enforce/audit are deprecated, use Enforce/Audit instead."
if spec . ValidationFailureAction == "enforce" || spec . ValidationFailureAction == "audit" {
return [ ] string { msg }
}
for _ , override := range spec . ValidationFailureActionOverrides {
if override . Action == "enforce" || override . Action == "audit" {
return [ ] string { msg }
}
}
return nil
}
2021-11-04 23:26:22 -07:00
// Validate checks the policy and rules declarations for required configurations
2022-11-03 09:05:23 +00:00
func Validate ( policy kyvernov1 . PolicyInterface , client dclient . Interface , mock bool , openApiManager openapi . Manager ) ( [ ] string , error ) {
var warnings [ ] string
2022-03-23 08:33:15 +01:00
namespaced := policy . IsNamespaced ( )
2022-03-31 08:44:00 +02:00
spec := policy . GetSpec ( )
2022-04-04 22:16:45 +02:00
background := spec . BackgroundProcessingEnabled ( )
2022-05-06 13:46:36 +08:00
onPolicyUpdate := spec . GetMutateExistingOnPolicyUpdate ( )
2022-09-12 13:44:28 +05:30
if ! mock {
2022-10-12 18:54:16 +02:00
openapicontroller . NewController ( client , openApiManager ) . CheckSync ( context . TODO ( ) )
2022-09-12 13:44:28 +05:30
}
2021-11-03 11:16:55 -07:00
2022-11-07 23:16:53 +01:00
warnings = append ( warnings , checkValidationFailureAction ( spec ) ... )
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
err := ValidateVariables ( policy , background )
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , err
2021-10-22 01:48:22 +05:30
}
2022-05-05 18:34:49 +05:30
if onPolicyUpdate {
err := ValidateOnPolicyUpdate ( policy , onPolicyUpdate )
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , err
2022-05-05 18:34:49 +05:30
}
}
2021-10-22 01:48:22 +05:30
var res [ ] * metav1 . APIResourceList
2022-03-21 12:51:12 +01:00
clusterResources := sets . NewString ( )
2021-11-03 11:16:55 -07:00
if ! mock && namespaced {
2022-03-21 09:53:12 +01:00
// Get all the cluster type kind supported by cluster
2022-11-17 13:13:51 +05:30
res , err = discovery . ServerPreferredResources ( client . Discovery ( ) . DiscoveryInterface ( ) )
2021-10-22 01:48:22 +05:30
if err != nil {
2022-11-07 18:54:44 +05:30
if discovery . IsGroupDiscoveryFailedError ( err ) {
err := err . ( * discovery . ErrGroupDiscoveryFailed )
for gv , err := range err . Groups {
2022-11-11 07:46:27 +01:00
logging . Error ( err , "failed to list api resources" , "group" , gv )
2022-11-07 18:54:44 +05:30
}
} else {
return warnings , err
}
2021-10-22 01:48:22 +05:30
}
for _ , resList := range res {
for _ , r := range resList . APIResources {
if ! r . Namespaced {
2022-03-21 12:51:12 +01:00
clusterResources . Insert ( r . Kind )
2021-10-22 01:48:22 +05:30
}
}
}
2022-03-21 12:51:12 +01:00
}
2021-10-22 01:48:22 +05:30
2022-03-23 08:33:15 +01:00
if errs := policy . Validate ( clusterResources ) ; len ( errs ) != 0 {
2022-11-03 09:05:23 +00:00
return warnings , errs . ToAggregate ( )
2021-10-22 01:48:22 +05:30
}
2022-10-06 11:46:12 +05:30
if ! namespaced {
err := validateNamespaces ( spec , specPath . Child ( "validationFailureActionOverrides" ) )
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , err
2022-10-06 11:46:12 +05:30
}
}
2022-03-28 16:01:27 +02:00
rules := autogen . ComputeRules ( policy )
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 )
2022-05-17 08:19:03 +02:00
// check for forward slash
2021-09-06 02:52:51 -07:00
if err := validateJSONPatchPathForForwardSlash ( rule . Mutation . PatchesJSON6902 ) ; err != nil {
2022-11-03 09:05:23 +00:00
return warnings , fmt . Errorf ( "path must begin with a forward slash: spec.rules[%d]: %s" , i , err )
2021-09-06 02:52:51 -07:00
}
2022-10-17 20:55:03 +05:30
if err := validateJSONPatch ( rule . Mutation . PatchesJSON6902 , i ) ; err != nil {
2022-11-03 09:05:23 +00:00
return warnings , fmt . Errorf ( "%s" , err )
2022-10-17 20:55:03 +05:30
}
2021-09-06 02:52:51 -07:00
2020-12-09 15:37:45 -08:00
if jsonPatchOnPod ( rule ) {
2022-04-13 01:11:37 -07:00
msg := "Pods managed by workload controllers should not be directly mutated using policies. " +
"Use the autogen feature or write policies that match Pod controllers."
2022-10-03 12:55:39 +02:00
logging . V ( 1 ) . Info ( msg )
2022-11-03 09:05:23 +00:00
warnings = append ( warnings , msg )
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-11-03 09:05:23 +00:00
return warnings , 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-11-03 09:05:23 +00:00
return warnings , err
2021-10-14 22:44:11 +05:30
}
2020-10-14 17:39:45 -07:00
if err := validateRuleContext ( rule ) ; err != nil {
2022-11-03 09:05:23 +00:00
return warnings , fmt . Errorf ( "path: spec.rules[%d]: %v" , i , err )
2020-10-14 17:39:45 -07:00
}
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-11-03 09:05:23 +00:00
return warnings , 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-11-03 09:05:23 +00:00
return warnings , validateMatchKindHelper ( rule )
2021-07-29 01:29:53 +05:30
}
}
} else {
if len ( rule . MatchResources . Kinds ) == 0 {
2022-11-03 09:05:23 +00:00
return warnings , validateMatchKindHelper ( rule )
2020-02-26 16:08:56 +05:30
}
2021-07-14 23:49:15 +05:30
}
2022-10-17 16:10:42 +02:00
// validate Cluster Resources in namespaced policy
// For namespaced policy, ClusterResource type field and values are not allowed in match and exclude
if namespaced {
2022-11-17 13:13:51 +05:30
return warnings , checkClusterResourceInMatchAndExclude ( rule , clusterResources , policy . GetNamespace ( ) , mock , res )
2022-10-17 16:10:42 +02:00
}
// validate rule actions
// - Mutate
// - Validate
// - Generate
if err := validateActions ( i , & rules [ i ] , client , mock ) ; err != nil {
2022-11-03 09:05:23 +00:00
return warnings , err
2022-10-17 16:10:42 +02:00
}
2022-10-14 11:09:57 +05:30
if rule . HasVerifyImages ( ) {
verifyImagePath := rulePath . Child ( "verifyImages" )
for index , i := range rule . VerifyImages {
errs = append ( errs , i . Validate ( verifyImagePath . Index ( index ) ) ... )
}
if len ( errs ) != 0 {
2022-11-03 09:05:23 +00:00
return warnings , errs . ToAggregate ( )
2022-10-14 11:09:57 +05:30
}
}
2022-05-17 08:19:03 +02:00
podOnlyMap := make ( map [ string ] bool ) // Validate that Kind is only Pod
2022-03-16 00:50:33 -04:00
podOnlyMap [ "Pod" ] = true
if reflect . DeepEqual ( common . GetKindsFromRule ( rule ) , podOnlyMap ) && podControllerAutoGenExclusion ( policy ) {
2022-04-13 01:11:37 -07:00
msg := "Policies that match Pods apply to all Pods including those created and managed by controllers " +
"excluded from autogen. Use preconditions to exclude the Pods managed by controllers which are " +
"excluded from autogen. Refer to https://kyverno.io/docs/writing-policies/autogen/ for details."
2022-11-03 09:05:23 +00:00
warnings = append ( warnings , msg )
2022-03-16 00:50:33 -04:00
}
2022-05-17 08:19:03 +02:00
// 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-11-17 19:37:03 +05:30
wildcardErr := validateWildcard ( value . ResourceDescription . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
2022-03-31 08:44:00 +02:00
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , policy )
2022-03-08 18:29:33 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , 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-11-17 19:37:03 +05:30
wildcardErr := validateWildcard ( value . ResourceDescription . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
2022-03-31 08:44:00 +02:00
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , policy )
2022-03-08 18:29:33 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , 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-11-17 19:37:03 +05:30
wildcardErr := validateWildcard ( value . ResourceDescription . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
2022-03-31 08:44:00 +02:00
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , policy )
2022-03-08 18:29:33 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , 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-11-17 19:37:03 +05:30
wildcardErr := validateWildcard ( value . ResourceDescription . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
2022-03-08 18:29:33 +05:30
if ! utils . ContainsString ( value . ResourceDescription . Kinds , "*" ) {
2022-03-31 08:44:00 +02:00
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , policy )
2022-03-08 18:29:33 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , 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
}
}
2022-11-17 19:37:03 +05:30
2021-10-14 12:45:32 +05:30
if ! utils . ContainsString ( rule . MatchResources . Kinds , "*" ) {
2022-03-31 08:44:00 +02:00
err := validateKinds ( rule . MatchResources . Kinds , mock , client , policy )
2021-10-14 12:45:32 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , errors . Wrapf ( err , "match resource kind is invalid" )
2021-10-14 12:45:32 +05:30
}
2022-03-31 08:44:00 +02:00
err = validateKinds ( rule . ExcludeResources . Kinds , mock , client , policy )
2021-10-14 12:45:32 +05:30
if err != nil {
2022-11-03 09:05:23 +00:00
return warnings , errors . Wrapf ( err , "exclude resource kind is invalid" )
2021-10-14 12:45:32 +05:30
}
2022-11-17 19:37:03 +05:30
} else {
wildcardErr := validateWildcard ( rule . MatchResources . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
wildcardErr = validateWildcard ( rule . ExcludeResources . Kinds , spec , rule )
if wildcardErr != nil {
return warnings , wildcardErr
}
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-11-03 09:05:23 +00:00
return warnings , 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 {
2022-10-03 12:55:39 +02:00
logging . Error ( err , fmt . Sprintf ( "source resource %s/%s/%s not found." , rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name ) )
2020-12-30 12:10:41 +05:30
continue
}
2022-11-12 01:00:54 +05:30
err = UpdateSourceResource ( client , rule . Generation . Kind , rule . Generation . Clone . Namespace , policy . GetName ( ) , obj )
if err != nil {
logging . Error ( err , "failed to update source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
continue
2020-12-30 12:10:41 +05:30
}
2022-11-12 01:00:54 +05:30
logging . V ( 4 ) . Info ( "updated source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
}
if ! mock && len ( rule . Generation . CloneList . Kinds ) != 0 {
for _ , kind := range rule . Generation . CloneList . Kinds {
apiVersion , kind := kubeutils . GetKindFromGVK ( kind )
resources , err := client . ListResource ( apiVersion , kind , rule . Generation . CloneList . Namespace , rule . Generation . CloneList . Selector )
2020-12-30 12:10:41 +05:30
if err != nil {
2022-11-12 01:00:54 +05:30
logging . Error ( err , fmt . Sprintf ( "failed to list resources %s/%s." , kind , rule . Generation . CloneList . Namespace ) )
2020-12-30 12:10:41 +05:30
continue
}
2022-11-12 01:00:54 +05:30
for _ , rName := range resources . Items {
obj , err := client . GetResource ( apiVersion , kind , rule . Generation . CloneList . Namespace , rName . GetName ( ) )
if err != nil {
logging . Error ( err , fmt . Sprintf ( "source resource %s/%s/%s not found." , kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name ) )
continue
}
err = UpdateSourceResource ( client , kind , rule . Generation . CloneList . Namespace , policy . GetName ( ) , obj )
if err != nil {
logging . Error ( err , "failed to update source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
continue
}
}
2020-12-30 12:10:41 +05:30
}
}
2019-09-27 19:03:55 -07:00
}
2022-11-03 09:05:23 +00:00
if ! mock && ( spec . SchemaValidation == nil || * spec . SchemaValidation ) {
2022-10-12 13:38:48 +02:00
if err := openApiManager . ValidatePolicyMutation ( policy ) ; err != nil {
2022-11-03 09:05:23 +00:00
return warnings , err
2021-11-03 11:16:55 -07:00
}
}
2022-11-03 09:05:23 +00:00
return warnings , nil
2021-11-03 11:16:55 -07:00
}
2022-11-12 01:00:54 +05:30
func UpdateSourceResource ( client dclient . Interface , kind , namespace string , policyName string , obj * unstructured . Unstructured ) error {
updateSource := true
label := obj . GetLabels ( )
if len ( label ) == 0 {
label = make ( map [ string ] string )
label [ "generate.kyverno.io/clone-policy-name" ] = policyName
} else {
if label [ "generate.kyverno.io/clone-policy-name" ] != "" {
policyNames := label [ "generate.kyverno.io/clone-policy-name" ]
if ! strings . Contains ( policyNames , policyName ) {
policyNames = policyNames + "," + policyName
label [ "generate.kyverno.io/clone-policy-name" ] = policyNames
} else {
updateSource = false
}
} else {
label [ "generate.kyverno.io/clone-policy-name" ] = policyName
}
}
if updateSource {
logging . V ( 4 ) . Info ( "updating existing clone source labels" )
obj . SetLabels ( label )
obj . SetResourceVersion ( "" )
_ , err := client . UpdateResource ( obj . GetAPIVersion ( ) , kind , namespace , obj , false )
if err != nil {
logging . Error ( err , "failed to update source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
return err
}
logging . V ( 4 ) . Info ( "updated source" , "kind" , obj . GetKind ( ) , "name" , obj . GetName ( ) , "namespace" , obj . GetNamespace ( ) )
}
return nil
}
2022-05-17 13:12:43 +02:00
func ValidateVariables ( p kyvernov1 . PolicyInterface , backgroundMode bool ) error {
2021-11-03 11:16:55 -07:00
vars := hasVariables ( p )
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 )
}
}
2022-10-20 14:17:33 +02:00
if err := hasInvalidVariables ( p , backgroundMode ) ; err != nil {
return fmt . Errorf ( "policy contains invalid variables: %s" , err . Error ( ) )
}
2021-11-03 11:16:55 -07:00
return nil
}
// hasInvalidVariables - checks for unexpected variables in the policy
2022-05-17 13:12:43 +02:00
func hasInvalidVariables ( policy kyvernov1 . PolicyInterface , background bool ) error {
2022-03-28 16:01:27 +02:00
for _ , r := range autogen . ComputeRules ( policy ) {
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
2022-05-11 02:31:48 -07:00
for i , vi := range ruleCopy . VerifyImages {
for j := range vi . Attestations {
ruleCopy . VerifyImages [ i ] . Attestations [ j ] . Conditions = nil
2021-11-03 11:16:55 -07:00
}
}
ctx := buildContext ( ruleCopy , background )
2022-10-03 12:55:39 +02:00
if _ , err := variables . SubstituteAllInRule ( logging . GlobalLogger ( ) , ctx , * ruleCopy ) ; ! checkNotFoundErr ( err ) {
2021-11-03 11:16:55 -07:00
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
}
2022-05-17 13:12:43 +02:00
func ValidateOnPolicyUpdate ( p kyvernov1 . PolicyInterface , onPolicyUpdate bool ) error {
2022-05-05 18:34:49 +05:30
vars := hasVariables ( p )
if len ( vars ) == 0 {
return nil
}
if err := hasInvalidVariables ( p , onPolicyUpdate ) ; err != nil {
2022-05-24 20:19:36 +08:00
return fmt . Errorf ( "update event, policy contains invalid variables: %s" , err . Error ( ) )
2022-05-05 18:34:49 +05:30
}
if err := containsUserVariables ( p , vars ) ; err != nil {
2022-05-06 13:46:36 +08:00
return fmt . Errorf ( "only select variables are allowed in on policy update. Set spec.mutateExistingOnPolicyUpdate=false to disable update policy mode for this policy rule: %s " , err )
2022-05-05 18:34:49 +05:30
}
return nil
}
2021-11-03 11:16:55 -07:00
// for now forbidden sections are match, exclude and
2022-05-17 13:12:43 +02:00
func ruleForbiddenSectionsHaveVariables ( rule * kyvernov1 . Rule ) error {
2021-11-03 11:16:55 -07:00
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
2022-05-17 13:12:43 +02:00
func hasVariables ( policy kyvernov1 . PolicyInterface ) [ ] [ ] string {
2021-11-03 11:16:55 -07:00
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
}
2022-09-30 13:56:47 +02:00
func buildContext ( rule * kyvernov1 . Rule , background bool ) * enginecontext . MockContext {
2021-11-03 11:16:55 -07:00
re := getAllowedVariables ( background )
2022-09-30 13:56:47 +02:00
ctx := enginecontext . NewMockContext ( re )
2021-11-03 11:16:55 -07:00
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
}
2022-09-30 13:56:47 +02:00
func addContextVariables ( entries [ ] kyvernov1 . ContextEntry , ctx * enginecontext . MockContext ) {
2021-11-03 11:16:55 -07:00
for _ , contextEntry := range entries {
2022-04-25 12:06:07 +01:00
if contextEntry . APICall != nil || contextEntry . ImageRegistry != nil || contextEntry . Variable != 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
2022-09-30 13:56:47 +02:00
case enginecontext . InvalidVariableError :
2021-11-03 11:16:55 -07:00
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
}
2022-10-03 12:55:39 +02:00
_ , err = variables . ValidateElementInForEach ( logging . GlobalLogger ( ) , jsonInterface )
2021-10-14 22:44:11 +05:30
return err
}
2022-05-17 13:12:43 +02:00
func validateMatchKindHelper ( rule kyvernov1 . Rule ) error {
2021-07-29 01:29:53 +05:30
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-08-29 06:52:22 +05:30
// isLabelAndAnnotationsString :- Validate if labels and annotations contains only string values
2022-05-17 13:12:43 +02:00
func isLabelAndAnnotationsString ( rule kyvernov1 . Rule ) bool {
2022-05-24 18:15:23 +05:30
checkLabelAnnotation := func ( metaKey map [ string ] interface { } ) bool {
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
}
2020-08-29 06:52:22 +05:30
// 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
2022-05-24 18:15:23 +05:30
return checkLabelAnnotation ( metaKey )
2020-08-29 06:52:22 +05:30
}
}
2022-05-24 18:15:23 +05:30
if k == "spec" {
metadata , _ := jsonq . NewQuery ( patternMap ) . Object ( "spec" , "template" , "metadata" )
return checkLabelAnnotation ( metadata )
}
2020-08-29 06:52:22 +05:30
}
return true
}
2022-05-24 18:15:23 +05:30
if rule . HasValidate ( ) {
if rule . Validation . ForEachValidation != nil {
for _ , foreach := range rule . Validation . ForEachValidation {
patternMap , ok := foreach . GetPattern ( ) . ( map [ string ] interface { } )
if ok {
return checkMetadata ( patternMap )
}
}
} else {
patternMap , ok := rule . Validation . GetPattern ( ) . ( map [ string ] interface { } )
if ok {
return checkMetadata ( patternMap )
} else if rule . Validation . GetAnyPattern ( ) != nil {
anyPatterns , err := rule . Validation . DeserializeAnyPattern ( )
if err != nil {
2022-10-03 12:55:39 +02:00
logging . Error ( err , "failed to deserialize anyPattern, expect type array" )
2022-05-24 18:15:23 +05:30
return false
}
for _ , pattern := range anyPatterns {
patternMap , ok := pattern . ( map [ string ] interface { } )
if ok {
ret := checkMetadata ( patternMap )
if ! ret {
return ret
}
}
}
}
2020-11-13 16:25:51 -08:00
}
2022-05-24 18:15:23 +05:30
}
2020-11-13 16:25:51 -08:00
2022-05-24 18:15:23 +05:30
if rule . HasMutate ( ) {
if rule . Mutation . ForEachMutation != nil {
for _ , foreach := range rule . Mutation . ForEachMutation {
forEachStrategicMergeMap , ok := foreach . GetPatchStrategicMerge ( ) . ( map [ string ] interface { } )
if ok {
return checkMetadata ( forEachStrategicMergeMap )
2020-08-29 06:52:22 +05:30
}
}
2022-05-24 18:15:23 +05:30
} else {
strategicMergeMap , ok := rule . Mutation . GetPatchStrategicMerge ( ) . ( map [ string ] interface { } )
if ok {
return checkMetadata ( strategicMergeMap )
}
2020-08-29 06:52:22 +05:30
}
}
2022-05-24 18:15:23 +05:30
2020-08-29 06:52:22 +05:30
return true
}
2022-05-17 13:12:43 +02:00
func ruleOnlyDealsWithResourceMetaData ( rule kyvernov1 . 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 {
2022-10-03 12:55:39 +02:00
logging . 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-05-17 13:12:43 +02:00
func validateResources ( path * field . Path , rule kyvernov1 . 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 . ExcludeResources . UserInfo . Validate ( path . Child ( "exclude" ) ) ; len ( errs ) != 0 {
return "exclude" , errs . ToAggregate ( )
2019-12-04 18:50:51 -08:00
}
2022-05-17 13:12:43 +02:00
if ( len ( rule . MatchResources . Any ) > 0 || len ( rule . MatchResources . All ) > 0 ) && ! reflect . DeepEqual ( rule . MatchResources . ResourceDescription , kyvernov1 . 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
}
2022-05-17 13:12:43 +02:00
if ( len ( rule . ExcludeResources . Any ) > 0 || len ( rule . ExcludeResources . All ) > 0 ) && ! reflect . DeepEqual ( rule . ExcludeResources . ResourceDescription , kyvernov1 . 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 . 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
}
}
2022-05-17 08:19:03 +02:00
// 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
}
}
2022-05-17 08:19:03 +02:00
// 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 ] {
2022-05-10 11:24:27 +02:00
return 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" )
2021-03-02 10:01:06 +05:30
}
// conditions are currently in the form of []interface{}
kyvernoConditions , err := utils . ApiextensionsJsonToKyvernoConditions ( conditions )
if err != nil {
2022-05-10 11:24:27 +02:00
return schemaKey , err
2021-03-02 10:01:06 +05:30
}
switch typedConditions := kyvernoConditions . ( type ) {
2022-05-17 13:12:43 +02:00
case kyvernov1 . AnyAllConditions :
2021-03-02 10:01:06 +05:30
// validating the conditions under 'any', if there are any
2022-05-17 13:12:43 +02:00
if ! reflect . DeepEqual ( typedConditions , kyvernov1 . AnyAllConditions { } ) && typedConditions . AnyConditions != nil {
2021-03-02 10:01:06 +05:30
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
2022-05-17 13:12:43 +02:00
if ! reflect . DeepEqual ( typedConditions , kyvernov1 . AnyAllConditions { } ) && typedConditions . AllConditions != nil {
2021-03-02 10:01:06 +05:30
for i , condition := range typedConditions . AllConditions {
if path , err := validateConditionValues ( condition ) ; err != nil {
return fmt . Sprintf ( "%s.all[%d].%s" , schemaKey , i , path ) , err
}
}
}
2022-05-17 13:12:43 +02:00
case [ ] kyvernov1 . Condition : // backwards compatibility
2021-03-02 10:01:06 +05:30
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'
2022-05-17 13:12:43 +02:00
func validateConditionValues ( c kyvernov1 . 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
}
}
2022-05-17 13:12:43 +02:00
func validateValuesKeyRequest ( c kyvernov1 . 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}}
2022-05-17 13:12:43 +02:00
func validateConditionValuesKeyRequestOperation ( c kyvernov1 . Condition ) ( string , error ) {
2021-02-02 02:57:16 +05:30
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
2022-05-17 13:12:43 +02:00
func validateRuleContext ( rule kyvernov1 . Rule ) error {
2020-10-14 17:39:45 -07:00
if rule . Context == nil || len ( rule . Context ) == 0 {
return nil
}
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" )
}
2022-04-25 12:06:07 +01:00
for _ , v := range [ ] string { "images" , "request" , "serviceAccountName" , "serviceAccountNamespace" , "element" , "elementIndex" } {
if entry . Name == v || strings . HasPrefix ( entry . Name , v + "." ) {
return fmt . Errorf ( "entry name %s is invalid as it conflicts with a pre-defined variable %s" , entry . Name , v )
}
}
2020-10-19 12:36:55 -07:00
2021-02-01 12:59:13 -08:00
var err error
2022-04-25 12:06:07 +01:00
if entry . ConfigMap != nil && entry . APICall == nil && entry . ImageRegistry == nil && entry . Variable == nil {
2021-02-01 12:59:13 -08:00
err = validateConfigMap ( entry )
2022-04-25 12:06:07 +01:00
} else if entry . ConfigMap == nil && entry . APICall != nil && entry . ImageRegistry == nil && entry . Variable == nil {
2021-02-01 12:59:13 -08:00
err = validateAPICall ( entry )
2022-04-25 12:06:07 +01:00
} else if entry . ConfigMap == nil && entry . APICall == nil && entry . ImageRegistry != nil && entry . Variable == nil {
2022-01-17 04:06:44 +00:00
err = validateImageRegistry ( entry )
2022-04-25 12:06:07 +01:00
} else if entry . ConfigMap == nil && entry . APICall == nil && entry . ImageRegistry == nil && entry . Variable != nil {
err = validateVariable ( entry )
2021-02-01 12:59:13 -08:00
} else {
2022-04-25 12:06:07 +01:00
return fmt . Errorf ( "exactly one of configMap or apiCall or imageRegistry or variable 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
}
}
return nil
}
2022-05-17 13:12:43 +02:00
func validateVariable ( entry kyvernov1 . ContextEntry ) error {
2022-04-25 12:06:07 +01: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 . Variable . JMESPath , func ( s string ) string { return "kyvernojmespathvariable" } )
if ! strings . Contains ( jmesPath , "kyvernojmespathvariable" ) && entry . Variable . JMESPath != "" {
if _ , err := jmespath . NewParser ( ) . Parse ( entry . Variable . JMESPath ) ; err != nil {
return fmt . Errorf ( "failed to parse JMESPath %s: %v" , entry . Variable . JMESPath , err )
}
2021-02-01 12:59:13 -08:00
}
2022-04-25 12:06:07 +01:00
if entry . Variable . Value == nil && jmesPath == "" {
return fmt . Errorf ( "a variable must define a value or a jmesPath expression" )
2021-02-01 12:59:13 -08:00
}
2022-04-25 12:06:07 +01:00
if entry . Variable . Default != nil && jmesPath == "" {
return fmt . Errorf ( "a variable must define a default value only when a jmesPath expression is defined" )
2022-01-17 04:06:44 +00:00
}
2022-04-25 12:06:07 +01:00
return nil
}
2022-01-17 04:06:44 +00:00
2022-05-17 13:12:43 +02:00
func validateConfigMap ( entry kyvernov1 . ContextEntry ) error {
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
}
2022-05-17 13:12:43 +02:00
func validateAPICall ( entry kyvernov1 . ContextEntry ) error {
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-05-17 13:12:43 +02:00
func validateImageRegistry ( entry kyvernov1 . ContextEntry ) error {
2022-01-17 04:06:44 +00:00
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" } )
2022-05-16 15:38:57 +02:00
// it's no use validating a reference that contains a variable
2022-01-17 04:06:44 +00:00
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
2022-05-17 13:12:43 +02:00
func validateMatchedResourceDescription ( rd kyvernov1 . ResourceDescription ) ( string , error ) {
if reflect . DeepEqual ( rd , kyvernov1 . ResourceDescription { } ) {
2019-10-21 14:22:31 -07:00
return "" , fmt . Errorf ( "match resources not specified" )
2019-09-27 16:31:27 -07:00
}
2019-10-21 14:22:31 -07:00
return "" , nil
2019-10-03 18:19:47 -07:00
}
2020-08-19 21:37:23 +05:30
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
// Match and Exclude block
2022-11-17 13:13:51 +05:30
func checkClusterResourceInMatchAndExclude ( rule kyvernov1 . Rule , clusterResources sets . String , policyNamespace string , mock bool , res [ ] * metav1 . APIResourceList ) error {
2021-10-22 01:48:22 +05:30
if ! mock {
// 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
for _ , resList := range res {
for _ , r := range resList . APIResources {
2022-11-17 13:13:51 +05:30
if r . Kind == generateResourceKind {
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 )
}
2022-11-17 13:13:51 +05:30
if rule . Generation . Namespace != policyNamespace {
return fmt . Errorf ( "path: spec.rules[%v]: a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v" , rule . Name , policyNamespace , rule . Generation . Namespace )
}
if rule . Generation . Clone . Namespace != policyNamespace {
return fmt . Errorf ( "path: spec.rules[%v]: a namespaced policy cannot clone resource in other namespace, expected: %v, received: %v" , rule . Name , policyNamespace , rule . Generation . Clone . Namespace )
}
2021-10-22 01:48:22 +05:30
} 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 )
}
2022-11-17 13:13:51 +05:30
if policyNamespace != "" {
return fmt . Errorf ( "path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources" , rule . Name )
}
}
} else if len ( rule . Generation . CloneList . Kinds ) != 0 {
for _ , kind := range rule . Generation . CloneList . Kinds {
_ , splitkind := kubeutils . GetKindFromGVK ( kind )
if r . Kind == splitkind {
if r . Namespaced {
if rule . Generation . CloneList . Namespace != policyNamespace {
return fmt . Errorf ( "path: spec.rules[%v]: a namespaced policy cannot clone resource in other namespace, expected: %v, received: %v" , rule . Name , policyNamespace , rule . Generation . Namespace )
}
} else {
if policyNamespace != "" {
return fmt . Errorf ( "path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources" , rule . Name )
}
}
}
2021-10-22 01:48:22 +05:30
}
}
}
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
2022-05-17 13:12:43 +02:00
func jsonPatchOnPod ( rule kyvernov1 . Rule ) bool {
2020-12-09 15:37:45 -08:00
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-05-17 13:12:43 +02:00
func podControllerAutoGenExclusion ( policy kyvernov1 . PolicyInterface ) bool {
2022-03-16 00:50:33 -04:00
annotations := policy . GetAnnotations ( )
2022-05-17 13:12:43 +02:00
val , ok := annotations [ kyvernov1 . PodControllersAnnotation ]
2022-04-22 00:10:02 -07:00
if ! ok || val == "none" {
return false
}
2022-03-16 00:50:33 -04:00
reorderVal := strings . Split ( strings . ToLower ( val ) , "," )
sort . Slice ( reorderVal , func ( i , j int ) bool { return reorderVal [ i ] < reorderVal [ j ] } )
2022-05-10 11:24:27 +02:00
if ok && ! reflect . DeepEqual ( reorderVal , [ ] string { "cronjob" , "daemonset" , "deployment" , "job" , "statefulset" } ) {
2022-03-16 00:50:33 -04:00
return true
}
return false
}
2022-11-17 19:37:03 +05:30
// validateWildcard check for an Match/Exclude block contains "*"
func validateWildcard ( kinds [ ] string , spec * kyvernov1 . Spec , rule kyvernov1 . Rule ) error {
if utils . ContainsString ( kinds , "*" ) && spec . BackgroundProcessingEnabled ( ) {
return fmt . Errorf ( "wildcard policy not allowed in background mode. Set spec.background=false to disable background mode for this policy rule " )
}
if utils . ContainsString ( kinds , "*" ) && len ( kinds ) > 1 {
return fmt . Errorf ( "wildard policy can not deal more than one kind" )
}
if utils . ContainsString ( kinds , "*" ) {
if rule . HasGenerate ( ) || rule . HasVerifyImages ( ) || rule . Validation . ForEachValidation != nil {
return fmt . Errorf ( "wildcard policy does not support rule type" )
}
if rule . HasValidate ( ) {
if rule . Validation . GetPattern ( ) != nil || rule . Validation . GetAnyPattern ( ) != nil {
if ! ruleOnlyDealsWithResourceMetaData ( rule ) {
return fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind" )
}
}
if rule . Validation . Deny != nil {
kyvernoConditions , _ := utils . ApiextensionsJsonToKyvernoConditions ( rule . Validation . Deny . GetAnyAllConditions ( ) )
switch typedConditions := kyvernoConditions . ( type ) {
case [ ] kyvernov1 . Condition : // backwards compatibility
for _ , condition := range typedConditions {
key := condition . GetKey ( )
if ! strings . Contains ( key . ( string ) , "request.object.metadata." ) && ( ! wildCardAllowedVariables . MatchString ( key . ( string ) ) || strings . Contains ( key . ( string ) , "request.object.spec" ) ) {
return fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind" )
}
}
}
}
}
if rule . HasMutate ( ) {
if ! ruleOnlyDealsWithResourceMetaData ( rule ) {
return fmt . Errorf ( "policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind" )
}
}
}
return nil
}
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
2022-05-17 13:12:43 +02:00
func validateKinds ( kinds [ ] string , mock bool , client dclient . Interface , p kyvernov1 . PolicyInterface ) error {
2021-10-05 00:30:57 +05:30
for _ , kind := range kinds {
2022-04-01 10:34:25 +02:00
gv , k := kubeutils . GetKindFromGVK ( kind )
2022-02-23 22:27:18 +05:30
2022-04-01 10:34:25 +02:00
if ! mock && ! kubeutils . SkipSubResources ( k ) && ! strings . Contains ( kind , "*" ) {
2022-05-03 07:30:04 +02:00
_ , _ , err := client . Discovery ( ) . FindResource ( gv , k )
2022-02-23 22:27:18 +05:30
if err != nil {
2022-09-12 13:44:28 +05:30
return fmt . Errorf ( "unable to convert GVK to GVR for kinds %s, err: %s" , k , err )
2022-02-23 22:27:18 +05:30
}
}
2021-10-05 00:30:57 +05:30
}
return nil
}
2022-10-06 11:46:12 +05:30
func validateWildcardsWithNamespaces ( enforce , audit , enforceW , auditW [ ] string ) error {
pat , ns , notOk := utils . CheckWildcardNamespaces ( auditW , enforce )
if notOk {
return fmt . Errorf ( "wildcard pattern '%s' matches with namespace '%s'" , pat , ns )
}
pat , ns , notOk = utils . CheckWildcardNamespaces ( enforceW , audit )
if notOk {
return fmt . Errorf ( "wildcard pattern '%s' matches with namespace '%s'" , pat , ns )
}
pat1 , pat2 , notOk := utils . CheckWildcardNamespaces ( auditW , enforceW )
if notOk {
return fmt . Errorf ( "wildcard pattern '%s' conflicts with the pattern '%s'" , pat1 , pat2 )
}
pat1 , pat2 , notOk = utils . CheckWildcardNamespaces ( enforceW , auditW )
if notOk {
return fmt . Errorf ( "wildcard pattern '%s' conflicts with the pattern '%s'" , pat1 , pat2 )
}
return nil
}
func validateNamespaces ( s * kyvernov1 . Spec , path * field . Path ) error {
action := map [ string ] sets . String {
2022-11-01 09:56:52 +00:00
"enforce" : sets . NewString ( ) ,
"audit" : sets . NewString ( ) ,
"enforceW" : sets . NewString ( ) ,
"auditW" : sets . NewString ( ) ,
2022-10-06 11:46:12 +05:30
}
for i , vfa := range s . ValidationFailureActionOverrides {
patternList , nsList := utils . SeperateWildcards ( vfa . Namespaces )
2022-11-01 09:56:52 +00:00
if vfa . Action . Audit ( ) {
if action [ "enforce" ] . HasAny ( nsList ... ) {
2022-10-06 11:46:12 +05:30
return fmt . Errorf ( "conflicting namespaces found in path: %s: %s" , path . Index ( i ) . Child ( "namespaces" ) . String ( ) ,
2022-11-01 09:56:52 +00:00
strings . Join ( action [ "enforce" ] . Intersection ( sets . NewString ( nsList ... ) ) . List ( ) , ", " ) )
2022-10-06 11:46:12 +05:30
}
action [ "auditW" ] . Insert ( patternList ... )
2022-11-01 09:56:52 +00:00
} else if vfa . Action . Enforce ( ) {
if action [ "audit" ] . HasAny ( nsList ... ) {
2022-10-06 11:46:12 +05:30
return fmt . Errorf ( "conflicting namespaces found in path: %s: %s" , path . Index ( i ) . Child ( "namespaces" ) . String ( ) ,
2022-11-01 09:56:52 +00:00
strings . Join ( action [ "audit" ] . Intersection ( sets . NewString ( nsList ... ) ) . List ( ) , ", " ) )
2022-10-06 11:46:12 +05:30
}
action [ "enforceW" ] . Insert ( patternList ... )
}
2022-11-01 09:56:52 +00:00
action [ strings . ToLower ( string ( vfa . Action ) ) ] . Insert ( nsList ... )
2022-10-06 11:46:12 +05:30
2022-11-01 09:56:52 +00:00
err := validateWildcardsWithNamespaces ( action [ "enforce" ] . List ( ) , action [ "audit" ] . List ( ) , action [ "enforceW" ] . List ( ) , action [ "auditW" ] . List ( ) )
2022-10-06 11:46:12 +05:30
if err != nil {
return fmt . Errorf ( "path: %s: %s" , path . Index ( i ) . Child ( "namespaces" ) . String ( ) , err . Error ( ) )
}
}
return nil
}