diff --git a/pkg/apis/kops/topology.go b/pkg/apis/kops/topology.go index 649823cee52f9..c34964d34f8a2 100644 --- a/pkg/apis/kops/topology.go +++ b/pkg/apis/kops/topology.go @@ -26,6 +26,11 @@ var SupportedTopologies = []string{ TopologyPrivate, } +var SupportedDnsTypes = []string{ + string(DNSTypePublic), + string(DNSTypePrivate), +} + type TopologySpec struct { // The environment to launch the Kubernetes masters in public|private Masters string `json:"masters,omitempty"` diff --git a/pkg/apis/kops/validation/legacy.go b/pkg/apis/kops/validation/legacy.go index 836b855b12618..c2610deaa666e 100644 --- a/pkg/apis/kops/validation/legacy.go +++ b/pkg/apis/kops/validation/legacy.go @@ -19,9 +19,7 @@ package validation import ( "fmt" "net" - "strings" - "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/util" @@ -48,25 +46,6 @@ func ValidateCluster(c *kops.Cluster, strict bool) field.ErrorList { return allErrs } - if c.ObjectMeta.Name == "" { - allErrs = append(allErrs, field.Required(field.NewPath("objectMeta", "name"), "Cluster Name is required (e.g. --name=mycluster.myzone.com)")) - } else { - // Must be a dns name - errs := validation.IsDNS1123Subdomain(c.ObjectMeta.Name) - if len(errs) != 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("objectMeta", "name"), c.ObjectMeta.Name, fmt.Sprintf("Cluster Name must be a valid DNS name (e.g. --name=mycluster.myzone.com) errors: %s", strings.Join(errs, ", ")))) - } else if !strings.Contains(c.ObjectMeta.Name, ".") { - // Tolerate if this is a cluster we are importing for upgrade - if c.ObjectMeta.Annotations[kops.AnnotationNameManagement] != kops.AnnotationValueManagementImported { - allErrs = append(allErrs, field.Invalid(field.NewPath("objectMeta", "name"), c.ObjectMeta.Name, "Cluster Name must be a fully-qualified DNS name (e.g. --name=mycluster.myzone.com)")) - } - } - } - - if c.Spec.Assets != nil && c.Spec.Assets.ContainerProxy != nil && c.Spec.Assets.ContainerRegistry != nil { - allErrs = append(allErrs, field.Forbidden(fieldSpec.Child("Assets", "ContainerProxy"), "ContainerProxy cannot be used in conjunction with ContainerRegistry as represent mutually exclusive concepts. Please consult the documentation for details.")) - } - requiresSubnets := true requiresNetworkCIDR := true requiresSubnetCIDR := true @@ -376,116 +355,6 @@ func ValidateCluster(c *kops.Cluster, strict bool) field.ErrorList { } } - // NodeAuthorization - if c.Spec.NodeAuthorization != nil { - // @check the feature gate is enabled for this - if !featureflag.EnableNodeAuthorization.Enabled() { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "nodeAuthorization"), "node authorization is experimental feature; set `export KOPS_FEATURE_FLAGS=EnableNodeAuthorization`")) - } else { - if c.Spec.NodeAuthorization.NodeAuthorizer == nil { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "nodeAuthorization"), "no node authorization policy has been set")) - } else { - path := field.NewPath("spec", "nodeAuthorization").Child("nodeAuthorizer") - if c.Spec.NodeAuthorization.NodeAuthorizer.Port < 0 || c.Spec.NodeAuthorization.NodeAuthorizer.Port >= 65535 { - allErrs = append(allErrs, field.Invalid(path.Child("port"), c.Spec.NodeAuthorization.NodeAuthorizer.Port, "invalid port")) - } - if c.Spec.NodeAuthorization.NodeAuthorizer.Timeout != nil && c.Spec.NodeAuthorization.NodeAuthorizer.Timeout.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(path.Child("timeout"), c.Spec.NodeAuthorization.NodeAuthorizer.Timeout, "must be greater than zero")) - } - if c.Spec.NodeAuthorization.NodeAuthorizer.TokenTTL != nil && c.Spec.NodeAuthorization.NodeAuthorizer.TokenTTL.Duration < 0 { - allErrs = append(allErrs, field.Invalid(path.Child("tokenTTL"), c.Spec.NodeAuthorization.NodeAuthorizer.TokenTTL, "must be greater than or equal to zero")) - } - - // @question: we could probably just default these settings in the model when the node-authorizer is enabled?? - if c.Spec.KubeAPIServer == nil { - allErrs = append(allErrs, field.Required(field.NewPath("spec", "kubeAPIServer"), "bootstrap token authentication is not enabled in the kube-apiserver")) - } else if c.Spec.KubeAPIServer.EnableBootstrapAuthToken == nil { - allErrs = append(allErrs, field.Required(field.NewPath("spec", "kubeAPIServer", "enableBootstrapAuthToken"), "kube-apiserver has not been configured to use bootstrap tokens")) - } else if !fi.BoolValue(c.Spec.KubeAPIServer.EnableBootstrapAuthToken) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "kubeAPIServer", "enableBootstrapAuthToken"), "bootstrap tokens in the kube-apiserver has been disabled")) - } - } - } - } - - // UpdatePolicy - if c.Spec.UpdatePolicy != nil { - switch *c.Spec.UpdatePolicy { - case kops.UpdatePolicyExternal: - // Valid - default: - allErrs = append(allErrs, field.NotSupported(fieldSpec.Child("updatePolicy"), *c.Spec.UpdatePolicy, []string{kops.UpdatePolicyExternal})) - } - } - - // KubeProxy - if c.Spec.KubeProxy != nil { - kubeProxyPath := fieldSpec.Child("kubeProxy") - master := c.Spec.KubeProxy.Master - - for i, x := range c.Spec.KubeProxy.IPVSExcludeCIDRS { - if _, _, err := net.ParseCIDR(x); err != nil { - allErrs = append(allErrs, field.Invalid(kubeProxyPath.Child("ipvsExcludeCidrs").Index(i), x, "Invalid network CIDR")) - } - } - - if master != "" && !isValidAPIServersURL(master) { - allErrs = append(allErrs, field.Invalid(kubeProxyPath.Child("master"), master, "Not a valid APIServer URL")) - } - } - - // KubeAPIServer - if c.Spec.KubeAPIServer != nil { - if len(c.Spec.KubeAPIServer.AdmissionControl) > 0 { - if len(c.Spec.KubeAPIServer.DisableAdmissionPlugins) > 0 { - allErrs = append(allErrs, field.Forbidden(fieldSpec.Child("kubeAPIServer", "disableAdmissionPlugins"), - "disableAdmissionPlugins is mutually exclusive, you cannot use both admissionControl and disableAdmissionPlugins together")) - } - } - } - - // Kubelet - allErrs = append(allErrs, validateKubelet(c.Spec.Kubelet, c, fieldSpec.Child("kubelet"))...) - allErrs = append(allErrs, validateKubelet(c.Spec.MasterKubelet, c, fieldSpec.Child("masterKubelet"))...) - - // Topology support - if c.Spec.Topology != nil { - if c.Spec.Topology.Masters != "" && c.Spec.Topology.Nodes != "" { - allErrs = append(allErrs, IsValidValue(fieldSpec.Child("topology", "masters"), &c.Spec.Topology.Masters, kops.SupportedTopologies)...) - allErrs = append(allErrs, IsValidValue(fieldSpec.Child("topology", "nodes"), &c.Spec.Topology.Nodes, kops.SupportedTopologies)...) - } else { - allErrs = append(allErrs, field.Required(fieldSpec.Child("masters"), "topology requires non-nil values for masters and nodes")) - } - if c.Spec.Topology.Bastion != nil { - bastion := c.Spec.Topology.Bastion - if c.Spec.Topology.Masters == kops.TopologyPublic || c.Spec.Topology.Nodes == kops.TopologyPublic { - allErrs = append(allErrs, field.Forbidden(fieldSpec.Child("topology", "bastion"), "bastion requires masters and nodes to have private topology")) - } - if bastion.IdleTimeoutSeconds != nil && *bastion.IdleTimeoutSeconds <= 0 { - allErrs = append(allErrs, field.Invalid(fieldSpec.Child("topology", "bastion", "idleTimeoutSeconds"), *bastion.IdleTimeoutSeconds, "bastion idleTimeoutSeconds should be greater than zero")) - } - if bastion.IdleTimeoutSeconds != nil && *bastion.IdleTimeoutSeconds > 3600 { - allErrs = append(allErrs, field.Invalid(fieldSpec.Child("topology", "bastion", "idleTimeoutSeconds"), *bastion.IdleTimeoutSeconds, "bastion idleTimeoutSeconds cannot be greater than one hour")) - } - - } - } - // Egress specification support - { - for i, s := range c.Spec.Subnets { - if s.Egress == "" { - continue - } - fieldSubnet := fieldSpec.Child("subnets").Index(i) - if !strings.HasPrefix(s.Egress, "nat-") && !strings.HasPrefix(s.Egress, "i-") && s.Egress != kops.EgressExternal { - allErrs = append(allErrs, field.Invalid(fieldSubnet.Child("egress"), s.Egress, "egress must be of type NAT Gateway or NAT EC2 Instance or 'External'")) - } - if s.Egress != kops.EgressExternal && s.Type != "Private" { - allErrs = append(allErrs, field.Forbidden(fieldSubnet.Child("egress"), "egress can only be specified for private subnets")) - } - } - } - allErrs = append(allErrs, newValidateCluster(c)...) return allErrs @@ -550,46 +419,6 @@ func DeepValidate(c *kops.Cluster, groups []*kops.InstanceGroup, strict bool, cl return nil } -func validateKubelet(k *kops.KubeletConfigSpec, c *kops.Cluster, kubeletPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if k != nil { - - { - // Flag removed in 1.6 - if k.APIServers != "" { - allErrs = append(allErrs, field.Forbidden( - kubeletPath.Child("apiServers"), - "api-servers flag was removed in 1.6")) - } - } - - { - // Flag removed in 1.10 - if k.RequireKubeconfig != nil { - allErrs = append(allErrs, field.Forbidden( - kubeletPath.Child("requireKubeconfig"), - "require-kubeconfig flag was removed in 1.10. (Please be sure you are not using a cluster config from `kops get cluster --full`)")) - } - } - - if k.BootstrapKubeconfig != "" { - if c.Spec.KubeAPIServer == nil { - allErrs = append(allErrs, field.Required(kubeletPath.Root().Child("spec").Child("kubeAPIServer"), "bootstrap token require the NodeRestriction admissions controller")) - } - } - - if k.TopologyManagerPolicy != "" { - allErrs = append(allErrs, IsValidValue(kubeletPath.Child("topologyManagerPolicy"), &k.TopologyManagerPolicy, []string{"none", "best-effort", "restricted", "single-numa-node"})...) - if !c.IsKubernetesGTE("1.18") { - allErrs = append(allErrs, field.Forbidden(kubeletPath.Child("topologyManagerPolicy"), "topologyManagerPolicy requires at least Kubernetes 1.18")) - } - } - - } - return allErrs -} - func isExperimentalClusterDNS(k *kops.KubeletConfigSpec, dns *kops.KubeDNSConfig) bool { return k != nil && k.ClusterDNS != dns.ServerIP && dns.NodeLocalDNS != nil && k.ClusterDNS != dns.NodeLocalDNS.LocalIP diff --git a/pkg/apis/kops/validation/validation.go b/pkg/apis/kops/validation/validation.go index ced6c7b96be96..494180f355b01 100644 --- a/pkg/apis/kops/validation/validation.go +++ b/pkg/apis/kops/validation/validation.go @@ -27,6 +27,7 @@ import ( "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kops/pkg/featureflag" "k8s.io/kops/upup/pkg/fi" "k8s.io/apimachinery/pkg/api/validation" @@ -41,6 +42,23 @@ import ( func newValidateCluster(cluster *kops.Cluster) field.ErrorList { allErrs := validation.ValidateObjectMeta(&cluster.ObjectMeta, false, validation.NameIsDNSSubdomain, field.NewPath("metadata")) + + clusterName := cluster.ObjectMeta.Name + if clusterName == "" { + allErrs = append(allErrs, field.Required(field.NewPath("objectMeta", "name"), "Cluster Name is required (e.g. --name=mycluster.myzone.com)")) + } else { + // Must be a dns name + errs := utilvalidation.IsDNS1123Subdomain(clusterName) + if len(errs) != 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("objectMeta", "name"), clusterName, fmt.Sprintf("Cluster Name must be a valid DNS name (e.g. --name=mycluster.myzone.com) errors: %s", strings.Join(errs, ", ")))) + } else if !strings.Contains(clusterName, ".") { + // Tolerate if this is a cluster we are importing for upgrade + if cluster.ObjectMeta.Annotations[kops.AnnotationNameManagement] != kops.AnnotationValueManagementImported { + allErrs = append(allErrs, field.Invalid(field.NewPath("objectMeta", "name"), clusterName, "Cluster Name must be a fully-qualified DNS name (e.g. --name=mycluster.myzone.com)")) + } + } + } + allErrs = append(allErrs, validateClusterSpec(&cluster.Spec, cluster, field.NewPath("spec"))...) // Additional cloud-specific validation rules @@ -79,6 +97,13 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie allErrs = append(allErrs, validateCIDR(cidr, fieldPath.Child("additionalNetworkCIDRs").Index(i))...) } + if spec.Topology != nil { + allErrs = append(allErrs, validateTopology(spec.Topology, fieldPath.Child("topology"))...) + } + + // UpdatePolicy + allErrs = append(allErrs, IsValidValue(fieldPath.Child("updatePolicy"), spec.UpdatePolicy, []string{kops.UpdatePolicyExternal})...) + // Hooks for i := range spec.Hooks { allErrs = append(allErrs, validateHookSpec(&spec.Hooks[i], fieldPath.Child("hooks").Index(i))...) @@ -91,7 +116,19 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie } if spec.KubeAPIServer != nil { - allErrs = append(allErrs, validateKubeAPIServer(spec.KubeAPIServer, fieldPath.Child("kubeAPIServer"))...) + allErrs = append(allErrs, validateKubeAPIServer(spec.KubeAPIServer, c, fieldPath.Child("kubeAPIServer"))...) + } + + if spec.KubeProxy != nil { + allErrs = append(allErrs, validateKubeProxy(spec.KubeProxy, fieldPath.Child("kubeProxy"))...) + } + + if spec.Kubelet != nil { + allErrs = append(allErrs, validateKubelet(spec.Kubelet, c, fieldPath.Child("kubelet"))...) + } + + if spec.MasterKubelet != nil { + allErrs = append(allErrs, validateKubelet(spec.MasterKubelet, c, fieldPath.Child("masterKubelet"))...) } if spec.Networking != nil { @@ -101,6 +138,10 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie } } + if spec.NodeAuthorization != nil { + allErrs = append(allErrs, validateNodeAuthorization(spec.NodeAuthorization, c, fieldPath.Child("nodeAuthorization"))...) + } + // IAM additionalPolicies if spec.AdditionalPolicies != nil { for k, v := range *spec.AdditionalPolicies { @@ -131,6 +172,12 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie allErrs = append(allErrs, validateDockerConfig(spec.Docker, fieldPath.Child("docker"))...) } + if spec.Assets != nil { + if spec.Assets.ContainerProxy != nil && spec.Assets.ContainerRegistry != nil { + allErrs = append(allErrs, field.Forbidden(fieldPath.Child("assets", "containerProxy"), "containerProxy cannot be used in conjunction with containerRegistry")) + } + } + if spec.RollingUpdate != nil { allErrs = append(allErrs, validateRollingUpdate(spec.RollingUpdate, fieldPath.Child("rollingUpdate"), false)...) } @@ -159,6 +206,42 @@ func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList { return allErrs } +func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if topology.Masters == "" { + allErrs = append(allErrs, field.Required(fieldPath.Child("masters"), "")) + } else { + allErrs = append(allErrs, IsValidValue(fieldPath.Child("masters"), &topology.Masters, kops.SupportedTopologies)...) + } + + if topology.Nodes == "" { + allErrs = append(allErrs, field.Required(fieldPath.Child("nodes"), "")) + } else { + allErrs = append(allErrs, IsValidValue(fieldPath.Child("nodes"), &topology.Nodes, kops.SupportedTopologies)...) + } + + if topology.Bastion != nil { + bastion := topology.Bastion + if topology.Masters == kops.TopologyPublic || topology.Nodes == kops.TopologyPublic { + allErrs = append(allErrs, field.Forbidden(fieldPath.Child("bastion"), "bastion requires masters and nodes to have private topology")) + } + if bastion.IdleTimeoutSeconds != nil && *bastion.IdleTimeoutSeconds <= 0 { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("bastion", "idleTimeoutSeconds"), *bastion.IdleTimeoutSeconds, "bastion idleTimeoutSeconds should be greater than zero")) + } + if bastion.IdleTimeoutSeconds != nil && *bastion.IdleTimeoutSeconds > 3600 { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("bastion", "idleTimeoutSeconds"), *bastion.IdleTimeoutSeconds, "bastion idleTimeoutSeconds cannot be greater than one hour")) + } + } + + if topology.DNS != nil { + value := string(topology.DNS.Type) + allErrs = append(allErrs, IsValidValue(fieldPath.Child("dns", "type"), &value, kops.SupportedDnsTypes)...) + } + + return allErrs +} + func validateSubnets(subnets []kops.ClusterSubnetSpec, fieldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -210,6 +293,14 @@ func validateSubnet(subnet *kops.ClusterSubnetSpec, fieldPath *field.Path) field allErrs = append(allErrs, validateCIDR(subnet.CIDR, fieldPath.Child("cidr"))...) } + if subnet.Egress != "" { + if !strings.HasPrefix(subnet.Egress, "nat-") && !strings.HasPrefix(subnet.Egress, "i-") && subnet.Egress != kops.EgressExternal { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("egress"), subnet.Egress, "egress must be of type NAT Gateway or NAT EC2 Instance or 'External'")) + } + if subnet.Egress != kops.EgressExternal && subnet.Type != "Private" { + allErrs = append(allErrs, field.Forbidden(fieldPath.Child("egress"), "egress can only be specified for private subnets")) + } + } return allErrs } @@ -272,9 +363,16 @@ func validateExecContainerAction(v *kops.ExecContainerAction, fldPath *field.Pat return allErrs } -func validateKubeAPIServer(v *kops.KubeAPIServerConfig, fldPath *field.Path) field.ErrorList { +func validateKubeAPIServer(v *kops.KubeAPIServerConfig, c *kops.Cluster, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + if len(v.AdmissionControl) > 0 { + if len(v.DisableAdmissionPlugins) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("disableAdmissionPlugins"), + "disableAdmissionPlugins is mutually exclusive, you cannot use both admissionControl and disableAdmissionPlugins together")) + } + } + proxyClientCertIsNil := v.ProxyClientCertFile == nil proxyClientKeyIsNil := v.ProxyClientKeyFile == nil @@ -299,6 +397,99 @@ func validateKubeAPIServer(v *kops.KubeAPIServerConfig, fldPath *field.Path) fie return allErrs } +func validateKubeProxy(k *kops.KubeProxyConfig, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + master := k.Master + + for i, x := range k.IPVSExcludeCIDRS { + if _, _, err := net.ParseCIDR(x); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("ipvsExcludeCidrs").Index(i), x, "Invalid network CIDR")) + } + } + + if master != "" && !isValidAPIServersURL(master) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("master"), master, "Not a valid APIServer URL")) + } + + return allErrs +} + +func validateKubelet(k *kops.KubeletConfigSpec, c *kops.Cluster, kubeletPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if k != nil { + + { + // Flag removed in 1.6 + if k.APIServers != "" { + allErrs = append(allErrs, field.Forbidden( + kubeletPath.Child("apiServers"), + "api-servers flag was removed in 1.6")) + } + } + + { + // Flag removed in 1.10 + if k.RequireKubeconfig != nil { + allErrs = append(allErrs, field.Forbidden( + kubeletPath.Child("requireKubeconfig"), + "require-kubeconfig flag was removed in 1.10. (Please be sure you are not using a cluster config from `kops get cluster --full`)")) + } + } + + if k.BootstrapKubeconfig != "" { + if c.Spec.KubeAPIServer == nil { + allErrs = append(allErrs, field.Required(kubeletPath.Root().Child("spec").Child("kubeAPIServer"), "bootstrap token require the NodeRestriction admissions controller")) + } + } + + if k.TopologyManagerPolicy != "" { + allErrs = append(allErrs, IsValidValue(kubeletPath.Child("topologyManagerPolicy"), &k.TopologyManagerPolicy, []string{"none", "best-effort", "restricted", "single-numa-node"})...) + if !c.IsKubernetesGTE("1.18") { + allErrs = append(allErrs, field.Forbidden(kubeletPath.Child("topologyManagerPolicy"), "topologyManagerPolicy requires at least Kubernetes 1.18")) + } + } + + } + return allErrs +} + +func validateNodeAuthorization(n *kops.NodeAuthorizationSpec, c *kops.Cluster, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // @check the feature gate is enabled for this + if !featureflag.EnableNodeAuthorization.Enabled() { + return field.ErrorList{field.Forbidden(fldPath, "node authorization is experimental feature; set `export KOPS_FEATURE_FLAGS=EnableNodeAuthorization`")} + } + + authorizerPath := fldPath.Child("nodeAuthorizer") + if c.Spec.NodeAuthorization.NodeAuthorizer == nil { + allErrs = append(allErrs, field.Required(authorizerPath, "no node authorization policy has been set")) + } else { + if c.Spec.NodeAuthorization.NodeAuthorizer.Port < 0 || n.NodeAuthorizer.Port >= 65535 { + allErrs = append(allErrs, field.Invalid(authorizerPath.Child("port"), n.NodeAuthorizer.Port, "invalid port")) + } + if c.Spec.NodeAuthorization.NodeAuthorizer.Timeout != nil && n.NodeAuthorizer.Timeout.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(authorizerPath.Child("timeout"), n.NodeAuthorizer.Timeout, "must be greater than zero")) + } + if c.Spec.NodeAuthorization.NodeAuthorizer.TokenTTL != nil && n.NodeAuthorizer.TokenTTL.Duration < 0 { + allErrs = append(allErrs, field.Invalid(authorizerPath.Child("tokenTTL"), n.NodeAuthorizer.TokenTTL, "must be greater than or equal to zero")) + } + + // @question: we could probably just default these settings in the model when the node-authorizer is enabled?? + if c.Spec.KubeAPIServer == nil { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "kubeAPIServer"), "bootstrap token authentication is not enabled in the kube-apiserver")) + } else if c.Spec.KubeAPIServer.EnableBootstrapAuthToken == nil { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "kubeAPIServer", "enableBootstrapAuthToken"), "kube-apiserver has not been configured to use bootstrap tokens")) + } else if !fi.BoolValue(c.Spec.KubeAPIServer.EnableBootstrapAuthToken) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "kubeAPIServer", "enableBootstrapAuthToken"), "bootstrap tokens in the kube-apiserver has been disabled")) + } + } + + return allErrs +} + func validateNetworking(c *kops.ClusterSpec, v *kops.NetworkingSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} optionTaken := false diff --git a/pkg/apis/kops/validation/validation_test.go b/pkg/apis/kops/validation/validation_test.go index 094616612c982..2d94659f0399d 100644 --- a/pkg/apis/kops/validation/validation_test.go +++ b/pkg/apis/kops/validation/validation_test.go @@ -208,7 +208,12 @@ func TestValidateKubeAPIServer(t *testing.T) { }, } for _, g := range grid { - errs := validateKubeAPIServer(&g.Input, field.NewPath("KubeAPIServer")) + cluster := &kops.Cluster{ + Spec: kops.ClusterSpec{ + KubernetesVersion: "1.16.0", + }, + } + errs := validateKubeAPIServer(&g.Input, cluster, field.NewPath("KubeAPIServer")) testErrors(t, g.Input, errs, g.ExpectedErrors) diff --git a/upup/pkg/fi/cloudup/validation_test.go b/upup/pkg/fi/cloudup/validation_test.go index ab1a4758af69c..cc7e50047959b 100644 --- a/upup/pkg/fi/cloudup/validation_test.go +++ b/upup/pkg/fi/cloudup/validation_test.go @@ -174,7 +174,7 @@ func TestValidate_ContainerRegistry_and_ContainerProxy_exclusivity(t *testing.T) proxy := "https://proxy.example.com/" c.Spec.Assets.ContainerProxy = &proxy - expectErrorFromValidate(t, c, "ContainerProxy cannot be used in conjunction with ContainerRegistry") + expectErrorFromValidate(t, c, "containerProxy cannot be used in conjunction with containerRegistry") c.Spec.Assets.ContainerRegistry = nil expectNoErrorFromValidate(t, c)