feat(init): Add initToken parameter to userdata (#664)

Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
This commit is contained in:
Brad Beam
2019-05-20 14:23:38 -05:00
committed by GitHub
parent bbbd1f70d1
commit a64de7ed51
15 changed files with 156 additions and 86 deletions

View File

@@ -7,6 +7,7 @@ package cmd
import (
stdlibx509 "crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"path"
@@ -16,6 +17,7 @@ import (
"github.com/spf13/cobra"
"github.com/talos-systems/talos/cmd/osctl/pkg/helpers"
"github.com/talos-systems/talos/pkg/crypto/x509"
"github.com/talos-systems/talos/pkg/userdata/token"
)
// genCmd represents the gen command
@@ -39,13 +41,13 @@ var caCmd = &cobra.Command{
if err != nil {
helpers.Fatalf("error generating CA: %s", err)
}
if err := ioutil.WriteFile(organization+".crt", ca.CrtPEM, 0400); err != nil {
if err := ioutil.WriteFile(organization+".crt", ca.CrtPEM, 0600); err != nil {
helpers.Fatalf("error writing CA certificate: %s", err)
}
if err := ioutil.WriteFile(organization+".sha256", []byte(x509.Hash(ca.Crt)), 0400); err != nil {
if err := ioutil.WriteFile(organization+".sha256", []byte(x509.Hash(ca.Crt)), 0600); err != nil {
helpers.Fatalf("error writing certificate hash: %s", err)
}
if err := ioutil.WriteFile(organization+".key", ca.KeyPEM, 0400); err != nil {
if err := ioutil.WriteFile(organization+".key", ca.KeyPEM, 0600); err != nil {
helpers.Fatalf("error writing key: %s", err)
}
},
@@ -61,7 +63,7 @@ var keyCmd = &cobra.Command{
if err != nil {
helpers.Fatalf("error generating key: %s", err)
}
if err := ioutil.WriteFile(name+".key", key.KeyPEM, 0400); err != nil {
if err := ioutil.WriteFile(name+".key", key.KeyPEM, 0600); err != nil {
helpers.Fatalf("error writing key: %s", err)
}
},
@@ -97,7 +99,7 @@ var csrCmd = &cobra.Command{
if err != nil {
helpers.Fatalf("error generating CSR: %s", err)
}
if err := ioutil.WriteFile(strings.TrimSuffix(key, path.Ext(key))+".csr", csr.X509CertificateRequestPEM, 0400); err != nil {
if err := ioutil.WriteFile(strings.TrimSuffix(key, path.Ext(key))+".csr", csr.X509CertificateRequestPEM, 0600); err != nil {
helpers.Fatalf("error writing CSR: %s", err)
}
},
@@ -149,7 +151,7 @@ var crtCmd = &cobra.Command{
if err != nil {
helpers.Fatalf("error signing certificate: %s", err)
}
if err := ioutil.WriteFile(name+".crt", signedCrt.X509CertificatePEM, 0400); err != nil {
if err := ioutil.WriteFile(name+".crt", signedCrt.X509CertificatePEM, 0600); err != nil {
helpers.Fatalf("error writing certificate: %s", err)
}
},
@@ -177,15 +179,29 @@ var keypairCmd = &cobra.Command{
if err != nil {
helpers.Fatalf("error generating CA: %s", err)
}
if err := ioutil.WriteFile(organization+".crt", ca.CrtPEM, 0400); err != nil {
if err := ioutil.WriteFile(organization+".crt", ca.CrtPEM, 0600); err != nil {
helpers.Fatalf("error writing certificate: %s", err)
}
if err := ioutil.WriteFile(organization+".key", ca.KeyPEM, 0400); err != nil {
if err := ioutil.WriteFile(organization+".key", ca.KeyPEM, 0600); err != nil {
helpers.Fatalf("error writing key: %s", err)
}
},
}
// inittoken represents the gen token command
var inittokenCmd = &cobra.Command{
Use: "token",
Short: "Generates a UUIDv1 token",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
initToken, err := token.NewToken()
if err != nil {
helpers.Fatalf("Failed to generate new init token: %s", err)
}
fmt.Println(initToken.String())
},
}
func init() {
// Certificate Authorities
caCmd.Flags().StringVar(&organization, "organization", "", "X.509 distinguished name for the Organization")
@@ -213,6 +229,6 @@ func init() {
csrCmd.Flags().StringVar(&ip, "ip", "", "generate the certificate for this IP address")
helpers.Should(cobra.MarkFlagRequired(csrCmd.Flags(), "ip"))
genCmd.AddCommand(caCmd, keypairCmd, keyCmd, csrCmd, crtCmd)
genCmd.AddCommand(caCmd, keypairCmd, keyCmd, csrCmd, crtCmd, inittokenCmd)
rootCmd.AddCommand(genCmd)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/talos-systems/talos/cmd/osctl/pkg/helpers"
"github.com/talos-systems/talos/pkg/crypto/x509"
"github.com/talos-systems/talos/pkg/userdata"
"github.com/talos-systems/talos/pkg/userdata/token"
yaml "gopkg.in/yaml.v2"
)
@@ -61,6 +62,19 @@ var injectKubernetesCmd = &cobra.Command{
},
}
// injectTokenCmd represents the inject token command
// nolint: dupl
var injectTokenCmd = &cobra.Command{
Use: "token",
Short: "inject token data.",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := inject(args, "", "", injectTokenData); err != nil {
helpers.Fatalf("%s", err)
}
},
}
// nolint: dupl
func injectOSData(u *userdata.UserData, crt, key string) (err error) {
if u.Security == nil {
@@ -89,6 +103,21 @@ func injectIdentityData(u *userdata.UserData, crt, key string) (err error) {
return nil
}
// nolint: dupl
func injectTokenData(u *userdata.UserData, crt, key string) (err error) {
if u.Services.Kubeadm == nil {
helpers.Fatalf("[services.kubeadm] must be defined in userdata")
}
tok, err := token.NewToken()
if err != nil {
return
}
u.Services.Kubeadm.Token = tok
return nil
}
// nolint: dupl
func injectKubernetesData(u *userdata.UserData, crt, key string) (err error) {
if u.Security == nil {
@@ -142,10 +171,20 @@ func newSecurity() *userdata.Security {
}
func init() {
injectCmd.PersistentFlags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
helpers.Should(injectCmd.MarkPersistentFlagRequired("crt"))
injectCmd.PersistentFlags().StringVar(&key, "key", "", "the path to the PKI key")
helpers.Should(injectCmd.MarkPersistentFlagRequired("key"))
injectCmd.AddCommand(injectOSCmd, injectIdentityCmd, injectKubernetesCmd)
injectOSCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
injectIdentityCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
injectKubernetesCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
helpers.Should(injectOSCmd.MarkFlagRequired("crt"))
helpers.Should(injectIdentityCmd.MarkFlagRequired("crt"))
helpers.Should(injectKubernetesCmd.MarkFlagRequired("crt"))
injectOSCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
injectIdentityCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
injectKubernetesCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
helpers.Should(injectOSCmd.MarkFlagRequired("key"))
helpers.Should(injectIdentityCmd.MarkFlagRequired("key"))
helpers.Should(injectKubernetesCmd.MarkFlagRequired("key"))
injectCmd.AddCommand(injectOSCmd, injectIdentityCmd, injectKubernetesCmd, injectTokenCmd)
rootCmd.AddCommand(injectCmd)
}

View File

@@ -19,6 +19,7 @@ up: talosconfig
down:
@$(DOCKER_COMPOSE) down -v $(SERVICES)
.PHONY: talosconfig
talosconfig:
@mkdir -p pki
@./gen.sh $(IP_ADDR)

View File

@@ -47,6 +47,9 @@ ${OSCTL} inject os \
--key talos.key \
../userdata/master-1.yaml
# Inject bootstrap/init token
echo "Injecting init token"
${OSCTL} inject token ../userdata/master-1.yaml
# Inject Kubernetes PKI

View File

@@ -36,4 +36,7 @@ services:
trustd:
username: 'dev'
password: 'talos_trust_dev'
endpoints:
- 10.5.0.7
- 10.5.0.8
debug: true

View File

@@ -32,7 +32,6 @@ services:
password: talos_trust_dev
endpoints:
- 10.5.0.6
- 10.5.0.7
bootstrapNode: 10.5.0.6
proxyd: null
osd: null

View File

@@ -91,7 +91,7 @@ func Prepare(s string, inContainer bool, data *userdata.UserData) (err error) {
func generatePKI(data *userdata.UserData) (err error) {
log.Println("generating node identity PKI")
if data.IsBootstrap() {
if data.Services.Kubeadm.IsBootstrap() {
log.Println("generating PKI locally")
var csr *x509.CertificateSigningRequest
if csr, err = data.NewIdentityCSR(); err != nil {

View File

@@ -308,7 +308,7 @@ func startSystemServices(startupErrCh chan<- error, data *userdata.UserData) {
},
},
}
if !data.IsWorker() {
if data.Services.Kubeadm.IsControlPlane() {
masterReqs := []*ctrdrunner.ImportRequest{
{
Path: "/usr/images/proxyd.tar",
@@ -338,7 +338,7 @@ func startSystemServices(startupErrCh chan<- error, data *userdata.UserData) {
&services.NTPd{},
)
// Start the services common to all master nodes.
if data.IsMaster() {
if data.Services.Kubeadm.IsControlPlane() {
svcs.Start(
&services.Trustd{},
&services.Proxyd{},
@@ -374,7 +374,7 @@ func startKubernetesServices(startupErrCh chan<- error, data *userdata.UserData)
Path: "/usr/images/pause.tar",
},
}
if !data.IsWorker() {
if data.Services.Kubeadm.IsControlPlane() {
reqs = append(reqs, &ctrdrunner.ImportRequest{Path: "/usr/images/etcd.tar"})
}
if err := ctrdrunner.Import(criconstants.K8sContainerdNamespace, reqs...); err != nil {

View File

@@ -50,7 +50,7 @@ func (k *Kubeadm) PreFunc(data *userdata.UserData) (err error) {
return err
}
if data.IsBootstrap() {
if data.Services.Kubeadm.IsBootstrap() {
// Write out all certs we've been provided
certs := []struct {
Cert *x509.PEMEncodedCertificateAndKey
@@ -87,7 +87,7 @@ func (k *Kubeadm) PreFunc(data *userdata.UserData) (err error) {
return err
}
}
} else if data.IsControlPlane() {
} else if data.Services.Kubeadm.IsControlPlane() {
if data.Services.Trustd == nil || data.Services.Trustd.BootstrapNode == "" {
return nil
}
@@ -156,7 +156,7 @@ func (k *Kubeadm) Runner(data *userdata.UserData) (runner.Runner, error) {
certificateKey := "--certificate-key=" + encoded
switch {
case data.IsBootstrap():
case data.Services.Kubeadm.IsBootstrap():
args.ProcessArgs = []string{
"kubeadm",
"init",
@@ -167,7 +167,7 @@ func (k *Kubeadm) Runner(data *userdata.UserData) (runner.Runner, error) {
"--skip-certificate-key-print",
"--experimental-upload-certs",
}
case data.IsControlPlane():
case data.Services.Kubeadm.IsControlPlane():
args.ProcessArgs = []string{
"kubeadm",
"join",
@@ -228,7 +228,7 @@ func enforceMasterOverrides(initConfiguration *kubeadmapi.InitConfiguration) {
func writeKubeadmConfig(data *userdata.UserData) (err error) {
var b []byte
if data.IsBootstrap() {
if data.Services.Kubeadm.IsBootstrap() {
initConfiguration, ok := data.Services.Kubeadm.Configuration.(*kubeadmapi.InitConfiguration)
if !ok {
return errors.New("expected InitConfiguration")

View File

@@ -17,6 +17,7 @@ import (
"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.
@@ -38,6 +39,7 @@ type Input struct {
KubernetesVersion string
KubeadmTokens *KubeadmTokens
TrustdInfo *TrustdInfo
InitToken *token.Token
}
// Certs holds the base64 encoded keys and certificates.
@@ -99,6 +101,12 @@ func NewInput(clustername string, masterIPs []string) (input *Input, err error)
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 {
@@ -167,6 +175,7 @@ func NewInput(clustername string, masterIPs []string) (input *Input, err error)
KubernetesVersion: constants.KubernetesVersion,
KubeadmTokens: kubeadmTokens,
TrustdInfo: trustdInfo,
InitToken: tok,
}
return input, nil

View File

@@ -19,6 +19,7 @@ services:
init:
cni: flannel
kubeadm:
initToken: {{ .InitToken }}
certificateKey: '{{ .KubeadmTokens.CertKey }}'
configuration: |
apiVersion: kubeadm.k8s.io/v1beta1

View File

@@ -7,6 +7,7 @@ package userdata
import (
"errors"
"github.com/talos-systems/talos/pkg/userdata/token"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@@ -23,10 +24,10 @@ type Kubeadm struct {
Configuration runtime.Object `yaml:"-"`
ConfigurationStr string `yaml:"configuration"`
ExtraArgs []string `yaml:"extraArgs,omitempty"`
CertificateKey string `yaml:"certificateKey,omitempty"`
IgnorePreflightErrors []string `yaml:"ignorePreflightErrors,omitempty"`
bootstrap bool
ExtraArgs []string `yaml:"extraArgs,omitempty"`
CertificateKey string `yaml:"certificateKey,omitempty"`
IgnorePreflightErrors []string `yaml:"ignorePreflightErrors,omitempty"`
Token *token.Token `yaml:"initToken,omitempty"`
controlPlane bool
}
@@ -37,18 +38,6 @@ func (kdm *Kubeadm) MarshalYAML() (interface{}, error) {
return nil, err
}
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
if err != nil {
return nil, err
}
if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) {
kdm.bootstrap = true
}
if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
kdm.bootstrap = false
}
kdm.ConfigurationStr = string(b)
type KubeadmAlias Kubeadm
@@ -82,7 +71,7 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
kdm.Configuration = cfg
kdm.bootstrap = true
kdm.controlPlane = true
}
if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
cfg, err := kubeadmutil.UnmarshalFromYamlForCodecs(b, kubeadmapi.SchemeGroupVersion, kubeadmscheme.Codecs)
@@ -90,7 +79,6 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
kdm.Configuration = cfg
kdm.bootstrap = false
joinConfiguration, ok := cfg.(*kubeadm.JoinConfiguration)
if !ok {
return errors.New("expected JoinConfiguration")
@@ -107,3 +95,21 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// IsControlPlane indicates if the current kubeadm configuration is a worker
// acting as a master.
func (kdm *Kubeadm) IsControlPlane() bool {
return kdm.controlPlane
}
// IsBootstrap indicates if the current kubeadm configuration is a master init
// configuration.
func (kdm *Kubeadm) IsBootstrap() bool {
return kdm.Token != nil && kdm.IsControlPlane() && !kdm.Token.Expired()
}
// IsWorker indicates if the current kubeadm configuration is a worker
// configuration.
func (kdm *Kubeadm) IsWorker() bool {
return !kdm.IsControlPlane()
}

View File

@@ -34,7 +34,7 @@ func NewToken() (*Token, error) {
// FromString returns a token parsed from a string.
func FromString(input string) (*Token, error) {
uuid, err := uuid.FromBytes([]byte(input))
uuid, err := uuid.Parse(input)
if err != nil {
return nil, err
}
@@ -57,3 +57,32 @@ func (t *Token) Expired() bool {
return t2-t1 < BootstrapTTL
}
func (t *Token) String() string {
return t.uuid.String()
}
// UnmarshalYAML implements the unmarshaller interface so we can
// represent a UUID v1 token as a string.
func (t *Token) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stoken string
if err := unmarshal(&stoken); err != nil {
return err
}
token, err := FromString(stoken)
if err != nil {
return err
}
*t = *token
return nil
}
// MarshalYAML implements the marshaller interface so we can
// represent a UUID v1 token as a string.
func (t *Token) MarshalYAML() (interface{}, error) {
return t.uuid.String(), nil
}

View File

@@ -43,20 +43,6 @@ type UserData struct {
func (data *UserData) Validate() error {
var result *multierror.Error
var nodeType string
switch {
case data.IsBootstrap():
nodeType = "init"
case data.IsMaster():
nodeType = "master"
case data.IsWorker():
nodeType = "worker"
default:
// TODO make an error
return result.ErrorOrNil()
}
// All nodeType checks
result = multierror.Append(result, data.Services.Validate(CheckServices()))
result = multierror.Append(result, data.Services.Trustd.Validate(CheckTrustdAuth(), CheckTrustdEndpointsAreValidIPs()))
@@ -69,13 +55,13 @@ func (data *UserData) Validate() error {
}
}
switch nodeType {
case "init":
switch {
case data.Services.Kubeadm.IsBootstrap():
result = multierror.Append(result, data.Security.OS.Validate(CheckOSCA()))
result = multierror.Append(result, data.Security.Kubernetes.Validate(CheckKubernetesCA()))
case "master":
case data.Services.Kubeadm.IsControlPlane():
result = multierror.Append(result, data.Services.Trustd.Validate(CheckTrustdEndpointsArePresent()))
case "worker":
case data.Services.Kubeadm.IsWorker():
result = multierror.Append(result, data.Services.Trustd.Validate(CheckTrustdEndpointsArePresent()))
}
@@ -122,30 +108,6 @@ func (data *UserData) WriteFiles() (err error) {
return nil
}
// IsBootstrap indicates if the current kubeadm configuration is a master init
// configuration.
func (data *UserData) IsBootstrap() bool {
return data.Services.Kubeadm.bootstrap
}
// IsControlPlane indicates if the current kubeadm configuration is a worker
// acting as a master.
func (data *UserData) IsControlPlane() bool {
return data.Services.Kubeadm.controlPlane
}
// IsMaster indicates if the current kubeadm configuration is a master
// configuration.
func (data *UserData) IsMaster() bool {
return data.Services.Kubeadm.bootstrap || data.Services.Kubeadm.controlPlane
}
// IsWorker indicates if the current kubeadm configuration is a worker
// configuration.
func (data *UserData) IsWorker() bool {
return !data.IsMaster()
}
// NewIdentityCSR creates a new CSR for the node's identity certificate.
func (data *UserData) NewIdentityCSR() (csr *x509.CertificateSigningRequest, err error) {
var key *x509.Key

View File

@@ -56,7 +56,7 @@ func testUDServer() *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
count++
log.Printf("Request %d\n", count)
if count == 4 {
if count == 3 {
// nolint: errcheck
w.Write([]byte(testConfig))
return
@@ -97,6 +97,7 @@ services:
init:
cni: flannel
kubeadm:
initToken: 528d1ad6-3485-49ad-94cd-0f44a35877ac
certificateKey: 'test'
configuration: |
apiVersion: kubeadm.k8s.io/v1beta1
@@ -225,4 +226,5 @@ const kubeadmConfig = `configuration: |
networkName: ""
sourceVip: ""
certificateKey: test
initToken: 528d1ad6-3485-49ad-94cd-0f44a35877ac
`