mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-30 02:51:11 +02:00
* refactor(init): Allow kubeadm init on controlplane This shifts the cluster formation from init(bootstrap) and join(control plane) to init(control plane). This makes use of the previously implemented initToken to provide a TTL for cluster initialization to take place and allows us to mostly treat all control plane nodes equal. This also sets up the path for us to handle master upgrades and not be concerned with odd behavior when upgrading the previously defined init node. To facilitate kubeadm init across all control plane nodes, we make use of the initToken to run `kubeadm init phase certs` command to generate any missing certificates once. All other control plane nodes will attempt to sync the necessary certs/files via all defined trustd endpoints and being the startup process. * feat(init): Add service runner context to PreFunc Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
266 lines
6.8 KiB
Go
266 lines
6.8 KiB
Go
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package generate
|
|
|
|
import (
|
|
"bytes"
|
|
stdlibx509 "crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"errors"
|
|
"math/rand"
|
|
"net"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
"github.com/talos-systems/talos/pkg/crypto/x509"
|
|
"github.com/talos-systems/talos/pkg/userdata/token"
|
|
)
|
|
|
|
// CertStrings holds the string representation of a certificate and key.
|
|
type CertStrings struct {
|
|
Crt string
|
|
Key string
|
|
}
|
|
|
|
// Input holds info about certs, ips, and node type.
|
|
type Input struct {
|
|
Certs *Certs
|
|
MasterIPs []string
|
|
Index int
|
|
ClusterName string
|
|
ServiceDomain string
|
|
PodNet []string
|
|
ServiceNet []string
|
|
Endpoints string
|
|
KubernetesVersion string
|
|
KubeadmTokens *KubeadmTokens
|
|
TrustdInfo *TrustdInfo
|
|
InitToken *token.Token
|
|
IP net.IP
|
|
}
|
|
|
|
// Certs holds the base64 encoded keys and certificates.
|
|
type Certs struct {
|
|
AdminCert string
|
|
AdminKey string
|
|
OsCert string
|
|
OsKey string
|
|
K8sCert string
|
|
K8sKey string
|
|
}
|
|
|
|
// KubeadmTokens holds the senesitve kubeadm data.
|
|
type KubeadmTokens struct {
|
|
BootstrapToken string
|
|
CertKey string
|
|
}
|
|
|
|
// TrustdInfo holds the trustd credentials.
|
|
type TrustdInfo struct {
|
|
Token string
|
|
}
|
|
|
|
// RandomString returns a string of length n.
|
|
func RandomString(n int) string {
|
|
var letter = []rune("abcdefghijklmnopqrstuvwxy0123456789")
|
|
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letter[rand.Intn(len(letter))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// NewInput generates the sensitive data required to generate all userdata
|
|
// types.
|
|
// nolint: gocyclo
|
|
func NewInput(clustername string, masterIPs []string) (input *Input, err error) {
|
|
kubeadmTokens := &KubeadmTokens{
|
|
BootstrapToken: RandomString(6) + "." + RandomString(16),
|
|
CertKey: RandomString(26),
|
|
}
|
|
|
|
trustdInfo := &TrustdInfo{
|
|
Token: RandomString(6) + "." + RandomString(16),
|
|
}
|
|
|
|
// Generate Kubernetes CA.
|
|
opts := []x509.Option{x509.RSA(true), x509.Organization("talos-k8s")}
|
|
k8sCert, err := x509.NewSelfSignedCertificateAuthority(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate Talos CA.
|
|
opts = []x509.Option{x509.RSA(false), x509.Organization("talos-os")}
|
|
osCert, err := x509.NewSelfSignedCertificateAuthority(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create the init token
|
|
tok, err := token.NewToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate the admin talosconfig.
|
|
adminKey, err := x509.NewKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pemBlock, _ := pem.Decode(adminKey.KeyPEM)
|
|
if pemBlock == nil {
|
|
return nil, errors.New("failed to decode admin key pem")
|
|
}
|
|
adminKeyEC, err := stdlibx509.ParseECPrivateKey(pemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ips := []net.IP{net.ParseIP("127.0.0.1")}
|
|
opts = []x509.Option{x509.IPAddresses(ips)}
|
|
csr, err := x509.NewCertificateSigningRequest(adminKeyEC, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
csrPemBlock, _ := pem.Decode(csr.X509CertificateRequestPEM)
|
|
if csrPemBlock == nil {
|
|
return nil, errors.New("failed to decode csr pem")
|
|
}
|
|
ccsr, err := stdlibx509.ParseCertificateRequest(csrPemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
caPemBlock, _ := pem.Decode(osCert.CrtPEM)
|
|
if caPemBlock == nil {
|
|
return nil, errors.New("failed to decode ca cert pem")
|
|
}
|
|
caCrt, err := stdlibx509.ParseCertificate(caPemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
caKeyPemBlock, _ := pem.Decode(osCert.KeyPEM)
|
|
if caKeyPemBlock == nil {
|
|
return nil, errors.New("failed to decode ca key pem")
|
|
}
|
|
caKey, err := stdlibx509.ParseECPrivateKey(caKeyPemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
adminCrt, err := x509.NewCertificateFromCSR(caCrt, caKey, ccsr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certs := &Certs{
|
|
AdminCert: base64.StdEncoding.EncodeToString(adminCrt.X509CertificatePEM),
|
|
AdminKey: base64.StdEncoding.EncodeToString(adminKey.KeyPEM),
|
|
OsCert: base64.StdEncoding.EncodeToString(osCert.CrtPEM),
|
|
OsKey: base64.StdEncoding.EncodeToString(osCert.KeyPEM),
|
|
K8sCert: base64.StdEncoding.EncodeToString(k8sCert.CrtPEM),
|
|
K8sKey: base64.StdEncoding.EncodeToString(k8sCert.KeyPEM),
|
|
}
|
|
|
|
input = &Input{
|
|
Certs: certs,
|
|
MasterIPs: masterIPs,
|
|
PodNet: []string{"10.244.0.0/16"},
|
|
ServiceNet: []string{"10.96.0.0/12"},
|
|
ServiceDomain: "cluster.local",
|
|
ClusterName: clustername,
|
|
Endpoints: strings.Join(masterIPs, ", "),
|
|
KubernetesVersion: constants.KubernetesVersion,
|
|
KubeadmTokens: kubeadmTokens,
|
|
TrustdInfo: trustdInfo,
|
|
InitToken: tok,
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// Type represents a userdata type.
|
|
type Type int
|
|
|
|
const (
|
|
// TypeInit indicates a userdata type should correspond to the kubeadm
|
|
// InitConfiguration type.
|
|
TypeInit Type = iota
|
|
// TypeControlPlane indicates a userdata type should correspond to the
|
|
// kubeadm JoinConfiguration type that has the ControlPlane field
|
|
// defined.
|
|
TypeControlPlane
|
|
// TypeJoin indicates a userdata type should correspond to the kubeadm
|
|
// JoinConfiguration type.
|
|
TypeJoin
|
|
)
|
|
|
|
// Sring returns the string representation of Type.
|
|
func (t Type) String() string {
|
|
return [...]string{"Init", "ControlPlane", "Join"}[t]
|
|
}
|
|
|
|
// Userdata returns the talos userdata for a given node type.
|
|
func Userdata(t Type, in *Input) (string, error) {
|
|
var template string
|
|
switch t {
|
|
case TypeInit:
|
|
template = initTempl
|
|
case TypeControlPlane:
|
|
template = controlPlaneTempl
|
|
case TypeJoin:
|
|
template = workerTempl
|
|
default:
|
|
return "", errors.New("failed to determine userdata type to generate")
|
|
}
|
|
|
|
var err error
|
|
var ud string
|
|
|
|
ud, err = renderTemplate(in, template)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// TODO: We cant implement this currently because of
|
|
// issues with kubeadm dependency mismatch between
|
|
// talos and clusterapi//kubebuilder.
|
|
// We should figure out way we can work around/through
|
|
// this
|
|
/*
|
|
// Create an actual userdata struct from the
|
|
// generated data so we can call validate
|
|
// and ensure we are providing proper data
|
|
data := &userdata.UserData{}
|
|
if err = yaml.Unmarshal([]byte(ud), data); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err = data.Validate(); err != nil {
|
|
return "", err
|
|
}
|
|
*/
|
|
|
|
return ud, nil
|
|
}
|
|
|
|
// renderTemplate will output a templated string.
|
|
func renderTemplate(in *Input, udTemplate string) (string, error) {
|
|
// So we can have a simple add func
|
|
funcs := template.FuncMap{"add": add}
|
|
|
|
templ := template.Must(template.New("udTemplate").Funcs(funcs).Parse(udTemplate))
|
|
var buf bytes.Buffer
|
|
if err := templ.Execute(&buf, in); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func add(a, b int) int {
|
|
return a + b
|
|
}
|