1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 02:18:15 +00:00

fix: target scope validation for the generate rule (#7479)

* fix target scope validation for generate

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* add kuttl tests

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
shuting 2023-06-13 18:26:56 +08:00 committed by GitHub
parent 5fa6e1fa48
commit 5ce80c4e68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 777 additions and 243 deletions

View file

@ -620,9 +620,15 @@ type CloneList struct {
Selector *metav1.LabelSelector `json:"selector,omitempty" yaml:"selector,omitempty"`
}
func (g *Generation) Validate(path *field.Path, clusterResources sets.Set[string]) (errs field.ErrorList) {
if err := g.validateTargetsScope(clusterResources); err != nil {
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if namespaced {
if err := g.validateTargetsScope(clusterResources, policyNamespace); err != nil {
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
}
} else {
if g.GetNamespace() == "" && g.CloneList.Namespace == "" {
errs = append(errs, field.Forbidden(path.Child("generate"), "target namespace must be set in a clusterpolicy"))
}
}
generateType, _ := g.GetTypeAndSync()
@ -662,15 +668,28 @@ func (g *Generation) SetData(in apiextensions.JSON) {
g.RawData = ToJSON(in)
}
func (g *Generation) validateTargetsScope(clusterResources sets.Set[string]) error {
func (g *Generation) validateTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
target := g.ResourceSpec
if clusterResources.Has(target.GetKind()) {
if target.GetNamespace() != "" {
return fmt.Errorf("the target namespace must not be set for cluster-wide resource: %v", target.GetKind())
if clusterResources.Has(target.GetAPIVersion() + "/" + target.GetKind()) {
return fmt.Errorf("the target must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
}
if g.GetNamespace() != policyNamespace {
return fmt.Errorf("a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v", policyNamespace, g.GetNamespace())
}
if g.Clone.Name != "" {
if g.Clone.Namespace != policyNamespace {
return fmt.Errorf("a namespaced policy cannot clone resources from other namespaces, expected: %v, received: %v", policyNamespace, g.Clone.Namespace)
}
} else {
if target.GetNamespace() == "" {
return fmt.Errorf("the target namespace must be set for namespaced resource: %v", target.GetKind())
}
for _, kind := range g.CloneList.Kinds {
if clusterResources.Has(kind) {
return fmt.Errorf("the source in cloneList must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
}
if g.CloneList.Namespace != policyNamespace {
return fmt.Errorf("a namespaced policy cannot clone resources from other namespace, expected: %v, received: %v", policyNamespace, g.CloneList.Namespace)
}
}

View file

@ -1133,7 +1133,7 @@ func Test_Validate_ClusterPolicy_Generate_Variables(t *testing.T) {
var rule *Rule
err := json.Unmarshal(testcase.rule, &rule)
assert.NilError(t, err, testcase.name)
errs := rule.ValidateGenerate(path, nil)
errs := rule.ValidateGenerate(path, false, "", nil)
assert.Equal(t, len(errs) != 0, testcase.shouldFail, testcase.name)
}
}

View file

@ -404,12 +404,12 @@ func (r *Rule) ValidatePSaControlNames(path *field.Path) (errs field.ErrorList)
return errs
}
func (r *Rule) ValidateGenerate(path *field.Path, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (r *Rule) ValidateGenerate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if !r.HasGenerate() {
return nil
}
return r.Generation.Validate(path, clusterResources)
return r.Generation.Validate(path, namespaced, policyNamespace, clusterResources)
}
// Validate implements programmatic validation
@ -420,6 +420,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.ValidateGenerate(path, clusterResources)...)
errs = append(errs, r.ValidateGenerate(path, namespaced, policyNamespace, clusterResources)...)
return errs
}

View file

@ -105,7 +105,7 @@ func (p *ClusterPolicy) IsReady() bool {
func (p *ClusterPolicy) Validate(clusterResources sets.Set[string]) (errs field.ErrorList) {
errs = append(errs, kyvernov1.ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
errs = append(errs, kyvernov1.ValidatePolicyName(field.NewPath("name"), p.Name)...)
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), clusterResources)...)
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.Namespace, clusterResources)...)
return errs
}

View file

@ -105,7 +105,7 @@ func (p *Policy) IsReady() bool {
func (p *Policy) Validate(clusterResources sets.Set[string]) (errs field.ErrorList) {
errs = append(errs, kyvernov1.ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
errs = append(errs, kyvernov1.ValidatePolicyName(field.NewPath("name"), p.Name)...)
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), clusterResources)...)
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.Namespace, clusterResources)...)
return errs
}

View file

@ -13,7 +13,7 @@ func Test_Validate_RuleType_EmptyRule(t *testing.T) {
Name: "validate-user-privilege",
}
path := field.NewPath("dummy")
errs := subject.Validate(path, false, nil)
errs := subject.Validate(path, false, "", nil)
assert.Equal(t, len(errs), 1)
assert.Equal(t, errs[0].Field, "dummy")
assert.Equal(t, errs[0].Type, field.ErrorTypeInvalid)
@ -94,7 +94,7 @@ func Test_Validate_RuleType_MultipleRule(t *testing.T) {
assert.NilError(t, err)
for _, rule := range policy.Spec.Rules {
path := field.NewPath("dummy")
errs := rule.Validate(path, false, nil)
errs := rule.Validate(path, false, "", nil)
assert.Assert(t, len(errs) != 0)
}
}
@ -153,7 +153,7 @@ func Test_Validate_RuleType_SingleRule(t *testing.T) {
assert.NilError(t, err)
for _, rule := range policy.Spec.Rules {
path := field.NewPath("dummy")
errs := rule.Validate(path, false, nil)
errs := rule.Validate(path, false, "", nil)
assert.Assert(t, len(errs) == 0)
}
}
@ -534,7 +534,7 @@ func Test_Validate_ClusterPolicy_Generate_Variables(t *testing.T) {
var rule *Rule
err := json.Unmarshal(testcase.rule, &rule)
assert.NilError(t, err, testcase.name)
errs := rule.ValidateGenerate(path, nil)
errs := rule.ValidateGenerate(path, false, "", nil)
assert.Equal(t, len(errs) != 0, testcase.shouldFail, testcase.name)
}
}

View file

@ -183,20 +183,20 @@ 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) ValidateGenerate(path *field.Path, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (r *Rule) ValidateGenerate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if !r.HasGenerate() {
return nil
}
return r.Generation.Validate(path, clusterResources)
return r.Generation.Validate(path, namespaced, policyNamespace, clusterResources)
}
// Validate implements programmatic validation
func (r *Rule) Validate(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace string, 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.ValidateGenerate(path, clusterResources)...)
errs = append(errs, r.ValidateGenerate(path, namespaced, policyNamespace, clusterResources)...)
return errs
}

View file

@ -47,7 +47,7 @@ func Test_Validate_UniqueRuleName(t *testing.T) {
}},
}
path := field.NewPath("dummy")
errs := subject.Validate(path, false, nil)
errs := subject.Validate(path, false, "", nil)
assert.Equal(t, len(errs), 1)
assert.Equal(t, errs[0].Field, "dummy.rules[1].name")
assert.Equal(t, errs[0].Type, field.ErrorTypeInvalid)

View file

@ -216,10 +216,10 @@ func (s *Spec) ValidateRuleNames(path *field.Path) (errs field.ErrorList) {
}
// ValidateRules implements programmatic validation of Rules
func (s *Spec) ValidateRules(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (s *Spec) ValidateRules(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
errs = append(errs, s.ValidateRuleNames(path)...)
for i, rule := range s.Rules {
errs = append(errs, rule.Validate(path.Index(i), namespaced, clusterResources)...)
errs = append(errs, rule.Validate(path.Index(i), namespaced, policyNamespace, clusterResources)...)
}
return errs
}
@ -232,14 +232,14 @@ func (s *Spec) ValidateDeprecatedFields(path *field.Path) (errs field.ErrorList)
}
// Validate implements programmatic validation
func (s *Spec) Validate(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (s *Spec) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if err := s.ValidateDeprecatedFields(path); err != nil {
errs = append(errs, err...)
}
if s.WebhookTimeoutSeconds != nil && (*s.WebhookTimeoutSeconds < 1 || *s.WebhookTimeoutSeconds > 30) {
errs = append(errs, field.Invalid(path.Child("webhookTimeoutSeconds"), s.WebhookTimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
}
errs = append(errs, s.ValidateRules(path.Child("rules"), namespaced, clusterResources)...)
errs = append(errs, s.ValidateRules(path.Child("rules"), namespaced, policyNamespace, clusterResources)...)
if namespaced && len(s.ValidationFailureActionOverrides) > 0 {
errs = append(errs, field.Forbidden(path.Child("validationFailureActionOverrides"), "Use of validationFailureActionOverrides is supported only with ClusterPolicy"))
}

View file

@ -33,6 +33,7 @@ func FetchClusteredResources(logger logr.Logger, client dclient.Interface) (sets
for _, resList := range res {
for _, r := range resList.APIResources {
if !r.Namespaced {
clusterResources.Insert(resList.GroupVersion + "/" + r.Kind)
clusterResources.Insert(r.Kind)
}
}

View file

@ -8,8 +8,6 @@ import (
"fmt"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
@ -48,63 +46,6 @@ func immutableGenerateFields(new, old kyvernov1.PolicyInterface) error {
return nil
}
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
// Match and Exclude block
func checkClusterResourceInMatchAndExclude(rule kyvernov1.Rule, clusterResources sets.Set[string], policyNamespace string, mock bool, res []*metav1.APIResourceList) error {
if !mock {
// 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)
}
if rule.Generation.Namespace != policyNamespace {
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
}
if rule.Generation.Clone.Name != "" {
if rule.Generation.Clone.Namespace != policyNamespace {
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resources to or from other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Clone.Namespace)
}
}
} 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)
}
if policyNamespace != "" {
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
}
}
} else if len(rule.Generation.CloneList.Kinds) != 0 {
for _, kind := range rule.Generation.CloneList.Kinds {
_, splitkind := kubeutils.GetKindFromGVK(kind)
if r.Kind == splitkind {
if r.Namespaced {
if rule.Generation.CloneList.Namespace != policyNamespace {
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resource in other namespace, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
}
} else {
if policyNamespace != "" {
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
}
}
}
}
}
}
}
}
}
return nil
}
func resetMutableFields(rule kyvernov1.Rule) *kyvernov1.Rule {
new := new(kyvernov1.Rule)
rule.DeepCopyInto(new)

View file

@ -165,6 +165,7 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
for _, resList := range res {
for _, r := range resList.APIResources {
if !r.Namespaced {
clusterResources.Insert(resList.GroupVersion + "/" + r.Kind)
clusterResources.Insert(r.Kind)
}
}
@ -277,14 +278,6 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
}
}
// validate Cluster Resources in namespaced policy
// For namespaced policy, ClusterResource type field and values are not allowed in match and exclude
if policy.IsNamespaced() {
if err := checkClusterResourceInMatchAndExclude(rule, clusterResources, policy.GetNamespace(), mock, res); err != nil {
return warnings, err
}
}
if err := validateActions(i, &rules[i], client, mock, username); err != nil {
return warnings, err
}

View file

@ -13,7 +13,6 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
@ -1094,136 +1093,6 @@ func Test_Namespced_Policy(t *testing.T) {
assert.Assert(t, err != nil)
}
func Test_Namespaced_Generate_Policy(t *testing.T) {
testcases := []struct {
description string
rule []byte
policyNamespace string
expectedError error
}{
{
description: "Only generate resource where the policy exists",
rule: []byte(`
{"name": "gen-zk",
"generate": {
"synchronize": false,
"apiVersion": "v1",
"kind": "ConfigMap",
"name": "zk",
"namespace": "default",
"data": {
"kind": "ConfigMap",
"metadata": {
"labels": {
"somekey": "somevalue"
}
},
"data": {
"ZK_ADDRESS": "192.168.10.10:2181",
"KAFKA_ADDRESS": "192.168.10.13:9092"
}
}
}
}`),
policyNamespace: "poltest",
expectedError: errors.New("path: spec.rules[gen-zk]: a namespaced policy cannot generate resources in other namespaces, expected: poltest, received: default"),
},
{
description: "Not allowed to clone resource outside the policy namespace",
rule: []byte(`
{
"name": "sync-image-pull-secret",
"generate": {
"apiVersion": "v1",
"kind": "Secret",
"name": "secret-basic-auth-gen",
"namespace": "poltest",
"synchronize": true,
"clone": {
"namespace": "default",
"name": "secret-basic-auth"
}
}
}`),
policyNamespace: "poltest",
expectedError: errors.New("path: spec.rules[sync-image-pull-secret]: a namespaced policy cannot clone resources to or from other namespaces, expected: poltest, received: default"),
},
{
description: "Do not mention the namespace to generate cluster scoped resource",
rule: []byte(`
{
"name": "sync-clone",
"generate": {
"apiVersion": "storage.k8s.io/v1",
"kind": "StorageClass",
"name": "local-class",
"namespace": "poltest",
"synchronize": true,
"clone": {
"name": "pv-class"
}
}
}`),
policyNamespace: "poltest",
expectedError: errors.New("path: spec.rules[sync-clone]: do not mention the namespace to generate a non namespaced resource"),
},
{
description: "Not allowed to clone cluster scoped resource",
rule: []byte(`
{
"name": "sync-clone",
"generate": {
"apiVersion": "storage.k8s.io/v1",
"kind": "StorageClass",
"name": "local-class",
"synchronize": true,
"clone": {
"name": "pv-class"
}
}
}`),
policyNamespace: "poltest",
expectedError: errors.New("path: spec.rules[sync-clone]: a namespaced policy cannot generate cluster-wide resources"),
},
{
description: "Not allowed to multi clone cluster scoped resource",
rule: []byte(`
{
"name": "sync-multi-clone",
"generate": {
"namespace": "staging",
"synchronize": true,
"cloneList": {
"namespace": "staging",
"kinds": [
"storage.k8s.io/v1/StorageClass"
],
"selector": {
"matchLabels": {
"allowedToBeCloned": "true"
}
}
}
}
}`),
policyNamespace: "staging",
expectedError: errors.New("path: spec.rules[sync-multi-clone]: a namespaced policy cannot generate cluster-wide resources"),
},
}
for _, tc := range testcases {
t.Run(tc.description, func(t *testing.T) {
var rule kyverno.Rule
_ = json.Unmarshal(tc.rule, &rule)
err := checkClusterResourceInMatchAndExclude(rule, sets.New[string](), tc.policyNamespace, false, testResourceList())
if tc.expectedError != nil {
assert.Error(t, err, tc.expectedError.Error())
} else {
assert.NilError(t, err)
}
})
}
}
func Test_patchesJson6902_Policy(t *testing.T) {
rawPolicy := []byte(`
{

View file

@ -1,15 +1,27 @@
apiVersion: kyverno.io/v1
kind: Policy
kind: ClusterPolicy
metadata:
name: create-default-pdb
namespace: hello-world
annotations:
policies.kyverno.io/title: Add Pod Disruption Budget
policies.kyverno.io/category: Sample
kyverno.io/kyverno-version: 1.6.2
policies.kyverno.io/minversion: 1.6.0
policies.kyverno.io/subject: Deployment
policies.kyverno.io/description: >-
A PodDisruptionBudget limits the number of Pods of a replicated application that
are down simultaneously from voluntary disruptions. For example, a quorum-based
application would like to ensure that the number of replicas running is never brought
below the number needed for a quorum. As an application owner, you can create a PodDisruptionBudget (PDB)
for each application. This policy will create a PDB resource whenever a new Deployment is created.
spec:
rules:
- name: create-default-pdb
match:
resources:
kinds:
- Deployment
any:
- resources:
kinds:
- Deployment
generate:
apiVersion: policy/v1
kind: PodDisruptionBudget

View file

@ -0,0 +1,234 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
name: roles.iam.aws.crossplane.io
spec:
group: iam.aws.crossplane.io
names:
categories:
- crossplane
- managed
- aws
kind: Role
listKind: RoleList
plural: roles
shortNames:
- iamrole
singular: role
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=='Ready')].status
name: READY
type: string
- jsonPath: .status.conditions[?(@.type=='Synced')].status
name: SYNCED
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
description: An Role is a managed resource that represents an AWS IAM Role.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: An RoleSpec defines the desired state of an Role.
properties:
deletionPolicy:
default: Delete
description: DeletionPolicy specifies what will happen to the underlying
external when this managed resource is deleted - either "Delete"
or "Orphan" the external resource.
enum:
- Orphan
- Delete
type: string
forProvider:
description: RoleParameters define the desired state of an AWS IAM
Role.
properties:
assumeRolePolicyDocument:
description: AssumeRolePolicyDocument is the the trust relationship
policy document that grants an entity permission to assume the
role.
type: string
description:
description: Description is a description of the role.
type: string
maxSessionDuration:
description: 'MaxSessionDuration is the duration (in seconds)
that you want to set for the specified role. The default maximum
of one hour is applied. This setting can have a value from 1
hour to 12 hours. Default: 3600'
format: int32
type: integer
path:
description: 'Path is the path to the role. Default: /'
type: string
permissionsBoundary:
description: PermissionsBoundary is the ARN of the policy that
is used to set the permissions boundary for the role.
type: string
tags:
description: Tags. For more information about tagging, see Tagging
IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
items:
description: Tag represents user-provided metadata that can
be associated with a IAM role. For more information about
tagging, see Tagging IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
properties:
key:
description: The key name that can be used to look up or
retrieve the associated value. For example, Department
or Cost Center are common choices.
type: string
value:
description: "The value associated with this tag. For example,
tags with a key name of Department could have values such
as Human Resources, Accounting, and Support. Tags with
a key name of Cost Center might have values that consist
of the number associated with the different cost centers
in your company. Typically, many resources have tags with
the same key name but with different values. \n AWS always
interprets the tag Value as a single string. If you need
to store an array, you can store comma-separated values
in the string. However, you must interpret the value in
your code."
type: string
required:
- key
type: object
type: array
required:
- assumeRolePolicyDocument
type: object
providerConfigRef:
default:
name: default
description: ProviderConfigReference specifies how the provider that
will be used to create, observe, update, and delete this managed
resource should be configured.
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
providerRef:
description: 'ProviderReference specifies the provider that will be
used to create, observe, update, and delete this managed resource.
Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`'
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
- namespace
type: object
required:
- forProvider
type: object
status:
description: An RoleStatus represents the observed state of an Role.
properties:
atProvider:
description: RoleExternalStatus keeps the state for the external resource
properties:
arn:
description: ARN is the Amazon Resource Name (ARN) specifying
the role. For more information about ARNs and how to use them
in policies, see IAM Identifiers (http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the IAM User Guide guide.
type: string
roleID:
description: RoleID is the stable and unique string identifying
the role. For more information about IDs, see IAM Identifiers
(http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the Using IAM guide.
type: string
required:
- arn
- roleID
type: object
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions:
- v1beta1

View file

@ -0,0 +1,20 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kyverno:background-controller:additional
labels:
app.kubernetes.io/component: background-controller
app.kubernetes.io/instance: kyverno
app.kubernetes.io/part-of: kyverno
rules:
- apiGroups:
- '*'
resources:
- namespaces
verbs:
- create
- update
- patch
- delete
- get
- list

View file

@ -4,4 +4,6 @@ apply:
- file: policy-namespaced-target.yaml
shouldFail: true
- file: policy-cluster-target.yaml
shouldFail: true
shouldFail: false
- file: policy-pass.yaml
shouldFail: false

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
delete:
- apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: roles.iam.aws.crossplane.io

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
delete:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
name: kyverno:background-controller:additional

View file

@ -9,4 +9,5 @@ The test fails if the policy creation is allowed, otherwise passes.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/7038
https://github.com/kyverno/kyverno/issues/7038
https://github.com/kyverno/kyverno/issues/7470

View file

@ -0,0 +1,59 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: target-namespace-scope-pass-1
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: iam.aws.crossplane.io/v1beta1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: "{{request.object.metadata.name}}"
synchronize: true
match:
any:
- resources:
kinds:
- Namespace
names:
- dev-*
name: role-per-namespace
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: user-per-namespace-pass-2
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: rbac.authorization.k8s.io/v1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: "{{request.object.metadata.name}}"
synchronize: true
match:
any:
- resources:
kinds:
- Namespace
names:
- dev-*
name: role-per-namespace

View file

@ -13,7 +13,7 @@ spec:
kinds:
- ConfigMap
generate:
namespace: "{{request.object.metadata.name}}"
namespace: default
synchronize : true
cloneList:
namespace: default

View file

@ -13,7 +13,7 @@ spec:
kinds:
- ConfigMap
generate:
namespace: "{{request.object.metadata.name}}"
namespace: default
synchronize : true
cloneList:
namespace: default

View file

@ -13,7 +13,7 @@ spec:
kinds:
- ConfigMap
generate:
namespace: "{{request.object.metadata.name}}"
namespace: default
synchronize : true
cloneList:
namespace: update-clonelist-ns

View file

@ -13,7 +13,7 @@ spec:
kinds:
-
generate:
namespace: "{{request.object.metadata.name}}"
namespace: default
synchronize : true
cloneList:
namespace: default

View file

@ -0,0 +1,234 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
name: roles.iam.aws.crossplane.io
spec:
group: iam.aws.crossplane.io
names:
categories:
- crossplane
- managed
- aws
kind: Role
listKind: RoleList
plural: roles
shortNames:
- iamrole
singular: role
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=='Ready')].status
name: READY
type: string
- jsonPath: .status.conditions[?(@.type=='Synced')].status
name: SYNCED
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
description: An Role is a managed resource that represents an AWS IAM Role.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: An RoleSpec defines the desired state of an Role.
properties:
deletionPolicy:
default: Delete
description: DeletionPolicy specifies what will happen to the underlying
external when this managed resource is deleted - either "Delete"
or "Orphan" the external resource.
enum:
- Orphan
- Delete
type: string
forProvider:
description: RoleParameters define the desired state of an AWS IAM
Role.
properties:
assumeRolePolicyDocument:
description: AssumeRolePolicyDocument is the the trust relationship
policy document that grants an entity permission to assume the
role.
type: string
description:
description: Description is a description of the role.
type: string
maxSessionDuration:
description: 'MaxSessionDuration is the duration (in seconds)
that you want to set for the specified role. The default maximum
of one hour is applied. This setting can have a value from 1
hour to 12 hours. Default: 3600'
format: int32
type: integer
path:
description: 'Path is the path to the role. Default: /'
type: string
permissionsBoundary:
description: PermissionsBoundary is the ARN of the policy that
is used to set the permissions boundary for the role.
type: string
tags:
description: Tags. For more information about tagging, see Tagging
IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
items:
description: Tag represents user-provided metadata that can
be associated with a IAM role. For more information about
tagging, see Tagging IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
properties:
key:
description: The key name that can be used to look up or
retrieve the associated value. For example, Department
or Cost Center are common choices.
type: string
value:
description: "The value associated with this tag. For example,
tags with a key name of Department could have values such
as Human Resources, Accounting, and Support. Tags with
a key name of Cost Center might have values that consist
of the number associated with the different cost centers
in your company. Typically, many resources have tags with
the same key name but with different values. \n AWS always
interprets the tag Value as a single string. If you need
to store an array, you can store comma-separated values
in the string. However, you must interpret the value in
your code."
type: string
required:
- key
type: object
type: array
required:
- assumeRolePolicyDocument
type: object
providerConfigRef:
default:
name: default
description: ProviderConfigReference specifies how the provider that
will be used to create, observe, update, and delete this managed
resource should be configured.
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
providerRef:
description: 'ProviderReference specifies the provider that will be
used to create, observe, update, and delete this managed resource.
Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`'
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
- namespace
type: object
required:
- forProvider
type: object
status:
description: An RoleStatus represents the observed state of an Role.
properties:
atProvider:
description: RoleExternalStatus keeps the state for the external resource
properties:
arn:
description: ARN is the Amazon Resource Name (ARN) specifying
the role. For more information about ARNs and how to use them
in policies, see IAM Identifiers (http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the IAM User Guide guide.
type: string
roleID:
description: RoleID is the stable and unique string identifying
the role. For more information about IDs, see IAM Identifiers
(http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the Using IAM guide.
type: string
required:
- arn
- roleID
type: object
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions:
- v1beta1

View file

@ -1,5 +0,0 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: policy-namespaced-target.yaml
shouldFail: true

View file

@ -0,0 +1,13 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: policy-pass.yaml
shouldFail: false
- file: policy-fail-0.yaml
shouldFail: true
- file: policy-fail-1.yaml
shouldFail: true
- file: policy-fail-2.yaml
shouldFail: true
- file: policy-fail-3.yaml
shouldFail: true

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
delete:
- apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: roles.iam.aws.crossplane.io

View file

@ -0,0 +1,31 @@
---
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: pol-target-namespace-scope-fail-1
namespace: default
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: iam.aws.crossplane.io/v1beta1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: default
synchronize: true
match:
any:
- resources:
kinds:
- Secret
names:
- dev-*
name: role-per-namespace

View file

@ -0,0 +1,31 @@
---
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: pol-target-namespace-scope-fail-2
namespace: default
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: rbac.authorization.k8s.io/v1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: "{{request.object.metadata.name}}"
synchronize: true
match:
any:
- resources:
kinds:
- Secret
names:
- dev-*
name: role-per-namespace

View file

@ -0,0 +1,31 @@
---
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: pol-target-namespace-scope-fail-3
namespace: default
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: rbac.authorization.k8s.io/v1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: test
synchronize: true
match:
any:
- resources:
kinds:
- Secret
names:
- dev-*
name: role-per-namespace

View file

@ -0,0 +1,30 @@
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: user-per-namespace-pass
namespace: default
spec:
generateExistingOnPolicyUpdate: true
rules:
- generate:
apiVersion: rbac.authorization.k8s.io/v1
data:
rules:
- verbs:
- "*"
apiGroups:
- "*"
resources:
- "*"
kind: Role
name: superuser
namespace: default
synchronize: true
match:
any:
- resources:
kinds:
- Secret
names:
- dev-*
name: role-per-namespace