feat: add ability to generate userdata secrets (#581)
Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
2a4b56d4a1
commit
20662217a2
2
go.sum
2
go.sum
@ -157,8 +157,6 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
223
pkg/userdata/generate/generate.go
Normal file
223
pkg/userdata/generate/generate.go
Normal file
@ -0,0 +1,223 @@
|
||||
/* 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"
|
||||
"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 {
|
||||
Username string
|
||||
Password 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{
|
||||
Username: RandomString(14),
|
||||
Password: RandomString(24),
|
||||
}
|
||||
|
||||
// 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,
|
||||
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")
|
||||
}
|
||||
|
||||
ud, err := renderTemplate(in, template)
|
||||
if 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
|
||||
}
|
61
pkg/userdata/generate/generate_test.go
Normal file
61
pkg/userdata/generate/generate_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
/* 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
input *generate.Input
|
||||
)
|
||||
|
||||
type GenerateSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestGenerateSuite(t *testing.T) {
|
||||
suite.Run(t, new(GenerateSuite))
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) SetupSuite() {
|
||||
var err error
|
||||
input, err = generate.NewInput("test", []string{"10.0.1.5", "10.0.1.6", "10.0.1.7"})
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
||||
dataString, err := generate.Userdata(generate.TypeInit, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &userdata.UserData{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
||||
dataString, err := generate.Userdata(generate.TypeControlPlane, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &userdata.UserData{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
||||
dataString, err := generate.Userdata(generate.TypeJoin, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &userdata.UserData{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateTalosconfigSuccess() {
|
||||
_, err := generate.Talosconfig(input)
|
||||
suite.Require().NoError(err)
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/* 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"
|
||||
"errors"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
Type string // Valid values are init, controlplane, or worker.
|
||||
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 {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Userdata will return the talos userdata for a given node type.
|
||||
func Userdata(in *Input) (string, error) {
|
||||
templateData := ""
|
||||
switch udtype := in.Type; udtype {
|
||||
case "init":
|
||||
templateData = initTempl
|
||||
case "controlplane":
|
||||
templateData = controlPlaneTempl
|
||||
case "worker":
|
||||
templateData = workerTempl
|
||||
default:
|
||||
return "", errors.New("unable to determine userdata type to generate")
|
||||
}
|
||||
|
||||
ud, err := renderTemplate(in, templateData)
|
||||
if 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
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
/* 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
)
|
||||
|
||||
const (
|
||||
expectedInitConfig = `---
|
||||
version: ""
|
||||
security:
|
||||
os:
|
||||
ca:
|
||||
crt: ""
|
||||
key: ""
|
||||
kubernetes:
|
||||
ca:
|
||||
crt: ""
|
||||
key: ""
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
certificateKey: 'testcrtkey'
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: InitConfiguration
|
||||
apiEndpoint:
|
||||
advertiseAddress: 10.0.1.5
|
||||
bindPort: 6443
|
||||
bootstrapTokens:
|
||||
- token: 'abcdef.1234567890123456789'
|
||||
ttl: 0s
|
||||
nodeRegistration:
|
||||
taints: []
|
||||
kubeletExtraArgs:
|
||||
node-labels: ""
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
---
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: ClusterConfiguration
|
||||
clusterName: test
|
||||
controlPlaneEndpoint: 10.0.1.5
|
||||
apiServer:
|
||||
certSANs: [ "10.0.1.5","10.0.1.6","10.0.1.7" ]
|
||||
extraArgs:
|
||||
runtime-config: settings.k8s.io/v1alpha1=true
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
controllerManager:
|
||||
extraArgs:
|
||||
terminated-pod-gc-threshold: '100'
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
scheduler:
|
||||
extraArgs:
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
networking:
|
||||
dnsDomain: cluster.local
|
||||
podSubnet: 10.244.0.0/16
|
||||
serviceSubnet: 10.96.0.0/12
|
||||
---
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
mode: ipvs
|
||||
ipvs:
|
||||
scheduler: lc
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ ]
|
||||
certSANs: [ "10.0.1.5" ]
|
||||
`
|
||||
|
||||
expectedControlPlaneConfig = `---
|
||||
version: ""
|
||||
security: null
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
certificateKey: 'testcrtkey'
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: JoinConfiguration
|
||||
controlPlane:
|
||||
apiEndpoint:
|
||||
advertiseAddress: 10.0.1.5
|
||||
bindPort: 6443
|
||||
discovery:
|
||||
bootstrapToken:
|
||||
token: 'abcdef.1234567890123456789'
|
||||
unsafeSkipCAVerification: true
|
||||
apiServerEndpoint: 10.0.1.5:443
|
||||
nodeRegistration:
|
||||
taints: []
|
||||
kubeletExtraArgs:
|
||||
node-labels: ""
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ "10.0.1.5" ]
|
||||
bootstrapNode: "10.0.1.5"
|
||||
certSANs: [ "10.0.1.5" ]
|
||||
`
|
||||
|
||||
expectedWorkerConfig = `---
|
||||
version: ""
|
||||
security: null
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: JoinConfiguration
|
||||
discovery:
|
||||
bootstrapToken:
|
||||
token: 'abcdef.1234567890123456789'
|
||||
unsafeSkipCAVerification: true
|
||||
apiServerEndpoint: 10.0.1.5:443
|
||||
nodeRegistration:
|
||||
taints: []
|
||||
kubeletExtraArgs:
|
||||
node-labels: ""
|
||||
feature-gates: ExperimentalCriticalPodAnnotation=true
|
||||
token: 'abcdef.1234567890123456789'
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ "10.0.1.5","10.0.1.6","10.0.1.7" ]
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
input = generate.Input{
|
||||
Certs: &generate.Certs{},
|
||||
MasterIPs: []string{"10.0.1.5", "10.0.1.6", "10.0.1.7"},
|
||||
PodNet: []string{"10.244.0.0/16"},
|
||||
ServiceNet: []string{"10.96.0.0/12"},
|
||||
ServiceDomain: "cluster.local",
|
||||
ClusterName: "test",
|
||||
KubeadmTokens: &generate.KubeadmTokens{
|
||||
BootstrapToken: "abcdef.1234567890123456789",
|
||||
CertKey: "testcrtkey",
|
||||
},
|
||||
TrustdInfo: &generate.TrustdInfo{
|
||||
Username: "test",
|
||||
Password: "test",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type GenerateSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestGenerateSuite(t *testing.T) {
|
||||
suite.Run(t, new(GenerateSuite))
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
||||
i := input
|
||||
i.Type = "init"
|
||||
userdata, err := generate.Userdata(&i)
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(userdata, expectedInitConfig)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
||||
i := input
|
||||
i.Type = "controlplane"
|
||||
userdata, err := generate.Userdata(&i)
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(userdata, expectedControlPlaneConfig)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
||||
i := input
|
||||
i.Type = "worker"
|
||||
userdata, err := generate.Userdata(&i)
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(userdata, expectedWorkerConfig)
|
||||
}
|
Loading…
Reference in New Issue
Block a user