talos/pkg/userdata/generate/generate.go
Brad Beam d8249c8779
refactor(init): Allow kubeadm init on controlplane (#658)
* 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>
2019-05-24 16:05:49 -05:00

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
}