Use Certificate Requests to issue Kubelet Certificates and set CA (#261)

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2022-07-15 09:33:11 +02:00 committed by GitHub
parent 49e98286a9
commit c6ff34f4d2
13 changed files with 451 additions and 159 deletions

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/edgelesssys/constellation/bootstrapper/internal/diskencryption" "github.com/edgelesssys/constellation/bootstrapper/internal/diskencryption"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubelet"
"github.com/edgelesssys/constellation/bootstrapper/nodestate" "github.com/edgelesssys/constellation/bootstrapper/nodestate"
"github.com/edgelesssys/constellation/bootstrapper/role" "github.com/edgelesssys/constellation/bootstrapper/role"
"github.com/edgelesssys/constellation/internal/cloud/metadata" "github.com/edgelesssys/constellation/internal/cloud/metadata"
@ -39,6 +40,7 @@ type JoinClient struct {
diskUUID string diskUUID string
nodeName string nodeName string
role role.Role role role.Role
validIPs []net.IP
disk encryptedDisk disk encryptedDisk
fileHandler file.Handler fileHandler file.Handler
@ -190,6 +192,11 @@ func (c *JoinClient) join(serviceEndpoint string) error {
ctx, cancel := c.timeoutCtx() ctx, cancel := c.timeoutCtx()
defer cancel() defer cancel()
certificateRequest, kubeletKey, err := kubelet.GetCertificateRequest(c.nodeName, c.validIPs)
if err != nil {
return err
}
conn, err := c.dialer.Dial(ctx, serviceEndpoint) conn, err := c.dialer.Dial(ctx, serviceEndpoint)
if err != nil { if err != nil {
c.log.With(zap.String("endpoint", serviceEndpoint), zap.Error(err)).Errorf("Join service unreachable") c.log.With(zap.String("endpoint", serviceEndpoint), zap.Error(err)).Errorf("Join service unreachable")
@ -200,7 +207,7 @@ func (c *JoinClient) join(serviceEndpoint string) error {
protoClient := joinproto.NewAPIClient(conn) protoClient := joinproto.NewAPIClient(conn)
req := &joinproto.IssueJoinTicketRequest{ req := &joinproto.IssueJoinTicketRequest{
DiskUuid: c.diskUUID, DiskUuid: c.diskUUID,
NodeName: c.nodeName, CertificateRequest: certificateRequest,
IsControlPlane: c.role == role.ControlPlane, IsControlPlane: c.role == role.ControlPlane,
} }
ticket, err := protoClient.IssueJoinTicket(ctx, req) ticket, err := protoClient.IssueJoinTicket(ctx, req)
@ -209,10 +216,10 @@ func (c *JoinClient) join(serviceEndpoint string) error {
return fmt.Errorf("issuing join ticket: %w", err) return fmt.Errorf("issuing join ticket: %w", err)
} }
return c.startNodeAndJoin(ticket) return c.startNodeAndJoin(ticket, kubeletKey)
} }
func (c *JoinClient) startNodeAndJoin(ticket *joinproto.IssueJoinTicketResponse) (retErr error) { func (c *JoinClient) startNodeAndJoin(ticket *joinproto.IssueJoinTicketResponse, kubeletKey []byte) (retErr error) {
ctx, cancel := context.WithTimeout(context.Background(), c.joinTimeout) ctx, cancel := context.WithTimeout(context.Background(), c.joinTimeout)
defer cancel() defer cancel()
@ -244,6 +251,12 @@ func (c *JoinClient) startNodeAndJoin(ticket *joinproto.IssueJoinTicketResponse)
return fmt.Errorf("writing control plane files: %w", err) return fmt.Errorf("writing control plane files: %w", err)
} }
} }
if err := c.fileHandler.Write(kubelet.CertificateFilename, ticket.KubeletCert, file.OptMkdirAll); err != nil {
return fmt.Errorf("writing kubelet certificate: %w", err)
}
if err := c.fileHandler.Write(kubelet.KeyFilename, kubeletKey, file.OptMkdirAll); err != nil {
return fmt.Errorf("writing kubelet key: %w", err)
}
state := nodestate.NodeState{ state := nodestate.NodeState{
Role: c.role, Role: c.role,
@ -285,8 +298,17 @@ func (c *JoinClient) getNodeMetadata() error {
return errors.New("got instance metadata with unknown role") return errors.New("got instance metadata with unknown role")
} }
var ips []net.IP
for _, ip := range inst.PrivateIPs {
ips = append(ips, net.ParseIP(ip))
}
for _, ip := range inst.PublicIPs {
ips = append(ips, net.ParseIP(ip))
}
c.nodeName = inst.Name c.nodeName = inst.Name
c.role = inst.Role c.role = inst.Role
c.validIPs = ips
return nil return nil
} }

View file

@ -0,0 +1,49 @@
package kubelet
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"net"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
const (
// CertificateFilename is the path to the kubelets certificate.
CertificateFilename = "/run/state/kubelet/pki/kubelet-client-crt.pem"
// KeyFilename is the path to the kubelets private key.
KeyFilename = "/run/state/kubelet/pki/kubelet-client-key.pem"
)
// GetCertificateRequest returns a certificate request and macthing private key for the kubelet.
func GetCertificateRequest(nodeName string, ips []net.IP) (certificateRequest []byte, privateKey []byte, err error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
keyBytes, err := x509.MarshalECPrivateKey(privK)
if err != nil {
return nil, nil, err
}
kubeletKey := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
})
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{constants.NodesGroup},
CommonName: constants.NodesUserPrefix + nodeName,
},
IPAddresses: ips,
}
certificateRequest, err = x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
if err != nil {
return nil, nil, err
}
return certificateRequest, kubeletKey, nil
}

View file

@ -3,12 +3,14 @@ package k8sapi
import ( import (
"path/filepath" "path/filepath"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubelet"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconf "k8s.io/kubelet/config/v1beta1" kubeletconf "k8s.io/kubelet/config/v1beta1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
// Uses types defined here: https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta3/ // Uses types defined here: https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta3/
@ -62,6 +64,11 @@ func (c *CoreOSConfiguration) InitConfiguration(externalCloudProvider bool) Kube
"audit-log-maxbackup": "10", // CIS benchmark - Default value of Rancher "audit-log-maxbackup": "10", // CIS benchmark - Default value of Rancher
"audit-log-maxsize": "100", // CIS benchmark - Default value of Rancher "audit-log-maxsize": "100", // CIS benchmark - Default value of Rancher
"profiling": "false", // CIS benchmark "profiling": "false", // CIS benchmark
"kubelet-certificate-authority": filepath.Join(
kubeconstants.KubernetesDir,
kubeconstants.DefaultCertificateDir,
kubeconstants.CACertName,
),
"tls-cipher-suites": "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256," + "tls-cipher-suites": "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256," +
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
@ -134,6 +141,8 @@ func (c *CoreOSConfiguration) InitConfiguration(externalCloudProvider bool) Kube
Effect: corev1.TaintEffectPreferNoSchedule, Effect: corev1.TaintEffectPreferNoSchedule,
}, },
}, },
TLSCertFile: kubelet.CertificateFilename,
TLSPrivateKeyFile: kubelet.KeyFilename,
}, },
} }
} }

View file

@ -2,18 +2,28 @@ package k8sapi
import ( import (
"context" "context"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubelet"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/bootstrapper/util"
"github.com/edgelesssys/constellation/internal/file"
"github.com/edgelesssys/constellation/internal/logger" "github.com/edgelesssys/constellation/internal/logger"
"github.com/spf13/afero"
"go.uber.org/zap" "go.uber.org/zap"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
const ( const (
@ -35,12 +45,14 @@ type Client interface {
// KubernetesUtil provides low level management of the kubernetes cluster. // KubernetesUtil provides low level management of the kubernetes cluster.
type KubernetesUtil struct { type KubernetesUtil struct {
inst installer inst installer
file file.Handler
} }
// NewKubernetesUtil creates a new KubernetesUtil. // NewKubernetesUtil creates a new KubernetesUtil.
func NewKubernetesUtil() *KubernetesUtil { func NewKubernetesUtil() *KubernetesUtil {
return &KubernetesUtil{ return &KubernetesUtil{
inst: newOSInstaller(), inst: newOSInstaller(),
file: file.NewHandler(afero.NewOsFs()),
} }
} }
@ -58,7 +70,9 @@ func (k *KubernetesUtil) InstallComponents(ctx context.Context, version string)
return enableSystemdUnit(ctx, kubeletServiceEtcPath) return enableSystemdUnit(ctx, kubeletServiceEtcPath)
} }
func (k *KubernetesUtil) InitCluster(ctx context.Context, initConfig []byte, log *logger.Logger) error { func (k *KubernetesUtil) InitCluster(
ctx context.Context, initConfig []byte, nodeName string, ips []net.IP, log *logger.Logger,
) error {
// TODO: audit policy should be user input // TODO: audit policy should be user input
auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal() auditPolicy, err := resources.NewDefaultAuditPolicy().Marshal()
if err != nil { if err != nil {
@ -77,8 +91,40 @@ func (k *KubernetesUtil) InitCluster(ctx context.Context, initConfig []byte, log
return fmt.Errorf("writing kubeadm init yaml config %v: %w", initConfigFile.Name(), err) return fmt.Errorf("writing kubeadm init yaml config %v: %w", initConfigFile.Name(), err)
} }
cmd := exec.CommandContext(ctx, kubeadmPath, "init", "-v=5", "--config", initConfigFile.Name()) // preflight
log.Infof("Running kubeadm preflight checks")
cmd := exec.CommandContext(ctx, kubeadmPath, "init", "phase", "preflight", "-v=5", "--config", initConfigFile.Name())
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return fmt.Errorf("kubeadm init phase preflight failed (code %v) with: %s", exitErr.ExitCode(), out)
}
return fmt.Errorf("kubeadm init: %w", err)
}
// create CA certs
log.Infof("Creating Kubernetes control-plane certificates and keys")
cmd = exec.CommandContext(ctx, kubeadmPath, "init", "phase", "certs", "all", "-v=5", "--config", initConfigFile.Name())
out, err = cmd.CombinedOutput()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return fmt.Errorf("kubeadm init phase certs all failed (code %v) with: %s", exitErr.ExitCode(), out)
}
return fmt.Errorf("kubeadm init: %w", err)
}
// create kubelet key and CA signed certificate for the node
log.Infof("Creating signed kubelet certificate")
if err := k.createSignedKubeletCert(nodeName, ips); err != nil {
return err
}
// initialize the cluster
log.Infof("Initializing the cluster using kubeadm init")
cmd = exec.CommandContext(ctx, kubeadmPath, "init", "-v=5", "--skip-phases=preflight,certs", "--config", initConfigFile.Name())
out, err = cmd.CombinedOutput()
if err != nil { if err != nil {
var exitErr *exec.ExitError var exitErr *exec.ExitError
if errors.As(err, &exitErr) { if errors.As(err, &exitErr) {
@ -312,3 +358,92 @@ func (k *KubernetesUtil) RestartKubelet() error {
defer cancel() defer cancel()
return restartSystemdUnit(ctx, "kubelet.service") return restartSystemdUnit(ctx, "kubelet.service")
} }
// createSignedKubeletCert manually creates a Kubernetes CA signed kubelet certificate for the bootstrapper node.
// This is necessary because this node does not request a certificate from the join service.
func (k *KubernetesUtil) createSignedKubeletCert(nodeName string, ips []net.IP) error {
certRequestRaw, kubeletKey, err := kubelet.GetCertificateRequest(nodeName, ips)
if err != nil {
return err
}
if err := k.file.Write(kubelet.KeyFilename, kubeletKey, file.OptMkdirAll); err != nil {
return err
}
parentCertRaw, err := k.file.Read(filepath.Join(
constants.KubernetesDir,
constants.DefaultCertificateDir,
constants.CACertName,
))
if err != nil {
return err
}
parentCertPEM, _ := pem.Decode(parentCertRaw)
parentCert, err := x509.ParseCertificate(parentCertPEM.Bytes)
if err != nil {
return err
}
parentKeyRaw, err := k.file.Read(filepath.Join(
constants.KubernetesDir,
constants.DefaultCertificateDir,
constants.CAKeyName,
))
if err != nil {
return err
}
parentKeyPEM, _ := pem.Decode(parentKeyRaw)
var parentKey any
switch parentKeyPEM.Type {
case "EC PRIVATE KEY":
parentKey, err = x509.ParseECPrivateKey(parentKeyPEM.Bytes)
case "RSA PRIVATE KEY":
parentKey, err = x509.ParsePKCS1PrivateKey(parentKeyPEM.Bytes)
case "PRIVATE KEY":
parentKey, err = x509.ParsePKCS8PrivateKey(parentKeyPEM.Bytes)
default:
err = fmt.Errorf("unsupported key type %q", parentCertPEM.Type)
}
if err != nil {
return err
}
certRequest, err := x509.ParseCertificateRequest(certRequestRaw)
if err != nil {
return err
}
serialNumber, err := util.GenerateCertificateSerialNumber()
if err != nil {
return err
}
now := time.Now()
// Create the kubelet certificate
// For a reference on the certificate fields, see: https://kubernetes.io/docs/setup/best-practices/certificates/
certTmpl := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: now.Add(-2 * time.Hour),
NotAfter: now.Add(24 * 365 * time.Hour),
Subject: certRequest.Subject,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
IsCA: false,
BasicConstraintsValid: true,
IPAddresses: certRequest.IPAddresses,
}
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, certRequest.PublicKey, parentKey)
if err != nil {
return err
}
kubeletCert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certRaw,
})
return k.file.Write(kubelet.CertificateFilename, kubeletCert, file.OptMkdirAll)
}

View file

@ -2,6 +2,7 @@ package kubernetes
import ( import (
"context" "context"
"net"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi" "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources"
@ -10,7 +11,7 @@ import (
type clusterUtil interface { type clusterUtil interface {
InstallComponents(ctx context.Context, version string) error InstallComponents(ctx context.Context, version string) error
InitCluster(ctx context.Context, initConfig []byte, log *logger.Logger) error InitCluster(ctx context.Context, initConfig []byte, nodeName string, ips []net.IP, log *logger.Logger) error
JoinCluster(ctx context.Context, joinConfig []byte, log *logger.Logger) error JoinCluster(ctx context.Context, joinConfig []byte, log *logger.Logger) error
SetupPodNetwork(context.Context, k8sapi.SetupPodNetworkInput) error SetupPodNetwork(context.Context, k8sapi.SetupPodNetworkInput) error
SetupAccessManager(kubectl k8sapi.Client, sshUsers resources.Marshaler) error SetupAccessManager(kubectl k8sapi.Client, sshUsers resources.Marshaler) error

View file

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net"
"os/exec" "os/exec"
"strings" "strings"
@ -96,6 +97,7 @@ func (k *KubeWrapper) InitCluster(
var subnetworkPodCIDR string var subnetworkPodCIDR string
var controlPlaneEndpointIP string // this is the IP in "kubeadm init --control-plane-endpoint=<IP/DNS>:<port>" hence the unfortunate name var controlPlaneEndpointIP string // this is the IP in "kubeadm init --control-plane-endpoint=<IP/DNS>:<port>" hence the unfortunate name
var nodeIP string var nodeIP string
var validIPs []net.IP
// Step 1: retrieve cloud metadata for Kubernetes configuration // Step 1: retrieve cloud metadata for Kubernetes configuration
if k.providerMetadata.Supported() { if k.providerMetadata.Supported() {
@ -104,6 +106,12 @@ func (k *KubeWrapper) InitCluster(
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving own instance metadata failed: %w", err) return nil, fmt.Errorf("retrieving own instance metadata failed: %w", err)
} }
for _, ip := range instance.PrivateIPs {
validIPs = append(validIPs, net.ParseIP(ip))
}
for _, ip := range instance.PublicIPs {
validIPs = append(validIPs, net.ParseIP(ip))
}
nodeName = k8sCompliantHostname(instance.Name) nodeName = k8sCompliantHostname(instance.Name)
providerID = instance.ProviderID providerID = instance.ProviderID
if len(instance.PrivateIPs) > 0 { if len(instance.PrivateIPs) > 0 {
@ -152,7 +160,7 @@ func (k *KubeWrapper) InitCluster(
return nil, fmt.Errorf("encoding kubeadm init configuration as YAML: %w", err) return nil, fmt.Errorf("encoding kubeadm init configuration as YAML: %w", err)
} }
log.Infof("Initializing Kubernetes cluster") log.Infof("Initializing Kubernetes cluster")
if err := k.clusterUtil.InitCluster(ctx, initConfigYAML, log); err != nil { if err := k.clusterUtil.InitCluster(ctx, initConfigYAML, nodeName, validIPs, log); err != nil {
return nil, fmt.Errorf("kubeadm init: %w", err) return nil, fmt.Errorf("kubeadm init: %w", err)
} }
kubeConfig, err := k.GetKubeconfig() kubeConfig, err := k.GetKubeconfig()

View file

@ -3,6 +3,7 @@ package kubernetes
import ( import (
"context" "context"
"errors" "errors"
"net"
"regexp" "regexp"
"testing" "testing"
@ -496,7 +497,7 @@ func (s *stubClusterUtil) InstallComponents(ctx context.Context, version string)
return s.installComponentsErr return s.installComponentsErr
} }
func (s *stubClusterUtil) InitCluster(ctx context.Context, initConfig []byte, log *logger.Logger) error { func (s *stubClusterUtil) InitCluster(ctx context.Context, initConfig []byte, nodeName string, ips []net.IP, log *logger.Logger) error {
s.initConfigs = append(s.initConfigs, initConfig) s.initConfigs = append(s.initConfigs, initConfig)
return s.initClusterErr return s.initClusterErr
} }

View file

@ -1,18 +1,18 @@
package kubernetesca package kubernetesca
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/edgelesssys/constellation/bootstrapper/util" "github.com/edgelesssys/constellation/bootstrapper/util"
"github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/file"
"github.com/edgelesssys/constellation/internal/logger" "github.com/edgelesssys/constellation/internal/logger"
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
const ( const (
@ -35,22 +35,22 @@ func New(log *logger.Logger, fileHandler file.Handler) *KubernetesCA {
} }
// GetCertificate creates a certificate for a node and signs it using the Kubernetes root CA. // GetCertificate creates a certificate for a node and signs it using the Kubernetes root CA.
func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte, err error) { func (c KubernetesCA) GetCertificate(csr []byte) (cert []byte, err error) {
c.log.Debugf("Loading Kubernetes CA certificate") c.log.Debugf("Loading Kubernetes CA certificate")
parentCertRaw, err := c.file.Read(caCertFilename) parentCertRaw, err := c.file.Read(caCertFilename)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
parentCertPEM, _ := pem.Decode(parentCertRaw) parentCertPEM, _ := pem.Decode(parentCertRaw)
parentCert, err := x509.ParseCertificate(parentCertPEM.Bytes) parentCert, err := x509.ParseCertificate(parentCertPEM.Bytes)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
c.log.Debugf("Loading Kubernetes CA private key") c.log.Debugf("Loading Kubernetes CA private key")
parentKeyRaw, err := c.file.Read(caKeyFilename) parentKeyRaw, err := c.file.Read(caKeyFilename)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
parentKeyPEM, _ := pem.Decode(parentKeyRaw) parentKeyPEM, _ := pem.Decode(parentKeyRaw)
var parentKey any var parentKey any
@ -62,30 +62,34 @@ func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte,
case "PRIVATE KEY": case "PRIVATE KEY":
parentKey, err = x509.ParsePKCS8PrivateKey(parentKeyPEM.Bytes) parentKey, err = x509.ParsePKCS8PrivateKey(parentKeyPEM.Bytes)
default: default:
return nil, nil, fmt.Errorf("unsupported key type %q", parentCertPEM.Type) return nil, fmt.Errorf("unsupported key type %q", parentCertPEM.Type)
} }
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
c.log.Infof("Creating kubelet private key") certRequest, err := x509.ParseCertificateRequest(csr)
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
keyBytes, err := x509.MarshalECPrivateKey(privK) if err := certRequest.CheckSignature(); err != nil {
if err != nil { return nil, err
return nil, nil, err
} }
kubeletKey := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
})
c.log.Infof("Creating kubelet certificate") c.log.Infof("Creating kubelet certificate")
if len(certRequest.Subject.Organization) != 1 {
return nil, errors.New("certificate request must have exactly one organization")
}
if certRequest.Subject.Organization[0] != kubeconstants.NodesGroup {
return nil, fmt.Errorf("certificate request must have organization %q but has %q", kubeconstants.NodesGroup, certRequest.Subject.Organization[0])
}
if !strings.HasPrefix(certRequest.Subject.CommonName, kubeconstants.NodesUserPrefix) {
return nil, fmt.Errorf("certificate request must have common name prefix %q but is %q", kubeconstants.NodesUserPrefix, certRequest.Subject.CommonName)
}
serialNumber, err := util.GenerateCertificateSerialNumber() serialNumber, err := util.GenerateCertificateSerialNumber()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
now := time.Now() now := time.Now()
@ -95,25 +99,25 @@ func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte,
SerialNumber: serialNumber, SerialNumber: serialNumber,
NotBefore: now.Add(-2 * time.Hour), NotBefore: now.Add(-2 * time.Hour),
NotAfter: now.Add(24 * 365 * time.Hour), NotAfter: now.Add(24 * 365 * time.Hour),
Subject: pkix.Name{ Subject: certRequest.Subject,
Organization: []string{"system:nodes"},
CommonName: fmt.Sprintf("system:node:%s", nodeName),
},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
}, },
IsCA: false, IsCA: false,
BasicConstraintsValid: true, BasicConstraintsValid: true,
IPAddresses: certRequest.IPAddresses,
} }
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, &privK.PublicKey, parentKey)
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, certRequest.PublicKey, parentKey)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
kubeletCert := pem.EncodeToMemory(&pem.Block{ kubeletCert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Type: "CERTIFICATE",
Bytes: certRaw, Bytes: certRaw,
}) })
return kubeletCert, kubeletKey, nil return kubeletCert, nil
} }

View file

@ -10,6 +10,7 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"math/big" "math/big"
"strings"
"testing" "testing"
"time" "time"
@ -19,6 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -38,45 +40,126 @@ Q29uc3RlbGxhdGlvbg==
invalidCert := []byte(`-----BEGIN CERTIFICATE----- invalidCert := []byte(`-----BEGIN CERTIFICATE-----
Q29uc3RlbGxhdGlvbg== Q29uc3RlbGxhdGlvbg==
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
defaultSigningRequestFunc := func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{kubeconstants.NodesGroup},
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
}
testCases := map[string]struct { testCases := map[string]struct {
caCert []byte caCert []byte
caKey []byte caKey []byte
createSigningRequest func() ([]byte, error)
wantErr bool wantErr bool
}{ }{
"success ec key": { "success ec key": {
caCert: ecCert, caCert: ecCert,
caKey: ecKey, caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
}, },
"success rsa key": { "success rsa key": {
caCert: rsaCert, caCert: rsaCert,
caKey: rsaKey, caKey: rsaKey,
createSigningRequest: defaultSigningRequestFunc,
}, },
"success any key": { "success any key": {
caCert: testCert, caCert: testCert,
caKey: testKey, caKey: testKey,
createSigningRequest: defaultSigningRequestFunc,
}, },
"unsupported key": { "unsupported key": {
caCert: ecCert, caCert: ecCert,
caKey: unsupportedKey, caKey: unsupportedKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true, wantErr: true,
}, },
"invalid key": { "invalid key": {
caCert: ecCert, caCert: ecCert,
caKey: invalidKey, caKey: invalidKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true, wantErr: true,
}, },
"invalid certificate": { "invalid certificate": {
caCert: invalidCert, caCert: invalidCert,
caKey: ecKey, caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true, wantErr: true,
}, },
"no ca certificate": { "no ca certificate": {
caKey: ecKey, caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true, wantErr: true,
}, },
"no ca key": { "no ca key": {
caCert: ecCert, caCert: ecCert,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"no signing request": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) { return nil, nil },
wantErr: true,
},
"incorrect common name format": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{kubeconstants.NodesGroup},
CommonName: "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
wantErr: true,
},
"incorrect organization format": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"test"},
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
wantErr: true,
},
"no organization": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
wantErr: true, wantErr: true,
}, },
} }
@ -100,8 +183,9 @@ Q29uc3RlbGxhdGlvbg==
file, file,
) )
nodeName := "test" signingRequest, err := tc.createSigningRequest()
kubeCert, kubeKey, err := ca.GetCertificate(nodeName) require.NoError(err)
kubeCert, err := ca.GetCertificate(signingRequest)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -112,19 +196,12 @@ Q29uc3RlbGxhdGlvbg==
require.NotNil(certPEM) require.NotNil(certPEM)
cert, err := x509.ParseCertificate(certPEM.Bytes) cert, err := x509.ParseCertificate(certPEM.Bytes)
require.NoError(err) require.NoError(err)
assert.Equal("system:node:"+nodeName, cert.Subject.CommonName) assert.True(strings.HasPrefix(cert.Subject.CommonName, kubeconstants.NodesUserPrefix))
assert.Equal("system:nodes", cert.Subject.Organization[0]) assert.Equal(kubeconstants.NodesGroup, cert.Subject.Organization[0])
assert.Equal(x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage) assert.Equal(x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
assert.Equal(x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[0]) assert.Equal(x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[0])
assert.False(cert.IsCA) assert.False(cert.IsCA)
assert.True(cert.BasicConstraintsValid) assert.True(cert.BasicConstraintsValid)
keyPEM, _ := pem.Decode(kubeKey)
require.NotNil(keyPEM)
key, err := x509.ParseECPrivateKey(keyPEM.Bytes)
require.NoError(err)
require.IsType(&ecdsa.PublicKey{}, cert.PublicKey)
assert.Equal(&key.PublicKey, cert.PublicKey.(*ecdsa.PublicKey))
}) })
} }
} }

View file

@ -93,7 +93,7 @@ func (s *Server) IssueJoinTicket(ctx context.Context, req *joinproto.IssueJoinTi
} }
log.Infof("Creating signed kubelet certificate") log.Infof("Creating signed kubelet certificate")
kubeletCert, kubeletKey, err := s.ca.GetCertificate(req.NodeName) kubeletCert, err := s.ca.GetCertificate(req.CertificateRequest)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "unable to generate kubelet certificate: %s", err) return nil, status.Errorf(codes.Internal, "unable to generate kubelet certificate: %s", err)
} }
@ -124,7 +124,6 @@ func (s *Server) IssueJoinTicket(ctx context.Context, req *joinproto.IssueJoinTi
Token: kubeArgs.Token, Token: kubeArgs.Token,
DiscoveryTokenCaCertHash: kubeArgs.CACertHashes[0], DiscoveryTokenCaCertHash: kubeArgs.CACertHashes[0],
KubeletCert: kubeletCert, KubeletCert: kubeletCert,
KubeletKey: kubeletKey,
ControlPlaneFiles: controlPlaneFiles, ControlPlaneFiles: controlPlaneFiles,
}, nil }, nil
} }
@ -144,5 +143,5 @@ type dataKeyGetter interface {
type certificateAuthority interface { type certificateAuthority interface {
// GetCertificate returns a certificate and private key, signed by the issuer. // GetCertificate returns a certificate and private key, signed by the issuer.
GetCertificate(nodeName string) (kubeletCert []byte, kubeletKey []byte, err error) GetCertificate(certificateRequest []byte) (kubeletCert []byte, err error)
} }

View file

@ -49,33 +49,33 @@ func TestIssueJoinTicket(t *testing.T) {
"worker node": { "worker node": {
kubeadm: stubTokenGetter{token: testJoinToken}, kubeadm: stubTokenGetter{token: testJoinToken},
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: mustMarshalID(testID), id: mustMarshalID(testID),
}, },
"GetDataKey fails": { "GetDataKey fails": {
kubeadm: stubTokenGetter{token: testJoinToken}, kubeadm: stubTokenGetter{token: testJoinToken},
kms: stubKeyGetter{getDataKeyErr: someErr}, kms: stubKeyGetter{getDataKeyErr: someErr},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: mustMarshalID(testID), id: mustMarshalID(testID),
wantErr: true, wantErr: true,
}, },
"loading IDs fails": { "loading IDs fails": {
kubeadm: stubTokenGetter{token: testJoinToken}, kubeadm: stubTokenGetter{token: testJoinToken},
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: []byte{0x1, 0x2, 0x3}, id: []byte{0x1, 0x2, 0x3},
wantErr: true, wantErr: true,
}, },
"no ID file": { "no ID file": {
kubeadm: stubTokenGetter{token: testJoinToken}, kubeadm: stubTokenGetter{token: testJoinToken},
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
wantErr: true, wantErr: true,
}, },
"GetJoinToken fails": { "GetJoinToken fails": {
kubeadm: stubTokenGetter{getJoinTokenErr: someErr}, kubeadm: stubTokenGetter{getJoinTokenErr: someErr},
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: mustMarshalID(testID), id: mustMarshalID(testID),
wantErr: true, wantErr: true,
}, },
@ -93,14 +93,14 @@ func TestIssueJoinTicket(t *testing.T) {
files: map[string][]byte{"test": {0x1, 0x2, 0x3}}, files: map[string][]byte{"test": {0x1, 0x2, 0x3}},
}, },
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: mustMarshalID(testID), id: mustMarshalID(testID),
}, },
"GetControlPlaneCertificateKey fails": { "GetControlPlaneCertificateKey fails": {
isControlPlane: true, isControlPlane: true,
kubeadm: stubTokenGetter{token: testJoinToken, certificateKeyErr: someErr}, kubeadm: stubTokenGetter{token: testJoinToken, certificateKeyErr: someErr},
kms: stubKeyGetter{dataKey: testKey}, kms: stubKeyGetter{dataKey: testKey},
ca: stubCA{cert: testCert, key: testKey}, ca: stubCA{cert: testCert},
id: mustMarshalID(testID), id: mustMarshalID(testID),
wantErr: true, wantErr: true,
}, },
@ -125,7 +125,6 @@ func TestIssueJoinTicket(t *testing.T) {
req := &joinproto.IssueJoinTicketRequest{ req := &joinproto.IssueJoinTicketRequest{
DiskUuid: "uuid", DiskUuid: "uuid",
NodeName: "test",
IsControlPlane: tc.isControlPlane, IsControlPlane: tc.isControlPlane,
} }
resp, err := api.IssueJoinTicket(context.Background(), req) resp, err := api.IssueJoinTicket(context.Background(), req)
@ -145,7 +144,6 @@ func TestIssueJoinTicket(t *testing.T) {
assert.Equal(tc.kubeadm.token.CACertHashes[0], resp.DiscoveryTokenCaCertHash) assert.Equal(tc.kubeadm.token.CACertHashes[0], resp.DiscoveryTokenCaCertHash)
assert.Equal(tc.kubeadm.token.Token, resp.Token) assert.Equal(tc.kubeadm.token.Token, resp.Token)
assert.Equal(tc.ca.cert, resp.KubeletCert) assert.Equal(tc.ca.cert, resp.KubeletCert)
assert.Equal(tc.ca.key, resp.KubeletKey)
if tc.isControlPlane { if tc.isControlPlane {
assert.Len(resp.ControlPlaneFiles, len(tc.kubeadm.files)) assert.Len(resp.ControlPlaneFiles, len(tc.kubeadm.files))
@ -188,10 +186,9 @@ func (f stubKeyGetter) GetDataKey(context.Context, string, int) ([]byte, error)
type stubCA struct { type stubCA struct {
cert []byte cert []byte
key []byte
getCertErr error getCertErr error
} }
func (f stubCA) GetCertificate(string) ([]byte, []byte, error) { func (f stubCA) GetCertificate(csr []byte) ([]byte, error) {
return f.cert, f.key, f.getCertErr return f.cert, f.getCertErr
} }

View file

@ -26,7 +26,7 @@ type IssueJoinTicketRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
DiskUuid string `protobuf:"bytes,1,opt,name=disk_uuid,json=diskUuid,proto3" json:"disk_uuid,omitempty"` DiskUuid string `protobuf:"bytes,1,opt,name=disk_uuid,json=diskUuid,proto3" json:"disk_uuid,omitempty"`
NodeName string `protobuf:"bytes,2,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"` CertificateRequest []byte `protobuf:"bytes,2,opt,name=certificate_request,json=certificateRequest,proto3" json:"certificate_request,omitempty"`
IsControlPlane bool `protobuf:"varint,3,opt,name=is_control_plane,json=isControlPlane,proto3" json:"is_control_plane,omitempty"` IsControlPlane bool `protobuf:"varint,3,opt,name=is_control_plane,json=isControlPlane,proto3" json:"is_control_plane,omitempty"`
} }
@ -69,11 +69,11 @@ func (x *IssueJoinTicketRequest) GetDiskUuid() string {
return "" return ""
} }
func (x *IssueJoinTicketRequest) GetNodeName() string { func (x *IssueJoinTicketRequest) GetCertificateRequest() []byte {
if x != nil { if x != nil {
return x.NodeName return x.CertificateRequest
} }
return "" return nil
} }
func (x *IssueJoinTicketRequest) GetIsControlPlane() bool { func (x *IssueJoinTicketRequest) GetIsControlPlane() bool {
@ -91,12 +91,11 @@ type IssueJoinTicketResponse struct {
StateDiskKey []byte `protobuf:"bytes,1,opt,name=state_disk_key,json=stateDiskKey,proto3" json:"state_disk_key,omitempty"` StateDiskKey []byte `protobuf:"bytes,1,opt,name=state_disk_key,json=stateDiskKey,proto3" json:"state_disk_key,omitempty"`
OwnerId []byte `protobuf:"bytes,2,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` OwnerId []byte `protobuf:"bytes,2,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"`
ClusterId []byte `protobuf:"bytes,3,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"` ClusterId []byte `protobuf:"bytes,3,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"`
KubeletKey []byte `protobuf:"bytes,4,opt,name=kubelet_key,json=kubeletKey,proto3" json:"kubelet_key,omitempty"` KubeletCert []byte `protobuf:"bytes,4,opt,name=kubelet_cert,json=kubeletCert,proto3" json:"kubelet_cert,omitempty"`
KubeletCert []byte `protobuf:"bytes,5,opt,name=kubelet_cert,json=kubeletCert,proto3" json:"kubelet_cert,omitempty"` ApiServerEndpoint string `protobuf:"bytes,5,opt,name=api_server_endpoint,json=apiServerEndpoint,proto3" json:"api_server_endpoint,omitempty"`
ApiServerEndpoint string `protobuf:"bytes,6,opt,name=api_server_endpoint,json=apiServerEndpoint,proto3" json:"api_server_endpoint,omitempty"` Token string `protobuf:"bytes,6,opt,name=token,proto3" json:"token,omitempty"`
Token string `protobuf:"bytes,7,opt,name=token,proto3" json:"token,omitempty"` DiscoveryTokenCaCertHash string `protobuf:"bytes,7,opt,name=discovery_token_ca_cert_hash,json=discoveryTokenCaCertHash,proto3" json:"discovery_token_ca_cert_hash,omitempty"`
DiscoveryTokenCaCertHash string `protobuf:"bytes,8,opt,name=discovery_token_ca_cert_hash,json=discoveryTokenCaCertHash,proto3" json:"discovery_token_ca_cert_hash,omitempty"` ControlPlaneFiles []*ControlPlaneCertOrKey `protobuf:"bytes,8,rep,name=control_plane_files,json=controlPlaneFiles,proto3" json:"control_plane_files,omitempty"`
ControlPlaneFiles []*ControlPlaneCertOrKey `protobuf:"bytes,9,rep,name=control_plane_files,json=controlPlaneFiles,proto3" json:"control_plane_files,omitempty"`
} }
func (x *IssueJoinTicketResponse) Reset() { func (x *IssueJoinTicketResponse) Reset() {
@ -152,13 +151,6 @@ func (x *IssueJoinTicketResponse) GetClusterId() []byte {
return nil return nil
} }
func (x *IssueJoinTicketResponse) GetKubeletKey() []byte {
if x != nil {
return x.KubeletKey
}
return nil
}
func (x *IssueJoinTicketResponse) GetKubeletCert() []byte { func (x *IssueJoinTicketResponse) GetKubeletCert() []byte {
if x != nil { if x != nil {
return x.KubeletCert return x.KubeletCert
@ -253,54 +245,53 @@ var File_join_proto protoreflect.FileDescriptor
var file_join_proto_rawDesc = []byte{ var file_join_proto_rawDesc = []byte{
0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6a, 0x6f, 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6a, 0x6f,
0x69, 0x6e, 0x22, 0x7c, 0x0a, 0x16, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x6e, 0x22, 0x90, 0x01, 0x0a, 0x16, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e,
0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a,
0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x52, 0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x65,
0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x69,
0x52, 0x0e, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x18,
0x22, 0x94, 0x03, 0x0a, 0x17, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x22, 0xf3, 0x02, 0x0a, 0x17, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a,
0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x6b, 0x4b, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x6b, 0x5f,
0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x44, 0x69, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72,
0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72,
0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64,
0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49,
0x0c, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x5f, 0x63, 0x65, 0x72,
0x0c, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x05, 0x20, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74,
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x43, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76,
0x12, 0x2e, 0x0a, 0x13, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x65, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61, 0x09, 0x52, 0x11, 0x61, 0x70, 0x69, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70,
0x70, 0x69, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20,
0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x3e, 0x0a, 0x1c, 0x64, 0x69,
0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x3e, 0x0a, 0x1c, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x61,
0x65, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x64, 0x69, 0x52, 0x18, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x61, 0x43, 0x65, 0x43, 0x61, 0x43, 0x65, 0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4f, 0x0a, 0x13, 0x63, 0x6f,
0x72, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65,
0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x63,
0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72,
0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x5f, 0x6b, 0x65, 0x79, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x43, 0x0a, 0x19, 0x63,
0x6e, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x43, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72,
0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x5f, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x55, 0x0a, 0x03, 0x32, 0x55, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x4e, 0x0a, 0x0f, 0x49, 0x73, 0x73, 0x75, 0x65,
0x41, 0x50, 0x49, 0x12, 0x4e, 0x0a, 0x0f, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6a, 0x6f, 0x69,
0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65,
0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52,
0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75,
0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79,
0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f,
0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6a, 0x6f, 0x69, 0x6e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View file

@ -11,7 +11,7 @@ service API {
message IssueJoinTicketRequest { message IssueJoinTicketRequest {
string disk_uuid = 1; string disk_uuid = 1;
string node_name = 2; bytes certificate_request = 2;
bool is_control_plane = 3; bool is_control_plane = 3;
} }
@ -19,12 +19,11 @@ message IssueJoinTicketResponse {
bytes state_disk_key = 1; bytes state_disk_key = 1;
bytes owner_id = 2; bytes owner_id = 2;
bytes cluster_id = 3; bytes cluster_id = 3;
bytes kubelet_key = 4; bytes kubelet_cert = 4;
bytes kubelet_cert = 5; string api_server_endpoint = 5;
string api_server_endpoint = 6; string token = 6;
string token = 7; string discovery_token_ca_cert_hash = 7;
string discovery_token_ca_cert_hash = 8; repeated control_plane_cert_or_key control_plane_files = 8;
repeated control_plane_cert_or_key control_plane_files = 9;
} }
message control_plane_cert_or_key { message control_plane_cert_or_key {