talos/pkg/userdata/generate/generate.go
Brad Beam a5d31d97ff
feat: Validate userdata (#593)
* feat: Validate userdata

Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
2019-05-02 13:10:16 -05:00

246 lines
6.2 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/pkg/crypto/x509"
)
// 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
KubeadmTokens *KubeadmTokens
TrustdInfo *TrustdInfo
}
// 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
}
// 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[1:], ", "),
KubeadmTokens: kubeadmTokens,
TrustdInfo: trustdInfo,
}
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) {
templ := template.Must(template.New("udTemplate").Parse(udTemplate))
var buf bytes.Buffer
if err := templ.Execute(&buf, in); err != nil {
return "", err
}
return buf.String(), nil
}