k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/predicates/rules/rules_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rules
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	adreg "k8s.io/api/admissionregistration/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/apiserver/pkg/admission"
    29  )
    30  
    31  type ruleTest struct {
    32  	rule    adreg.RuleWithOperations
    33  	match   []admission.Attributes
    34  	noMatch []admission.Attributes
    35  }
    36  type tests map[string]ruleTest
    37  
    38  func a(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
    39  	return admission.NewAttributesRecord(
    40  		nil, nil,
    41  		schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource},
    42  		"ns", name,
    43  		schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource,
    44  		operation,
    45  		operationOptions,
    46  		false,
    47  		nil,
    48  	)
    49  }
    50  
    51  func namespacedAttributes(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
    52  	return admission.NewAttributesRecord(
    53  		nil, nil,
    54  		schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource},
    55  		"ns", name,
    56  		schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource,
    57  		operation,
    58  		operationOptions,
    59  		false,
    60  		nil,
    61  	)
    62  }
    63  
    64  func clusterScopedAttributes(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
    65  	return admission.NewAttributesRecord(
    66  		nil, nil,
    67  		schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource},
    68  		"", name,
    69  		schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource,
    70  		operation,
    71  		operationOptions,
    72  		false,
    73  		nil,
    74  	)
    75  }
    76  
    77  func attrList(a ...admission.Attributes) []admission.Attributes {
    78  	return a
    79  }
    80  
    81  func TestGroup(t *testing.T) {
    82  	table := tests{
    83  		"wildcard": {
    84  			rule: adreg.RuleWithOperations{
    85  				Rule: adreg.Rule{
    86  					APIGroups: []string{"*"},
    87  				},
    88  			},
    89  			match: attrList(
    90  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
    91  			),
    92  		},
    93  		"exact": {
    94  			rule: adreg.RuleWithOperations{
    95  				Rule: adreg.Rule{
    96  					APIGroups: []string{"g1", "g2"},
    97  				},
    98  			},
    99  			match: attrList(
   100  				a("g1", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   101  				a("g2", "v2", "r3", "", "name", admission.Create, &metav1.CreateOptions{}),
   102  			),
   103  			noMatch: attrList(
   104  				a("g3", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   105  				a("g4", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   106  			),
   107  		},
   108  	}
   109  
   110  	for name, tt := range table {
   111  		for _, m := range tt.match {
   112  			r := Matcher{tt.rule, m}
   113  			if !r.group() {
   114  				t.Errorf("%v: expected match %#v", name, m)
   115  			}
   116  		}
   117  		for _, m := range tt.noMatch {
   118  			r := Matcher{tt.rule, m}
   119  			if r.group() {
   120  				t.Errorf("%v: expected no match %#v", name, m)
   121  			}
   122  		}
   123  	}
   124  }
   125  
   126  func TestVersion(t *testing.T) {
   127  	table := tests{
   128  		"wildcard": {
   129  			rule: adreg.RuleWithOperations{
   130  				Rule: adreg.Rule{
   131  					APIVersions: []string{"*"},
   132  				},
   133  			},
   134  			match: attrList(
   135  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   136  			),
   137  		},
   138  		"exact": {
   139  			rule: adreg.RuleWithOperations{
   140  				Rule: adreg.Rule{
   141  					APIVersions: []string{"v1", "v2"},
   142  				},
   143  			},
   144  			match: attrList(
   145  				a("g1", "v1", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   146  				a("g2", "v2", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   147  			),
   148  			noMatch: attrList(
   149  				a("g1", "v3", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   150  				a("g2", "v4", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   151  			),
   152  		},
   153  	}
   154  	for name, tt := range table {
   155  		for _, m := range tt.match {
   156  			r := Matcher{tt.rule, m}
   157  			if !r.version() {
   158  				t.Errorf("%v: expected match %#v", name, m)
   159  			}
   160  		}
   161  		for _, m := range tt.noMatch {
   162  			r := Matcher{tt.rule, m}
   163  			if r.version() {
   164  				t.Errorf("%v: expected no match %#v", name, m)
   165  			}
   166  		}
   167  	}
   168  }
   169  
   170  func TestOperation(t *testing.T) {
   171  	table := tests{
   172  		"wildcard": {
   173  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.OperationAll}},
   174  			match: attrList(
   175  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   176  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   177  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   178  				a("g", "v", "r", "", "name", admission.Connect, nil),
   179  			),
   180  		},
   181  		"create": {
   182  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Create}},
   183  			match: attrList(
   184  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   185  			),
   186  			noMatch: attrList(
   187  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   188  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   189  				a("g", "v", "r", "", "name", admission.Connect, nil),
   190  			),
   191  		},
   192  		"update": {
   193  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update}},
   194  			match: attrList(
   195  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   196  			),
   197  			noMatch: attrList(
   198  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   199  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   200  				a("g", "v", "r", "", "name", admission.Connect, nil),
   201  			),
   202  		},
   203  		"delete": {
   204  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Delete}},
   205  			match: attrList(
   206  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   207  			),
   208  			noMatch: attrList(
   209  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   210  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   211  				a("g", "v", "r", "", "name", admission.Connect, nil),
   212  			),
   213  		},
   214  		"connect": {
   215  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Connect}},
   216  			match: attrList(
   217  				a("g", "v", "r", "", "name", admission.Connect, nil),
   218  			),
   219  			noMatch: attrList(
   220  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   221  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   222  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   223  			),
   224  		},
   225  		"multiple": {
   226  			rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update, adreg.Delete}},
   227  			match: attrList(
   228  				a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}),
   229  				a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}),
   230  			),
   231  			noMatch: attrList(
   232  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   233  				a("g", "v", "r", "", "name", admission.Connect, nil),
   234  			),
   235  		},
   236  	}
   237  	for name, tt := range table {
   238  		for _, m := range tt.match {
   239  			r := Matcher{tt.rule, m}
   240  			if !r.operation() {
   241  				t.Errorf("%v: expected match %#v", name, m)
   242  			}
   243  		}
   244  		for _, m := range tt.noMatch {
   245  			r := Matcher{tt.rule, m}
   246  			if r.operation() {
   247  				t.Errorf("%v: expected no match %#v", name, m)
   248  			}
   249  		}
   250  	}
   251  }
   252  
   253  func TestResource(t *testing.T) {
   254  	table := tests{
   255  		"no subresources": {
   256  			rule: adreg.RuleWithOperations{
   257  				Rule: adreg.Rule{
   258  					Resources: []string{"*"},
   259  				},
   260  			},
   261  			match: attrList(
   262  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   263  				a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}),
   264  			),
   265  			noMatch: attrList(
   266  				a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   267  				a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}),
   268  			),
   269  		},
   270  		"r & subresources": {
   271  			rule: adreg.RuleWithOperations{
   272  				Rule: adreg.Rule{
   273  					Resources: []string{"r/*"},
   274  				},
   275  			},
   276  			match: attrList(
   277  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   278  				a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   279  			),
   280  			noMatch: attrList(
   281  				a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}),
   282  				a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}),
   283  			),
   284  		},
   285  		"r & subresources or r2": {
   286  			rule: adreg.RuleWithOperations{
   287  				Rule: adreg.Rule{
   288  					Resources: []string{"r/*", "r2"},
   289  				},
   290  			},
   291  			match: attrList(
   292  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   293  				a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   294  				a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}),
   295  			),
   296  			noMatch: attrList(
   297  				a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}),
   298  			),
   299  		},
   300  		"proxy or exec": {
   301  			rule: adreg.RuleWithOperations{
   302  				Rule: adreg.Rule{
   303  					Resources: []string{"*/proxy", "*/exec"},
   304  				},
   305  			},
   306  			match: attrList(
   307  				a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   308  				a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}),
   309  				a("2", "v", "r3", "proxy", "name", admission.Create, &metav1.CreateOptions{}),
   310  			),
   311  			noMatch: attrList(
   312  				a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   313  				a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}),
   314  				a("2", "v", "r4", "scale", "name", admission.Create, &metav1.CreateOptions{}),
   315  			),
   316  		},
   317  	}
   318  	for name, tt := range table {
   319  		for _, m := range tt.match {
   320  			r := Matcher{tt.rule, m}
   321  			if !r.resource() {
   322  				t.Errorf("%v: expected match %#v", name, m)
   323  			}
   324  		}
   325  		for _, m := range tt.noMatch {
   326  			r := Matcher{tt.rule, m}
   327  			if r.resource() {
   328  				t.Errorf("%v: expected no match %#v", name, m)
   329  			}
   330  		}
   331  	}
   332  }
   333  
   334  func TestScope(t *testing.T) {
   335  	cluster := adreg.ClusterScope
   336  	namespace := adreg.NamespacedScope
   337  	allscopes := adreg.AllScopes
   338  	table := tests{
   339  		"cluster scope": {
   340  			rule: adreg.RuleWithOperations{
   341  				Rule: adreg.Rule{
   342  					Resources: []string{"*"},
   343  					Scope:     &cluster,
   344  				},
   345  			},
   346  			match: attrList(
   347  				clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   348  				clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   349  				clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   350  				clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   351  				namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   352  				namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   353  			),
   354  			noMatch: attrList(
   355  				namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   356  				namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   357  			),
   358  		},
   359  		"namespace scope": {
   360  			rule: adreg.RuleWithOperations{
   361  				Rule: adreg.Rule{
   362  					Resources: []string{"*"},
   363  					Scope:     &namespace,
   364  				},
   365  			},
   366  			match: attrList(
   367  				namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   368  				namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   369  			),
   370  			noMatch: attrList(
   371  				clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   372  				clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   373  				namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   374  				namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   375  				clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   376  				clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   377  			),
   378  		},
   379  		"all scopes": {
   380  			rule: adreg.RuleWithOperations{
   381  				Rule: adreg.Rule{
   382  					Resources: []string{"*"},
   383  					Scope:     &allscopes,
   384  				},
   385  			},
   386  			match: attrList(
   387  				namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   388  				namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   389  				clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}),
   390  				clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}),
   391  				clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   392  				clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   393  				namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}),
   394  				namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}),
   395  			),
   396  			noMatch: attrList(),
   397  		},
   398  	}
   399  	keys := sets.NewString()
   400  	for name := range table {
   401  		keys.Insert(name)
   402  	}
   403  	for _, name := range keys.List() {
   404  		tt := table[name]
   405  		for i, m := range tt.match {
   406  			t.Run(fmt.Sprintf("%s_match_%d", name, i), func(t *testing.T) {
   407  				r := Matcher{tt.rule, m}
   408  				if !r.scope() {
   409  					t.Errorf("%v: expected match %#v", name, m)
   410  				}
   411  			})
   412  		}
   413  		for i, m := range tt.noMatch {
   414  			t.Run(fmt.Sprintf("%s_nomatch_%d", name, i), func(t *testing.T) {
   415  				r := Matcher{tt.rule, m}
   416  				if r.scope() {
   417  					t.Errorf("%v: expected no match %#v", name, m)
   418  				}
   419  			})
   420  		}
   421  	}
   422  }