mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
disallow variabels in clone/cloneList (#6438)
Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
parent
ea306d6d7f
commit
c8a3b19d2c
14 changed files with 747 additions and 83 deletions
|
@ -3,6 +3,7 @@ package v1
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
@ -560,6 +561,24 @@ type CloneList struct {
|
|||
Selector *metav1.LabelSelector `json:"selector,omitempty" yaml:"selector,omitempty"`
|
||||
}
|
||||
|
||||
func (g *Generation) Validate() error {
|
||||
generateType, _ := g.GetTypeAndSync()
|
||||
if generateType == Data {
|
||||
return nil
|
||||
}
|
||||
|
||||
newGeneration := Generation{
|
||||
ResourceSpec: ResourceSpec{
|
||||
Kind: g.ResourceSpec.GetKind(),
|
||||
APIVersion: g.ResourceSpec.GetAPIVersion(),
|
||||
},
|
||||
Clone: g.Clone,
|
||||
CloneList: g.CloneList,
|
||||
}
|
||||
|
||||
return regex.ObjectHasVariables(newGeneration)
|
||||
}
|
||||
|
||||
func (g *Generation) GetData() apiextensions.JSON {
|
||||
return FromJSON(g.RawData)
|
||||
}
|
||||
|
|
|
@ -834,3 +834,306 @@ func Test_Validate_ClusterPolicy_MutateRuleTargetNamespace(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ClusterPolicy_Generate_Variables(t *testing.T) {
|
||||
path := field.NewPath("dummy")
|
||||
testcases := []struct {
|
||||
name string
|
||||
rule []byte
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "clone-name",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "{{request.object.metadata.name}}"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "clone-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"kinds": [
|
||||
"v1/Secret",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"allowedToBeCloned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-kinds",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "default",
|
||||
"kinds": [
|
||||
"{{request.object.metadata.kind}}",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"allowedToBeCloned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-selector",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "default",
|
||||
"kinds": [
|
||||
"v1/Secret",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"{{request.object.metadata.name}}": "clone"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-kind",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "{{request.object.metadata.kind}}",
|
||||
"name": "regcred",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-apiversion",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"kind": "Secret",
|
||||
"apiVersion": "{{request.object.metadata.apiVersion}}",
|
||||
"name": "regcred",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-name",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "{{request.object.metadata.name}}",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
var rule *Rule
|
||||
err := json.Unmarshal(testcase.rule, &rule)
|
||||
assert.NilError(t, err, testcase.name)
|
||||
errs := rule.ValidateGenerateVariables(path)
|
||||
assert.Equal(t, len(errs) != 0, testcase.shouldFail, testcase.name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -395,6 +395,17 @@ func (r *Rule) ValidatePSaControlNames(path *field.Path) (errs field.ErrorList)
|
|||
return errs
|
||||
}
|
||||
|
||||
func (r *Rule) ValidateGenerateVariables(path *field.Path) (errs field.ErrorList) {
|
||||
if !r.HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.Generation.Validate(); err != nil {
|
||||
errs = append(errs, field.Forbidden(path.Child("generate").Child("clone/cloneList"), fmt.Sprintf("Generation Rule Clone/CloneList \"%s\" should not have variables", r.Name)))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate implements programmatic validation
|
||||
func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
errs = append(errs, r.ValidateRuleType(path)...)
|
||||
|
@ -403,5 +414,6 @@ func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace strin
|
|||
errs = append(errs, r.ExcludeResources.Validate(path.Child("exclude"), namespaced, clusterResources)...)
|
||||
errs = append(errs, r.ValidateMutationRuleTargetNamespace(path, namespaced, policyNamespace)...)
|
||||
errs = append(errs, r.ValidatePSaControlNames(path)...)
|
||||
errs = append(errs, r.ValidateGenerateVariables(path)...)
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -16,11 +16,10 @@ limitations under the License.
|
|||
package v2alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"golang.org/x/exp/slices"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
@ -42,9 +41,6 @@ type PolicyException struct {
|
|||
Spec PolicyExceptionSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// regexVariables represents regex for '{{}}'
|
||||
var regexVariables = regexp.MustCompile(`\{\{[^{}]*\}\}`)
|
||||
|
||||
// Validate implements programmatic validation
|
||||
func (p *PolicyException) Validate() (errs field.ErrorList) {
|
||||
if err := ValidateVariables(p); err != nil {
|
||||
|
@ -55,19 +51,7 @@ func (p *PolicyException) Validate() (errs field.ErrorList) {
|
|||
}
|
||||
|
||||
func ValidateVariables(polex *PolicyException) error {
|
||||
return objectHasVariables(polex)
|
||||
}
|
||||
|
||||
func objectHasVariables(object interface{}) error {
|
||||
var err error
|
||||
objectJSON, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(regexVariables.FindAllStringSubmatch(string(objectJSON), -1)) > 0 {
|
||||
return fmt.Errorf("variables are not allowed")
|
||||
}
|
||||
return nil
|
||||
return regex.ObjectHasVariables(polex)
|
||||
}
|
||||
|
||||
// Contains returns true if it contains an exception for the given policy/rule pair
|
||||
|
|
|
@ -235,3 +235,306 @@ func Test_doesMatchExcludeConflict(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ClusterPolicy_Generate_Variables(t *testing.T) {
|
||||
path := field.NewPath("dummy")
|
||||
testcases := []struct {
|
||||
name string
|
||||
rule []byte
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "clone-name",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "{{request.object.metadata.name}}"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "clone-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"kinds": [
|
||||
"v1/Secret",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"allowedToBeCloned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-kinds",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "default",
|
||||
"kinds": [
|
||||
"{{request.object.metadata.kind}}",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"allowedToBeCloned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "cloneList-selector",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "sync-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"namespace": "test",
|
||||
"synchronize": true,
|
||||
"cloneList": {
|
||||
"namespace": "default",
|
||||
"kinds": [
|
||||
"v1/Secret",
|
||||
"v1/ConfigMap"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"{{request.object.metadata.name}}": "clone"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-namespace",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "regcred",
|
||||
"namespace": "{{request.object.metadata.name}}",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-kind",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "{{request.object.metadata.kind}}",
|
||||
"name": "regcred",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-apiversion",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"kind": "Secret",
|
||||
"apiVersion": "{{request.object.metadata.apiVersion}}",
|
||||
"name": "regcred",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "generate-downstream-name",
|
||||
rule: []byte(`
|
||||
{
|
||||
"name": "clone-secret",
|
||||
"match": {
|
||||
"any": [
|
||||
{
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generate": {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"name": "{{request.object.metadata.name}}",
|
||||
"namespace": "default",
|
||||
"synchronize": true,
|
||||
"clone": {
|
||||
"namespace": "default",
|
||||
"name": "regcred"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
var rule *Rule
|
||||
err := json.Unmarshal(testcase.rule, &rule)
|
||||
assert.NilError(t, err, testcase.name)
|
||||
errs := rule.ValidateGenerateVariables(path)
|
||||
assert.Equal(t, len(errs) != 0, testcase.shouldFail, testcase.name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,11 +174,23 @@ func (r *Rule) ValidateMatchExcludeConflict(path *field.Path) (errs field.ErrorL
|
|||
return append(errs, field.Invalid(path, r, "Rule is matching an empty set"))
|
||||
}
|
||||
|
||||
func (r *Rule) ValidateGenerateVariables(path *field.Path) (errs field.ErrorList) {
|
||||
if !r.HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.Generation.Validate(); err != nil {
|
||||
errs = append(errs, field.Forbidden(path.Child("generate").Child("clone/cloneList"), fmt.Sprintf("Generation Rule Clone/CloneList \"%s\" should not have variables", r.Name)))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate implements programmatic validation
|
||||
func (r *Rule) Validate(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
errs = append(errs, r.ValidateRuleType(path)...)
|
||||
errs = append(errs, r.ValidateMatchExcludeConflict(path)...)
|
||||
errs = append(errs, r.MatchResources.Validate(path.Child("match"), namespaced, clusterResources)...)
|
||||
errs = append(errs, r.ExcludeResources.Validate(path.Child("exclude"), namespaced, clusterResources)...)
|
||||
errs = append(errs, r.ValidateGenerateVariables(path)...)
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
engineContext "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
||||
|
@ -99,7 +99,7 @@ type ApplyPolicyConfig struct {
|
|||
// HasVariables - check for variables in the policy
|
||||
func HasVariables(policy kyvernov1.PolicyInterface) [][]string {
|
||||
policyRaw, _ := json.Marshal(policy)
|
||||
matches := variables.RegexVariables.FindAllStringSubmatch(string(policyRaw), -1)
|
||||
matches := regex.RegexVariables.FindAllStringSubmatch(string(policyRaw), -1)
|
||||
return matches
|
||||
}
|
||||
|
||||
|
|
30
pkg/engine/variables/regex/utils.go
Normal file
30
pkg/engine/variables/regex/utils.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package regex
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// IsVariable returns true if the element contains a 'valid' variable {{}}
|
||||
func IsVariable(value string) bool {
|
||||
groups := RegexVariables.FindAllStringSubmatch(value, -1)
|
||||
return len(groups) != 0
|
||||
}
|
||||
|
||||
// IsReference returns true if the element contains a 'valid' reference $()
|
||||
func IsReference(value string) bool {
|
||||
groups := RegexReferences.FindAllStringSubmatch(value, -1)
|
||||
return len(groups) != 0
|
||||
}
|
||||
|
||||
func ObjectHasVariables(object interface{}) error {
|
||||
var err error
|
||||
objectJSON, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(RegexVariables.FindAllStringSubmatch(string(objectJSON), -1)) > 0 {
|
||||
return fmt.Errorf("variables are not allowed")
|
||||
}
|
||||
return nil
|
||||
}
|
19
pkg/engine/variables/regex/vars.go
Normal file
19
pkg/engine/variables/regex/vars.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package regex
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
RegexVariables = regexp.MustCompile(`(^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
||||
|
||||
RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||
|
||||
// RegexReferences is the Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\'
|
||||
RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
|
||||
|
||||
// RegexEscpReferences is the Regex for '\$(...)'
|
||||
RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`)
|
||||
|
||||
RegexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||
|
||||
RegexElementIndex = regexp.MustCompile(`{{\s*elementIndex\d*\s*}}`)
|
||||
)
|
22
pkg/engine/variables/regex/vars_test.go
Normal file
22
pkg/engine/variables/regex/vars_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package regex
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func Test_RegexVariables(t *testing.T) {
|
||||
vars := RegexVariables.FindAllString("tag: {{ value }}", -1)
|
||||
assert.Equal(t, len(vars), 1)
|
||||
assert.Equal(t, vars[0], " {{ value }}")
|
||||
|
||||
res := RegexVariables.ReplaceAllString("tag: {{ value }}", "${1}test")
|
||||
assert.Equal(t, res, "tag: test")
|
||||
}
|
||||
|
||||
func Test_IsVariable(t *testing.T) {
|
||||
assert.Equal(t, IsVariable("{{ foo }}"), true)
|
||||
assert.Equal(t, IsVariable("{{ foo {{foo2}} }}"), true)
|
||||
assert.Equal(t, IsVariable("\\{{ foo }}"), false)
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -15,41 +14,16 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
jsonUtils "github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
|
||||
)
|
||||
|
||||
var RegexVariables = regexp.MustCompile(`(^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
||||
|
||||
var RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||
|
||||
// RegexReferences is the Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\'
|
||||
var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
|
||||
|
||||
// RegexEscpReferences is the Regex for '\$(...)'
|
||||
var RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`)
|
||||
|
||||
var regexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||
|
||||
var regexElementIndex = regexp.MustCompile(`{{\s*elementIndex\d*\s*}}`)
|
||||
|
||||
// IsVariable returns true if the element contains a 'valid' variable {{}}
|
||||
func IsVariable(value string) bool {
|
||||
groups := RegexVariables.FindAllStringSubmatch(value, -1)
|
||||
return len(groups) != 0
|
||||
}
|
||||
|
||||
// IsReference returns true if the element contains a 'valid' reference $()
|
||||
func IsReference(value string) bool {
|
||||
groups := RegexReferences.FindAllStringSubmatch(value, -1)
|
||||
return len(groups) != 0
|
||||
}
|
||||
|
||||
// ReplaceAllVars replaces all variables with the value defined in the replacement function
|
||||
// This is used to avoid validation errors
|
||||
func ReplaceAllVars(src string, repl func(string) string) string {
|
||||
wrapper := func(s string) string {
|
||||
initial := len(regexVariableInit.FindAllString(s, -1)) > 0
|
||||
initial := len(regex.RegexVariableInit.FindAllString(s, -1)) > 0
|
||||
prefix := ""
|
||||
|
||||
if !initial {
|
||||
|
@ -60,7 +34,7 @@ func ReplaceAllVars(src string, repl func(string) string) string {
|
|||
return prefix + repl(s)
|
||||
}
|
||||
|
||||
return RegexVariables.ReplaceAllStringFunc(src, wrapper)
|
||||
return regex.RegexVariables.ReplaceAllStringFunc(src, wrapper)
|
||||
}
|
||||
|
||||
func newPreconditionsVariableResolver(log logr.Logger) VariableResolver {
|
||||
|
@ -255,9 +229,9 @@ func validateElementInForEach(log logr.Logger) jsonUtils.Action {
|
|||
if !ok {
|
||||
return data.Element, nil
|
||||
}
|
||||
vars := RegexVariables.FindAllString(value, -1)
|
||||
vars := regex.RegexVariables.FindAllString(value, -1)
|
||||
for _, v := range vars {
|
||||
initial := len(regexVariableInit.FindAllString(v, -1)) > 0
|
||||
initial := len(regex.RegexVariableInit.FindAllString(v, -1)) > 0
|
||||
|
||||
if !initial {
|
||||
v = v[1:]
|
||||
|
@ -290,7 +264,7 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
|
|||
return data.Element, nil
|
||||
}
|
||||
|
||||
for _, v := range RegexReferences.FindAllString(value, -1) {
|
||||
for _, v := range regex.RegexReferences.FindAllString(value, -1) {
|
||||
initial := v[:2] == `$(`
|
||||
old := v
|
||||
|
||||
|
@ -333,7 +307,7 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
|
|||
}
|
||||
}
|
||||
|
||||
for _, v := range RegexEscpReferences.FindAllString(value, -1) {
|
||||
for _, v := range regex.RegexEscpReferences.FindAllString(value, -1) {
|
||||
value = strings.Replace(value, v, v[1:], -1)
|
||||
}
|
||||
|
||||
|
@ -358,11 +332,11 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
|
|||
|
||||
isDeleteRequest := IsDeleteRequest(ctx)
|
||||
|
||||
vars := RegexVariables.FindAllString(value, -1)
|
||||
vars := regex.RegexVariables.FindAllString(value, -1)
|
||||
for len(vars) > 0 {
|
||||
originalPattern := value
|
||||
for _, v := range vars {
|
||||
initial := len(regexVariableInit.FindAllString(v, -1)) > 0
|
||||
initial := len(regex.RegexVariableInit.FindAllString(v, -1)) > 0
|
||||
old := v
|
||||
|
||||
if !initial {
|
||||
|
@ -419,10 +393,10 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
|
|||
}
|
||||
|
||||
// check for nested variables in strings
|
||||
vars = RegexVariables.FindAllString(value, -1)
|
||||
vars = regex.RegexVariables.FindAllString(value, -1)
|
||||
}
|
||||
|
||||
for _, v := range RegexEscpVariables.FindAllString(value, -1) {
|
||||
for _, v := range regex.RegexEscpVariables.FindAllString(value, -1) {
|
||||
value = strings.Replace(value, v, v[1:], -1)
|
||||
}
|
||||
|
||||
|
@ -515,7 +489,7 @@ func valFromReferenceToString(value interface{}, operator string) (string, error
|
|||
}
|
||||
|
||||
func FindAndShiftReferences(log logr.Logger, value, shift, pivot string) string {
|
||||
for _, reference := range RegexReferences.FindAllString(value, -1) {
|
||||
for _, reference := range regex.RegexReferences.FindAllString(value, -1) {
|
||||
initial := reference[:2] == `$(`
|
||||
oldReference := reference
|
||||
|
||||
|
@ -581,19 +555,19 @@ func replaceSubstituteVariables(document interface{}) interface{} {
|
|||
}
|
||||
|
||||
for {
|
||||
if len(regexElementIndex.FindAllSubmatch(rawDocument, -1)) == 0 {
|
||||
if len(regex.RegexElementIndex.FindAllSubmatch(rawDocument, -1)) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
rawDocument = regexElementIndex.ReplaceAll(rawDocument, []byte(`0`))
|
||||
rawDocument = regex.RegexElementIndex.ReplaceAll(rawDocument, []byte(`0`))
|
||||
}
|
||||
|
||||
for {
|
||||
if len(RegexVariables.FindAllSubmatch(rawDocument, -1)) == 0 {
|
||||
if len(regex.RegexVariables.FindAllSubmatch(rawDocument, -1)) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
rawDocument = RegexVariables.ReplaceAll(rawDocument, []byte(`${1}placeholderValue`))
|
||||
rawDocument = regex.RegexVariables.ReplaceAll(rawDocument, []byte(`${1}placeholderValue`))
|
||||
}
|
||||
|
||||
var output interface{}
|
||||
|
|
|
@ -1180,21 +1180,6 @@ func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
|||
assert.Equal(t, fmt.Sprintf("%v", pattern), "{{request.object.metadata.annotations.target}}")
|
||||
}
|
||||
|
||||
func Test_RegexVariables(t *testing.T) {
|
||||
vars := RegexVariables.FindAllString("tag: {{ value }}", -1)
|
||||
assert.Equal(t, len(vars), 1)
|
||||
assert.Equal(t, vars[0], " {{ value }}")
|
||||
|
||||
res := RegexVariables.ReplaceAllString("tag: {{ value }}", "${1}test")
|
||||
assert.Equal(t, res, "tag: test")
|
||||
}
|
||||
|
||||
func Test_IsVariable(t *testing.T) {
|
||||
assert.Equal(t, IsVariable("{{ foo }}"), true)
|
||||
assert.Equal(t, IsVariable("{{ foo {{foo2}} }}"), true)
|
||||
assert.Equal(t, IsVariable("\\{{ foo }}"), false)
|
||||
}
|
||||
|
||||
func Test_ReplaceAllVars(t *testing.T) {
|
||||
result := ReplaceAllVars("{{ foo }}", func(s string) string { return "test" })
|
||||
assert.Equal(t, result, "test")
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/kyverno/kyverno/pkg/policy/common"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
"github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
|
@ -112,7 +112,7 @@ func (g *Generate) validateClone(c kyvernov1.CloneFrom, cl kyvernov1.CloneList,
|
|||
|
||||
namespace := c.Namespace
|
||||
// Skip if there is variable defined
|
||||
if !variables.IsVariable(kind) && !variables.IsVariable(namespace) {
|
||||
if !regex.IsVariable(kind) && !regex.IsVariable(namespace) {
|
||||
// GET
|
||||
ok, err := g.authCheck.CanIGet(context.TODO(), kind, namespace)
|
||||
if err != nil {
|
||||
|
@ -131,7 +131,7 @@ func (g *Generate) validateClone(c kyvernov1.CloneFrom, cl kyvernov1.CloneList,
|
|||
func (g *Generate) canIGenerate(kind, namespace string) error {
|
||||
// Skip if there is variable defined
|
||||
authCheck := g.authCheck
|
||||
if !variables.IsVariable(kind) && !variables.IsVariable(namespace) {
|
||||
if !regex.IsVariable(kind) && !regex.IsVariable(namespace) {
|
||||
// CREATE
|
||||
ok, err := authCheck.CanICreate(context.TODO(), kind, namespace)
|
||||
if err != nil {
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
|
||||
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
|
||||
|
@ -558,7 +559,7 @@ func ruleForbiddenSectionsHaveVariables(rule *kyvernov1.Rule) error {
|
|||
// hasVariables - check for variables in the policy
|
||||
func hasVariables(policy kyvernov1.PolicyInterface) [][]string {
|
||||
policyRaw, _ := json.Marshal(policy)
|
||||
matches := variables.RegexVariables.FindAllStringSubmatch(string(policyRaw), -1)
|
||||
matches := regex.RegexVariables.FindAllStringSubmatch(string(policyRaw), -1)
|
||||
return matches
|
||||
}
|
||||
|
||||
|
@ -579,7 +580,7 @@ func jsonPatchPathHasVariables(patch string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
vars := variables.RegexVariables.FindAllString(path, -1)
|
||||
vars := regex.RegexVariables.FindAllString(path, -1)
|
||||
if len(vars) > 0 {
|
||||
return errOperationForbidden
|
||||
}
|
||||
|
@ -606,7 +607,7 @@ func imageRefHasVariables(verifyImages []kyvernov1.ImageVerification) error {
|
|||
for _, verifyImage := range verifyImages {
|
||||
verifyImage = *verifyImage.Convert()
|
||||
for _, imageRef := range verifyImage.ImageReferences {
|
||||
matches := variables.RegexVariables.FindAllString(imageRef, -1)
|
||||
matches := regex.RegexVariables.FindAllString(imageRef, -1)
|
||||
if len(matches) > 0 {
|
||||
return fmt.Errorf("variables are not allowed in image reference")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue