mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
refactor: Rule names validation (#3406)
* refactor: UserInfo validation Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> * refactor: Rule type validation Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> * refactor: Rule names validation Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
This commit is contained in:
parent
adcb71f1d6
commit
cc212ac766
6 changed files with 181 additions and 169 deletions
|
@ -8,118 +8,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Spec contains a list of Rule instances and other policy controls.
|
||||
type Spec struct {
|
||||
// Rules is a list of Rule instances. A Policy contains multiple rules and
|
||||
// each rule can validate, mutate, or generate resources.
|
||||
Rules []Rule `json:"rules,omitempty" yaml:"rules,omitempty"`
|
||||
|
||||
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled.
|
||||
// Rules within the same policy share the same failure behavior.
|
||||
// Allowed values are Ignore or Fail. Defaults to Fail.
|
||||
// +optional
|
||||
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" yaml:"failurePolicy,omitempty"`
|
||||
|
||||
// ValidationFailureAction controls if a validation policy rule failure should disallow
|
||||
// the admission review request (enforce), or allow (audit) the admission review request
|
||||
// and report an error in a policy report. Optional. The default value is "audit".
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=audit;enforce
|
||||
ValidationFailureAction string `json:"validationFailureAction,omitempty" yaml:"validationFailureAction,omitempty"`
|
||||
|
||||
// ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction
|
||||
// namespace-wise. It overrides ValidationFailureAction for the specified namespaces.
|
||||
// +optional
|
||||
ValidationFailureActionOverrides []ValidationFailureActionOverride `json:"validationFailureActionOverrides,omitempty" yaml:"validationFailureActionOverrides,omitempty"`
|
||||
|
||||
// Background controls if rules are applied to existing resources during a background scan.
|
||||
// Optional. Default value is "true". The value must be set to "false" if the policy rule
|
||||
// uses variables that are only available in the admission review request (e.g. user name).
|
||||
// +optional
|
||||
Background *bool `json:"background,omitempty" yaml:"background,omitempty"`
|
||||
|
||||
// SchemaValidation skips policy validation checks.
|
||||
// Optional. The default value is set to "true", it must be set to "false" to disable the validation checks.
|
||||
// +optional
|
||||
SchemaValidation *bool `json:"schemaValidation,omitempty" yaml:"schemaValidation,omitempty"`
|
||||
|
||||
// WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy.
|
||||
// After the configured time expires, the admission request may fail, or may simply ignore the policy results,
|
||||
// based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
|
||||
WebhookTimeoutSeconds *int32 `json:"webhookTimeoutSeconds,omitempty" yaml:"webhookTimeoutSeconds,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Spec) GetRules() []Rule {
|
||||
return s.Rules
|
||||
}
|
||||
|
||||
func (s *Spec) SetRules(rules []Rule) {
|
||||
s.Rules = rules
|
||||
}
|
||||
|
||||
// HasMutateOrValidateOrGenerate checks for rule types
|
||||
func (s *Spec) HasMutateOrValidateOrGenerate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasMutate() || rule.HasValidate() || rule.HasGenerate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMutate checks for mutate rule types
|
||||
func (s *Spec) HasMutate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasMutate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasValidate checks for validate rule types
|
||||
func (s *Spec) HasValidate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasValidate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasGenerate checks for generate rule types
|
||||
func (s *Spec) HasGenerate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasGenerate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasVerifyImages checks for image verification rule types
|
||||
func (s *Spec) HasVerifyImages() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasVerifyImages() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// BackgroundProcessingEnabled checks if background is set to true
|
||||
func (s *Spec) BackgroundProcessingEnabled() bool {
|
||||
if s.Background == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return *s.Background
|
||||
}
|
||||
|
||||
// FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled.
|
||||
// +kubebuilder:validation:Enum=Ignore;Fail
|
||||
type FailurePolicyType string
|
||||
|
|
|
@ -18,7 +18,6 @@ func Test_Validate_RuleType_EmptyRule(t *testing.T) {
|
|||
assert.Equal(t, errs[0].Field, "dummy")
|
||||
assert.Equal(t, errs[0].Type, field.ErrorTypeInvalid)
|
||||
assert.Equal(t, errs[0].Detail, "No operation defined in the rule 'validate-user-privilege'.(supported operations: mutate,validate,generate,verifyImages)")
|
||||
|
||||
}
|
||||
|
||||
func Test_Validate_RuleType_MultipleRule(t *testing.T) {
|
||||
|
|
38
api/kyverno/v1/spec_test.go
Normal file
38
api/kyverno/v1/spec_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func Test_Validate_UniqueRuleName(t *testing.T) {
|
||||
subject := Spec{
|
||||
Rules: []Rule{{
|
||||
Name: "deny-privileged-disallowpriviligedescalation",
|
||||
MatchResources: MatchResources{
|
||||
ResourceDescription: ResourceDescription{
|
||||
Kinds: []string{
|
||||
"Pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "deny-privileged-disallowpriviligedescalation",
|
||||
MatchResources: MatchResources{
|
||||
ResourceDescription: ResourceDescription{
|
||||
Kinds: []string{
|
||||
"Pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
path := field.NewPath("dummy")
|
||||
errs := subject.Validate(path)
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
assert.Equal(t, errs[0].Field, "dummy.rules[1].name")
|
||||
assert.Equal(t, errs[0].Type, field.ErrorTypeInvalid)
|
||||
assert.Equal(t, errs[0].Detail, "Duplicate rule name: 'deny-privileged-disallowpriviligedescalation'")
|
||||
}
|
141
api/kyverno/v1/spec_types.go
Normal file
141
api/kyverno/v1/spec_types.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// Spec contains a list of Rule instances and other policy controls.
|
||||
type Spec struct {
|
||||
// Rules is a list of Rule instances. A Policy contains multiple rules and
|
||||
// each rule can validate, mutate, or generate resources.
|
||||
Rules []Rule `json:"rules,omitempty" yaml:"rules,omitempty"`
|
||||
|
||||
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled.
|
||||
// Rules within the same policy share the same failure behavior.
|
||||
// Allowed values are Ignore or Fail. Defaults to Fail.
|
||||
// +optional
|
||||
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" yaml:"failurePolicy,omitempty"`
|
||||
|
||||
// ValidationFailureAction controls if a validation policy rule failure should disallow
|
||||
// the admission review request (enforce), or allow (audit) the admission review request
|
||||
// and report an error in a policy report. Optional. The default value is "audit".
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=audit;enforce
|
||||
ValidationFailureAction string `json:"validationFailureAction,omitempty" yaml:"validationFailureAction,omitempty"`
|
||||
|
||||
// ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction
|
||||
// namespace-wise. It overrides ValidationFailureAction for the specified namespaces.
|
||||
// +optional
|
||||
ValidationFailureActionOverrides []ValidationFailureActionOverride `json:"validationFailureActionOverrides,omitempty" yaml:"validationFailureActionOverrides,omitempty"`
|
||||
|
||||
// Background controls if rules are applied to existing resources during a background scan.
|
||||
// Optional. Default value is "true". The value must be set to "false" if the policy rule
|
||||
// uses variables that are only available in the admission review request (e.g. user name).
|
||||
// +optional
|
||||
Background *bool `json:"background,omitempty" yaml:"background,omitempty"`
|
||||
|
||||
// SchemaValidation skips policy validation checks.
|
||||
// Optional. The default value is set to "true", it must be set to "false" to disable the validation checks.
|
||||
// +optional
|
||||
SchemaValidation *bool `json:"schemaValidation,omitempty" yaml:"schemaValidation,omitempty"`
|
||||
|
||||
// WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy.
|
||||
// After the configured time expires, the admission request may fail, or may simply ignore the policy results,
|
||||
// based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
|
||||
WebhookTimeoutSeconds *int32 `json:"webhookTimeoutSeconds,omitempty" yaml:"webhookTimeoutSeconds,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Spec) GetRules() []Rule {
|
||||
return s.Rules
|
||||
}
|
||||
|
||||
func (s *Spec) SetRules(rules []Rule) {
|
||||
s.Rules = rules
|
||||
}
|
||||
|
||||
// HasMutateOrValidateOrGenerate checks for rule types
|
||||
func (s *Spec) HasMutateOrValidateOrGenerate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasMutate() || rule.HasValidate() || rule.HasGenerate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMutate checks for mutate rule types
|
||||
func (s *Spec) HasMutate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasMutate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasValidate checks for validate rule types
|
||||
func (s *Spec) HasValidate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasValidate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasGenerate checks for generate rule types
|
||||
func (s *Spec) HasGenerate() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasGenerate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasVerifyImages checks for image verification rule types
|
||||
func (s *Spec) HasVerifyImages() bool {
|
||||
for _, rule := range s.Rules {
|
||||
if rule.HasVerifyImages() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// BackgroundProcessingEnabled checks if background is set to true
|
||||
func (s *Spec) BackgroundProcessingEnabled() bool {
|
||||
if s.Background == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return *s.Background
|
||||
}
|
||||
|
||||
// ValidateRuleNames checks if the rule names are unique across a policy
|
||||
func (s *Spec) ValidateRuleNames(path *field.Path) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
names := sets.NewString()
|
||||
for i, rule := range s.Rules {
|
||||
rulePath := path.Index(i)
|
||||
if names.Has(rule.Name) {
|
||||
errs = append(errs, field.Invalid(rulePath.Child("name"), rule, fmt.Sprintf(`Duplicate rule name: '%s'`, rule.Name)))
|
||||
}
|
||||
names.Insert(rule.Name)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate implements programmatic validation
|
||||
func (s *Spec) Validate(path *field.Path) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
errs = append(errs, s.ValidateRuleNames(path.Child("rules"))...)
|
||||
return errs
|
||||
}
|
|
@ -95,8 +95,8 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
|
|||
return nil, fmt.Errorf("invalid policy name %s: must be no more than 63 characters", policy.Name)
|
||||
}
|
||||
|
||||
if path, err := validateUniqueRuleName(*policy); err != nil {
|
||||
return nil, fmt.Errorf("path: spec.%s: %v", path, err)
|
||||
if errs := policy.Spec.Validate(specPath); len(errs) != 0 {
|
||||
return nil, errs.ToAggregate()
|
||||
}
|
||||
|
||||
if policy.ObjectMeta.Namespace != "" {
|
||||
|
@ -1096,19 +1096,6 @@ func validateConditionValuesKeyRequestOperation(c kyverno.Condition) (string, er
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// validateUniqueRuleName checks if the rule names are unique across a policy
|
||||
func validateUniqueRuleName(p kyverno.ClusterPolicy) (string, error) {
|
||||
var ruleNames []string
|
||||
|
||||
for i, rule := range p.Spec.GetRules() {
|
||||
if utils.ContainsString(ruleNames, rule.Name) {
|
||||
return fmt.Sprintf("rule[%d]", i), fmt.Errorf(`duplicate rule name: '%s'`, rule.Name)
|
||||
}
|
||||
ruleNames = append(ruleNames, rule.Name)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func validateRuleContext(rule kyverno.Rule) error {
|
||||
if rule.Context == nil || len(rule.Context) == 0 {
|
||||
return nil
|
||||
|
|
|
@ -13,47 +13,6 @@ import (
|
|||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func Test_Validate_UniqueRuleName(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"rules": [
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {}
|
||||
},
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var policy *kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateUniqueRuleName(*policy)
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_Empty(t *testing.T) {
|
||||
var err error
|
||||
rawResourcedescirption := []byte(`{}`)
|
||||
|
|
Loading…
Reference in a new issue