Validate CABundle when writing CRD
This commit is contained in:
		@@ -28,7 +28,6 @@ import (
 | 
				
			|||||||
	"unicode/utf8"
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	celgo "github.com/google/cel-go/cel"
 | 
						celgo "github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
				
			||||||
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
						apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
				
			||||||
@@ -94,6 +93,8 @@ func ValidateCustomResourceDefinition(ctx context.Context, obj *apiextensions.Cu
 | 
				
			|||||||
		requireMapListKeysMapSetValidation:       true,
 | 
							requireMapListKeysMapSetValidation:       true,
 | 
				
			||||||
		// strictCost is always true to enforce cost limits.
 | 
							// strictCost is always true to enforce cost limits.
 | 
				
			||||||
		celEnvironmentSet: environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true),
 | 
							celEnvironmentSet: environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true),
 | 
				
			||||||
 | 
							// allowInvalidCABundle is set to true since the CRD is not established yet.
 | 
				
			||||||
 | 
							allowInvalidCABundle: true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	allErrs := genericvalidation.ValidateObjectMeta(&obj.ObjectMeta, false, nameValidationFn, field.NewPath("metadata"))
 | 
						allErrs := genericvalidation.ValidateObjectMeta(&obj.ObjectMeta, false, nameValidationFn, field.NewPath("metadata"))
 | 
				
			||||||
@@ -140,6 +141,9 @@ type validationOptions struct {
 | 
				
			|||||||
	suppressPerExpressionCost bool
 | 
						suppressPerExpressionCost bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	celEnvironmentSet *environment.EnvSet
 | 
						celEnvironmentSet *environment.EnvSet
 | 
				
			||||||
 | 
						// allowInvalidCABundle allows an invalid conversion webhook CABundle on update only if the existing CABundle is invalid.
 | 
				
			||||||
 | 
						// An invalid CABundle is also permitted on create and before a CRD is in an Established=True condition.
 | 
				
			||||||
 | 
						allowInvalidCABundle bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type preexistingExpressions struct {
 | 
					type preexistingExpressions struct {
 | 
				
			||||||
@@ -234,6 +238,7 @@ func ValidateCustomResourceDefinitionUpdate(ctx context.Context, obj, oldObj *ap
 | 
				
			|||||||
		versionsWithUnchangedSchemas:             findVersionsWithUnchangedSchemas(obj, oldObj),
 | 
							versionsWithUnchangedSchemas:             findVersionsWithUnchangedSchemas(obj, oldObj),
 | 
				
			||||||
		// strictCost is always true to enforce cost limits.
 | 
							// strictCost is always true to enforce cost limits.
 | 
				
			||||||
		celEnvironmentSet:    environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true),
 | 
							celEnvironmentSet:    environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true),
 | 
				
			||||||
 | 
							allowInvalidCABundle: allowInvalidCABundle(oldObj),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return validateCustomResourceDefinitionUpdate(ctx, obj, oldObj, opts)
 | 
						return validateCustomResourceDefinitionUpdate(ctx, obj, oldObj, opts)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -485,7 +490,7 @@ func validateCustomResourceDefinitionSpec(ctx context.Context, spec *apiextensio
 | 
				
			|||||||
	if (spec.Conversion != nil && spec.Conversion.Strategy != apiextensions.NoneConverter) && (spec.PreserveUnknownFields == nil || *spec.PreserveUnknownFields) {
 | 
						if (spec.Conversion != nil && spec.Conversion.Strategy != apiextensions.NoneConverter) && (spec.PreserveUnknownFields == nil || *spec.PreserveUnknownFields) {
 | 
				
			||||||
		allErrs = append(allErrs, field.Invalid(fldPath.Child("conversion").Child("strategy"), spec.Conversion.Strategy, "must be None if spec.preserveUnknownFields is true"))
 | 
							allErrs = append(allErrs, field.Invalid(fldPath.Child("conversion").Child("strategy"), spec.Conversion.Strategy, "must be None if spec.preserveUnknownFields is true"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	allErrs = append(allErrs, validateCustomResourceConversion(spec.Conversion, opts.requireRecognizedConversionReviewVersion, fldPath.Child("conversion"))...)
 | 
						allErrs = append(allErrs, validateCustomResourceConversion(spec.Conversion, opts.requireRecognizedConversionReviewVersion, fldPath.Child("conversion"), opts)...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -545,6 +550,20 @@ func validateConversionReviewVersions(versions []string, requireRecognizedVersio
 | 
				
			|||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Allows invalid CA Bundle to be specified only if the existing CABundle is invalid
 | 
				
			||||||
 | 
					// or if the CRD is not established yet.
 | 
				
			||||||
 | 
					func allowInvalidCABundle(oldCRD *apiextensions.CustomResourceDefinition) bool {
 | 
				
			||||||
 | 
						if !apiextensions.IsCRDConditionTrue(oldCRD, apiextensions.Established) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						oldConversion := oldCRD.Spec.Conversion
 | 
				
			||||||
 | 
						if oldConversion == nil || oldConversion.WebhookClientConfig == nil ||
 | 
				
			||||||
 | 
							len(oldConversion.WebhookClientConfig.CABundle) == 0 {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return len(webhook.ValidateCABundle(field.NewPath("caBundle"), oldConversion.WebhookClientConfig.CABundle)) > 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// hasValidConversionReviewVersion return true if there is a valid version or if the list is empty.
 | 
					// hasValidConversionReviewVersion return true if there is a valid version or if the list is empty.
 | 
				
			||||||
func hasValidConversionReviewVersionOrEmpty(versions []string) bool {
 | 
					func hasValidConversionReviewVersionOrEmpty(versions []string) bool {
 | 
				
			||||||
	if len(versions) < 1 {
 | 
						if len(versions) < 1 {
 | 
				
			||||||
@@ -558,12 +577,7 @@ func hasValidConversionReviewVersionOrEmpty(versions []string) bool {
 | 
				
			|||||||
	return false
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateCustomResourceConversion statically validates
 | 
					func validateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, requireRecognizedVersion bool, fldPath *field.Path, opts validationOptions) field.ErrorList {
 | 
				
			||||||
func ValidateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, fldPath *field.Path) field.ErrorList {
 | 
					 | 
				
			||||||
	return validateCustomResourceConversion(conversion, true, fldPath)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func validateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
 | 
					 | 
				
			||||||
	allErrs := field.ErrorList{}
 | 
						allErrs := field.ErrorList{}
 | 
				
			||||||
	if conversion == nil {
 | 
						if conversion == nil {
 | 
				
			||||||
		return allErrs
 | 
							return allErrs
 | 
				
			||||||
@@ -582,6 +596,9 @@ func validateCustomResourceConversion(conversion *apiextensions.CustomResourceCo
 | 
				
			|||||||
			case cc.Service != nil:
 | 
								case cc.Service != nil:
 | 
				
			||||||
				allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhookClientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
 | 
									allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhookClientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if len(cc.CABundle) > 0 && !opts.allowInvalidCABundle {
 | 
				
			||||||
 | 
									allErrs = append(allErrs, webhook.ValidateCABundle(fldPath.Child("webhookClientConfig").Child("caBundle"), cc.CABundle)...)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		allErrs = append(allErrs, validateConversionReviewVersions(conversion.ConversionReviewVersions, requireRecognizedVersion, fldPath.Child("conversionReviewVersions"))...)
 | 
							allErrs = append(allErrs, validateConversionReviewVersions(conversion.ConversionReviewVersions, requireRecognizedVersion, fldPath.Child("conversionReviewVersions"))...)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,29 @@ func (v validationMatch) contains(s string) validationMatch {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func strPtr(s string) *string { return &s }
 | 
					func strPtr(s string) *string { return &s }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	go run generate_cert.go  --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
 | 
				
			||||||
 | 
					var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS
 | 
				
			||||||
 | 
					MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
 | 
				
			||||||
 | 
					MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
 | 
				
			||||||
 | 
					MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn
 | 
				
			||||||
 | 
					zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy
 | 
				
			||||||
 | 
					+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi
 | 
				
			||||||
 | 
					vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG
 | 
				
			||||||
 | 
					f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he
 | 
				
			||||||
 | 
					7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC
 | 
				
			||||||
 | 
					AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE
 | 
				
			||||||
 | 
					DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES
 | 
				
			||||||
 | 
					eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+
 | 
				
			||||||
 | 
					2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV
 | 
				
			||||||
 | 
					x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL
 | 
				
			||||||
 | 
					1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr
 | 
				
			||||||
 | 
					UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP
 | 
				
			||||||
 | 
					uvEQdw==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
					func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			||||||
	singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
 | 
						singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
@@ -235,6 +258,107 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
				invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
 | 
									invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "webhookconfig: invalid CABundle should be allowed on Create",
 | 
				
			||||||
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group: "group.com",
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "Plural",
 | 
				
			||||||
 | 
											ListKind: "PluralList",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: []byte("Cg=="),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										PreserveUnknownFields: ptr.To(false),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errors: []validationMatch{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "webhookconfig: valid CABundle",
 | 
				
			||||||
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group: "group.com",
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "Plural",
 | 
				
			||||||
 | 
											ListKind: "PluralList",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: exampleCert,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										PreserveUnknownFields: ptr.To(false),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
 | 
										Conditions: []apiextensions.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
											{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errors: []validationMatch{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "webhookconfig: both service and URL provided",
 | 
								name: "webhookconfig: both service and URL provided",
 | 
				
			||||||
			resource: &apiextensions.CustomResourceDefinition{
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
@@ -5543,6 +5667,362 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
 | 
				
			|||||||
				invalid("spec", "conversion", "conversionReviewVersions"),
 | 
									invalid("spec", "conversion", "conversionReviewVersions"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "webhookconfig: existing invalid CABundle update should pass",
 | 
				
			||||||
 | 
								old: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: []byte("Cg=="),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conditions: []apiextensions.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
											{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: []byte("Cg=="),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										PreserveUnknownFields: ptr.To(false),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conditions: []apiextensions.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
											{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errors: []validationMatch{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "webhookconfig: existing valid CABundle should be able to transition to invalid pre-serving",
 | 
				
			||||||
 | 
								old: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: exampleCert,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: []byte("Cg=="),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										PreserveUnknownFields: ptr.To(false),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errors: []validationMatch{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "webhookconfig: update to invalid CABundle should fail if existing is valid",
 | 
				
			||||||
 | 
								old: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: exampleCert,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conditions: []apiextensions.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
											{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								resource: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:            "plural.group.com",
 | 
				
			||||||
 | 
										ResourceVersion: "42",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
										Group:   "group.com",
 | 
				
			||||||
 | 
										Version: "version",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:    "version2",
 | 
				
			||||||
 | 
												Served:  true,
 | 
				
			||||||
 | 
												Storage: false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conversion: &apiextensions.CustomResourceConversion{
 | 
				
			||||||
 | 
											Strategy: apiextensions.ConversionStrategyType("Webhook"),
 | 
				
			||||||
 | 
											WebhookClientConfig: &apiextensions.WebhookClientConfig{
 | 
				
			||||||
 | 
												Service: &apiextensions.ServiceReference{
 | 
				
			||||||
 | 
													Name:      "n",
 | 
				
			||||||
 | 
													Namespace: "ns",
 | 
				
			||||||
 | 
													Port:      443,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												CABundle: []byte("Cg=="),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ConversionReviewVersions: []string{"version2"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 | 
				
			||||||
 | 
												Type: "object",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Scope: apiextensions.ResourceScope("Cluster"),
 | 
				
			||||||
 | 
										Names: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										PreserveUnknownFields: ptr.To(false),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										AcceptedNames: apiextensions.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
											Plural:   "plural",
 | 
				
			||||||
 | 
											Singular: "singular",
 | 
				
			||||||
 | 
											Kind:     "kind",
 | 
				
			||||||
 | 
											ListKind: "listkind",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Conditions: []apiextensions.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
											{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errors: []validationMatch{
 | 
				
			||||||
 | 
									invalid("spec", "conversion", "webhookClientConfig", "caBundle"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "unchanged",
 | 
								name: "unchanged",
 | 
				
			||||||
			old: &apiextensions.CustomResourceDefinition{
 | 
								old: &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,9 @@ import (
 | 
				
			|||||||
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/util/webhook"
 | 
				
			||||||
	"k8s.io/client-go/tools/cache"
 | 
						"k8s.io/client-go/tools/cache"
 | 
				
			||||||
	"k8s.io/client-go/util/workqueue"
 | 
						"k8s.io/client-go/util/workqueue"
 | 
				
			||||||
	"k8s.io/klog/v2"
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
@@ -131,6 +133,21 @@ func (ec *EstablishingController) sync(key string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	crd := cachedCRD.DeepCopy()
 | 
						crd := cachedCRD.DeepCopy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the conversion webhook CABundle is invalid, set Established
 | 
				
			||||||
 | 
						// condition to false and provide a reason
 | 
				
			||||||
 | 
						if cachedCRD.Spec.Conversion != nil &&
 | 
				
			||||||
 | 
							cachedCRD.Spec.Conversion.Webhook != nil &&
 | 
				
			||||||
 | 
							cachedCRD.Spec.Conversion.Webhook.ClientConfig != nil &&
 | 
				
			||||||
 | 
							len(webhook.ValidateCABundle(field.NewPath(""), cachedCRD.Spec.Conversion.Webhook.ClientConfig.CABundle)) > 0 {
 | 
				
			||||||
 | 
							errorCondition := apiextensionsv1.CustomResourceDefinitionCondition{
 | 
				
			||||||
 | 
								Type:    apiextensionsv1.Established,
 | 
				
			||||||
 | 
								Status:  apiextensionsv1.ConditionFalse,
 | 
				
			||||||
 | 
								Reason:  "InvalidCABundle",
 | 
				
			||||||
 | 
								Message: "The conversion webhook CABundle is invalid",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							apiextensionshelpers.SetCRDCondition(crd, errorCondition)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		establishedCondition := apiextensionsv1.CustomResourceDefinitionCondition{
 | 
							establishedCondition := apiextensionsv1.CustomResourceDefinitionCondition{
 | 
				
			||||||
			Type:    apiextensionsv1.Established,
 | 
								Type:    apiextensionsv1.Established,
 | 
				
			||||||
			Status:  apiextensionsv1.ConditionTrue,
 | 
								Status:  apiextensionsv1.ConditionTrue,
 | 
				
			||||||
@@ -138,6 +155,7 @@ func (ec *EstablishingController) sync(key string) error {
 | 
				
			|||||||
			Message: "the initial names have been accepted",
 | 
								Message: "the initial names have been accepted",
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		apiextensionshelpers.SetCRDCondition(crd, establishedCondition)
 | 
							apiextensionshelpers.SetCRDCondition(crd, establishedCondition)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update server with new CRD condition.
 | 
						// Update server with new CRD condition.
 | 
				
			||||||
	_, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(context.TODO(), crd, metav1.UpdateOptions{})
 | 
						_, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(context.TODO(), crd, metav1.UpdateOptions{})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/test/integration/fixtures"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	go run generate_cert.go  --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
 | 
				
			||||||
 | 
					var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS
 | 
				
			||||||
 | 
					MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
 | 
				
			||||||
 | 
					MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
 | 
				
			||||||
 | 
					MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn
 | 
				
			||||||
 | 
					zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy
 | 
				
			||||||
 | 
					+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi
 | 
				
			||||||
 | 
					vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG
 | 
				
			||||||
 | 
					f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he
 | 
				
			||||||
 | 
					7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC
 | 
				
			||||||
 | 
					AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE
 | 
				
			||||||
 | 
					DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES
 | 
				
			||||||
 | 
					eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+
 | 
				
			||||||
 | 
					2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV
 | 
				
			||||||
 | 
					x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL
 | 
				
			||||||
 | 
					1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr
 | 
				
			||||||
 | 
					UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP
 | 
				
			||||||
 | 
					uvEQdw==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var invalidCert = []byte("Cg==")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Invalid CABundle should prevent new CRD from being set to Established
 | 
				
			||||||
 | 
					func TestInvalidCABundle(t *testing.T) {
 | 
				
			||||||
 | 
						tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer tearDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						crd := fixtures.NewRandomNameV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
 | 
				
			||||||
 | 
						crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
 | 
				
			||||||
 | 
							Strategy: apiextensionsv1.WebhookConverter,
 | 
				
			||||||
 | 
							Webhook: &apiextensionsv1.WebhookConversion{
 | 
				
			||||||
 | 
								ClientConfig: &apiextensionsv1.WebhookClientConfig{
 | 
				
			||||||
 | 
									CABundle: invalidCert,
 | 
				
			||||||
 | 
									Service: &apiextensionsv1.ServiceReference{
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
										Name:      "example",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ConversionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Ensure that Established is false with reason InvalidCABundle
 | 
				
			||||||
 | 
						err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) {
 | 
				
			||||||
 | 
							localCrd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crd.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							condition := findCRDCondition(localCrd, apiextensionsv1.Established)
 | 
				
			||||||
 | 
							if condition == nil {
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if condition.Status == apiextensionsv1.ConditionFalse && condition.Reason == "InvalidCABundle" {
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Valid CABundle should set CRD to Established.
 | 
				
			||||||
 | 
					func TestValidCABundle(t *testing.T) {
 | 
				
			||||||
 | 
						tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer tearDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						crd := fixtures.NewRandomNameV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
 | 
				
			||||||
 | 
						crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
 | 
				
			||||||
 | 
							Strategy: apiextensionsv1.WebhookConverter,
 | 
				
			||||||
 | 
							Webhook: &apiextensionsv1.WebhookConversion{
 | 
				
			||||||
 | 
								ClientConfig: &apiextensionsv1.WebhookClientConfig{
 | 
				
			||||||
 | 
									CABundle: exampleCert,
 | 
				
			||||||
 | 
									Service: &apiextensionsv1.ServiceReference{
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
										Name:      "example",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ConversionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// wait until the CRD is established
 | 
				
			||||||
 | 
						err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) {
 | 
				
			||||||
 | 
							localCrd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crd.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							condition := findCRDCondition(localCrd, apiextensionsv1.Established)
 | 
				
			||||||
 | 
							if condition == nil {
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if condition.Status == apiextensionsv1.ConditionTrue {
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// No CABundle should set CRD to Established.
 | 
				
			||||||
 | 
					func TestMissingCABundle(t *testing.T) {
 | 
				
			||||||
 | 
						tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer tearDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						crd := fixtures.NewRandomNameV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
 | 
				
			||||||
 | 
						crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) {
 | 
				
			||||||
 | 
							localCrd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crd.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							condition := findCRDCondition(localCrd, apiextensionsv1.Established)
 | 
				
			||||||
 | 
							if condition == nil {
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if condition.Status == apiextensionsv1.ConditionTrue {
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -23,8 +23,18 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation"
 | 
						"k8s.io/apimachinery/pkg/util/validation"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
 | 
						"k8s.io/client-go/transport"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ValidateCABundle(fldPath *field.Path, caBundle []byte) field.ErrorList {
 | 
				
			||||||
 | 
						var allErrors field.ErrorList
 | 
				
			||||||
 | 
						_, err := transport.TLSConfigFor(&transport.Config{TLS: transport.TLSConfig{CAData: caBundle}})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							allErrors = append(allErrors, field.Invalid(fldPath, caBundle, err.Error()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return allErrors
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateWebhookURL validates webhook's URL.
 | 
					// ValidateWebhookURL validates webhook's URL.
 | 
				
			||||||
func ValidateWebhookURL(fldPath *field.Path, URL string, forceHttps bool) field.ErrorList {
 | 
					func ValidateWebhookURL(fldPath *field.Path, URL string, forceHttps bool) field.ErrorList {
 | 
				
			||||||
	var allErrors field.ErrorList
 | 
						var allErrors field.ErrorList
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package webhook
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	go run generate_cert.go  --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
 | 
				
			||||||
 | 
					var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS
 | 
				
			||||||
 | 
					MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
 | 
				
			||||||
 | 
					MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
 | 
				
			||||||
 | 
					MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn
 | 
				
			||||||
 | 
					zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy
 | 
				
			||||||
 | 
					+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi
 | 
				
			||||||
 | 
					vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG
 | 
				
			||||||
 | 
					f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he
 | 
				
			||||||
 | 
					7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC
 | 
				
			||||||
 | 
					AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE
 | 
				
			||||||
 | 
					DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES
 | 
				
			||||||
 | 
					eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+
 | 
				
			||||||
 | 
					2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV
 | 
				
			||||||
 | 
					x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL
 | 
				
			||||||
 | 
					1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr
 | 
				
			||||||
 | 
					UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP
 | 
				
			||||||
 | 
					uvEQdw==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateCABundle(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							caBundle  []byte
 | 
				
			||||||
 | 
							expectErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "nil caBundle is valid",
 | 
				
			||||||
 | 
								caBundle:  nil,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								name:      "empty caBundle is valid",
 | 
				
			||||||
 | 
								caBundle:  []byte(""),
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								name:      "non empty caBundle with invalid certificate should not validate",
 | 
				
			||||||
 | 
								caBundle:  []byte("bogus"),
 | 
				
			||||||
 | 
								expectErr: true,
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								name:      "non empty caBundle with no certificate should not validate",
 | 
				
			||||||
 | 
								caBundle:  []byte("Cg=="),
 | 
				
			||||||
 | 
								expectErr: true,
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								name:      "non empty caBundle with valid certificate should validate",
 | 
				
			||||||
 | 
								caBundle:  exampleCert,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range tests {
 | 
				
			||||||
 | 
							errList := ValidateCABundle(field.NewPath(""), tc.caBundle)
 | 
				
			||||||
 | 
							if len(errList) > 0 && !tc.expectErr {
 | 
				
			||||||
 | 
								t.Errorf("Expected no error for test %s, got %v", tc.name, errList)
 | 
				
			||||||
 | 
							} else if len(errList) == 0 && tc.expectErr {
 | 
				
			||||||
 | 
								t.Errorf("Expected error for test %s, received no error", tc.name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user