1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/policy/validate.go

257 lines
7.1 KiB
Go
Raw Normal View History

package policy
import (
"errors"
"fmt"
"reflect"
2019-12-04 18:50:51 -08:00
"strings"
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"
dclient "github.com/nirmata/kyverno/pkg/dclient"
2019-12-04 18:50:51 -08:00
rbacv1 "k8s.io/api/rbac/v1"
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
func Validate(p kyverno.ClusterPolicy, client *dclient.Client, mock bool) error {
2019-10-21 14:22:31 -07:00
if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
}
593 feature (#594) * initial commit * background policy validation * correct message * skip non-background policy process for add/update * add Generate Request CR * generate Request Generator Initial * test generate request CR generation * initial commit gr generator * generate controller initial framework * add crd for generate request * gr cleanup controller initial commit * cleanup controller initial * generate mid-commit * generate rule processing * create PV on generate error * embed resource type * testing phase 1- generate resources with variable substitution * fix tests * comment broken test #586 * add printer column for state * return if existing resource for clone * set resync time to 2 mins & remove resource version check in update handler for gr * generate events for reporting * fix logs * initial commit * fix trailing quote in patch * remove comments * initial condition (equal & notequal) * initial support for conditions * initial support fo conditions in generate * support precondition checks * cleanup * re-evaluate GR on namespace update using dynamic informers * add status for generated resources * display loaded variable SA * support delete cleanup of generate request main resources * fix log * remove namespace from SA username * support multiple variables per statement for scalar values * fix fail variables * add check for userInfo * validation checks for conditions * update policy * refactor logs * code review * add openapispec for clusterpolicy preconditions * Update documentation * CR fixes * documentation * CR fixes * update variable * fix logs * update policy * pre-defined variables (serviceAccountName & serviceAccountNamespace) * update test
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 {
if err := ContainsUserInfo(p); err != nil {
// policy.spec.background -> "true"
// - cannot use variables with request.userInfo
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
return fmt.Errorf("userInfo is not allowed in match or exclude when backgroud policy mode is true. Set spec.background=false to disable background mode for this policy rule. %s ", err)
}
}
2019-10-21 14:22:31 -07:00
for i, rule := range p.Spec.Rules {
// only one type of rule is allowed per rule
if err := validateRuleType(rule); err != nil {
return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
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)
}
// 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)
}
// validate rule actions
// - Mutate
// - Validate
// - Generate
if err := validateActions(i, rule, client, mock); err != nil {
return err
}
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")
}
}
}
2020-01-25 14:53:12 +05:30
2020-03-04 19:16:26 +05:30
if err := openapi.ValidatePolicyMutation(p); err != nil {
return err
}
return nil
}
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-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-10-21 14:22:31 -07:00
return "", nil
}
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-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
}
// validateRuleType checks only one type of rule is defined per rule
func validateRuleType(r kyverno.Rule) error {
ruleTypes := []bool{r.HasMutate(), r.HasValidate(), r.HasGenerate()}
operationCount := func() int {
count := 0
for _, v := range ruleTypes {
if v {
count++
}
}
return count
}()
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)
} 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)
}
return nil
}
2019-10-03 18:19:47 -07:00
// validateResourceDescription checks if all necesarry fields are present and have values. Also checks a Selector.
// field type is checked through openapi
// Returns error if
// - kinds is empty array in matched resource block, i.e. kinds: []
// - selector is invalid
2019-10-21 14:22:31 -07:00
func validateMatchedResourceDescription(rd kyverno.ResourceDescription) (string, error) {
if reflect.DeepEqual(rd, kyverno.ResourceDescription{}) {
2019-10-21 14:22:31 -07:00
return "", fmt.Errorf("match resources not specified")
}
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
func validateResourceDescription(rd kyverno.ResourceDescription) error {
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
}