diff options
4 files changed, 1279 insertions, 0 deletions
diff --git a/recipes-containers/kubernetes/kubernetes/CVE-2021-25735-pre1.patch b/recipes-containers/kubernetes/kubernetes/CVE-2021-25735-pre1.patch new file mode 100644 index 00000000..2066188a --- /dev/null +++ b/recipes-containers/kubernetes/kubernetes/CVE-2021-25735-pre1.patch | |||
| @@ -0,0 +1,613 @@ | |||
| 1 | From e612ebfdff22e4bd27ad8345f7c82f074bfedf26 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: wojtekt <wojtekt@google.com> | ||
| 3 | Date: Tue, 26 Nov 2019 13:29:26 +0100 | ||
| 4 | Subject: [PATCH] Immutable field and validation | ||
| 5 | |||
| 6 | Upstream-Status: Backport [https://github.com/kubernetes/kubernetes/commit/e612ebfdff22e4bd27ad8345f7c82f074bfedf26] | ||
| 7 | CVE: CVE-2021-25735 #Dependency Patch1 | ||
| 8 | Signed-off-by: Vijay Anusuri <vanusuri@mvista.com> | ||
| 9 | --- | ||
| 10 | pkg/apis/core/types.go | 12 + | ||
| 11 | pkg/apis/core/validation/validation.go | 24 +- | ||
| 12 | pkg/apis/core/validation/validation_test.go | 209 ++++++++++++++++-- | ||
| 13 | pkg/features/kube_features.go | 7 + | ||
| 14 | pkg/registry/core/configmap/strategy.go | 28 ++- | ||
| 15 | pkg/registry/core/secret/strategy.go | 17 ++ | ||
| 16 | staging/src/k8s.io/api/core/v1/types.go | 16 ++ | ||
| 17 | .../k8sdeps/transformer/hash/hash.go | 20 +- | ||
| 18 | .../k8sdeps/transformer/hash/hash_test.go | 4 +- | ||
| 19 | .../src/k8s.io/kubectl/pkg/util/hash/hash.go | 20 +- | ||
| 20 | .../k8s.io/kubectl/pkg/util/hash/hash_test.go | 4 +- | ||
| 21 | 11 files changed, 322 insertions(+), 39 deletions(-) | ||
| 22 | |||
| 23 | diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go | ||
| 24 | index 74d22ae973e87..c5ada193effc4 100644 | ||
| 25 | --- a/src/import/pkg/apis/core/types.go | ||
| 26 | +++ b/src/import/pkg/apis/core/types.go | ||
| 27 | @@ -4735,6 +4735,12 @@ type Secret struct { | ||
| 28 | // +optional | ||
| 29 | metav1.ObjectMeta | ||
| 30 | |||
| 31 | + // Immutable field, if set, ensures that data stored in the Secret cannot | ||
| 32 | + // be updated (only object metadata can be modified). | ||
| 33 | + // This is an alpha field enabled by ImmutableEphemeralVolumes feature gate. | ||
| 34 | + // +optional | ||
| 35 | + Immutable *bool | ||
| 36 | + | ||
| 37 | // Data contains the secret data. Each key must consist of alphanumeric | ||
| 38 | // characters, '-', '_' or '.'. The serialized form of the secret data is a | ||
| 39 | // base64 encoded string, representing the arbitrary (possibly non-string) | ||
| 40 | @@ -4857,6 +4863,12 @@ type ConfigMap struct { | ||
| 41 | // +optional | ||
| 42 | metav1.ObjectMeta | ||
| 43 | |||
| 44 | + // Immutable field, if set, ensures that data stored in the ConfigMap cannot | ||
| 45 | + // be updated (only object metadata can be modified). | ||
| 46 | + // This is an alpha field enabled by ImmutableEphemeralVolumes feature gate. | ||
| 47 | + // +optional | ||
| 48 | + Immutable *bool | ||
| 49 | + | ||
| 50 | // Data contains the configuration data. | ||
| 51 | // Each key must consist of alphanumeric characters, '-', '_' or '.'. | ||
| 52 | // Values with non-UTF-8 byte sequences must use the BinaryData field. | ||
| 53 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 54 | index 4ad241c745b7d..8e3cfd9d9e423 100644 | ||
| 55 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 56 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 57 | @@ -5005,6 +5005,16 @@ func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList { | ||
| 58 | } | ||
| 59 | |||
| 60 | allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...) | ||
| 61 | + if oldSecret.Immutable != nil && *oldSecret.Immutable { | ||
| 62 | + if !reflect.DeepEqual(newSecret.Immutable, oldSecret.Immutable) { | ||
| 63 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set")) | ||
| 64 | + } | ||
| 65 | + if !reflect.DeepEqual(newSecret.Data, oldSecret.Data) { | ||
| 66 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set")) | ||
| 67 | + } | ||
| 68 | + // We don't validate StringData, as it was already converted back to Data | ||
| 69 | + // before validation is happening. | ||
| 70 | + } | ||
| 71 | |||
| 72 | allErrs = append(allErrs, ValidateSecret(newSecret)...) | ||
| 73 | return allErrs | ||
| 74 | @@ -5051,8 +5061,20 @@ func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList { | ||
| 75 | func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList { | ||
| 76 | allErrs := field.ErrorList{} | ||
| 77 | allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...) | ||
| 78 | - allErrs = append(allErrs, ValidateConfigMap(newCfg)...) | ||
| 79 | |||
| 80 | + if oldCfg.Immutable != nil && *oldCfg.Immutable { | ||
| 81 | + if !reflect.DeepEqual(newCfg.Immutable, oldCfg.Immutable) { | ||
| 82 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set")) | ||
| 83 | + } | ||
| 84 | + if !reflect.DeepEqual(newCfg.Data, oldCfg.Data) { | ||
| 85 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set")) | ||
| 86 | + } | ||
| 87 | + if !reflect.DeepEqual(newCfg.BinaryData, oldCfg.BinaryData) { | ||
| 88 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("binaryData"), "field is immutable when `immutable` is set")) | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + allErrs = append(allErrs, ValidateConfigMap(newCfg)...) | ||
| 93 | return allErrs | ||
| 94 | } | ||
| 95 | |||
| 96 | diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go | ||
| 97 | index 8ba68da00fe05..de8c1d49fc196 100644 | ||
| 98 | --- a/src/import/pkg/apis/core/validation/validation_test.go | ||
| 99 | +++ b/src/import/pkg/apis/core/validation/validation_test.go | ||
| 100 | @@ -13117,6 +13117,104 @@ func TestValidateSecret(t *testing.T) { | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | +func TestValidateSecretUpdate(t *testing.T) { | ||
| 105 | + validSecret := func() core.Secret { | ||
| 106 | + return core.Secret{ | ||
| 107 | + ObjectMeta: metav1.ObjectMeta{ | ||
| 108 | + Name: "foo", | ||
| 109 | + Namespace: "bar", | ||
| 110 | + ResourceVersion: "20", | ||
| 111 | + }, | ||
| 112 | + Data: map[string][]byte{ | ||
| 113 | + "data-1": []byte("bar"), | ||
| 114 | + }, | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + falseVal := false | ||
| 119 | + trueVal := true | ||
| 120 | + | ||
| 121 | + secret := validSecret() | ||
| 122 | + immutableSecret := validSecret() | ||
| 123 | + immutableSecret.Immutable = &trueVal | ||
| 124 | + mutableSecret := validSecret() | ||
| 125 | + mutableSecret.Immutable = &falseVal | ||
| 126 | + | ||
| 127 | + secretWithData := validSecret() | ||
| 128 | + secretWithData.Data["data-2"] = []byte("baz") | ||
| 129 | + immutableSecretWithData := validSecret() | ||
| 130 | + immutableSecretWithData.Immutable = &trueVal | ||
| 131 | + immutableSecretWithData.Data["data-2"] = []byte("baz") | ||
| 132 | + | ||
| 133 | + secretWithChangedData := validSecret() | ||
| 134 | + secretWithChangedData.Data["data-1"] = []byte("foo") | ||
| 135 | + immutableSecretWithChangedData := validSecret() | ||
| 136 | + immutableSecretWithChangedData.Immutable = &trueVal | ||
| 137 | + immutableSecretWithChangedData.Data["data-1"] = []byte("foo") | ||
| 138 | + | ||
| 139 | + tests := []struct { | ||
| 140 | + name string | ||
| 141 | + oldSecret core.Secret | ||
| 142 | + newSecret core.Secret | ||
| 143 | + valid bool | ||
| 144 | + }{ | ||
| 145 | + { | ||
| 146 | + name: "mark secret immutable", | ||
| 147 | + oldSecret: secret, | ||
| 148 | + newSecret: immutableSecret, | ||
| 149 | + valid: true, | ||
| 150 | + }, | ||
| 151 | + { | ||
| 152 | + name: "revert immutable secret", | ||
| 153 | + oldSecret: immutableSecret, | ||
| 154 | + newSecret: secret, | ||
| 155 | + valid: false, | ||
| 156 | + }, | ||
| 157 | + { | ||
| 158 | + name: "makr immutable secret mutable", | ||
| 159 | + oldSecret: immutableSecret, | ||
| 160 | + newSecret: mutableSecret, | ||
| 161 | + valid: false, | ||
| 162 | + }, | ||
| 163 | + { | ||
| 164 | + name: "add data in secret", | ||
| 165 | + oldSecret: secret, | ||
| 166 | + newSecret: secretWithData, | ||
| 167 | + valid: true, | ||
| 168 | + }, | ||
| 169 | + { | ||
| 170 | + name: "add data in immutable secret", | ||
| 171 | + oldSecret: immutableSecret, | ||
| 172 | + newSecret: immutableSecretWithData, | ||
| 173 | + valid: false, | ||
| 174 | + }, | ||
| 175 | + { | ||
| 176 | + name: "change data in secret", | ||
| 177 | + oldSecret: secret, | ||
| 178 | + newSecret: secretWithChangedData, | ||
| 179 | + valid: true, | ||
| 180 | + }, | ||
| 181 | + { | ||
| 182 | + name: "change data in immutable secret", | ||
| 183 | + oldSecret: immutableSecret, | ||
| 184 | + newSecret: immutableSecretWithChangedData, | ||
| 185 | + valid: false, | ||
| 186 | + }, | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + for _, tc := range tests { | ||
| 190 | + t.Run(tc.name, func(t *testing.T) { | ||
| 191 | + errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret) | ||
| 192 | + if tc.valid && len(errs) > 0 { | ||
| 193 | + t.Errorf("Unexpected error: %v", errs) | ||
| 194 | + } | ||
| 195 | + if !tc.valid && len(errs) == 0 { | ||
| 196 | + t.Errorf("Unexpected lack of error") | ||
| 197 | + } | ||
| 198 | + }) | ||
| 199 | + } | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | func TestValidateDockerConfigSecret(t *testing.T) { | ||
| 203 | validDockerSecret := func() core.Secret { | ||
| 204 | return core.Secret{ | ||
| 205 | @@ -13731,40 +13829,105 @@ func TestValidateConfigMapUpdate(t *testing.T) { | ||
| 206 | Data: data, | ||
| 207 | } | ||
| 208 | } | ||
| 209 | + validConfigMap := func() core.ConfigMap { | ||
| 210 | + return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"}) | ||
| 211 | + } | ||
| 212 | |||
| 213 | - var ( | ||
| 214 | - validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"}) | ||
| 215 | - noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"}) | ||
| 216 | - ) | ||
| 217 | + falseVal := false | ||
| 218 | + trueVal := true | ||
| 219 | + | ||
| 220 | + configMap := validConfigMap() | ||
| 221 | + immutableConfigMap := validConfigMap() | ||
| 222 | + immutableConfigMap.Immutable = &trueVal | ||
| 223 | + mutableConfigMap := validConfigMap() | ||
| 224 | + mutableConfigMap.Immutable = &falseVal | ||
| 225 | + | ||
| 226 | + configMapWithData := validConfigMap() | ||
| 227 | + configMapWithData.Data["key-2"] = "value-2" | ||
| 228 | + immutableConfigMapWithData := validConfigMap() | ||
| 229 | + immutableConfigMapWithData.Immutable = &trueVal | ||
| 230 | + immutableConfigMapWithData.Data["key-2"] = "value-2" | ||
| 231 | + | ||
| 232 | + configMapWithChangedData := validConfigMap() | ||
| 233 | + configMapWithChangedData.Data["key"] = "foo" | ||
| 234 | + immutableConfigMapWithChangedData := validConfigMap() | ||
| 235 | + immutableConfigMapWithChangedData.Immutable = &trueVal | ||
| 236 | + immutableConfigMapWithChangedData.Data["key"] = "foo" | ||
| 237 | + | ||
| 238 | + noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"}) | ||
| 239 | |||
| 240 | cases := []struct { | ||
| 241 | - name string | ||
| 242 | - newCfg core.ConfigMap | ||
| 243 | - oldCfg core.ConfigMap | ||
| 244 | - isValid bool | ||
| 245 | + name string | ||
| 246 | + newCfg core.ConfigMap | ||
| 247 | + oldCfg core.ConfigMap | ||
| 248 | + valid bool | ||
| 249 | }{ | ||
| 250 | { | ||
| 251 | - name: "valid", | ||
| 252 | - newCfg: validConfigMap, | ||
| 253 | - oldCfg: validConfigMap, | ||
| 254 | - isValid: true, | ||
| 255 | + name: "valid", | ||
| 256 | + newCfg: configMap, | ||
| 257 | + oldCfg: configMap, | ||
| 258 | + valid: true, | ||
| 259 | }, | ||
| 260 | { | ||
| 261 | - name: "invalid", | ||
| 262 | - newCfg: noVersion, | ||
| 263 | - oldCfg: validConfigMap, | ||
| 264 | - isValid: false, | ||
| 265 | + name: "invalid", | ||
| 266 | + newCfg: noVersion, | ||
| 267 | + oldCfg: configMap, | ||
| 268 | + valid: false, | ||
| 269 | + }, | ||
| 270 | + { | ||
| 271 | + name: "mark configmap immutable", | ||
| 272 | + oldCfg: configMap, | ||
| 273 | + newCfg: immutableConfigMap, | ||
| 274 | + valid: true, | ||
| 275 | + }, | ||
| 276 | + { | ||
| 277 | + name: "revert immutable configmap", | ||
| 278 | + oldCfg: immutableConfigMap, | ||
| 279 | + newCfg: configMap, | ||
| 280 | + valid: false, | ||
| 281 | + }, | ||
| 282 | + { | ||
| 283 | + name: "mark immutable configmap mutable", | ||
| 284 | + oldCfg: immutableConfigMap, | ||
| 285 | + newCfg: mutableConfigMap, | ||
| 286 | + valid: false, | ||
| 287 | + }, | ||
| 288 | + { | ||
| 289 | + name: "add data in configmap", | ||
| 290 | + oldCfg: configMap, | ||
| 291 | + newCfg: configMapWithData, | ||
| 292 | + valid: true, | ||
| 293 | + }, | ||
| 294 | + { | ||
| 295 | + name: "add data in immutable configmap", | ||
| 296 | + oldCfg: immutableConfigMap, | ||
| 297 | + newCfg: immutableConfigMapWithData, | ||
| 298 | + valid: false, | ||
| 299 | + }, | ||
| 300 | + { | ||
| 301 | + name: "change data in configmap", | ||
| 302 | + oldCfg: configMap, | ||
| 303 | + newCfg: configMapWithChangedData, | ||
| 304 | + valid: true, | ||
| 305 | + }, | ||
| 306 | + { | ||
| 307 | + name: "change data in immutable configmap", | ||
| 308 | + oldCfg: immutableConfigMap, | ||
| 309 | + newCfg: immutableConfigMapWithChangedData, | ||
| 310 | + valid: false, | ||
| 311 | }, | ||
| 312 | } | ||
| 313 | |||
| 314 | for _, tc := range cases { | ||
| 315 | - errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg) | ||
| 316 | - if tc.isValid && len(errs) > 0 { | ||
| 317 | - t.Errorf("%v: unexpected error: %v", tc.name, errs) | ||
| 318 | - } | ||
| 319 | - if !tc.isValid && len(errs) == 0 { | ||
| 320 | - t.Errorf("%v: unexpected non-error", tc.name) | ||
| 321 | - } | ||
| 322 | + t.Run(tc.name, func(t *testing.T) { | ||
| 323 | + errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg) | ||
| 324 | + if tc.valid && len(errs) > 0 { | ||
| 325 | + t.Errorf("Unexpected error: %v", errs) | ||
| 326 | + } | ||
| 327 | + if !tc.valid && len(errs) == 0 { | ||
| 328 | + t.Errorf("Unexpected lack of error") | ||
| 329 | + } | ||
| 330 | + }) | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go | ||
| 335 | index 309dbb2955663..00da711112d71 100644 | ||
| 336 | --- a/src/import/pkg/features/kube_features.go | ||
| 337 | +++ b/src/import/pkg/features/kube_features.go | ||
| 338 | @@ -548,6 +548,12 @@ const ( | ||
| 339 | // | ||
| 340 | // Enables topology aware service routing | ||
| 341 | ServiceTopology featuregate.Feature = "ServiceTopology" | ||
| 342 | + | ||
| 343 | + // owner: @wojtek-t | ||
| 344 | + // alpha: v1.18 | ||
| 345 | + // | ||
| 346 | + // Enables a feature to make secrets and configmaps data immutable. | ||
| 347 | + ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes" | ||
| 348 | ) | ||
| 349 | |||
| 350 | func init() { | ||
| 351 | @@ -634,6 +640,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS | ||
| 352 | AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta}, | ||
| 353 | PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta}, | ||
| 354 | ServiceTopology: {Default: false, PreRelease: featuregate.Alpha}, | ||
| 355 | + ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha}, | ||
| 356 | |||
| 357 | // inherited features from generic apiserver, relisted here to get a conflict if it is changed | ||
| 358 | // unintentionally on either side: | ||
| 359 | diff --git a/pkg/registry/core/configmap/strategy.go b/pkg/registry/core/configmap/strategy.go | ||
| 360 | index 4f8bf42e3bd60..d592c181c0c2e 100644 | ||
| 361 | --- a/src/import/pkg/registry/core/configmap/strategy.go | ||
| 362 | +++ b/src/import/pkg/registry/core/configmap/strategy.go | ||
| 363 | @@ -28,9 +28,11 @@ import ( | ||
| 364 | "k8s.io/apiserver/pkg/registry/rest" | ||
| 365 | pkgstorage "k8s.io/apiserver/pkg/storage" | ||
| 366 | "k8s.io/apiserver/pkg/storage/names" | ||
| 367 | + utilfeature "k8s.io/apiserver/pkg/util/feature" | ||
| 368 | "k8s.io/kubernetes/pkg/api/legacyscheme" | ||
| 369 | api "k8s.io/kubernetes/pkg/apis/core" | ||
| 370 | "k8s.io/kubernetes/pkg/apis/core/validation" | ||
| 371 | + "k8s.io/kubernetes/pkg/features" | ||
| 372 | ) | ||
| 373 | |||
| 374 | // strategy implements behavior for ConfigMap objects | ||
| 375 | @@ -54,7 +56,8 @@ func (strategy) NamespaceScoped() bool { | ||
| 376 | } | ||
| 377 | |||
| 378 | func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { | ||
| 379 | - _ = obj.(*api.ConfigMap) | ||
| 380 | + configMap := obj.(*api.ConfigMap) | ||
| 381 | + dropDisabledFields(configMap, nil) | ||
| 382 | } | ||
| 383 | |||
| 384 | func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { | ||
| 385 | @@ -72,12 +75,9 @@ func (strategy) AllowCreateOnUpdate() bool { | ||
| 386 | } | ||
| 387 | |||
| 388 | func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) { | ||
| 389 | - _ = oldObj.(*api.ConfigMap) | ||
| 390 | - _ = newObj.(*api.ConfigMap) | ||
| 391 | -} | ||
| 392 | - | ||
| 393 | -func (strategy) AllowUnconditionalUpdate() bool { | ||
| 394 | - return true | ||
| 395 | + oldConfigMap := oldObj.(*api.ConfigMap) | ||
| 396 | + newConfigMap := newObj.(*api.ConfigMap) | ||
| 397 | + dropDisabledFields(newConfigMap, oldConfigMap) | ||
| 398 | } | ||
| 399 | |||
| 400 | func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Object) field.ErrorList { | ||
| 401 | @@ -86,6 +86,20 @@ func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Objec | ||
| 402 | return validation.ValidateConfigMapUpdate(newCfg, oldCfg) | ||
| 403 | } | ||
| 404 | |||
| 405 | +func isImmutableInUse(configMap *api.ConfigMap) bool { | ||
| 406 | + return configMap != nil && configMap.Immutable != nil | ||
| 407 | +} | ||
| 408 | + | ||
| 409 | +func dropDisabledFields(configMap *api.ConfigMap, oldConfigMap *api.ConfigMap) { | ||
| 410 | + if !utilfeature.DefaultFeatureGate.Enabled(features.ImmutableEphemeralVolumes) && !isImmutableInUse(oldConfigMap) { | ||
| 411 | + configMap.Immutable = nil | ||
| 412 | + } | ||
| 413 | +} | ||
| 414 | + | ||
| 415 | +func (strategy) AllowUnconditionalUpdate() bool { | ||
| 416 | + return true | ||
| 417 | +} | ||
| 418 | + | ||
| 419 | // GetAttrs returns labels and fields of a given object for filtering purposes. | ||
| 420 | func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { | ||
| 421 | configMap, ok := obj.(*api.ConfigMap) | ||
| 422 | diff --git a/pkg/registry/core/secret/strategy.go b/pkg/registry/core/secret/strategy.go | ||
| 423 | index 1701805065e6c..0d5908d8975f1 100644 | ||
| 424 | --- a/src/import/pkg/registry/core/secret/strategy.go | ||
| 425 | +++ b/src/import/pkg/registry/core/secret/strategy.go | ||
| 426 | @@ -29,9 +29,11 @@ import ( | ||
| 427 | "k8s.io/apiserver/pkg/registry/rest" | ||
| 428 | pkgstorage "k8s.io/apiserver/pkg/storage" | ||
| 429 | "k8s.io/apiserver/pkg/storage/names" | ||
| 430 | + utilfeature "k8s.io/apiserver/pkg/util/feature" | ||
| 431 | "k8s.io/kubernetes/pkg/api/legacyscheme" | ||
| 432 | api "k8s.io/kubernetes/pkg/apis/core" | ||
| 433 | "k8s.io/kubernetes/pkg/apis/core/validation" | ||
| 434 | + "k8s.io/kubernetes/pkg/features" | ||
| 435 | ) | ||
| 436 | |||
| 437 | // strategy implements behavior for Secret objects | ||
| 438 | @@ -53,6 +55,8 @@ func (strategy) NamespaceScoped() bool { | ||
| 439 | } | ||
| 440 | |||
| 441 | func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { | ||
| 442 | + secret := obj.(*api.Secret) | ||
| 443 | + dropDisabledFields(secret, nil) | ||
| 444 | } | ||
| 445 | |||
| 446 | func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { | ||
| 447 | @@ -67,12 +71,25 @@ func (strategy) AllowCreateOnUpdate() bool { | ||
| 448 | } | ||
| 449 | |||
| 450 | func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { | ||
| 451 | + newSecret := obj.(*api.Secret) | ||
| 452 | + oldSecret := old.(*api.Secret) | ||
| 453 | + dropDisabledFields(newSecret, oldSecret) | ||
| 454 | } | ||
| 455 | |||
| 456 | func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { | ||
| 457 | return validation.ValidateSecretUpdate(obj.(*api.Secret), old.(*api.Secret)) | ||
| 458 | } | ||
| 459 | |||
| 460 | +func isImmutableInUse(secret *api.Secret) bool { | ||
| 461 | + return secret != nil && secret.Immutable != nil | ||
| 462 | +} | ||
| 463 | + | ||
| 464 | +func dropDisabledFields(secret *api.Secret, oldSecret *api.Secret) { | ||
| 465 | + if !utilfeature.DefaultFeatureGate.Enabled(features.ImmutableEphemeralVolumes) && !isImmutableInUse(oldSecret) { | ||
| 466 | + secret.Immutable = nil | ||
| 467 | + } | ||
| 468 | +} | ||
| 469 | + | ||
| 470 | func (strategy) AllowUnconditionalUpdate() bool { | ||
| 471 | return true | ||
| 472 | } | ||
| 473 | diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go | ||
| 474 | index a78372aeaffa7..1e3aa51730427 100644 | ||
| 475 | --- a/src/import/staging/src/k8s.io/api/core/v1/types.go | ||
| 476 | +++ b/src/import/staging/src/k8s.io/api/core/v1/types.go | ||
| 477 | @@ -5424,6 +5424,14 @@ type Secret struct { | ||
| 478 | // +optional | ||
| 479 | metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` | ||
| 480 | |||
| 481 | + // Immutable, if set to true, ensures that data stored in the Secret cannot | ||
| 482 | + // be updated (only object metadata can be modified). | ||
| 483 | + // If not set to true, the field can be modified at any time. | ||
| 484 | + // Defaulted to nil. | ||
| 485 | + // This is an alpha field enabled by ImmutableEphemeralVolumes feature gate. | ||
| 486 | + // +optional | ||
| 487 | + Immutable *bool `json:"immutable,omitempty"` | ||
| 488 | + | ||
| 489 | // Data contains the secret data. Each key must consist of alphanumeric | ||
| 490 | // characters, '-', '_' or '.'. The serialized form of the secret data is a | ||
| 491 | // base64 encoded string, representing the arbitrary (possibly non-string) | ||
| 492 | @@ -5557,6 +5565,14 @@ type ConfigMap struct { | ||
| 493 | // +optional | ||
| 494 | metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` | ||
| 495 | |||
| 496 | + // Immutable, if set to true, ensures that data stored in the ConfigMap cannot | ||
| 497 | + // be updated (only object metadata can be modified). | ||
| 498 | + // If not set to true, the field can be modified at any time. | ||
| 499 | + // Defaulted to nil. | ||
| 500 | + // This is an alpha field enabled by ImmutableEphemeralVolumes feature gate. | ||
| 501 | + // +optional | ||
| 502 | + Immutable *bool `json:"immutable,omitempty"` | ||
| 503 | + | ||
| 504 | // Data contains the configuration data. | ||
| 505 | // Each key must consist of alphanumeric characters, '-', '_' or '.'. | ||
| 506 | // Values with non-UTF-8 byte sequences must use the BinaryData field. | ||
| 507 | diff --git a/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash.go b/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash.go | ||
| 508 | index 17e24ff3e6443..85bf1e731c3fb 100644 | ||
| 509 | --- a/src/import/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash.go | ||
| 510 | +++ b/src/import/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash.go | ||
| 511 | @@ -90,7 +90,14 @@ func SecretHash(sec *v1.Secret) (string, error) { | ||
| 512 | // Data, Kind, and Name are taken into account. | ||
| 513 | func encodeConfigMap(cm *v1.ConfigMap) (string, error) { | ||
| 514 | // json.Marshal sorts the keys in a stable order in the encoding | ||
| 515 | - m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data} | ||
| 516 | + m := map[string]interface{}{ | ||
| 517 | + "kind": "ConfigMap", | ||
| 518 | + "name": cm.Name, | ||
| 519 | + "data": cm.Data, | ||
| 520 | + } | ||
| 521 | + if cm.Immutable != nil { | ||
| 522 | + m["immutable"] = *cm.Immutable | ||
| 523 | + } | ||
| 524 | if len(cm.BinaryData) > 0 { | ||
| 525 | m["binaryData"] = cm.BinaryData | ||
| 526 | } | ||
| 527 | @@ -105,7 +112,16 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) { | ||
| 528 | // Data, Kind, Name, and Type are taken into account. | ||
| 529 | func encodeSecret(sec *v1.Secret) (string, error) { | ||
| 530 | // json.Marshal sorts the keys in a stable order in the encoding | ||
| 531 | - data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data}) | ||
| 532 | + m := map[string]interface{}{ | ||
| 533 | + "kind": "Secret", | ||
| 534 | + "type": sec.Type, | ||
| 535 | + "name": sec.Name, | ||
| 536 | + "data": sec.Data, | ||
| 537 | + } | ||
| 538 | + if sec.Immutable != nil { | ||
| 539 | + m["immutable"] = *sec.Immutable | ||
| 540 | + } | ||
| 541 | + data, err := json.Marshal(m) | ||
| 542 | if err != nil { | ||
| 543 | return "", err | ||
| 544 | } | ||
| 545 | diff --git a/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash_test.go b/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash_test.go | ||
| 546 | index 2d336f35a824e..144fe444e4cac 100644 | ||
| 547 | --- a/src/import/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash_test.go | ||
| 548 | +++ b/src/import/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash/hash_test.go | ||
| 549 | @@ -178,8 +178,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta). | ||
| 550 | obj interface{} | ||
| 551 | expect int | ||
| 552 | }{ | ||
| 553 | - {"ConfigMap", v1.ConfigMap{}, 4}, | ||
| 554 | - {"Secret", v1.Secret{}, 5}, | ||
| 555 | + {"ConfigMap", v1.ConfigMap{}, 5}, | ||
| 556 | + {"Secret", v1.Secret{}, 6}, | ||
| 557 | } | ||
| 558 | for _, c := range cases { | ||
| 559 | val := reflect.ValueOf(c.obj) | ||
| 560 | diff --git a/staging/src/k8s.io/kubectl/pkg/util/hash/hash.go b/staging/src/k8s.io/kubectl/pkg/util/hash/hash.go | ||
| 561 | index de0036245d2f1..1b20f384b7098 100644 | ||
| 562 | --- a/src/import/staging/src/k8s.io/kubectl/pkg/util/hash/hash.go | ||
| 563 | +++ b/src/import/staging/src/k8s.io/kubectl/pkg/util/hash/hash.go | ||
| 564 | @@ -56,7 +56,14 @@ func SecretHash(sec *v1.Secret) (string, error) { | ||
| 565 | // Data, Kind, and Name are taken into account. | ||
| 566 | func encodeConfigMap(cm *v1.ConfigMap) (string, error) { | ||
| 567 | // json.Marshal sorts the keys in a stable order in the encoding | ||
| 568 | - m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data} | ||
| 569 | + m := map[string]interface{}{ | ||
| 570 | + "kind": "ConfigMap", | ||
| 571 | + "name": cm.Name, | ||
| 572 | + "data": cm.Data, | ||
| 573 | + } | ||
| 574 | + if cm.Immutable != nil { | ||
| 575 | + m["immutable"] = *cm.Immutable | ||
| 576 | + } | ||
| 577 | if len(cm.BinaryData) > 0 { | ||
| 578 | m["binaryData"] = cm.BinaryData | ||
| 579 | } | ||
| 580 | @@ -70,8 +77,17 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) { | ||
| 581 | // encodeSecret encodes a Secret. | ||
| 582 | // Data, Kind, Name, and Type are taken into account. | ||
| 583 | func encodeSecret(sec *v1.Secret) (string, error) { | ||
| 584 | + m := map[string]interface{}{ | ||
| 585 | + "kind": "Secret", | ||
| 586 | + "type": sec.Type, | ||
| 587 | + "name": sec.Name, | ||
| 588 | + "data": sec.Data, | ||
| 589 | + } | ||
| 590 | + if sec.Immutable != nil { | ||
| 591 | + m["immutable"] = *sec.Immutable | ||
| 592 | + } | ||
| 593 | // json.Marshal sorts the keys in a stable order in the encoding | ||
| 594 | - data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data}) | ||
| 595 | + data, err := json.Marshal(m) | ||
| 596 | if err != nil { | ||
| 597 | return "", err | ||
| 598 | } | ||
| 599 | diff --git a/staging/src/k8s.io/kubectl/pkg/util/hash/hash_test.go b/staging/src/k8s.io/kubectl/pkg/util/hash/hash_test.go | ||
| 600 | index f527a98a2026c..455459c3b3df5 100644 | ||
| 601 | --- a/src/import/staging/src/k8s.io/kubectl/pkg/util/hash/hash_test.go | ||
| 602 | +++ b/src/import/staging/src/k8s.io/kubectl/pkg/util/hash/hash_test.go | ||
| 603 | @@ -164,8 +164,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta). | ||
| 604 | obj interface{} | ||
| 605 | expect int | ||
| 606 | }{ | ||
| 607 | - {"ConfigMap", v1.ConfigMap{}, 4}, | ||
| 608 | - {"Secret", v1.Secret{}, 5}, | ||
| 609 | + {"ConfigMap", v1.ConfigMap{}, 5}, | ||
| 610 | + {"Secret", v1.Secret{}, 6}, | ||
| 611 | } | ||
| 612 | for _, c := range cases { | ||
| 613 | val := reflect.ValueOf(c.obj) | ||
diff --git a/recipes-containers/kubernetes/kubernetes/CVE-2021-25735.patch b/recipes-containers/kubernetes/kubernetes/CVE-2021-25735.patch new file mode 100644 index 00000000..dce50f3e --- /dev/null +++ b/recipes-containers/kubernetes/kubernetes/CVE-2021-25735.patch | |||
| @@ -0,0 +1,535 @@ | |||
| 1 | From 7d4efe7ad8cf06c0c1d6092cf1f77964edb8016f Mon Sep 17 00:00:00 2001 | ||
| 2 | From: David Eads <deads@redhat.com> | ||
| 3 | Date: Fri, 29 Jan 2021 13:47:31 -0500 | ||
| 4 | Subject: [PATCH 1/8] tweak validation to avoid mutation | ||
| 5 | |||
| 6 | Upstream-Status: Backport [https://github.com/kubernetes/kubernetes/commit/d57f0641d60b73934ebc2cdf4b6a63182217d10c] | ||
| 7 | CVE: CVE-2021-25735 | ||
| 8 | Signed-off-by: Vijay Anusuri <vanusuri@mvista.com> | ||
| 9 | --- | ||
| 10 | pkg/apis/core/validation/validation.go | 46 +++++++++----------------- | ||
| 11 | 1 file changed, 15 insertions(+), 31 deletions(-) | ||
| 12 | |||
| 13 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 14 | index 8e3cfd9d9e4..89e5b5811c4 100644 | ||
| 15 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 16 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 17 | @@ -29,8 +29,6 @@ import ( | ||
| 18 | "unicode" | ||
| 19 | "unicode/utf8" | ||
| 20 | |||
| 21 | - "k8s.io/klog" | ||
| 22 | - | ||
| 23 | "k8s.io/api/core/v1" | ||
| 24 | apiequality "k8s.io/apimachinery/pkg/api/equality" | ||
| 25 | "k8s.io/apimachinery/pkg/api/resource" | ||
| 26 | @@ -4530,11 +4528,8 @@ func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { | ||
| 27 | addresses[address] = true | ||
| 28 | } | ||
| 29 | |||
| 30 | - if len(oldNode.Spec.PodCIDRs) == 0 { | ||
| 31 | - // Allow the controller manager to assign a CIDR to a node if it doesn't have one. | ||
| 32 | - //this is a no op for a string slice. | ||
| 33 | - oldNode.Spec.PodCIDRs = node.Spec.PodCIDRs | ||
| 34 | - } else { | ||
| 35 | + // Allow the controller manager to assign a CIDR to a node if it doesn't have one. | ||
| 36 | + if len(oldNode.Spec.PodCIDRs) > 0 { | ||
| 37 | // compare the entire slice | ||
| 38 | if len(oldNode.Spec.PodCIDRs) != len(node.Spec.PodCIDRs) { | ||
| 39 | allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDRs"), "node updates may not change podCIDR except from \"\" to valid")) | ||
| 40 | @@ -4548,46 +4543,35 @@ func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { | ||
| 41 | } | ||
| 42 | |||
| 43 | // Allow controller manager updating provider ID when not set | ||
| 44 | - if len(oldNode.Spec.ProviderID) == 0 { | ||
| 45 | - oldNode.Spec.ProviderID = node.Spec.ProviderID | ||
| 46 | - } else { | ||
| 47 | - if oldNode.Spec.ProviderID != node.Spec.ProviderID { | ||
| 48 | - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid")) | ||
| 49 | - } | ||
| 50 | + if len(oldNode.Spec.ProviderID) > 0 && oldNode.Spec.ProviderID != node.Spec.ProviderID { | ||
| 51 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid")) | ||
| 52 | } | ||
| 53 | |||
| 54 | if node.Spec.ConfigSource != nil { | ||
| 55 | allErrs = append(allErrs, validateNodeConfigSourceSpec(node.Spec.ConfigSource, field.NewPath("spec", "configSource"))...) | ||
| 56 | } | ||
| 57 | - oldNode.Spec.ConfigSource = node.Spec.ConfigSource | ||
| 58 | if node.Status.Config != nil { | ||
| 59 | allErrs = append(allErrs, validateNodeConfigStatus(node.Status.Config, field.NewPath("status", "config"))...) | ||
| 60 | } | ||
| 61 | - oldNode.Status.Config = node.Status.Config | ||
| 62 | - | ||
| 63 | - // TODO: move reset function to its own location | ||
| 64 | - // Ignore metadata changes now that they have been tested | ||
| 65 | - oldNode.ObjectMeta = node.ObjectMeta | ||
| 66 | - // Allow users to update capacity | ||
| 67 | - oldNode.Status.Capacity = node.Status.Capacity | ||
| 68 | - // Allow users to unschedule node | ||
| 69 | - oldNode.Spec.Unschedulable = node.Spec.Unschedulable | ||
| 70 | - // Clear status | ||
| 71 | - oldNode.Status = node.Status | ||
| 72 | |||
| 73 | // update taints | ||
| 74 | if len(node.Spec.Taints) > 0 { | ||
| 75 | allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) | ||
| 76 | } | ||
| 77 | - oldNode.Spec.Taints = node.Spec.Taints | ||
| 78 | |||
| 79 | - // We made allowed changes to oldNode, and now we compare oldNode to node. Any remaining differences indicate changes to protected fields. | ||
| 80 | - // TODO: Add a 'real' error type for this error and provide print actual diffs. | ||
| 81 | - if !apiequality.Semantic.DeepEqual(oldNode, node) { | ||
| 82 | - klog.V(4).Infof("Update failed validation %#v vs %#v", oldNode, node) | ||
| 83 | - allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "node updates may only change labels, taints, or capacity (or configSource, if the DynamicKubeletConfig feature gate is enabled)")) | ||
| 84 | + if node.Spec.DoNotUseExternalID != oldNode.Spec.DoNotUseExternalID { | ||
| 85 | + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "externalID"), "may not be updated")) | ||
| 86 | } | ||
| 87 | |||
| 88 | + // status and metadata are allowed change (barring restrictions above), so separately test spec field. | ||
| 89 | + // spec only has a few fields, so check the ones we don't allow changing | ||
| 90 | + // 1. PodCIDRs - immutable after first set - checked above | ||
| 91 | + // 2. ProviderID - immutable after first set - checked above | ||
| 92 | + // 3. Unschedulable - allowed to change | ||
| 93 | + // 4. Taints - allowed to change | ||
| 94 | + // 5. ConfigSource - allowed to change (and checked above) | ||
| 95 | + // 6. DoNotUseExternalID - immutable - checked above | ||
| 96 | + | ||
| 97 | return allErrs | ||
| 98 | } | ||
| 99 | |||
| 100 | -- | ||
| 101 | 2.25.1 | ||
| 102 | |||
| 103 | |||
| 104 | From 0ef8605f4535713f17ede4bcf162ad513cbf6900 Mon Sep 17 00:00:00 2001 | ||
| 105 | From: David Eads <deads@redhat.com> | ||
| 106 | Date: Mon, 15 Feb 2021 16:21:42 -0500 | ||
| 107 | Subject: [PATCH 2/8] remove unnecessary mutations in validation | ||
| 108 | |||
| 109 | These mutations are already done in the strategy | ||
| 110 | --- | ||
| 111 | pkg/apis/core/validation/validation.go | 22 ++-------------------- | ||
| 112 | 1 file changed, 2 insertions(+), 20 deletions(-) | ||
| 113 | |||
| 114 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 115 | index 89e5b5811c4..3381f2a37c2 100644 | ||
| 116 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 117 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 118 | @@ -1874,13 +1874,11 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E | ||
| 119 | } | ||
| 120 | |||
| 121 | // ValidatePersistentVolumeStatusUpdate tests to see if the status update is legal for an end user to make. | ||
| 122 | -// newPv is updated with fields that cannot be changed. | ||
| 123 | func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList { | ||
| 124 | allErrs := ValidateObjectMetaUpdate(&newPv.ObjectMeta, &oldPv.ObjectMeta, field.NewPath("metadata")) | ||
| 125 | if len(newPv.ResourceVersion) == 0 { | ||
| 126 | allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) | ||
| 127 | } | ||
| 128 | - newPv.Spec = oldPv.Spec | ||
| 129 | return allErrs | ||
| 130 | } | ||
| 131 | |||
| 132 | @@ -2026,7 +2024,6 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVo | ||
| 133 | for r, qty := range newPvc.Status.Capacity { | ||
| 134 | allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) | ||
| 135 | } | ||
| 136 | - newPvc.Spec = oldPvc.Spec | ||
| 137 | return allErrs | ||
| 138 | } | ||
| 139 | |||
| 140 | @@ -3795,8 +3792,7 @@ func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerS | ||
| 141 | return allErrs | ||
| 142 | } | ||
| 143 | |||
| 144 | -// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields | ||
| 145 | -// that cannot be changed. | ||
| 146 | +// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. | ||
| 147 | func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList { | ||
| 148 | fldPath := field.NewPath("metadata") | ||
| 149 | allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) | ||
| 150 | @@ -3819,9 +3815,6 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList { | ||
| 151 | allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...) | ||
| 152 | allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), oldPod.Spec.RestartPolicy)...) | ||
| 153 | |||
| 154 | - // For status update we ignore changes to pod spec. | ||
| 155 | - newPod.Spec = oldPod.Spec | ||
| 156 | - | ||
| 157 | return allErrs | ||
| 158 | } | ||
| 159 | |||
| 160 | @@ -5287,7 +5280,6 @@ func ValidateResourceQuantityValue(resource string, value resource.Quantity, fld | ||
| 161 | } | ||
| 162 | |||
| 163 | // ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make. | ||
| 164 | -// newResourceQuota is updated with fields that cannot be changed. | ||
| 165 | func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { | ||
| 166 | allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) | ||
| 167 | allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...) | ||
| 168 | @@ -5306,12 +5298,10 @@ func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.Resour | ||
| 169 | allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, fieldImmutableErrorMsg)) | ||
| 170 | } | ||
| 171 | |||
| 172 | - newResourceQuota.Status = oldResourceQuota.Status | ||
| 173 | return allErrs | ||
| 174 | } | ||
| 175 | |||
| 176 | // ValidateResourceQuotaStatusUpdate tests to see if the status update is legal for an end user to make. | ||
| 177 | -// newResourceQuota is updated with fields that cannot be changed. | ||
| 178 | func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { | ||
| 179 | allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) | ||
| 180 | if len(newResourceQuota.ResourceVersion) == 0 { | ||
| 181 | @@ -5329,7 +5319,6 @@ func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *core. | ||
| 182 | allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) | ||
| 183 | allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) | ||
| 184 | } | ||
| 185 | - newResourceQuota.Spec = oldResourceQuota.Spec | ||
| 186 | return allErrs | ||
| 187 | } | ||
| 188 | |||
| 189 | @@ -5362,19 +5351,14 @@ func validateKubeFinalizerName(stringValue string, fldPath *field.Path) field.Er | ||
| 190 | } | ||
| 191 | |||
| 192 | // ValidateNamespaceUpdate tests to make sure a namespace update can be applied. | ||
| 193 | -// newNamespace is updated with fields that cannot be changed | ||
| 194 | func ValidateNamespaceUpdate(newNamespace *core.Namespace, oldNamespace *core.Namespace) field.ErrorList { | ||
| 195 | allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) | ||
| 196 | - newNamespace.Spec.Finalizers = oldNamespace.Spec.Finalizers | ||
| 197 | - newNamespace.Status = oldNamespace.Status | ||
| 198 | return allErrs | ||
| 199 | } | ||
| 200 | |||
| 201 | -// ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. newNamespace is updated with fields | ||
| 202 | -// that cannot be changed. | ||
| 203 | +// ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. | ||
| 204 | func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { | ||
| 205 | allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) | ||
| 206 | - newNamespace.Spec = oldNamespace.Spec | ||
| 207 | if newNamespace.DeletionTimestamp.IsZero() { | ||
| 208 | if newNamespace.Status.Phase != core.NamespaceActive { | ||
| 209 | allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Active' if `deletionTimestamp` is empty")) | ||
| 210 | @@ -5388,7 +5372,6 @@ func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *core.Namespace) f | ||
| 211 | } | ||
| 212 | |||
| 213 | // ValidateNamespaceFinalizeUpdate tests to see if the update is legal for an end user to make. | ||
| 214 | -// newNamespace is updated with fields that cannot be changed. | ||
| 215 | func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { | ||
| 216 | allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) | ||
| 217 | |||
| 218 | @@ -5397,7 +5380,6 @@ func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace) | ||
| 219 | idxPath := fldPath.Index(i) | ||
| 220 | allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]), idxPath)...) | ||
| 221 | } | ||
| 222 | - newNamespace.Status = oldNamespace.Status | ||
| 223 | return allErrs | ||
| 224 | } | ||
| 225 | |||
| 226 | -- | ||
| 227 | 2.25.1 | ||
| 228 | |||
| 229 | |||
| 230 | From 198ac41f97e11140b634274e34f0102e33806145 Mon Sep 17 00:00:00 2001 | ||
| 231 | From: David Eads <deads@redhat.com> | ||
| 232 | Date: Mon, 15 Feb 2021 16:55:41 -0500 | ||
| 233 | Subject: [PATCH 3/8] move secret mutation from validation to prepareforupdate | ||
| 234 | |||
| 235 | --- | ||
| 236 | pkg/apis/core/validation/validation.go | 4 ---- | ||
| 237 | pkg/registry/core/secret/strategy.go | 6 ++++++ | ||
| 238 | 2 files changed, 6 insertions(+), 4 deletions(-) | ||
| 239 | |||
| 240 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 241 | index 3381f2a37c2..9775b268e90 100644 | ||
| 242 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 243 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 244 | @@ -4977,10 +4977,6 @@ func ValidateSecret(secret *core.Secret) field.ErrorList { | ||
| 245 | func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList { | ||
| 246 | allErrs := ValidateObjectMetaUpdate(&newSecret.ObjectMeta, &oldSecret.ObjectMeta, field.NewPath("metadata")) | ||
| 247 | |||
| 248 | - if len(newSecret.Type) == 0 { | ||
| 249 | - newSecret.Type = oldSecret.Type | ||
| 250 | - } | ||
| 251 | - | ||
| 252 | allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...) | ||
| 253 | if oldSecret.Immutable != nil && *oldSecret.Immutable { | ||
| 254 | if !reflect.DeepEqual(newSecret.Immutable, oldSecret.Immutable) { | ||
| 255 | diff --git a/pkg/registry/core/secret/strategy.go b/pkg/registry/core/secret/strategy.go | ||
| 256 | index 0d5908d8975..aad00387ac1 100644 | ||
| 257 | --- a/src/import/pkg/registry/core/secret/strategy.go | ||
| 258 | +++ b/src/import/pkg/registry/core/secret/strategy.go | ||
| 259 | @@ -73,6 +73,12 @@ func (strategy) AllowCreateOnUpdate() bool { | ||
| 260 | func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { | ||
| 261 | newSecret := obj.(*api.Secret) | ||
| 262 | oldSecret := old.(*api.Secret) | ||
| 263 | + | ||
| 264 | + // this is weird, but consistent with what the validatedUpdate function used to do. | ||
| 265 | + if len(newSecret.Type) == 0 { | ||
| 266 | + newSecret.Type = oldSecret.Type | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | dropDisabledFields(newSecret, oldSecret) | ||
| 270 | } | ||
| 271 | |||
| 272 | -- | ||
| 273 | 2.25.1 | ||
| 274 | |||
| 275 | |||
| 276 | From 7973d58ea8fe93c2be920a766c7c5d6a4a2529e6 Mon Sep 17 00:00:00 2001 | ||
| 277 | From: David Eads <deads@redhat.com> | ||
| 278 | Date: Mon, 15 Feb 2021 17:18:11 -0500 | ||
| 279 | Subject: [PATCH 4/8] add markers for inspected validation mutation hits | ||
| 280 | |||
| 281 | --- | ||
| 282 | pkg/apis/core/validation/validation.go | 10 +++++----- | ||
| 283 | .../pkg/apis/apiextensions/validation/validation.go | 2 +- | ||
| 284 | 2 files changed, 6 insertions(+), 6 deletions(-) | ||
| 285 | |||
| 286 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 287 | index 9775b268e90..6f634db468e 100644 | ||
| 288 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 289 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 290 | @@ -1953,7 +1953,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl | ||
| 291 | // Claims are immutable in order to enforce quota, range limits, etc. without gaming the system. | ||
| 292 | if len(oldPvc.Spec.VolumeName) == 0 { | ||
| 293 | // volumeName changes are allowed once. | ||
| 294 | - oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName | ||
| 295 | + oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName // +k8s:verify-mutation:reason=clone | ||
| 296 | } | ||
| 297 | |||
| 298 | if validateStorageClassUpgrade(oldPvcClone.Annotations, newPvcClone.Annotations, | ||
| 299 | @@ -1969,7 +1969,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl | ||
| 300 | if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { | ||
| 301 | // lets make sure storage values are same. | ||
| 302 | if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil { | ||
| 303 | - newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] | ||
| 304 | + newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] // +k8s:verify-mutation:reason=clone | ||
| 305 | } | ||
| 306 | |||
| 307 | oldSize := oldPvc.Spec.Resources.Requests["storage"] | ||
| 308 | @@ -2317,13 +2317,13 @@ func GetVolumeMountMap(mounts []core.VolumeMount) map[string]string { | ||
| 309 | } | ||
| 310 | |||
| 311 | func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string { | ||
| 312 | - voldevices := make(map[string]string) | ||
| 313 | + volDevices := make(map[string]string) | ||
| 314 | |||
| 315 | for _, dev := range devices { | ||
| 316 | - voldevices[dev.Name] = dev.DevicePath | ||
| 317 | + volDevices[dev.Name] = dev.DevicePath | ||
| 318 | } | ||
| 319 | |||
| 320 | - return voldevices | ||
| 321 | + return volDevices | ||
| 322 | } | ||
| 323 | |||
| 324 | func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList { | ||
| 325 | diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go | ||
| 326 | index f570dd82a4b..2bc72643c85 100644 | ||
| 327 | --- a/src/import/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go | ||
| 328 | +++ b/src/import/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go | ||
| 329 | @@ -1304,7 +1304,7 @@ func validateAPIApproval(newCRD, oldCRD *apiextensions.CustomResourceDefinition, | ||
| 330 | var oldApprovalState *apihelpers.APIApprovalState | ||
| 331 | if oldCRD != nil { | ||
| 332 | t, _ := apihelpers.GetAPIApprovalState(oldCRD.Annotations) | ||
| 333 | - oldApprovalState = &t | ||
| 334 | + oldApprovalState = &t // +k8s:verify-mutation:reason=clone | ||
| 335 | } | ||
| 336 | newApprovalState, reason := apihelpers.GetAPIApprovalState(newCRD.Annotations) | ||
| 337 | |||
| 338 | -- | ||
| 339 | 2.25.1 | ||
| 340 | |||
| 341 | |||
| 342 | From 0b8dcbecdc093829aaccee7bf541ef8cae7f3848 Mon Sep 17 00:00:00 2001 | ||
| 343 | From: David Eads <deads@redhat.com> | ||
| 344 | Date: Mon, 15 Feb 2021 17:33:34 -0500 | ||
| 345 | Subject: [PATCH 5/8] remove pod toleration toleration seconds mutation | ||
| 346 | |||
| 347 | --- | ||
| 348 | pkg/apis/core/validation/validation.go | 16 ++++++++-------- | ||
| 349 | 1 file changed, 8 insertions(+), 8 deletions(-) | ||
| 350 | |||
| 351 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 352 | index 6f634db468e..4226047775b 100644 | ||
| 353 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 354 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 355 | @@ -2967,10 +2967,11 @@ func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldToleratio | ||
| 356 | allErrs := field.ErrorList{} | ||
| 357 | for _, old := range oldTolerations { | ||
| 358 | found := false | ||
| 359 | - old.TolerationSeconds = nil | ||
| 360 | - for _, new := range newTolerations { | ||
| 361 | - new.TolerationSeconds = nil | ||
| 362 | - if reflect.DeepEqual(old, new) { | ||
| 363 | + oldTolerationClone := old.DeepCopy() | ||
| 364 | + for _, newToleration := range newTolerations { | ||
| 365 | + // assign to our clone before doing a deep equal so we can allow tolerationseconds to change. | ||
| 366 | + oldTolerationClone.TolerationSeconds = newToleration.TolerationSeconds // +k8s:verify-mutation:reason=clone | ||
| 367 | + if reflect.DeepEqual(*oldTolerationClone, newToleration) { | ||
| 368 | found = true | ||
| 369 | break | ||
| 370 | } | ||
| 371 | @@ -3730,6 +3731,9 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList { | ||
| 372 | allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newPod.Spec.ActiveDeadlineSeconds, "must not update from a positive integer to nil value")) | ||
| 373 | } | ||
| 374 | |||
| 375 | + // Allow only additions to tolerations updates. | ||
| 376 | + allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) | ||
| 377 | + | ||
| 378 | // handle updateable fields by munging those fields prior to deep equal comparison. | ||
| 379 | mungedPod := *newPod | ||
| 380 | // munge spec.containers[*].image | ||
| 381 | @@ -3753,10 +3757,6 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList { | ||
| 382 | mungedPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds | ||
| 383 | } | ||
| 384 | |||
| 385 | - // Allow only additions to tolerations updates. | ||
| 386 | - mungedPod.Spec.Tolerations = oldPod.Spec.Tolerations | ||
| 387 | - allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) | ||
| 388 | - | ||
| 389 | if !apiequality.Semantic.DeepEqual(mungedPod.Spec, oldPod.Spec) { | ||
| 390 | // This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is". | ||
| 391 | //TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff | ||
| 392 | -- | ||
| 393 | 2.25.1 | ||
| 394 | |||
| 395 | |||
| 396 | From db5696ebe654a487c0216bd0646ffb9872bac39a Mon Sep 17 00:00:00 2001 | ||
| 397 | From: David Eads <deads@redhat.com> | ||
| 398 | Date: Mon, 15 Feb 2021 17:43:57 -0500 | ||
| 399 | Subject: [PATCH 6/8] full deepcopy on munged pod spec | ||
| 400 | |||
| 401 | --- | ||
| 402 | pkg/apis/core/validation/validation.go | 30 ++++++++++++++++---------- | ||
| 403 | 1 file changed, 19 insertions(+), 11 deletions(-) | ||
| 404 | |||
| 405 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 406 | index 4226047775b..d6eb9fe56f4 100644 | ||
| 407 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 408 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 409 | @@ -3734,33 +3734,41 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList { | ||
| 410 | // Allow only additions to tolerations updates. | ||
| 411 | allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) | ||
| 412 | |||
| 413 | + // the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have | ||
| 414 | + // so far and save the cost of a deep copy. | ||
| 415 | + if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) { | ||
| 416 | + return allErrs | ||
| 417 | + } | ||
| 418 | + | ||
| 419 | // handle updateable fields by munging those fields prior to deep equal comparison. | ||
| 420 | - mungedPod := *newPod | ||
| 421 | + mungedPodSpec := *newPod.Spec.DeepCopy() | ||
| 422 | // munge spec.containers[*].image | ||
| 423 | var newContainers []core.Container | ||
| 424 | - for ix, container := range mungedPod.Spec.Containers { | ||
| 425 | - container.Image = oldPod.Spec.Containers[ix].Image | ||
| 426 | + for ix, container := range mungedPodSpec.Containers { | ||
| 427 | + container.Image = oldPod.Spec.Containers[ix].Image // +k8s:verify-mutation:reason=clone | ||
| 428 | newContainers = append(newContainers, container) | ||
| 429 | } | ||
| 430 | - mungedPod.Spec.Containers = newContainers | ||
| 431 | + mungedPodSpec.Containers = newContainers | ||
| 432 | // munge spec.initContainers[*].image | ||
| 433 | var newInitContainers []core.Container | ||
| 434 | - for ix, container := range mungedPod.Spec.InitContainers { | ||
| 435 | - container.Image = oldPod.Spec.InitContainers[ix].Image | ||
| 436 | + for ix, container := range mungedPodSpec.InitContainers { | ||
| 437 | + container.Image = oldPod.Spec.InitContainers[ix].Image // +k8s:verify-mutation:reason=clone | ||
| 438 | newInitContainers = append(newInitContainers, container) | ||
| 439 | } | ||
| 440 | - mungedPod.Spec.InitContainers = newInitContainers | ||
| 441 | + mungedPodSpec.InitContainers = newInitContainers | ||
| 442 | // munge spec.activeDeadlineSeconds | ||
| 443 | - mungedPod.Spec.ActiveDeadlineSeconds = nil | ||
| 444 | + mungedPodSpec.ActiveDeadlineSeconds = nil | ||
| 445 | if oldPod.Spec.ActiveDeadlineSeconds != nil { | ||
| 446 | activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds | ||
| 447 | - mungedPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds | ||
| 448 | + mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds | ||
| 449 | } | ||
| 450 | + // tolerations are checked before the deep copy, so munge those too | ||
| 451 | + mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone | ||
| 452 | |||
| 453 | - if !apiequality.Semantic.DeepEqual(mungedPod.Spec, oldPod.Spec) { | ||
| 454 | + if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) { | ||
| 455 | // This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is". | ||
| 456 | //TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff | ||
| 457 | - specDiff := diff.ObjectDiff(mungedPod.Spec, oldPod.Spec) | ||
| 458 | + specDiff := diff.ObjectDiff(mungedPodSpec, oldPod.Spec) | ||
| 459 | allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)\n%v", specDiff))) | ||
| 460 | } | ||
| 461 | |||
| 462 | -- | ||
| 463 | 2.25.1 | ||
| 464 | |||
| 465 | |||
| 466 | From 888666d4d5b96328f7e9d96c0946e9d7c8222f81 Mon Sep 17 00:00:00 2001 | ||
| 467 | From: David Eads <deads@redhat.com> | ||
| 468 | Date: Wed, 17 Feb 2021 10:51:38 -0500 | ||
| 469 | Subject: [PATCH 7/8] deepcopy statefulsets | ||
| 470 | |||
| 471 | --- | ||
| 472 | pkg/apis/apps/validation/validation.go | 20 +++++++------------- | ||
| 473 | 1 file changed, 7 insertions(+), 13 deletions(-) | ||
| 474 | |||
| 475 | diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go | ||
| 476 | index 6ac73cb6b7e..03e0d2024dd 100644 | ||
| 477 | --- a/src/import/pkg/apis/apps/validation/validation.go | ||
| 478 | +++ b/src/import/pkg/apis/apps/validation/validation.go | ||
| 479 | @@ -144,21 +144,15 @@ func ValidateStatefulSet(statefulSet *apps.StatefulSet) field.ErrorList { | ||
| 480 | func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList { | ||
| 481 | allErrs := apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata")) | ||
| 482 | |||
| 483 | - restoreReplicas := statefulSet.Spec.Replicas | ||
| 484 | - statefulSet.Spec.Replicas = oldStatefulSet.Spec.Replicas | ||
| 485 | - | ||
| 486 | - restoreTemplate := statefulSet.Spec.Template | ||
| 487 | - statefulSet.Spec.Template = oldStatefulSet.Spec.Template | ||
| 488 | - | ||
| 489 | - restoreStrategy := statefulSet.Spec.UpdateStrategy | ||
| 490 | - statefulSet.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy | ||
| 491 | - | ||
| 492 | - if !apiequality.Semantic.DeepEqual(statefulSet.Spec, oldStatefulSet.Spec) { | ||
| 493 | + // statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this | ||
| 494 | + // deep copy right away. This avoids mutating our inputs | ||
| 495 | + newStatefulSetClone := statefulSet.DeepCopy() | ||
| 496 | + newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone | ||
| 497 | + newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone | ||
| 498 | + newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone | ||
| 499 | + if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) { | ||
| 500 | allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden")) | ||
| 501 | } | ||
| 502 | - statefulSet.Spec.Replicas = restoreReplicas | ||
| 503 | - statefulSet.Spec.Template = restoreTemplate | ||
| 504 | - statefulSet.Spec.UpdateStrategy = restoreStrategy | ||
| 505 | |||
| 506 | allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(statefulSet.Spec.Replicas), field.NewPath("spec", "replicas"))...) | ||
| 507 | return allErrs | ||
| 508 | -- | ||
| 509 | 2.25.1 | ||
| 510 | |||
| 511 | |||
| 512 | From bfbe634654ae1ac86033b69703ed78ade5d605ea Mon Sep 17 00:00:00 2001 | ||
| 513 | From: David Eads <deads@redhat.com> | ||
| 514 | Date: Wed, 17 Mar 2021 09:12:42 -0400 | ||
| 515 | Subject: [PATCH 8/8] bazel | ||
| 516 | |||
| 517 | --- | ||
| 518 | pkg/apis/core/validation/BUILD | 1 - | ||
| 519 | 1 file changed, 1 deletion(-) | ||
| 520 | |||
| 521 | diff --git a/pkg/apis/core/validation/BUILD b/pkg/apis/core/validation/BUILD | ||
| 522 | index 2db631180e6..00649a3a52c 100644 | ||
| 523 | --- a/src/import/pkg/apis/core/validation/BUILD | ||
| 524 | +++ b/src/import/pkg/apis/core/validation/BUILD | ||
| 525 | @@ -40,7 +40,6 @@ go_library( | ||
| 526 | "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", | ||
| 527 | "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", | ||
| 528 | "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", | ||
| 529 | - "//vendor/k8s.io/klog:go_default_library", | ||
| 530 | "//vendor/k8s.io/utils/net:go_default_library", | ||
| 531 | ], | ||
| 532 | ) | ||
| 533 | -- | ||
| 534 | 2.25.1 | ||
| 535 | |||
diff --git a/recipes-containers/kubernetes/kubernetes/CVE-2021-25737.patch b/recipes-containers/kubernetes/kubernetes/CVE-2021-25737.patch new file mode 100644 index 00000000..d1a97971 --- /dev/null +++ b/recipes-containers/kubernetes/kubernetes/CVE-2021-25737.patch | |||
| @@ -0,0 +1,128 @@ | |||
| 1 | From 901e8e07e1f031456ecd7fefce965aaa05916825 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Rob Scott <robertjscott@google.com> | ||
| 3 | Date: Fri, 9 Apr 2021 15:24:17 -0700 | ||
| 4 | Subject: [PATCH] Updating EndpointSlice validation to match Endpoints | ||
| 5 | validation | ||
| 6 | |||
| 7 | Upstream-Status: Backport [https://github.com/kubernetes/kubernetes/commit/901e8e07e1f031456ecd7fefce965aaa05916825] | ||
| 8 | CVE: CVE-2021-25737 | ||
| 9 | Signed-off-by: Vijay Anusuri <vanusuri@mvista.com> | ||
| 10 | --- | ||
| 11 | pkg/apis/core/validation/validation.go | 18 ++++++---- | ||
| 12 | pkg/apis/discovery/validation/validation.go | 2 ++ | ||
| 13 | .../discovery/validation/validation_test.go | 34 +++++++++++++++++-- | ||
| 14 | 3 files changed, 45 insertions(+), 9 deletions(-) | ||
| 15 | |||
| 16 | diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go | ||
| 17 | index 3daeb139d590d..c65cdd40f9061 100644 | ||
| 18 | --- a/src/import/pkg/apis/core/validation/validation.go | ||
| 19 | +++ b/src/import/pkg/apis/core/validation/validation.go | ||
| 20 | @@ -4014,7 +4014,7 @@ func ValidateService(service *core.Service, allowAppProtocol bool) field.ErrorLi | ||
| 21 | allErrs = append(allErrs, field.Invalid(idxPath, ip, msgs[i])) | ||
| 22 | } | ||
| 23 | } else { | ||
| 24 | - allErrs = append(allErrs, validateNonSpecialIP(ip, idxPath)...) | ||
| 25 | + allErrs = append(allErrs, ValidateNonSpecialIP(ip, idxPath)...) | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | @@ -5572,15 +5572,19 @@ func validateEndpointAddress(address *core.EndpointAddress, fldPath *field.Path) | ||
| 30 | allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg)) | ||
| 31 | } | ||
| 32 | } | ||
| 33 | - allErrs = append(allErrs, validateNonSpecialIP(address.IP, fldPath.Child("ip"))...) | ||
| 34 | + allErrs = append(allErrs, ValidateNonSpecialIP(address.IP, fldPath.Child("ip"))...) | ||
| 35 | return allErrs | ||
| 36 | } | ||
| 37 | |||
| 38 | -func validateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList { | ||
| 39 | - // We disallow some IPs as endpoints or external-ips. Specifically, | ||
| 40 | - // unspecified and loopback addresses are nonsensical and link-local | ||
| 41 | - // addresses tend to be used for node-centric purposes (e.g. metadata | ||
| 42 | - // service). | ||
| 43 | +// ValidateNonSpecialIP is used to validate Endpoints, EndpointSlices, and | ||
| 44 | +// external IPs. Specifically, this disallows unspecified and loopback addresses | ||
| 45 | +// are nonsensical and link-local addresses tend to be used for node-centric | ||
| 46 | +// purposes (e.g. metadata service). | ||
| 47 | +// | ||
| 48 | +// IPv6 references | ||
| 49 | +// - https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml | ||
| 50 | +// - https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml | ||
| 51 | +func ValidateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList { | ||
| 52 | allErrs := field.ErrorList{} | ||
| 53 | ip := net.ParseIP(ipAddress) | ||
| 54 | if ip == nil { | ||
| 55 | diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go | ||
| 56 | index 810f2ca124d57..3aa5128359d7f 100644 | ||
| 57 | --- a/src/import/pkg/apis/discovery/validation/validation.go | ||
| 58 | +++ b/src/import/pkg/apis/discovery/validation/validation.go | ||
| 59 | @@ -103,8 +103,10 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres | ||
| 60 | } | ||
| 61 | case discovery.AddressTypeIPv4: | ||
| 62 | allErrs = append(allErrs, validation.IsValidIPv4Address(addressPath.Index(i), address)...) | ||
| 63 | + allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...) | ||
| 64 | case discovery.AddressTypeIPv6: | ||
| 65 | allErrs = append(allErrs, validation.IsValidIPv6Address(addressPath.Index(i), address)...) | ||
| 66 | + allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...) | ||
| 67 | case discovery.AddressTypeFQDN: | ||
| 68 | allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...) | ||
| 69 | } | ||
| 70 | diff --git a/pkg/apis/discovery/validation/validation_test.go b/pkg/apis/discovery/validation/validation_test.go | ||
| 71 | index 060545f93ab31..3c8a5465128a9 100644 | ||
| 72 | --- a/src/import/pkg/apis/discovery/validation/validation_test.go | ||
| 73 | +++ b/src/import/pkg/apis/discovery/validation/validation_test.go | ||
| 74 | @@ -390,7 +390,7 @@ func TestValidateEndpointSlice(t *testing.T) { | ||
| 75 | }, | ||
| 76 | }, | ||
| 77 | "bad-ipv4": { | ||
| 78 | - expectedErrors: 2, | ||
| 79 | + expectedErrors: 3, | ||
| 80 | endpointSlice: &discovery.EndpointSlice{ | ||
| 81 | ObjectMeta: standardMeta, | ||
| 82 | AddressType: discovery.AddressTypeIPv4, | ||
| 83 | @@ -405,7 +405,7 @@ func TestValidateEndpointSlice(t *testing.T) { | ||
| 84 | }, | ||
| 85 | }, | ||
| 86 | "bad-ipv6": { | ||
| 87 | - expectedErrors: 2, | ||
| 88 | + expectedErrors: 4, | ||
| 89 | endpointSlice: &discovery.EndpointSlice{ | ||
| 90 | ObjectMeta: standardMeta, | ||
| 91 | AddressType: discovery.AddressTypeIPv6, | ||
| 92 | @@ -454,6 +454,36 @@ func TestValidateEndpointSlice(t *testing.T) { | ||
| 93 | expectedErrors: 3, | ||
| 94 | endpointSlice: &discovery.EndpointSlice{}, | ||
| 95 | }, | ||
| 96 | + "special-ipv4": { | ||
| 97 | + expectedErrors: 1, | ||
| 98 | + endpointSlice: &discovery.EndpointSlice{ | ||
| 99 | + ObjectMeta: standardMeta, | ||
| 100 | + AddressType: discovery.AddressTypeIPv4, | ||
| 101 | + Ports: []discovery.EndpointPort{{ | ||
| 102 | + Name: utilpointer.StringPtr("http"), | ||
| 103 | + Protocol: protocolPtr(api.ProtocolTCP), | ||
| 104 | + }}, | ||
| 105 | + Endpoints: []discovery.Endpoint{{ | ||
| 106 | + Addresses: []string{"127.0.0.1"}, | ||
| 107 | + Hostname: utilpointer.StringPtr("valid-123"), | ||
| 108 | + }}, | ||
| 109 | + }, | ||
| 110 | + }, | ||
| 111 | + "special-ipv6": { | ||
| 112 | + expectedErrors: 1, | ||
| 113 | + endpointSlice: &discovery.EndpointSlice{ | ||
| 114 | + ObjectMeta: standardMeta, | ||
| 115 | + AddressType: discovery.AddressTypeIPv6, | ||
| 116 | + Ports: []discovery.EndpointPort{{ | ||
| 117 | + Name: utilpointer.StringPtr("http"), | ||
| 118 | + Protocol: protocolPtr(api.ProtocolTCP), | ||
| 119 | + }}, | ||
| 120 | + Endpoints: []discovery.Endpoint{{ | ||
| 121 | + Addresses: []string{"fe80::9656:d028:8652:66b6"}, | ||
| 122 | + Hostname: utilpointer.StringPtr("valid-123"), | ||
| 123 | + }}, | ||
| 124 | + }, | ||
| 125 | + }, | ||
| 126 | } | ||
| 127 | |||
| 128 | for name, testCase := range testCases { | ||
diff --git a/recipes-containers/kubernetes/kubernetes_git.bb b/recipes-containers/kubernetes/kubernetes_git.bb index 2b0bfb7a..be3d7dbe 100644 --- a/recipes-containers/kubernetes/kubernetes_git.bb +++ b/recipes-containers/kubernetes/kubernetes_git.bb | |||
| @@ -14,6 +14,9 @@ SRC_URI = "git://github.com/kubernetes/kubernetes.git;branch=release-1.17;name=k | |||
| 14 | file://CVE-2020-8564.patch \ | 14 | file://CVE-2020-8564.patch \ |
| 15 | file://CVE-2020-8565.patch \ | 15 | file://CVE-2020-8565.patch \ |
| 16 | file://CVE-2020-8566.patch \ | 16 | file://CVE-2020-8566.patch \ |
| 17 | file://CVE-2021-25735-pre1.patch \ | ||
| 18 | file://CVE-2021-25735.patch \ | ||
| 19 | file://CVE-2021-25737.patch \ | ||
| 17 | " | 20 | " |
| 18 | 21 | ||
| 19 | DEPENDS += "rsync-native \ | 22 | DEPENDS += "rsync-native \ |
