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"
"reflect"
2019-12-04 18:50:51 -08:00
"strings"
2019-09-27 16:31:27 -07:00
2020-04-04 12:46:51 +05:30
"github.com/minio/minio/pkg/wildcard"
2020-03-04 19:16:26 +05:30
"github.com/nirmata/kyverno/pkg/openapi"
2019-11-13 13:41:08 -08:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2020-03-11 18:14:23 -07:00
dclient "github.com/nirmata/kyverno/pkg/dclient"
2019-12-04 18:50:51 -08:00
rbacv1 "k8s.io/api/rbac/v1"
2019-09-27 16:31:27 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
2019-10-21 14:22:31 -07:00
// Validate does some initial check to verify some conditions
// - One operation per rule
// - ResourceDescription mandatory checks
2020-03-29 09:09:26 +05:30
func Validate ( policyRaw [ ] byte , client * dclient . Client , mock bool , openAPIController * openapi . Controller ) error {
2020-03-24 23:12:45 +05:30
var p kyverno . ClusterPolicy
err := json . Unmarshal ( policyRaw , & p )
if err != nil {
return fmt . Errorf ( "failed to unmarshal policy admission request err %v" , err )
}
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-01-07 15:13:57 -08:00
if p . Spec . Background == nil {
//skipped policy mutation default -> skip validation -> will not be processed for background processing
return nil
}
if * p . Spec . Background {
2020-05-06 00:29:40 +05:30
if err := ContainsVariablesOtherThanObject ( p ) ; err != nil {
2019-12-30 17:08:50 -08:00
// policy.spec.background -> "true"
// - cannot use variables with request.userInfo
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
2020-02-18 15:00:59 -08:00
return fmt . Errorf ( "userInfo is not allowed in match or exclude when backgroud policy mode is true. Set spec.background=false to disable background mode for this policy rule. %s " , err )
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 {
// validate resource description
if path , err := validateResources ( rule ) ; err != nil {
return fmt . Errorf ( "path: spec.rules[%d].%s: %v" , i , path , err )
}
// validate rule types
// only one type of rule is allowed per rule
if err := validateRuleType ( rule ) ; err != nil {
// as there are more than 1 operation in rule, not need to evaluate it further
return fmt . Errorf ( "path: spec.rules[%d]: %v" , i , err )
2019-09-27 19:03:55 -07:00
}
2019-09-27 16:31:27 -07:00
2020-04-27 22:01:33 +05:30
if doesMatchAndExcludeConflict ( rule ) {
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
2020-03-17 17:23:18 -07:00
if err := validateActions ( i , rule , 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
// If a rules match block does not match any kind,
// we should only allow such rules to have metadata in its overlay
if len ( rule . MatchResources . Kinds ) == 0 {
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" )
}
}
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 {
if err := openAPIController . ValidatePolicyFields ( policyRaw ) ; err != nil {
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
}
2020-04-04 12:46:51 +05:30
// doesMatchAndExcludeConflict checks if the resultant
// of match and exclude block is not an empty set
func doesMatchAndExcludeConflict ( rule kyverno . Rule ) bool {
2020-03-20 20:23:34 +05:30
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
}
excludeMatchExpressions := make ( map [ string ] bool )
if rule . ExcludeResources . ResourceDescription . Selector != nil {
for _ , matchExpression := range rule . ExcludeResources . ResourceDescription . Selector . MatchExpressions {
matchExpressionRaw , _ := json . Marshal ( matchExpression )
excludeMatchExpressions [ string ( matchExpressionRaw ) ] = true
}
}
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
}
}
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 {
2020-04-04 14:49:50 +05:30
if len ( excludeMatchExpressions ) > 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 )
2020-04-04 14:49:50 +05:30
if ! excludeMatchExpressions [ 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
}
}
}
2020-04-04 12:46:51 +05:30
return true
2020-03-20 20:23:34 +05:30
}
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
}
}
for _ , pattern := range rule . Validation . AnyPattern {
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
}
2019-10-21 14:22:31 -07:00
// matched resources
if path , err := validateMatchedResourceDescription ( rule . MatchResources . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "resources.%s" , path ) , err
2019-09-27 16:31:27 -07:00
}
2019-10-21 14:22:31 -07:00
// exclude resources
if path , err := validateExcludeResourceDescription ( rule . ExcludeResources . ResourceDescription ) ; err != nil {
return fmt . Sprintf ( "resources.%s" , path ) , err
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
2019-10-21 14:22:31 -07:00
// ValidateUniqueRuleName checks if the rule names are unique across a policy
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 {
if containString ( ruleNames , rule . Name ) {
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 {
2020-04-14 19:06:48 +05:30
ruleTypes := [ ] bool { r . HasMutate ( ) , r . HasValidate ( ) , r . HasGenerate ( ) }
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 {
2019-10-21 14:22:31 -07:00
return fmt . Errorf ( "no operation defined in the rule '%s'.(supported operations: mutation,validation,generation)" , r . Name )
2019-10-03 16:49:41 -07:00
} else if operationCount != 1 {
return fmt . Errorf ( "multiple operations defined in the rule '%s', only one type of operation 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
}
2019-10-03 18:19:47 -07:00
// validateResourceDescription checks if all necesarry 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
}
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
}
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
}