Merge pull request #64267 from sttts/sttts-crd-objectmeta-pruning

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

apiextensions: add ObjectMeta validation and pruning

This is a critical pre-requisite for further multi-version support and especially for GA of CRDs: ObjectMeta must be schema-validated and pruned, like `json.Unmarshal` does this.

This PR adds this in the incoming request serializer and the storage decoder. The former errors when schema validation fails, the later just drops invalid typed fields.

Fixes #59451

```release-note
Meta data of CustomResources is now pruned and schema checked during deserialization of requests and when read from etcd. In the former case, invalid meta data is rejected, in the later it is dropped from the CustomResource objects.
```
This commit is contained in:
Kubernetes Submit Queue
2018-06-02 12:55:36 -07:00
committed by GitHub
17 changed files with 916 additions and 22 deletions

View File

@@ -12,6 +12,7 @@ go_library(
deps = [
"//pkg/apis/apps:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
],
)

View File

@@ -19,6 +19,7 @@ package fuzzer
import (
fuzz "github.com/google/gofuzz"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/pkg/apis/apps"
)
@@ -46,6 +47,12 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
if s.Status.CollisionCount == nil {
s.Status.CollisionCount = new(int32)
}
if s.Spec.Selector == nil {
s.Spec.Selector = &metav1.LabelSelector{MatchLabels: s.Spec.Template.Labels}
}
if len(s.Labels) == 0 {
s.Labels = s.Spec.Template.Labels
}
},
}
}

View File

@@ -182,6 +182,7 @@ func Convert_v1_Deployment_To_extensions_Deployment(in *appsv1.Deployment, out *
out.Spec.RollbackTo = new(extensions.RollbackConfig)
out.Spec.RollbackTo.Revision = revision64
}
out.Annotations = deepCopyStringMap(out.Annotations)
delete(out.Annotations, appsv1.DeprecatedRollbackTo)
} else {
out.Spec.RollbackTo = nil
@@ -195,6 +196,8 @@ func Convert_v1_Deployment_To_extensions_Deployment(in *appsv1.Deployment, out *
func Convert_extensions_Deployment_To_v1_Deployment(in *extensions.Deployment, out *appsv1.Deployment, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.Annotations = deepCopyStringMap(out.Annotations) // deep copy because we modify it below
if err := Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
@@ -235,9 +238,8 @@ func Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in *
func Convert_extensions_DaemonSet_To_v1_DaemonSet(in *extensions.DaemonSet, out *appsv1.DaemonSet, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if out.Annotations == nil {
out.Annotations = make(map[string]string)
}
out.Annotations = deepCopyStringMap(out.Annotations) // deep copy annotations because we change them below
out.Annotations[appsv1.DeprecatedTemplateGeneration] = strconv.FormatInt(in.Spec.TemplateGeneration, 10)
if err := Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec(&in.Spec, &out.Spec, s); err != nil {
return err
@@ -287,6 +289,7 @@ func Convert_v1_DaemonSet_To_extensions_DaemonSet(in *appsv1.DaemonSet, out *ext
return err
} else {
out.Spec.TemplateGeneration = value64
out.Annotations = deepCopyStringMap(out.Annotations)
delete(out.Annotations, appsv1.DeprecatedTemplateGeneration)
}
}
@@ -496,3 +499,11 @@ func Convert_apps_StatefulSetStatus_To_v1_StatefulSetStatus(in *apps.StatefulSet
}
return nil
}
func deepCopyStringMap(m map[string]string) map[string]string {
ret := make(map[string]string, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}

View File

@@ -415,6 +415,7 @@ func Convert_v1beta2_Deployment_To_extensions_Deployment(in *appsv1beta2.Deploym
out.Spec.RollbackTo = new(extensions.RollbackConfig)
out.Spec.RollbackTo.Revision = revision64
}
out.Annotations = deepCopyStringMap(out.Annotations)
delete(out.Annotations, appsv1beta2.DeprecatedRollbackTo)
} else {
out.Spec.RollbackTo = nil
@@ -440,6 +441,8 @@ func Convert_v1beta2_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *appsv1beta2
func Convert_extensions_Deployment_To_v1beta2_Deployment(in *extensions.Deployment, out *appsv1beta2.Deployment, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.Annotations = deepCopyStringMap(out.Annotations) // deep copy because we modify annotations below
if err := Convert_extensions_DeploymentSpec_To_v1beta2_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
@@ -463,9 +466,7 @@ func Convert_extensions_Deployment_To_v1beta2_Deployment(in *extensions.Deployme
func Convert_extensions_DaemonSet_To_v1beta2_DaemonSet(in *extensions.DaemonSet, out *appsv1beta2.DaemonSet, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if out.Annotations == nil {
out.Annotations = make(map[string]string)
}
out.Annotations = deepCopyStringMap(out.Annotations)
out.Annotations[appsv1beta2.DeprecatedTemplateGeneration] = strconv.FormatInt(in.Spec.TemplateGeneration, 10)
if err := Convert_extensions_DaemonSetSpec_To_v1beta2_DaemonSetSpec(&in.Spec, &out.Spec, s); err != nil {
return err
@@ -515,6 +516,7 @@ func Convert_v1beta2_DaemonSet_To_extensions_DaemonSet(in *appsv1beta2.DaemonSet
return err
} else {
out.Spec.TemplateGeneration = value64
out.Annotations = deepCopyStringMap(out.Annotations)
delete(out.Annotations, appsv1beta2.DeprecatedTemplateGeneration)
}
}
@@ -552,3 +554,11 @@ func Convert_v1beta2_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrate
}
return nil
}
func deepCopyStringMap(m map[string]string) map[string]string {
ret := make(map[string]string, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}

View File

@@ -32,6 +32,14 @@ func newBool(val bool) *bool {
// Funcs returns the fuzzer functions for the batch api group.
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(j *batch.Job, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
// match defaulting
if len(j.Labels) == 0 {
j.Labels = j.Spec.Template.Labels
}
},
func(j *batch.JobSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
completions := int32(c.Rand.Int31())

View File

@@ -93,6 +93,19 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
c.Fuzz(&j.ObjectMeta)
j.Target.Name = c.RandString()
},
func(j *core.ReplicationController, c fuzz.Continue) {
c.FuzzNoCustom(j)
// match defaulting
if j.Spec.Template != nil {
if len(j.Labels) == 0 {
j.Labels = j.Spec.Template.Labels
}
if len(j.Spec.Selector) == 0 {
j.Spec.Selector = j.Spec.Template.Labels
}
}
},
func(j *core.ReplicationControllerSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
//j.TemplateRef = nil // this is required for round trip

View File

@@ -12,6 +12,7 @@ go_library(
deps = [
"//pkg/apis/extensions:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
],

View File

@@ -21,6 +21,7 @@ import (
fuzz "github.com/google/gofuzz"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/pkg/apis/extensions"
@@ -29,6 +30,17 @@ import (
// Funcs returns the fuzzer functions for the extensions api group.
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(j *extensions.Deployment, c fuzz.Continue) {
c.FuzzNoCustom(j)
// match defaulting
if j.Spec.Selector == nil {
j.Spec.Selector = &metav1.LabelSelector{MatchLabels: j.Spec.Template.Labels}
}
if len(j.Labels) == 0 {
j.Labels = j.Spec.Template.Labels
}
},
func(j *extensions.DeploymentSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
rhl := int32(c.Rand.Int31())
@@ -54,6 +66,15 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
j.RollingUpdate = &rollingUpdate
}
},
func(j *extensions.DaemonSet, c fuzz.Continue) {
c.FuzzNoCustom(j)
// match defaulter
j.Spec.Template.Generation = 0
if len(j.ObjectMeta.Labels) == 0 {
j.ObjectMeta.Labels = j.Spec.Template.ObjectMeta.Labels
}
},
func(j *extensions.DaemonSetSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
rhl := int32(c.Rand.Int31())
@@ -78,5 +99,16 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
j.RollingUpdate = &rollingUpdate
}
},
func(j *extensions.ReplicaSet, c fuzz.Continue) {
c.FuzzNoCustom(j)
// match defaulter
if j.Spec.Selector == nil {
j.Spec.Selector = &metav1.LabelSelector{MatchLabels: j.Spec.Template.Labels}
}
if len(j.Labels) == 0 {
j.Labels = j.Spec.Template.Labels
}
},
}
}