knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/configmaps/configmaps_test.go (about) 1 /* 2 Copyright 2019 The Knative 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 configmaps 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "strconv" 25 "testing" 26 27 // Injection stuff 28 _ "knative.dev/pkg/client/injection/kube/client/fake" 29 _ "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/validatingwebhookconfiguration/fake" 30 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 31 32 admissionv1 "k8s.io/api/admission/v1" 33 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 34 authenticationv1 "k8s.io/api/authentication/v1" 35 corev1 "k8s.io/api/core/v1" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 fakekubeclientset "k8s.io/client-go/kubernetes/fake" 38 "knative.dev/pkg/configmap" 39 "knative.dev/pkg/system" 40 "knative.dev/pkg/webhook" 41 42 _ "knative.dev/pkg/system/testing" 43 44 . "knative.dev/pkg/logging/testing" 45 . "knative.dev/pkg/reconciler/testing" 46 . "knative.dev/pkg/webhook/testing" 47 ) 48 49 const ( 50 testConfigValidationName = "configmap.webhook.knative.dev" 51 testConfigValidationPath = "/cm" 52 testConfigName = "test-config" 53 ) 54 55 var ( 56 validations = configmap.Constructors{ 57 testConfigName: newConfigFromConfigMap, 58 } 59 initialConfigWebhook = &admissionregistrationv1.ValidatingWebhookConfiguration{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: testConfigValidationName, 62 }, 63 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 64 Name: testConfigValidationName, 65 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 66 Service: &admissionregistrationv1.ServiceReference{ 67 Namespace: system.Namespace(), 68 Name: "webhook", 69 }, 70 }, 71 NamespaceSelector: &metav1.LabelSelector{ 72 MatchExpressions: []metav1.LabelSelectorRequirement{{ 73 Key: "pkg.knative.dev/release", 74 Operator: metav1.LabelSelectorOpExists, 75 }}, 76 }, 77 }}, 78 } 79 ) 80 81 func newNonRunningTestConfigValidationController(t *testing.T) ( 82 kubeClient *fakekubeclientset.Clientset, 83 ac *reconciler, 84 ) { 85 t.Helper() 86 // Create fake clients 87 kubeClient = fakekubeclientset.NewSimpleClientset(initialConfigWebhook) 88 89 ac = newTestConfigValidationController(t) 90 return kubeClient, ac 91 } 92 93 func newTestConfigValidationController(t *testing.T) *reconciler { 94 ctx, _ := SetupFakeContext(t) 95 ctx = webhook.WithOptions(ctx, webhook.Options{ 96 SecretName: "webhook-secret", 97 }) 98 return NewAdmissionController(ctx, testConfigValidationName, testConfigValidationPath, 99 validations).Reconciler.(*reconciler) 100 } 101 102 func TestDeleteAllowedForConfigMap(t *testing.T) { 103 _, ac := newNonRunningTestConfigValidationController(t) 104 105 req := &admissionv1.AdmissionRequest{ 106 Operation: admissionv1.Delete, 107 } 108 109 if resp := ac.Admit(TestContextWithLogger(t), req); !resp.Allowed { 110 t.Fatal("Unexpected denial of delete") 111 } 112 } 113 114 func TestConnectAllowedForConfigMap(t *testing.T) { 115 _, ac := newNonRunningTestConfigValidationController(t) 116 117 req := &admissionv1.AdmissionRequest{ 118 Operation: admissionv1.Connect, 119 } 120 121 resp := ac.Admit(TestContextWithLogger(t), req) 122 if !resp.Allowed { 123 t.Fatalf("Unexpected denial of connect") 124 } 125 } 126 127 func TestNonConfigMapKindFails(t *testing.T) { 128 _, ac := newNonRunningTestConfigValidationController(t) 129 130 req := &admissionv1.AdmissionRequest{ 131 Operation: admissionv1.Create, 132 Kind: metav1.GroupVersionKind{ 133 Group: "pkg.knative.dev", 134 Version: "v1alpha1", 135 Kind: "Garbage", 136 }, 137 } 138 139 ExpectFailsWith(t, ac.Admit(TestContextWithLogger(t), req), "unhandled kind") 140 } 141 142 func TestAdmitCreateValidConfigMap(t *testing.T) { 143 _, ac := newNonRunningTestConfigValidationController(t) 144 145 r := createValidConfigMap() 146 ctx := TestContextWithLogger(t) 147 148 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 149 150 ExpectAllowed(t, resp) 151 } 152 153 func TestDenyInvalidCreateConfigMapWithWrongType(t *testing.T) { 154 _, ac := newNonRunningTestConfigValidationController(t) 155 156 r := createWrongTypeConfigMap() 157 ctx := TestContextWithLogger(t) 158 159 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 160 161 ExpectFailsWith(t, resp, "invalid syntax") 162 } 163 164 func TestDenyInvalidCreateConfigMapOutOfRange(t *testing.T) { 165 _, ac := newNonRunningTestConfigValidationController(t) 166 167 r := createWrongValueConfigMap() 168 ctx := TestContextWithLogger(t) 169 170 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 171 172 ExpectFailsWith(t, resp, "out of range") 173 } 174 175 func TestAdmitUpdateValidConfigMap(t *testing.T) { 176 _, ac := newNonRunningTestConfigValidationController(t) 177 178 r := createValidConfigMap() 179 ctx := TestContextWithLogger(t) 180 181 resp := ac.Admit(ctx, updateCreateConfigMapRequest(ctx, t, r)) 182 183 ExpectAllowed(t, resp) 184 } 185 186 func TestDenyInvalidUpdateConfigMapWithWrongType(t *testing.T) { 187 _, ac := newNonRunningTestConfigValidationController(t) 188 189 r := createWrongTypeConfigMap() 190 ctx := TestContextWithLogger(t) 191 192 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 193 194 ExpectFailsWith(t, resp, "invalid syntax") 195 } 196 197 func TestDenyInvalidUpdateConfigMapOutOfRange(t *testing.T) { 198 _, ac := newNonRunningTestConfigValidationController(t) 199 200 r := createWrongValueConfigMap() 201 ctx := TestContextWithLogger(t) 202 203 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 204 205 ExpectFailsWith(t, resp, "out of range") 206 } 207 208 func TestAllowConfigMapExample(t *testing.T) { 209 _, ac := newNonRunningTestConfigValidationController(t) 210 211 r := createValidConfigMap() 212 // Add an _example field but add no annotation. 213 r.Data[configmap.ExampleKey] = "bar" 214 ctx := TestContextWithLogger(t) 215 216 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 217 218 ExpectAllowed(t, resp) 219 } 220 221 func TestAllowUnknownConfigMapExample(t *testing.T) { 222 _, ac := newNonRunningTestConfigValidationController(t) 223 224 r := &corev1.ConfigMap{ 225 ObjectMeta: metav1.ObjectMeta{ 226 Name: "some-other-config", 227 Annotations: map[string]string{ 228 configmap.ExampleChecksumAnnotation: "foo", 229 }, 230 }, 231 Data: map[string]string{ 232 configmap.ExampleKey: "bar", 233 }, 234 } 235 ctx := TestContextWithLogger(t) 236 237 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 238 239 ExpectAllowed(t, resp) 240 } 241 242 func TestDenyInvalidUpdateConfigMapExample(t *testing.T) { 243 _, ac := newNonRunningTestConfigValidationController(t) 244 245 r := &corev1.ConfigMap{ 246 ObjectMeta: metav1.ObjectMeta{ 247 Name: testConfigName, 248 Annotations: map[string]string{ 249 configmap.ExampleChecksumAnnotation: "foo", 250 }, 251 }, 252 Data: map[string]string{ 253 configmap.ExampleKey: "bar", 254 }, 255 } 256 ctx := TestContextWithLogger(t) 257 258 resp := ac.Admit(ctx, createCreateConfigMapRequest(ctx, t, r)) 259 260 ExpectFailsWith(t, resp, fmt.Sprintf("a key in %q", configmap.ExampleKey)) 261 } 262 263 type config struct { 264 value float64 265 } 266 267 func newConfigFromConfigMap(configMap *corev1.ConfigMap) (*config, error) { 268 data := configMap.Data 269 cfg := &config{} 270 for _, b := range []struct { 271 key string 272 field *float64 273 }{{ 274 key: "value", 275 field: &cfg.value, 276 }} { 277 if raw, ok := data[b.key]; !ok { 278 return nil, errors.New("not found") 279 } else if val, err := strconv.ParseFloat(raw, 64); err != nil { 280 return nil, err 281 } else { 282 *b.field = val 283 } 284 } 285 286 // some sample validation on the value 287 if cfg.value > 2.0 || cfg.value < 0.0 { 288 return nil, errors.New("out of range") 289 } 290 291 return cfg, nil 292 } 293 294 func createValidConfigMap() *corev1.ConfigMap { 295 return createConfigMap("1.5") 296 } 297 298 func createWrongTypeConfigMap() *corev1.ConfigMap { 299 return createConfigMap("bad") 300 } 301 302 func createWrongValueConfigMap() *corev1.ConfigMap { 303 return createConfigMap("2.5") 304 } 305 306 func createConfigMap(value string) *corev1.ConfigMap { 307 return &corev1.ConfigMap{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Namespace: system.Namespace(), 310 Name: testConfigName, 311 }, 312 Data: map[string]string{ 313 "value": value, 314 }, 315 } 316 } 317 318 func createCreateConfigMapRequest(ctx context.Context, t *testing.T, r *corev1.ConfigMap) *admissionv1.AdmissionRequest { 319 return configMapRequest(t, r, admissionv1.Create) 320 } 321 322 func updateCreateConfigMapRequest(ctx context.Context, t *testing.T, r *corev1.ConfigMap) *admissionv1.AdmissionRequest { 323 return configMapRequest(t, r, admissionv1.Update) 324 } 325 326 func configMapRequest( 327 t *testing.T, 328 r *corev1.ConfigMap, 329 o admissionv1.Operation, 330 ) *admissionv1.AdmissionRequest { 331 t.Helper() 332 req := &admissionv1.AdmissionRequest{ 333 Operation: o, 334 Kind: metav1.GroupVersionKind{ 335 Group: "", 336 Version: "v1", 337 Kind: "ConfigMap", 338 }, 339 UserInfo: authenticationv1.UserInfo{Username: "mattmoor"}, 340 } 341 marshaled, err := json.Marshal(r) 342 if err != nil { 343 t.Fatal("Failed to marshal resource:", err) 344 } 345 req.Object.Raw = marshaled 346 req.Resource.Group = "" 347 return req 348 }