From 548dc411dd1c3569ebf49fce747d4fc89727582a Mon Sep 17 00:00:00 2001 From: Alex Hutchins Date: Fri, 30 Jul 2021 20:18:29 +0000 Subject: [PATCH 1/3] Add spec.api.bindAddress configuration Signed-off-by: Alex Hutchins Signed-off-by: gakio12 Signed-off-by: Phillip Schichtel # Conflicts: # cmd/controller/certificates.go --- cmd/controller/certificates.go | 3 +-- cmd/controller/controller.go | 1 + docs/configuration.md | 6 ++++-- pkg/apis/k0s/v1beta1/api.go | 20 ++++++++++++++++++- pkg/apis/k0s/v1beta1/api_test.go | 16 ++++++++++++++- pkg/component/controller/apiserver.go | 6 +++++- .../k0s/k0s.k0sproject.io_clusterconfigs.yaml | 4 ++++ 7 files changed, 49 insertions(+), 7 deletions(-) diff --git a/cmd/controller/certificates.go b/cmd/controller/certificates.go index 57402a00dba9..505191ce22d2 100644 --- a/cmd/controller/certificates.go +++ b/cmd/controller/certificates.go @@ -65,7 +65,7 @@ func (c *Certificates) Init(ctx context.Context) error { } c.CACert = string(cert) // Changing the URL here also requires changes in the "k0s kubeconfig admin" subcommand. - kubeConfigAPIUrl := fmt.Sprintf("https://localhost:%d", c.ClusterSpec.API.Port) + kubeConfigAPIUrl := fmt.Sprintf("https://%s:%d", c.ClusterSpec.API.APIServerAddress(), c.ClusterSpec.API.Port) apiServerUID, err := users.LookupUID(constant.ApiserverUser) if err != nil { @@ -73,7 +73,6 @@ func (c *Certificates) Init(ctx context.Context) error { apiServerUID = users.RootUID logrus.WithError(err).Warn("Files with key material for kube-apiserver user will be owned by root") } - eg.Go(func() error { // Front proxy CA if err := c.CertManager.EnsureCA("front-proxy-ca", "kubernetes-front-proxy-ca"); err != nil { diff --git a/cmd/controller/controller.go b/cmd/controller/controller.go index 042a6b50f33a..3769e4be1ef7 100644 --- a/cmd/controller/controller.go +++ b/cmd/controller/controller.go @@ -186,6 +186,7 @@ func (c *command) start(ctx context.Context) error { } logrus.Infof("using api address: %s", nodeConfig.Spec.API.Address) + logrus.Infof("using api bind-address: %s", nodeConfig.Spec.API.BindAddress) logrus.Infof("using listen port: %d", nodeConfig.Spec.API.Port) logrus.Infof("using sans: %s", nodeConfig.Spec.API.SANs) diff --git a/docs/configuration.md b/docs/configuration.md index a47a47ad58e7..244c66f41164 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -54,6 +54,7 @@ spec: address: 192.168.68.104 k0sApiPort: 9443 port: 6443 + bindAddress: 192.0.2.1 sans: - 192.168.68.104 controllerManager: {} @@ -113,9 +114,10 @@ spec: ### `spec.api` | Element | Description | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|--------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `externalAddress` | The loadbalancer address (for k0s controllers running behind a loadbalancer). Configures all cluster components to connect to this address and also configures this address for use when joining new nodes to the cluster. | -| `address` | Local address on which to bind an API. Also serves as one of the addresses pushed on the k0s create service certificate on the API. Defaults to first non-local address found on the node. | +| `address` | Local address on which to bind an API. Also serves as one of the addresses pushed on the k0s create service certificate on the API. Defaults to first non-local address found on the node. | +| `bindAddress` | The IP address for the Kubernetes API server to to listen on. The associated interface(s) must be reachable by the rest of the cluster. Will be added as an additional subject alternative name to the API server's TLS certificate. If blank or an unspecified address (`0.0.0.0` or `::`), all interfaces and IP address families will be used. This is effectively the value for the API server's `--bind-address` CLI flag. | | `sans` | List of additional addresses to push to API servers serving the certificate. | | `extraArgs` | Map of key-values (strings) for any extra arguments to pass down to Kubernetes api-server process. | | `port`¹ | Custom port for kube-api server to listen on (default: 6443) | diff --git a/pkg/apis/k0s/v1beta1/api.go b/pkg/apis/k0s/v1beta1/api.go index c771539fbd18..55a7b409023c 100644 --- a/pkg/apis/k0s/v1beta1/api.go +++ b/pkg/apis/k0s/v1beta1/api.go @@ -37,6 +37,10 @@ type APISpec struct { // Address on which to connect to the API server. Address string `json:"address,omitempty"` + // The IP address for the Kubernetes API server to listen on. + // +optional + BindAddress string `json:"bindAddress,omitempty"` + // The loadbalancer address (for k0s controllers running behind a loadbalancer) ExternalAddress string `json:"externalAddress,omitempty"` @@ -102,10 +106,21 @@ func (a *APISpec) getExternalURIForPort(port int) string { return fmt.Sprintf("https://%s:%d", addr, port) } -// Sans return the given SANS plus all local adresses and externalAddress if given +// APIServerAddress returns the address the API is listening on +func (a *APISpec) APIServerAddress() string { + if a.BindAddress == "" { + return "localhost" + } + return a.BindAddress +} + +// Sans return the given SANS plus all local addresses and externalAddress if given func (a *APISpec) Sans() []string { sans, _ := iface.AllAddresses() sans = append(sans, a.Address) + if a.BindAddress != "" { + sans = append(sans, a.BindAddress) + } sans = append(sans, a.SANs...) if a.ExternalAddress != "" { sans = append(sans, a.ExternalAddress) @@ -150,6 +165,9 @@ func (a *APISpec) Validate() []error { validateIPAddressOrDNSName(sansPath.Index(idx), san) } + if a.BindAddress != "" && !govalidator.IsIP(a.BindAddress) { + errors = append(errors, field.Invalid(field.NewPath("bindAddress"), a.BindAddress, "invalid IP address")) + } return errors } diff --git a/pkg/apis/k0s/v1beta1/api_test.go b/pkg/apis/k0s/v1beta1/api_test.go index dc96828ce6e0..52d90acb7919 100644 --- a/pkg/apis/k0s/v1beta1/api_test.go +++ b/pkg/apis/k0s/v1beta1/api_test.go @@ -45,7 +45,8 @@ func (s *APISuite) TestValidation() { s.Run("invalid_api_address", func() { a := APISpec{ - Address: "something.that.is.not.valid//(())", + Address: "something.that.is.not.valid//(())", + BindAddress: "0.0.0.0", } a.setDefaults() @@ -58,6 +59,7 @@ func (s *APISuite) TestValidation() { s.Run("invalid_sans_address", func() { a := APISpec{ + BindAddress: "0.0.0.0", SANs: []string{ "something.that.is.not.valid//(())", }, @@ -70,6 +72,18 @@ func (s *APISuite) TestValidation() { s.ErrorContains(errors[0], `sans[0]: Invalid value: "something.that.is.not.valid//(())": invalid IP address / DNS name`) } }) + + s.T().Run("invalid_api_bind_address", func(t *testing.T) { + a := APISpec{ + Address: "1.2.3.4", + BindAddress: "something.that.is.not.valid//(())", + } + + errors := a.Validate() + s.NotNil(errors) + s.Len(errors, 1) + s.Contains(errors[0].Error(), "invalid IP address") + }) } func TestApiSuite(t *testing.T) { diff --git a/pkg/component/controller/apiserver.go b/pkg/component/controller/apiserver.go index d51f3ca46428..9d7c043a4eb1 100644 --- a/pkg/component/controller/apiserver.go +++ b/pkg/component/controller/apiserver.go @@ -127,6 +127,10 @@ func (a *APIServer) Start(_ context.Context) error { "enable-admission-plugins": "NodeRestriction", } + if a.ClusterConfig.Spec.API.BindAddress != "" { + args["bind-address"] = a.ClusterConfig.Spec.API.BindAddress + } + apiAudiences := []string{"https://kubernetes.default.svc"} if a.EnableKonnectivity { @@ -232,7 +236,7 @@ func (a *APIServer) Ready() error { TLSClientConfig: tlsConfig, } client := &http.Client{Transport: tr} - resp, err := client.Get(fmt.Sprintf("https://localhost:%d/readyz?verbose", a.ClusterConfig.Spec.API.Port)) + resp, err := client.Get(fmt.Sprintf("https://%s:%d/readyz?verbose", a.ClusterConfig.Spec.API.APIServerAddress(), a.ClusterConfig.Spec.API.Port)) if err != nil { return err } diff --git a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml index f7d8d89717e7..912148154006 100644 --- a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml +++ b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml @@ -45,6 +45,10 @@ spec: address: description: Address on which to connect to the API server. type: string + bindAddress: + description: The IP address for the Kubernetes API server to listen + on. + type: string externalAddress: description: The loadbalancer address (for k0s controllers running behind a loadbalancer) From e799ec78db13ac8788a817e7b4590aa57acdf63c Mon Sep 17 00:00:00 2001 From: Phillip Schichtel Date: Tue, 16 Apr 2024 20:54:28 +0200 Subject: [PATCH 2/3] Add an integration test for spec.api.bindAddress Signed-off-by: Phillip Schichtel --- inttest/Makefile.variables | 1 + inttest/bind-address/bind_address_test.go | 179 ++++++++++++++++++++++ pkg/apis/k0s/v1beta1/api.go | 3 + 3 files changed, 183 insertions(+) create mode 100644 inttest/bind-address/bind_address_test.go diff --git a/inttest/Makefile.variables b/inttest/Makefile.variables index 257ad0e8718a..6e2d547c5ef2 100644 --- a/inttest/Makefile.variables +++ b/inttest/Makefile.variables @@ -15,6 +15,7 @@ smoketests := \ check-ap-updater-periodic \ check-backup \ check-basic \ + check-bind-address \ check-byocri \ check-calico \ check-capitalhostnames \ diff --git a/inttest/bind-address/bind_address_test.go b/inttest/bind-address/bind_address_test.go new file mode 100644 index 000000000000..dbf9bc781f00 --- /dev/null +++ b/inttest/bind-address/bind_address_test.go @@ -0,0 +1,179 @@ +/* +Copyright 2024 k0s 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 bind_address + +import ( + "context" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/k0sproject/k0s/inttest/common" + "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + kubeletv1beta1 "k8s.io/kubelet/config/v1beta1" + + testifysuite "github.com/stretchr/testify/suite" + "golang.org/x/sync/errgroup" + "sigs.k8s.io/yaml" +) + +const kubeSystem = "kube-system" + +type suite struct { + common.BootlooseSuite +} + +func (s *suite) TestCustomizedBindAddress() { + const controllerArgs = "--kube-controller-manager-extra-args='--node-monitor-period=3s --node-monitor-grace-period=9s'" + + ctx := s.Context() + + { + for i := 0; i < s.ControllerCount; i++ { + config, err := yaml.Marshal(&v1beta1.ClusterConfig{ + Spec: &v1beta1.ClusterSpec{ + API: func() *v1beta1.APISpec { + apiSpec := v1beta1.DefaultAPISpec() + apiSpec.Address = s.GetIPAddress(s.ControllerNode(i)) + return apiSpec + }(), + WorkerProfiles: v1beta1.WorkerProfiles{ + v1beta1.WorkerProfile{ + Name: "default", + Config: func() *runtime.RawExtension { + kubeletConfig := kubeletv1beta1.KubeletConfiguration{ + NodeStatusUpdateFrequency: metav1.Duration{Duration: 3 * time.Second}, + } + bytes, err := json.Marshal(kubeletConfig) + s.Require().NoError(err) + return &runtime.RawExtension{Raw: bytes} + }(), + }, + }, + }, + }) + s.Require().NoError(err) + s.WriteFileContent(s.ControllerNode(i), "/tmp/k0s.yaml", config) + } + } + + s.Run("controller_and_workers_get_up", func() { + s.Require().NoError(s.InitController(0, "--config=/tmp/k0s.yaml", controllerArgs)) + + s.T().Logf("Starting workers and waiting for cluster to become ready") + + token, err := s.GetJoinToken("worker") + s.Require().NoError(err) + s.Require().NoError(s.RunWorkersWithToken(token)) + + clients, err := s.KubeClient(s.ControllerNode(0)) + s.Require().NoError(err) + + eg, _ := errgroup.WithContext(ctx) + for i := 0; i < s.WorkerCount; i++ { + nodeName := s.WorkerNode(i) + eg.Go(func() error { + if err := s.WaitForNodeReady(nodeName, clients); err != nil { + return fmt.Errorf("Node %s is not ready: %w", nodeName, err) + } + return nil + }) + } + s.Require().NoError(eg.Wait()) + + s.Require().NoError(s.checkClusterReadiness(ctx, clients, 1)) + }) + + s.Run("join_new_controllers", func() { + token, err := s.GetJoinToken("controller") + s.Require().NoError(err) + + eg, _ := errgroup.WithContext(ctx) + eg.Go(func() error { return s.InitController(1, "--config=/tmp/k0s.yaml", controllerArgs, token) }) + eg.Go(func() error { return s.InitController(2, "--config=/tmp/k0s.yaml", controllerArgs, token) }) + + s.Require().NoError(eg.Wait()) + + clients, err := s.KubeClient(s.ControllerNode(1)) + s.Require().NoError(err) + + s.T().Logf("Checking if HA cluster is ready") + s.Require().NoError(s.checkClusterReadiness(ctx, clients, s.ControllerCount)) + }) +} + +func (s *suite) checkClusterReadiness(ctx context.Context, clients *kubernetes.Clientset, numControllers int, degradedControllers ...string) error { + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(func() error { + if err := common.WaitForKubeRouterReady(ctx, clients); err != nil { + return fmt.Errorf("kube-router did not start: %w", err) + } + s.T().Logf("kube-router is ready") + return nil + }) + + for _, lease := range []string{"kube-scheduler", "kube-controller-manager"} { + lease := lease + eg.Go(func() error { + id, err := common.WaitForLease(ctx, clients, lease, kubeSystem) + if err != nil { + return fmt.Errorf("%s has no leader: %w", lease, err) + } + s.T().Logf("%s has a leader: %q", lease, id) + return nil + }) + } + + for _, daemonSet := range []string{"kube-proxy", "konnectivity-agent"} { + daemonSet := daemonSet + eg.Go(func() error { + if err := common.WaitForDaemonSet(ctx, clients, daemonSet, "kube-system"); err != nil { + return fmt.Errorf("%s is not ready: %w", daemonSet, err) + } + s.T().Log(daemonSet, "is ready") + return nil + }) + } + + for _, deployment := range []string{"coredns", "metrics-server"} { + deployment := deployment + eg.Go(func() error { + if err := common.WaitForDeployment(ctx, clients, deployment, "kube-system"); err != nil { + return fmt.Errorf("%s did not become ready: %w", deployment, err) + } + s.T().Log(deployment, "is ready") + return nil + }) + } + + return eg.Wait() +} + +func TestCustomizedBindAddressSuite(t *testing.T) { + s := suite{ + common.BootlooseSuite{ + ControllerCount: 3, + WorkerCount: 2, + }, + } + testifysuite.Run(t, &s) +} diff --git a/pkg/apis/k0s/v1beta1/api.go b/pkg/apis/k0s/v1beta1/api.go index 55a7b409023c..32822d4fe3dd 100644 --- a/pkg/apis/k0s/v1beta1/api.go +++ b/pkg/apis/k0s/v1beta1/api.go @@ -97,6 +97,9 @@ func (a *APISpec) K0sControlPlaneAPIAddress() string { func (a *APISpec) getExternalURIForPort(port int) string { addr := a.Address + if a.BindAddress != "" { + addr = a.BindAddress + } if a.ExternalAddress != "" { addr = a.ExternalAddress } From a759281d26b651bf1324c952eeaaa2bb7621c906 Mon Sep 17 00:00:00 2001 From: Phillip Schichtel Date: Sun, 26 May 2024 01:38:14 +0200 Subject: [PATCH 3/3] replace BindAddress by Address and OnlyBindToAddress Signed-off-by: Phillip Schichtel --- cmd/controller/certificates.go | 11 ++++--- cmd/controller/controller.go | 1 - docs/configuration.md | 19 ++++++------ inttest/bind-address/bind_address_test.go | 3 +- pkg/apis/k0s/v1beta1/api.go | 31 +++++++------------ pkg/apis/k0s/v1beta1/api_test.go | 16 +--------- pkg/component/controller/apiserver.go | 9 ++++-- .../k0s/k0s.k0sproject.io_clusterconfigs.yaml | 8 ++--- 8 files changed, 40 insertions(+), 58 deletions(-) diff --git a/cmd/controller/certificates.go b/cmd/controller/certificates.go index 505191ce22d2..84fe17be4717 100644 --- a/cmd/controller/certificates.go +++ b/cmd/controller/certificates.go @@ -20,16 +20,16 @@ import ( "context" "errors" "fmt" - "net" - "os" - "path/filepath" - "github.com/k0sproject/k0s/internal/pkg/file" "github.com/k0sproject/k0s/internal/pkg/users" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" "github.com/k0sproject/k0s/pkg/certificate" "github.com/k0sproject/k0s/pkg/config" "github.com/k0sproject/k0s/pkg/constant" + "net" + "os" + "path/filepath" + "strconv" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" @@ -65,7 +65,8 @@ func (c *Certificates) Init(ctx context.Context) error { } c.CACert = string(cert) // Changing the URL here also requires changes in the "k0s kubeconfig admin" subcommand. - kubeConfigAPIUrl := fmt.Sprintf("https://%s:%d", c.ClusterSpec.API.APIServerAddress(), c.ClusterSpec.API.Port) + apiAddress := net.JoinHostPort(c.ClusterSpec.API.Address, strconv.Itoa(c.ClusterSpec.API.Port)) + kubeConfigAPIUrl := fmt.Sprintf("https://%s", apiAddress) apiServerUID, err := users.LookupUID(constant.ApiserverUser) if err != nil { diff --git a/cmd/controller/controller.go b/cmd/controller/controller.go index 3769e4be1ef7..042a6b50f33a 100644 --- a/cmd/controller/controller.go +++ b/cmd/controller/controller.go @@ -186,7 +186,6 @@ func (c *command) start(ctx context.Context) error { } logrus.Infof("using api address: %s", nodeConfig.Spec.API.Address) - logrus.Infof("using api bind-address: %s", nodeConfig.Spec.API.BindAddress) logrus.Infof("using listen port: %d", nodeConfig.Spec.API.Port) logrus.Infof("using sans: %s", nodeConfig.Spec.API.SANs) diff --git a/docs/configuration.md b/docs/configuration.md index 244c66f41164..613a6d94f640 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -54,7 +54,6 @@ spec: address: 192.168.68.104 k0sApiPort: 9443 port: 6443 - bindAddress: 192.0.2.1 sans: - 192.168.68.104 controllerManager: {} @@ -113,15 +112,15 @@ spec: ### `spec.api` -| Element | Description | -|--------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `externalAddress` | The loadbalancer address (for k0s controllers running behind a loadbalancer). Configures all cluster components to connect to this address and also configures this address for use when joining new nodes to the cluster. | -| `address` | Local address on which to bind an API. Also serves as one of the addresses pushed on the k0s create service certificate on the API. Defaults to first non-local address found on the node. | -| `bindAddress` | The IP address for the Kubernetes API server to to listen on. The associated interface(s) must be reachable by the rest of the cluster. Will be added as an additional subject alternative name to the API server's TLS certificate. If blank or an unspecified address (`0.0.0.0` or `::`), all interfaces and IP address families will be used. This is effectively the value for the API server's `--bind-address` CLI flag. | -| `sans` | List of additional addresses to push to API servers serving the certificate. | -| `extraArgs` | Map of key-values (strings) for any extra arguments to pass down to Kubernetes api-server process. | -| `port`¹ | Custom port for kube-api server to listen on (default: 6443) | -| `k0sApiPort`¹ | Custom port for k0s-api server to listen on (default: 9443) | +| Element | Description | +|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `address` | IP Address used by cluster components to talk to the API server. Also serves as one of the addresses pushed on the k0s create service certificate on the API. Defaults to first non-local address found on the node. | +| `onlyBindToAddress` | The API server binds too all interfaces by default. With this option set to `true`, the API server will only listen on the IP address configured by the `address` option (first non-local address by default). This can be necessary with multi-homed control plane nodes. | +| `externalAddress` | The loadbalancer address (for k0s controllers running behind a loadbalancer). Configures all cluster components to connect to this address and also configures this address for use when joining new nodes to the cluster. | +| `sans` | List of additional addresses to push to API servers serving the certificate. | +| `extraArgs` | Map of key-values (strings) for any extra arguments to pass down to Kubernetes api-server process. | +| `port`¹ | Custom port for kube-api server to listen on (default: 6443) | +| `k0sApiPort`¹ | Custom port for k0s-api server to listen on (default: 9443) | ¹ If `port` and `k0sApiPort` are used with the `externalAddress` element, the loadbalancer serving at `externalAddress` must listen on the same ports. diff --git a/inttest/bind-address/bind_address_test.go b/inttest/bind-address/bind_address_test.go index dbf9bc781f00..dcb13b7a1ee6 100644 --- a/inttest/bind-address/bind_address_test.go +++ b/inttest/bind-address/bind_address_test.go @@ -53,6 +53,7 @@ func (s *suite) TestCustomizedBindAddress() { API: func() *v1beta1.APISpec { apiSpec := v1beta1.DefaultAPISpec() apiSpec.Address = s.GetIPAddress(s.ControllerNode(i)) + apiSpec.OnlyBindToAddress = true return apiSpec }(), WorkerProfiles: v1beta1.WorkerProfiles{ @@ -172,7 +173,7 @@ func TestCustomizedBindAddressSuite(t *testing.T) { s := suite{ common.BootlooseSuite{ ControllerCount: 3, - WorkerCount: 2, + WorkerCount: 1, }, } testifysuite.Run(t, &s) diff --git a/pkg/apis/k0s/v1beta1/api.go b/pkg/apis/k0s/v1beta1/api.go index 32822d4fe3dd..cb043fb70152 100644 --- a/pkg/apis/k0s/v1beta1/api.go +++ b/pkg/apis/k0s/v1beta1/api.go @@ -37,9 +37,9 @@ type APISpec struct { // Address on which to connect to the API server. Address string `json:"address,omitempty"` - // The IP address for the Kubernetes API server to listen on. + // Whether to only bind to the IP given by the address option. // +optional - BindAddress string `json:"bindAddress,omitempty"` + OnlyBindToAddress bool `json:"onlyBindToAddress,omitempty"` // The loadbalancer address (for k0s controllers running behind a loadbalancer) ExternalAddress string `json:"externalAddress,omitempty"` @@ -97,9 +97,6 @@ func (a *APISpec) K0sControlPlaneAPIAddress() string { func (a *APISpec) getExternalURIForPort(port int) string { addr := a.Address - if a.BindAddress != "" { - addr = a.BindAddress - } if a.ExternalAddress != "" { addr = a.ExternalAddress } @@ -109,21 +106,10 @@ func (a *APISpec) getExternalURIForPort(port int) string { return fmt.Sprintf("https://%s:%d", addr, port) } -// APIServerAddress returns the address the API is listening on -func (a *APISpec) APIServerAddress() string { - if a.BindAddress == "" { - return "localhost" - } - return a.BindAddress -} - // Sans return the given SANS plus all local addresses and externalAddress if given func (a *APISpec) Sans() []string { sans, _ := iface.AllAddresses() sans = append(sans, a.Address) - if a.BindAddress != "" { - sans = append(sans, a.BindAddress) - } sans = append(sans, a.SANs...) if a.ExternalAddress != "" { sans = append(sans, a.ExternalAddress) @@ -132,6 +118,10 @@ func (a *APISpec) Sans() []string { return stringslice.Unique(sans) } +func isAnyAddress(address string) bool { + return address == "0.0.0.0" || address == "::" +} + // Validate validates APISpec struct func (a *APISpec) Validate() []error { if a == nil { @@ -143,6 +133,9 @@ func (a *APISpec) Validate() []error { if !govalidator.IsIP(a.Address) { errors = append(errors, field.Invalid(field.NewPath("address"), a.Address, "invalid IP address")) } + if isAnyAddress(a.Address) { + errors = append(errors, field.Invalid(field.NewPath("address"), a.Address, "invalid INADDR_ANY")) + } validateIPAddressOrDNSName := func(path *field.Path, san string) { if govalidator.IsIP(san) || govalidator.IsDNSName(san) { @@ -153,6 +146,9 @@ func (a *APISpec) Validate() []error { if a.ExternalAddress != "" { validateIPAddressOrDNSName(field.NewPath("externalAddress"), a.ExternalAddress) + if isAnyAddress(a.ExternalAddress) { + errors = append(errors, field.Invalid(field.NewPath("externalAddress"), a.Address, "invalid INADDR_ANY")) + } } for _, msg := range validation.IsValidPortNum(a.K0sAPIPort) { @@ -168,9 +164,6 @@ func (a *APISpec) Validate() []error { validateIPAddressOrDNSName(sansPath.Index(idx), san) } - if a.BindAddress != "" && !govalidator.IsIP(a.BindAddress) { - errors = append(errors, field.Invalid(field.NewPath("bindAddress"), a.BindAddress, "invalid IP address")) - } return errors } diff --git a/pkg/apis/k0s/v1beta1/api_test.go b/pkg/apis/k0s/v1beta1/api_test.go index 52d90acb7919..dc96828ce6e0 100644 --- a/pkg/apis/k0s/v1beta1/api_test.go +++ b/pkg/apis/k0s/v1beta1/api_test.go @@ -45,8 +45,7 @@ func (s *APISuite) TestValidation() { s.Run("invalid_api_address", func() { a := APISpec{ - Address: "something.that.is.not.valid//(())", - BindAddress: "0.0.0.0", + Address: "something.that.is.not.valid//(())", } a.setDefaults() @@ -59,7 +58,6 @@ func (s *APISuite) TestValidation() { s.Run("invalid_sans_address", func() { a := APISpec{ - BindAddress: "0.0.0.0", SANs: []string{ "something.that.is.not.valid//(())", }, @@ -72,18 +70,6 @@ func (s *APISuite) TestValidation() { s.ErrorContains(errors[0], `sans[0]: Invalid value: "something.that.is.not.valid//(())": invalid IP address / DNS name`) } }) - - s.T().Run("invalid_api_bind_address", func(t *testing.T) { - a := APISpec{ - Address: "1.2.3.4", - BindAddress: "something.that.is.not.valid//(())", - } - - errors := a.Validate() - s.NotNil(errors) - s.Len(errors, 1) - s.Contains(errors[0].Error(), "invalid IP address") - }) } func TestApiSuite(t *testing.T) { diff --git a/pkg/component/controller/apiserver.go b/pkg/component/controller/apiserver.go index 9d7c043a4eb1..7e3025978aef 100644 --- a/pkg/component/controller/apiserver.go +++ b/pkg/component/controller/apiserver.go @@ -22,11 +22,13 @@ import ( "crypto/x509" "fmt" "io" + "net" "net/http" "net/url" "os" "path" "path/filepath" + "strconv" "strings" "github.com/sirupsen/logrus" @@ -127,8 +129,8 @@ func (a *APIServer) Start(_ context.Context) error { "enable-admission-plugins": "NodeRestriction", } - if a.ClusterConfig.Spec.API.BindAddress != "" { - args["bind-address"] = a.ClusterConfig.Spec.API.BindAddress + if a.ClusterConfig.Spec.API.OnlyBindToAddress { + args["bind-address"] = a.ClusterConfig.Spec.API.Address } apiAudiences := []string{"https://kubernetes.default.svc"} @@ -236,7 +238,8 @@ func (a *APIServer) Ready() error { TLSClientConfig: tlsConfig, } client := &http.Client{Transport: tr} - resp, err := client.Get(fmt.Sprintf("https://%s:%d/readyz?verbose", a.ClusterConfig.Spec.API.APIServerAddress(), a.ClusterConfig.Spec.API.Port)) + apiAddress := net.JoinHostPort(a.ClusterConfig.Spec.API.Address, strconv.Itoa(a.ClusterConfig.Spec.API.Port)) + resp, err := client.Get(fmt.Sprintf("https://%s/readyz?verbose", apiAddress)) if err != nil { return err } diff --git a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml index 912148154006..353643e44207 100644 --- a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml +++ b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml @@ -45,10 +45,6 @@ spec: address: description: Address on which to connect to the API server. type: string - bindAddress: - description: The IP address for the Kubernetes API server to listen - on. - type: string externalAddress: description: The loadbalancer address (for k0s controllers running behind a loadbalancer) @@ -66,6 +62,10 @@ spec: maximum: 65535 minimum: 1 type: integer + onlyBindToAddress: + description: Whether to only bind to the IP given by the address + option. + type: boolean port: default: 6443 description: 'Custom port for kube-api server to listen on (default: