mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-21 14:41:12 +02:00
329 lines
8.3 KiB
Go
329 lines
8.3 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/rand"
|
|
stdlibx509 "crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"errors"
|
|
"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
|
|
}
|
|
|
|
// randBytes returns a random string consisting of the characters in
|
|
// validBootstrapTokenChars, with the length customized by the parameter
|
|
func randBytes(length int) (string, error) {
|
|
|
|
// validBootstrapTokenChars defines the characters a bootstrap token can consist of
|
|
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
|
|
// len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide
|
|
// the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we
|
|
// read that are >= 252 so the bytes we evenly divide the character set.
|
|
const maxByteValue = 252
|
|
|
|
var (
|
|
b byte
|
|
err error
|
|
token = make([]byte, length)
|
|
)
|
|
|
|
reader := bufio.NewReaderSize(rand.Reader, length*2)
|
|
for i := range token {
|
|
for {
|
|
if b, err = reader.ReadByte(); err != nil {
|
|
return "", err
|
|
}
|
|
if b < maxByteValue {
|
|
break
|
|
}
|
|
}
|
|
token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)]
|
|
}
|
|
|
|
return string(token), err
|
|
}
|
|
|
|
//genToken will generate a token of the format abc.123 (like kubeadm/trustd), where the length of the first string (before the dot)
|
|
//and length of the second string (after dot) are specified as inputs
|
|
func genToken(lenFirst int, lenSecond int) (string, error) {
|
|
|
|
var err error
|
|
var tokenTemp = make([]string, 2)
|
|
|
|
tokenTemp[0], err = randBytes(lenFirst)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
tokenTemp[1], err = randBytes(lenSecond)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return tokenTemp[0] + "." + tokenTemp[1], nil
|
|
}
|
|
|
|
// NewInput generates the sensitive data required to generate all userdata
|
|
// types.
|
|
// nolint: gocyclo
|
|
func NewInput(clustername string, masterIPs []string) (input *Input, err error) {
|
|
|
|
//Gen trustd token strings
|
|
kubeadmBootstrapToken, err := genToken(6, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//TODO: Can be dropped
|
|
//Gen kubeadm cert key
|
|
kubeadmCertKey, err := randBytes(26)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//Gen trustd token strings
|
|
trustdToken, err := genToken(6, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
kubeadmTokens := &KubeadmTokens{
|
|
BootstrapToken: kubeadmBootstrapToken,
|
|
CertKey: kubeadmCertKey,
|
|
}
|
|
|
|
trustdInfo := &TrustdInfo{
|
|
Token: trustdToken,
|
|
}
|
|
|
|
// 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
|
|
}
|