diff --git a/cmd/controller/certificates.go b/cmd/controller/certificates.go index 3ecce1d83e45..105e57cf0e10 100644 --- a/cmd/controller/certificates.go +++ b/cmd/controller/certificates.go @@ -23,6 +23,7 @@ import ( "net" "os" "path/filepath" + "strconv" "github.com/k0sproject/k0s/internal/pkg/file" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" @@ -64,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) 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 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 9f36f066d824..98ab667cd3d8 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..6756dbfcf762 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.OnlyToBindAddress = 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..eed460893e83 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"` + OnlyToBindAddress 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 593812042016..bf2985b2fe52 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" @@ -125,8 +127,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.OnlyToBindAddress { + args["bind-address"] = a.ClusterConfig.Spec.API.Address } apiAudiences := []string{"https://kubernetes.default.svc"} @@ -234,7 +236,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 905528cdf97e..4f26ea13b3f5 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: