mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-02 12:31:12 +02:00
fix: format IPv6 host entries properly
This reworks a bunch of the formatting for the userdata generation to output a cleaner talos config when using IPv6 masters and `osctl config generate`. Please note that this changes the scope of concern for master indexing, keeping `osctl` blissfully unaware of the master-reference chaining. All it does is report the index of the master it is trying to generate. The generator itself handles the reference chaining. Fixes #916, fixes #917, and fixes #918 Signed-off-by: Seán C McCord <ulexus@gmail.com>
This commit is contained in:
parent
142500ce3e
commit
ae77d6e053
@ -178,9 +178,6 @@ func createNodes(requests []*node.Request) (err error) {
|
|||||||
// node comes up
|
// node comes up
|
||||||
// 1 <- 2 <- 3 <- 4 <- 5 ...
|
// 1 <- 2 <- 3 <- 4 <- 5 ...
|
||||||
req.Input.Index = idx
|
req.Input.Index = idx
|
||||||
if req.Input.Index > 0 {
|
|
||||||
req.Input.Index--
|
|
||||||
}
|
|
||||||
if req.IP != nil {
|
if req.IP != nil {
|
||||||
req.Input.IP = req.IP
|
req.Input.IP = req.IP
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,6 @@ var configGenerateCmd = &cobra.Command{
|
|||||||
if input.Index == 0 {
|
if input.Index == 0 {
|
||||||
udType = generate.TypeInit
|
udType = generate.TypeInit
|
||||||
} else {
|
} else {
|
||||||
input.Index--
|
|
||||||
udType = generate.TypeControlPlane
|
udType = generate.TypeControlPlane
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ services:
|
|||||||
bootstrapToken:
|
bootstrapToken:
|
||||||
token: '{{ .KubeadmTokens.BootstrapToken }}'
|
token: '{{ .KubeadmTokens.BootstrapToken }}'
|
||||||
unsafeSkipCAVerification: true
|
unsafeSkipCAVerification: true
|
||||||
apiServerEndpoint: {{ index .MasterIPs .Index }}:6443
|
apiServerEndpoint: "{{ .GetControlPlaneEndpoint "6443" }}"
|
||||||
nodeRegistration:
|
nodeRegistration:
|
||||||
taints: []
|
taints: []
|
||||||
kubeletExtraArgs:
|
kubeletExtraArgs:
|
||||||
|
@ -12,15 +12,28 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/talos-systems/talos/pkg/constants"
|
"github.com/talos-systems/talos/pkg/constants"
|
||||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||||
|
tnet "github.com/talos-systems/talos/pkg/net"
|
||||||
"github.com/talos-systems/talos/pkg/userdata/token"
|
"github.com/talos-systems/talos/pkg/userdata/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultIPv4PodNet is the network to be used for kubernetes Pods when using IPv4-based master nodes
|
||||||
|
const DefaultIPv4PodNet = "10.244.0.0/16"
|
||||||
|
|
||||||
|
// DefaultIPv4ServiceNet is the network to be used for kubernetes Services when using IPv4-based master nodes
|
||||||
|
const DefaultIPv4ServiceNet = "10.96.0.0/12"
|
||||||
|
|
||||||
|
// DefaultIPv6PodNet is the network to be used for kubernetes Pods when using IPv6-based master nodes
|
||||||
|
const DefaultIPv6PodNet = "fc00:db8:10::/56"
|
||||||
|
|
||||||
|
// DefaultIPv6ServiceNet is the network to be used for kubernetes Services when using IPv6-based master nodes
|
||||||
|
const DefaultIPv6ServiceNet = "fc00:db8:20::/112"
|
||||||
|
|
||||||
// CertStrings holds the string representation of a certificate and key.
|
// CertStrings holds the string representation of a certificate and key.
|
||||||
type CertStrings struct {
|
type CertStrings struct {
|
||||||
Crt string
|
Crt string
|
||||||
@ -31,19 +44,61 @@ type CertStrings struct {
|
|||||||
type Input struct {
|
type Input struct {
|
||||||
Certs *Certs
|
Certs *Certs
|
||||||
MasterIPs []string
|
MasterIPs []string
|
||||||
Index int
|
|
||||||
ClusterName string
|
ClusterName string
|
||||||
ServiceDomain string
|
ServiceDomain string
|
||||||
PodNet []string
|
PodNet []string
|
||||||
ServiceNet []string
|
ServiceNet []string
|
||||||
Endpoints string
|
|
||||||
KubernetesVersion string
|
KubernetesVersion string
|
||||||
KubeadmTokens *KubeadmTokens
|
KubeadmTokens *KubeadmTokens
|
||||||
TrustdInfo *TrustdInfo
|
TrustdInfo *TrustdInfo
|
||||||
InitToken *token.Token
|
InitToken *token.Token
|
||||||
|
|
||||||
|
//
|
||||||
|
// Runtime variables
|
||||||
|
//
|
||||||
|
|
||||||
|
// Index is the index of the current master
|
||||||
|
Index int
|
||||||
|
|
||||||
|
// IP is the IP address of the current master
|
||||||
IP net.IP
|
IP net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Endpoints returns the formatted set of Master IP addresses
|
||||||
|
func (i *Input) Endpoints() (out string) {
|
||||||
|
if i == nil || len(i.MasterIPs) < 1 {
|
||||||
|
panic("cannot Endpoints without any Master IPs")
|
||||||
|
}
|
||||||
|
for index, addr := range i.MasterIPs {
|
||||||
|
if index > 0 {
|
||||||
|
out += ", "
|
||||||
|
}
|
||||||
|
out += fmt.Sprintf(`"%s"`, addr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControlPlaneEndpoint returns the formatted host:port of the first master node
|
||||||
|
func (i *Input) GetControlPlaneEndpoint(port string) string {
|
||||||
|
|
||||||
|
if i == nil || len(i.MasterIPs) < 1 {
|
||||||
|
panic("cannot GetControlPlaneEndpoint without any Master IPs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each master after the first should reference the next-lower master index.
|
||||||
|
// Thus, master-2 references master-1 and master-3 references master-2.
|
||||||
|
refMaster := 0
|
||||||
|
if i.Index > 1 {
|
||||||
|
refMaster = i.Index - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
return tnet.FormatAddress(i.MasterIPs[refMaster])
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(i.MasterIPs[refMaster], port)
|
||||||
|
}
|
||||||
|
|
||||||
// Certs holds the base64 encoded keys and certificates.
|
// Certs holds the base64 encoded keys and certificates.
|
||||||
type Certs struct {
|
type Certs struct {
|
||||||
AdminCert string
|
AdminCert string
|
||||||
@ -118,11 +173,34 @@ func genToken(lenFirst int, lenSecond int) (string, error) {
|
|||||||
return tokenTemp[0] + "." + tokenTemp[1], nil
|
return tokenTemp[0] + "." + tokenTemp[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isIPv6(addrs ...string) bool {
|
||||||
|
for _, a := range addrs {
|
||||||
|
if ip := net.ParseIP(a); ip != nil {
|
||||||
|
if ip.To4() == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// NewInput generates the sensitive data required to generate all userdata
|
// NewInput generates the sensitive data required to generate all userdata
|
||||||
// types.
|
// types.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func NewInput(clustername string, masterIPs []string) (input *Input, err error) {
|
func NewInput(clustername string, masterIPs []string) (input *Input, err error) {
|
||||||
|
|
||||||
|
var loopbackIP, podNet, serviceNet string
|
||||||
|
|
||||||
|
if isIPv6(masterIPs...) {
|
||||||
|
loopbackIP = "::1"
|
||||||
|
podNet = DefaultIPv6PodNet
|
||||||
|
serviceNet = DefaultIPv6ServiceNet
|
||||||
|
} else {
|
||||||
|
loopbackIP = "127.0.0.1"
|
||||||
|
podNet = DefaultIPv4PodNet
|
||||||
|
serviceNet = DefaultIPv4ServiceNet
|
||||||
|
}
|
||||||
|
|
||||||
//Gen trustd token strings
|
//Gen trustd token strings
|
||||||
kubeadmBootstrapToken, err := genToken(6, 16)
|
kubeadmBootstrapToken, err := genToken(6, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -184,7 +262,7 @@ func NewInput(clustername string, masterIPs []string) (input *Input, err error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ips := []net.IP{net.ParseIP("127.0.0.1")}
|
ips := []net.IP{net.ParseIP(loopbackIP)}
|
||||||
opts = []x509.Option{x509.IPAddresses(ips)}
|
opts = []x509.Option{x509.IPAddresses(ips)}
|
||||||
csr, err := x509.NewCertificateSigningRequest(adminKeyEC, opts...)
|
csr, err := x509.NewCertificateSigningRequest(adminKeyEC, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -231,11 +309,10 @@ func NewInput(clustername string, masterIPs []string) (input *Input, err error)
|
|||||||
input = &Input{
|
input = &Input{
|
||||||
Certs: certs,
|
Certs: certs,
|
||||||
MasterIPs: masterIPs,
|
MasterIPs: masterIPs,
|
||||||
PodNet: []string{"10.244.0.0/16"},
|
PodNet: []string{podNet},
|
||||||
ServiceNet: []string{"10.96.0.0/12"},
|
ServiceNet: []string{serviceNet},
|
||||||
ServiceDomain: "cluster.local",
|
ServiceDomain: "cluster.local",
|
||||||
ClusterName: clustername,
|
ClusterName: clustername,
|
||||||
Endpoints: strings.Join(masterIPs, ", "),
|
|
||||||
KubernetesVersion: constants.KubernetesVersion,
|
KubernetesVersion: constants.KubernetesVersion,
|
||||||
KubeadmTokens: kubeadmTokens,
|
KubeadmTokens: kubeadmTokens,
|
||||||
TrustdInfo: trustdInfo,
|
TrustdInfo: trustdInfo,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package generate_test
|
package generate_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
input *generate.Input
|
input *generate.Input
|
||||||
|
inputv6 *generate.Input
|
||||||
)
|
)
|
||||||
|
|
||||||
type GenerateSuite struct {
|
type GenerateSuite struct {
|
||||||
@ -30,8 +32,13 @@ func (suite *GenerateSuite) SetupSuite() {
|
|||||||
var err error
|
var err error
|
||||||
input, err = generate.NewInput("test", []string{"10.0.1.5", "10.0.1.6", "10.0.1.7"})
|
input, err = generate.NewInput("test", []string{"10.0.1.5", "10.0.1.6", "10.0.1.7"})
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
inputv6, err = generate.NewInput("test", []string{"2001:db8::1", "2001:db8::2", "2001:db8::3"})
|
||||||
|
suite.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is triggering a false positive for the dupl test, between TestGenerateControlPlaneSuccess
|
||||||
|
// nolint: dupl
|
||||||
func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
||||||
input.IP = net.ParseIP("10.0.1.5")
|
input.IP = net.ParseIP("10.0.1.5")
|
||||||
dataString, err := generate.Userdata(generate.TypeInit, input)
|
dataString, err := generate.Userdata(generate.TypeInit, input)
|
||||||
@ -39,8 +46,17 @@ func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
|||||||
data := &userdata.UserData{}
|
data := &userdata.UserData{}
|
||||||
err = yaml.Unmarshal([]byte(dataString), data)
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::1")
|
||||||
|
dataString, err = generate.Userdata(generate.TypeInit, inputv6)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
data = &userdata.UserData{}
|
||||||
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
|
suite.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is triggering a false positive for the dupl test, between TestGenerateInitSuccess
|
||||||
|
// nolint: dupl
|
||||||
func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
||||||
input.IP = net.ParseIP("10.0.1.6")
|
input.IP = net.ParseIP("10.0.1.6")
|
||||||
dataString, err := generate.Userdata(generate.TypeControlPlane, input)
|
dataString, err := generate.Userdata(generate.TypeControlPlane, input)
|
||||||
@ -48,6 +64,13 @@ func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
|||||||
data := &userdata.UserData{}
|
data := &userdata.UserData{}
|
||||||
err = yaml.Unmarshal([]byte(dataString), data)
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::2")
|
||||||
|
dataString, err = generate.Userdata(generate.TypeControlPlane, inputv6)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
data = &userdata.UserData{}
|
||||||
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
|
suite.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
||||||
@ -56,9 +79,63 @@ func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
|||||||
data := &userdata.UserData{}
|
data := &userdata.UserData{}
|
||||||
err = yaml.Unmarshal([]byte(dataString), data)
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
dataString, err = generate.Userdata(generate.TypeJoin, inputv6)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
data = &userdata.UserData{}
|
||||||
|
err = yaml.Unmarshal([]byte(dataString), data)
|
||||||
|
suite.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GenerateSuite) TestGenerateTalosconfigSuccess() {
|
func (suite *GenerateSuite) TestGenerateTalosconfigSuccess() {
|
||||||
_, err := generate.Talosconfig(input)
|
_, err := generate.Talosconfig(input)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = generate.Talosconfig(inputv6)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenerateSuite) TestGetControlPlaneEndpoint() {
|
||||||
|
ep := input.GetControlPlaneEndpoint("6443")
|
||||||
|
suite.Require().Equal(input.MasterIPs[0]+":6443", ep)
|
||||||
|
|
||||||
|
ep = input.GetControlPlaneEndpoint("443")
|
||||||
|
suite.Require().Equal(input.MasterIPs[0]+":443", ep)
|
||||||
|
|
||||||
|
ep = inputv6.GetControlPlaneEndpoint("6443")
|
||||||
|
suite.Require().Equal(fmt.Sprintf("[%s]:6443", inputv6.MasterIPs[0]), ep)
|
||||||
|
|
||||||
|
ep = input.GetControlPlaneEndpoint("")
|
||||||
|
suite.Require().Equal(input.MasterIPs[0], ep)
|
||||||
|
|
||||||
|
ep = inputv6.GetControlPlaneEndpoint("")
|
||||||
|
suite.Require().Equal(fmt.Sprintf("[%s]", inputv6.MasterIPs[0]), ep)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::1")
|
||||||
|
inputv6.Index = 0
|
||||||
|
suite.Require().Equal(
|
||||||
|
fmt.Sprintf("[%s]", inputv6.MasterIPs[0]),
|
||||||
|
inputv6.GetControlPlaneEndpoint(""),
|
||||||
|
)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::2")
|
||||||
|
inputv6.Index = 1
|
||||||
|
suite.Require().Equal(
|
||||||
|
fmt.Sprintf("[%s]", inputv6.MasterIPs[0]),
|
||||||
|
inputv6.GetControlPlaneEndpoint(""),
|
||||||
|
)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::3")
|
||||||
|
inputv6.Index = 2
|
||||||
|
suite.Require().Equal(
|
||||||
|
fmt.Sprintf("[%s]", inputv6.MasterIPs[1]),
|
||||||
|
inputv6.GetControlPlaneEndpoint(""),
|
||||||
|
)
|
||||||
|
|
||||||
|
inputv6.IP = net.ParseIP("2001:db8::d")
|
||||||
|
inputv6.Index = 0
|
||||||
|
suite.Require().Equal(
|
||||||
|
fmt.Sprintf("[%s]", inputv6.MasterIPs[0]),
|
||||||
|
inputv6.GetControlPlaneEndpoint(""),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,9 @@ services:
|
|||||||
kind: ClusterConfiguration
|
kind: ClusterConfiguration
|
||||||
clusterName: {{ .ClusterName }}
|
clusterName: {{ .ClusterName }}
|
||||||
kubernetesVersion: {{ .KubernetesVersion }}
|
kubernetesVersion: {{ .KubernetesVersion }}
|
||||||
controlPlaneEndpoint: {{ .IP }}:443
|
controlPlaneEndpoint: "{{ .GetControlPlaneEndpoint "443" }}"
|
||||||
apiServer:
|
apiServer:
|
||||||
certSANs: [ {{ range $i,$ip := .MasterIPs }}{{if $i}},{{end}}"{{$ip}}"{{end}}, "127.0.0.1" ]
|
certSANs: [ {{ range $i,$ip := .MasterIPs }}{{if $i}},{{end}}"{{$ip}}"{{end}}, "127.0.0.1", "::1" ]
|
||||||
extraArgs:
|
extraArgs:
|
||||||
runtime-config: settings.k8s.io/v1alpha1=true
|
runtime-config: settings.k8s.io/v1alpha1=true
|
||||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||||
@ -51,8 +51,8 @@ services:
|
|||||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||||
networking:
|
networking:
|
||||||
dnsDomain: {{ .ServiceDomain }}
|
dnsDomain: {{ .ServiceDomain }}
|
||||||
podSubnet: {{ index .PodNet 0 }}
|
podSubnet: "{{ index .PodNet 0 }}"
|
||||||
serviceSubnet: {{ index .ServiceNet 0 }}
|
serviceSubnet: "{{ index .ServiceNet 0 }}"
|
||||||
---
|
---
|
||||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||||
kind: KubeletConfiguration
|
kind: KubeletConfiguration
|
||||||
@ -67,5 +67,5 @@ services:
|
|||||||
trustd:
|
trustd:
|
||||||
token: '{{ .TrustdInfo.Token }}'
|
token: '{{ .TrustdInfo.Token }}'
|
||||||
endpoints: [ {{ .Endpoints }} ]
|
endpoints: [ {{ .Endpoints }} ]
|
||||||
certSANs: [ "{{ .IP }}", "127.0.0.1" ]
|
certSANs: [ "{{ .IP }}", "127.0.0.1", "::1" ]
|
||||||
`
|
`
|
||||||
|
@ -18,7 +18,7 @@ services:
|
|||||||
bootstrapToken:
|
bootstrapToken:
|
||||||
token: '{{ .KubeadmTokens.BootstrapToken }}'
|
token: '{{ .KubeadmTokens.BootstrapToken }}'
|
||||||
unsafeSkipCAVerification: true
|
unsafeSkipCAVerification: true
|
||||||
apiServerEndpoint: {{ index .MasterIPs 0 }}:443
|
apiServerEndpoint: "{{ .GetControlPlaneEndpoint "443" }}"
|
||||||
nodeRegistration:
|
nodeRegistration:
|
||||||
taints: []
|
taints: []
|
||||||
kubeletExtraArgs:
|
kubeletExtraArgs:
|
||||||
|
@ -12,7 +12,7 @@ func Talosconfig(in *Input) (string, error) {
|
|||||||
const talosconfigTempl = `context: {{ .ClusterName }}
|
const talosconfigTempl = `context: {{ .ClusterName }}
|
||||||
contexts:
|
contexts:
|
||||||
{{ .ClusterName }}:
|
{{ .ClusterName }}:
|
||||||
target: {{ index .MasterIPs 0 }}
|
target: {{ .GetControlPlaneEndpoint "" }}
|
||||||
ca: {{ .Certs.OsCert }}
|
ca: {{ .Certs.OsCert }}
|
||||||
crt: {{ .Certs.AdminCert }}
|
crt: {{ .Certs.AdminCert }}
|
||||||
key: {{ .Certs.AdminKey }}
|
key: {{ .Certs.AdminKey }}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user