diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go b/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go index c95cf9b35885..5a230588fdeb 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go @@ -11,6 +11,7 @@ import ( "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/dns" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/features" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/image" + "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/kubecontrollermanager" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/network" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/oauth" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/project" @@ -36,6 +37,9 @@ var AllCustomResourceValidators = []string{ rolebindingrestriction.PluginName, network.PluginName, + // the kubecontrollermanager operator resource has to exist in order to run deployments to deploy admission webhooks. + kubecontrollermanager.PluginName, + // this one is special because we don't work without it. securitycontextconstraints.DefaultingPluginName, } @@ -51,6 +55,7 @@ func RegisterCustomResourceValidation(plugins *admission.Plugins) { project.Register(plugins) config.Register(plugins) scheduler.Register(plugins) + kubecontrollermanager.Register(plugins) // This plugin validates the quota.openshift.io/v1 ClusterResourceQuota resources. // NOTE: This is only allowed because it is required to get a running control plane operator. diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/kubecontrollermanager/validate_kubecontrollermanager.go b/openshift-kube-apiserver/admission/customresourcevalidation/kubecontrollermanager/validate_kubecontrollermanager.go new file mode 100644 index 000000000000..be688a4fa96f --- /dev/null +++ b/openshift-kube-apiserver/admission/customresourcevalidation/kubecontrollermanager/validate_kubecontrollermanager.go @@ -0,0 +1,113 @@ +package kubecontrollermanager + +import ( + "fmt" + "io" + + operatorv1 "github.com/openshift/api/operator/v1" + + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission" + + "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation" +) + +const PluginName = "operator.openshift.io/ValidateKubeControllerManager" + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return customresourcevalidation.NewValidator( + map[schema.GroupResource]bool{ + operatorv1.Resource("kubecontrollermanagers"): true, + }, + map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{ + operatorv1.GroupVersion.WithKind("KubeControllerManager"): kubeControllerManagerV1{}, + }) + }) +} + +func toKubeControllerManager(uncastObj runtime.Object) (*operatorv1.KubeControllerManager, field.ErrorList) { + if uncastObj == nil { + return nil, nil + } + + allErrs := field.ErrorList{} + + obj, ok := uncastObj.(*operatorv1.KubeControllerManager) + if !ok { + return nil, append(allErrs, + field.NotSupported(field.NewPath("kind"), fmt.Sprintf("%T", uncastObj), []string{"KubeControllerManager"}), + field.NotSupported(field.NewPath("apiVersion"), fmt.Sprintf("%T", uncastObj), []string{"operator.openshift.io/v1"})) + } + + return obj, nil +} + +type kubeControllerManagerV1 struct { +} + +func validateKubeControllerManagerSpecCreate(spec operatorv1.KubeControllerManagerSpec) field.ErrorList { + allErrs := field.ErrorList{} + + // on create, we allow anything + return allErrs +} + +func validateKubeControllerManagerSpecUpdate(spec, oldSpec operatorv1.KubeControllerManagerSpec) field.ErrorList { + allErrs := field.ErrorList{} + + // on update, fail if we go from secure to insecure + if oldSpec.UseMoreSecureServiceCA && !spec.UseMoreSecureServiceCA { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.useMoreSecureServiceCA"), "once enabled, the more secure service-ca.crt cannot be disabled")) + } + + return allErrs +} + +func (kubeControllerManagerV1) ValidateCreate(uncastObj runtime.Object) field.ErrorList { + obj, allErrs := toKubeControllerManager(uncastObj) + if len(allErrs) > 0 { + return allErrs + } + + allErrs = append(allErrs, validation.ValidateObjectMeta(&obj.ObjectMeta, false, customresourcevalidation.RequireNameCluster, field.NewPath("metadata"))...) + allErrs = append(allErrs, validateKubeControllerManagerSpecCreate(obj.Spec)...) + + return allErrs +} + +func (kubeControllerManagerV1) ValidateUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { + obj, allErrs := toKubeControllerManager(uncastObj) + if len(allErrs) > 0 { + return allErrs + } + oldObj, allErrs := toKubeControllerManager(uncastOldObj) + if len(allErrs) > 0 { + return allErrs + } + + allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))...) + allErrs = append(allErrs, validateKubeControllerManagerSpecUpdate(obj.Spec, oldObj.Spec)...) + + return allErrs +} + +func (kubeControllerManagerV1) ValidateStatusUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { + obj, errs := toKubeControllerManager(uncastObj) + if len(errs) > 0 { + return errs + } + oldObj, errs := toKubeControllerManager(uncastOldObj) + if len(errs) > 0 { + return errs + } + + // TODO validate the obj. remember that status validation should *never* fail on spec validation errors. + errs = append(errs, validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))...) + + return errs +}