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
"errors"
"fmt"
2021-02-04 02:39:42 +05:30
"reflect"
2021-09-06 02:52:51 -07:00
"regexp"
2021-02-04 02:39:42 +05:30
"strings"
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-05 00:30:57 +05:30
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
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"
2019-12-04 18:50:51 -08:00
rbacv1 "k8s.io/api/rbac/v1"
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"
2021-09-06 02:52:51 -07:00
"k8s.io/apimachinery/pkg/util/yaml"
2020-11-13 16:25:51 -08:00
log "sigs.k8s.io/controller-runtime/pkg/log"
2019-09-27 16:31:27 -07:00
)
2021-09-06 02:52:51 -07:00
// validateJSONPatchPathForForwardSlash checks for forward slash
func validateJSONPatchPathForForwardSlash ( patch string ) error {
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
}
2019-10-21 14:22:31 -07:00
// Validate does some initial check to verify some conditions
// - One operation per rule
// - ResourceDescription mandatory checks
2021-01-06 16:32:02 -08:00
func Validate ( policy * kyverno . ClusterPolicy , client * dclient . Client , mock bool , openAPIController * openapi . Controller ) error {
p := * policy
2021-07-02 16:47:40 +03:00
if len ( common . PolicyHasVariables ( p ) ) > 0 {
err := common . PolicyHasNonAllowedVariables ( p )
if err != nil {
return fmt . Errorf ( "policy contains invalid variables: %s" , err . Error ( ) )
}
2020-08-24 03:41:03 +05:30
}
2021-02-19 09:09:41 -08:00
// policy name is stored in the label of the report change request
if len ( p . Name ) > 63 {
return fmt . Errorf ( "invalid policy name %s: must be no more than 63 characters" , p . Name )
}
2019-10-21 14:22:31 -07:00
if path , err := validateUniqueRuleName ( p ) ; err != nil {
return fmt . Errorf ( "path: spec.%s: %v" , path , err )
2019-10-03 16:49:41 -07:00
}
2020-11-25 10:11:59 -08:00
if p . Spec . Background == nil || * p . Spec . Background == true {
2020-05-06 00:29:40 +05:30
if err := ContainsVariablesOtherThanObject ( p ) ; err != nil {
2021-02-22 12:08:26 -08:00
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 )
2019-12-30 17:08:50 -08:00
}
}
2019-10-03 16:49:41 -07:00
2019-10-21 14:22:31 -07:00
for i , rule := range p . Spec . Rules {
2021-09-06 02:52:51 -07:00
//check for forward slash
if err := validateJSONPatchPathForForwardSlash ( rule . Mutation . PatchesJSON6902 ) ; err != nil {
return fmt . Errorf ( "path must begin with a forward slash: spec.rules[%d]: %s" , i , err )
}
2020-12-09 15:37:45 -08:00
if jsonPatchOnPod ( rule ) {
2021-07-21 12:10:52 +05:30
log . Log . V ( 1 ) . Info ( "pods managed by workload controllers cannot be mutated using policies. Use the auto-gen feature or write policies that match pod controllers." )
2020-12-09 15:37:45 -08:00
}
2019-10-21 14:22:31 -07:00
// validate resource description
if path , err := validateResources ( rule ) ; err != nil {
return fmt . Errorf ( "path: spec.rules[%d].%s: %v" , i , path , err )
}
2020-10-14 17:39:45 -07:00
2019-10-21 14:22:31 -07:00
// validate rule types
// only one type of rule is allowed per rule
if err := validateRuleType ( rule ) ; err != nil {
// as there are more than 1 operation in rule, not need to evaluate it further
return fmt . Errorf ( "path: spec.rules[%d]: %v" , i , err )
2019-09-27 19:03:55 -07:00
}
2020-10-14 17:39:45 -07:00
if err := validateRuleContext ( rule ) ; err != nil {
return fmt . Errorf ( "path: spec.rules[%d]: %v" , i , err )
}
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-10-07 04:48:28 +05:30
if ! mock {
2021-01-29 17:38:23 -08:00
res , err := client . DiscoveryClient . DiscoveryCache ( ) . ServerPreferredResources ( )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-10-07 04:48:28 +05:30
if p . ObjectMeta . Namespace != "" {
var Empty struct { }
clusterResourcesMap := make ( map [ string ] * struct { } )
// Get all the cluster type kind supported by cluster
for _ , resList := range res {
for _ , r := range resList . APIResources {
if ! r . Namespaced {
if _ , ok := clusterResourcesMap [ r . Kind ] ; ! ok {
clusterResourcesMap [ r . Kind ] = & Empty
}
2020-08-19 21:37:23 +05:30
}
}
}
2021-10-07 04:48:28 +05:30
clusterResources := make ( [ ] string , 0 , len ( clusterResourcesMap ) )
for k := range clusterResourcesMap {
clusterResources = append ( clusterResources , k )
}
return checkClusterResourceInMatchAndExclude ( rule , clusterResources )
2020-09-01 21:42:05 +05:30
}
2020-08-21 23:07:54 +05:30
2021-10-07 04:48:28 +05:30
// 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 {
if r . Kind == generateResourceKind {
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
}
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 ) {
2020-04-27 22:01:33 +05:30
return fmt . Errorf ( "path: spec.rules[%v]: rule is matching an empty set" , rule . Name )
}
2020-03-20 20:23:34 +05:30
2020-03-11 18:14:23 -07:00
// validate rule actions
// - Mutate
// - Validate
// - Generate
2021-10-03 23:39:55 -07:00
if err := validateActions ( i , & p . Spec . Rules [ i ] , client , mock ) ; err != nil {
2020-03-11 18:14:23 -07:00
return 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 {
return validateMatchKindHelper ( rule )
}
}
} else if len ( rule . MatchResources . All ) > 0 {
for _ , rmr := range rule . MatchResources . All {
if len ( rmr . Kinds ) == 0 {
return validateMatchKindHelper ( rule )
}
}
} else {
if len ( rule . MatchResources . Kinds ) == 0 {
return validateMatchKindHelper ( rule )
2020-02-26 16:08:56 +05:30
}
2021-07-14 23:49:15 +05:30
}
if utils . ContainsString ( rule . MatchResources . Kinds , "*" ) || utils . ContainsString ( rule . ExcludeResources . Kinds , "*" ) {
return fmt . Errorf ( "wildcards (*) are currently not supported in the match.resources.kinds field. at least one resource kind must be specified in a kind block." )
2020-02-26 16:08:56 +05:30
}
2020-08-29 06:52:22 +05:30
2021-08-21 03:58:49 +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 {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "the kind defined in the any match resource is invalid" )
}
}
for _ , value := range match . All {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "the kind defined in the all match resource is invalid" )
2021-08-21 03:58:49 +05:30
}
}
2021-10-05 00:30:57 +05:30
for _ , value := range exclude . Any {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "the kind defined in the any exclude resource is invalid" )
}
}
for _ , value := range exclude . All {
err := validateKinds ( value . ResourceDescription . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "the kind defined in the all exclude resource is invalid" )
}
}
err := validateKinds ( rule . MatchResources . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "match resource kind is invalid " )
}
err = validateKinds ( rule . ExcludeResources . Kinds , mock , client , p )
if err != nil {
return fmt . Errorf ( "exclude resource kind is invalid " )
}
2021-08-21 03:58:49 +05:30
2020-08-29 06:52:22 +05:30
// Validate string values in labels
if ! isLabelAndAnnotationsString ( rule ) {
return fmt . Errorf ( "labels and annotations supports only string values, \"use double quotes around the non string values\"" )
}
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 )
label [ "generate.kyverno.io/clone-policy-name" ] = p . GetName ( )
} else {
if label [ "generate.kyverno.io/clone-policy-name" ] != "" {
policyNames := label [ "generate.kyverno.io/clone-policy-name" ]
if ! strings . Contains ( policyNames , p . GetName ( ) ) {
policyNames = policyNames + "," + p . GetName ( )
label [ "generate.kyverno.io/clone-policy-name" ] = policyNames
} else {
updateSource = false
}
} else {
label [ "generate.kyverno.io/clone-policy-name" ] = p . GetName ( )
}
}
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
2020-04-01 19:06:13 +05:30
if ! mock {
2021-01-06 16:32:02 -08:00
if err := openAPIController . ValidatePolicyFields ( p ) ; err != nil {
2020-04-01 19:06:13 +05:30
return err
}
} else {
if err := openAPIController . ValidatePolicyMutation ( p ) ; err != nil {
return err
}
2020-03-04 19:16:26 +05:30
}
2019-09-27 19:03:55 -07:00
return nil
}
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" +
" the rule does not match an kind" )
}
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" )
}
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
}
patternMap , ok := rule . Validation . Pattern . ( map [ string ] interface { } )
if ok {
return checkMetadata ( patternMap )
2020-11-13 16:25:51 -08:00
} else if rule . Validation . AnyPattern != nil {
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 {
overlayMap , _ := rule . Mutation . Overlay . ( map [ string ] interface { } )
for k := range overlayMap {
if k != "metadata" {
return false
}
}
for _ , patch := range rule . Mutation . Patches {
if ! strings . HasPrefix ( patch . Path , "/metadata" ) {
return false
}
}
patternMap , _ := rule . Validation . Pattern . ( map [ string ] interface { } )
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
}
2019-10-21 14:22:31 -07:00
func validateResources ( rule kyverno . Rule ) ( string , error ) {
2019-12-04 18:50:51 -08:00
// validate userInfo in match and exclude
if path , err := validateUserInfo ( rule ) ; err != nil {
return fmt . Sprintf ( "resources.%s" , path ) , err
}
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 { } ) {
return "match." , fmt . Errorf ( "Can't specify any/all together with match resources" )
}
if ( len ( rule . ExcludeResources . Any ) > 0 || len ( rule . ExcludeResources . All ) > 0 ) && ! reflect . DeepEqual ( rule . ExcludeResources . ResourceDescription , kyverno . ResourceDescription { } ) {
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 {
return "match." , fmt . Errorf ( "Can't specify any and all together." )
}
if len ( rule . ExcludeResources . Any ) > 0 && len ( rule . ExcludeResources . All ) > 0 {
return "match." , fmt . Errorf ( "Can't specify any and all together." )
}
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
if rule . AnyAllConditions != nil {
if path , err := validateConditions ( rule . AnyAllConditions , "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
if rule . Validation . Deny != nil && rule . Validation . Deny . AnyAllConditions != nil {
if path , err := validateConditions ( rule . Validation . Deny . AnyAllConditions , "conditions" ) ; err != nil {
2021-02-02 02:57:16 +05:30
return fmt . Sprintf ( "validate.deny.%s" , path ) , err
}
}
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 ) {
switch strings . ReplaceAll ( c . Key . ( string ) , " " , "" ) {
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 ,
}
switch reflect . TypeOf ( c . Value ) . Kind ( ) {
case reflect . String :
2021-02-17 02:36:07 +05:30
valueStr := c . Value . ( string )
// 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 ] {
2021-02-02 02:57:16 +05:30
return fmt . Sprintf ( "value: %s" , c . Value . ( string ) ) , fmt . Errorf ( "unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]" , c . Value . ( string ) )
}
case reflect . Slice :
values := reflect . ValueOf ( c . Value )
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 :
return fmt . Sprintf ( "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 ( c . Value ) . 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
2021-02-02 02:57:16 +05:30
// validateUniqueRuleName checks if the rule names are unique across a policy
2019-10-21 14:22:31 -07:00
func validateUniqueRuleName ( p kyverno . ClusterPolicy ) ( string , error ) {
var ruleNames [ ] string
2019-09-27 19:03:55 -07:00
2019-10-21 14:22:31 -07:00
for i , rule := range p . Spec . Rules {
2020-12-15 15:21:39 -08:00
if utils . ContainsString ( ruleNames , rule . Name ) {
2019-10-21 14:22:31 -07:00
return fmt . Sprintf ( "rule[%d]" , i ) , fmt . Errorf ( ` duplicate rule name: '%s' ` , rule . Name )
}
ruleNames = append ( ruleNames , rule . Name )
2019-10-03 14:47:50 -07:00
}
2019-10-21 14:22:31 -07:00
return "" , nil
2019-09-27 16:31:27 -07:00
}
// validateRuleType checks only one type of rule is defined per rule
2019-10-18 17:45:24 -07:00
func validateRuleType ( r kyverno . Rule ) error {
2021-07-09 18:01:46 -07:00
ruleTypes := [ ] bool { r . HasMutate ( ) , r . HasValidate ( ) , r . HasGenerate ( ) , r . HasVerifyImages ( ) }
2019-09-27 16:31:27 -07:00
2019-10-03 16:49:41 -07:00
operationCount := func ( ) int {
count := 0
for _ , v := range ruleTypes {
if v {
count ++
}
}
return count
} ( )
2019-09-27 16:31:27 -07:00
2019-10-03 16:49:41 -07:00
if operationCount == 0 {
2021-07-09 18:01:46 -07:00
return fmt . Errorf ( "no operation defined in the rule '%s'.(supported operations: mutate,validate,generate,verifyImages)" , r . Name )
2019-10-03 16:49:41 -07:00
} else if operationCount != 1 {
2021-07-09 18:01:46 -07:00
return fmt . Errorf ( "multiple operations defined in the rule '%s', only one operation (mutate,validate,generate,verifyImages) is allowed per rule" , r . Name )
2019-09-27 16:31:27 -07:00
}
2019-10-03 16:49:41 -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 )
} else {
return fmt . Errorf ( "a configMap or apiCall is required for context entries" )
}
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 )
ruleString := strings . ReplaceAll ( string ( ruleBytes ) , " " , "" )
for _ , contextName := range contextNames {
2021-09-03 00:19:11 +05:30
if ! strings . Contains ( ruleString , fmt . Sprintf ( "{{" + contextName ) ) && ! strings . Contains ( ruleString , fmt . Sprintf ( "{{\\\"" + contextName ) ) {
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" )
}
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" )
}
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
}
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
func validateUserInfo ( rule kyverno . Rule ) ( string , error ) {
if err := validateRoles ( rule . MatchResources . Roles ) ; err != nil {
return "match.roles" , err
}
if err := validateSubjects ( rule . MatchResources . Subjects ) ; err != nil {
return "match.subjects" , err
}
if err := validateRoles ( rule . ExcludeResources . Roles ) ; err != nil {
return "exclude.roles" , err
}
if err := validateSubjects ( rule . ExcludeResources . Subjects ) ; err != nil {
return "exclude.subjects" , err
}
return "" , nil
}
// a role must in format namespace:name
func validateRoles ( roles [ ] string ) error {
if len ( roles ) == 0 {
return nil
}
for _ , r := range roles {
role := strings . Split ( r , ":" )
if len ( role ) != 2 {
return fmt . Errorf ( "invalid role %s, expect namespace:name" , r )
}
}
return nil
}
2019-12-05 11:55:00 -08:00
// a namespace should be set in kind ServiceAccount of a subject
2019-12-04 18:50:51 -08:00
func validateSubjects ( subjects [ ] rbacv1 . Subject ) error {
if len ( subjects ) == 0 {
return nil
}
for _ , subject := range subjects {
2019-12-05 11:55:00 -08:00
if subject . Kind == "ServiceAccount" {
2019-12-04 18:50:51 -08:00
if subject . Namespace == "" {
2019-12-05 11:57:34 -08:00
return fmt . Errorf ( "service account %s in subject expects a namespace" , subject . Name )
2019-12-04 18:50:51 -08:00
}
}
}
return nil
}
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 {
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
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
// Match and Exclude block
func checkClusterResourceInMatchAndExclude ( rule kyverno . Rule , clusterResources [ ] string ) error {
// 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" )
}
// 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 type value '%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 type value '%s' not allowed in exclude.resources.kinds" , kind )
}
}
}
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
func validateKinds ( kinds [ ] string , mock bool , client * dclient . Client , p kyverno . ClusterPolicy ) error {
for _ , kind := range kinds {
gv , k := comn . GetKindFromGVK ( kind )
if ! mock {
_ , _ , err := client . DiscoveryClient . FindResource ( gv , k )
if err != nil || strings . ToLower ( k ) == k {
return fmt . Errorf ( "match resource kind %s is invalid " , k )
}
}
if k == p . Kind {
return fmt . Errorf ( "kind and match resource kind should not be the same" )
}
}
return nil
}