Skip to content

Commit

Permalink
Pass certs via initConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
mateoflorido committed Apr 4, 2024
1 parent e6227da commit 62925dd
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 11 deletions.
7 changes: 4 additions & 3 deletions src/k8s/api/v1/cluster_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package v1

// JoinClusterRequest is used to request to add a node to the cluster.
type JoinClusterRequest struct {
Name string `json:"name"`
Address string `json:"address"`
Token string `json:"token"`
Name string `json:"name"`
Address string `json:"address"`
Token string `json:"token"`
Config JoinClusterConfig `json:"config"`
}

// RemoveNodeRequest is used to request to remove a node from the cluster.
Expand Down
39 changes: 39 additions & 0 deletions src/k8s/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ type BootstrapConfig struct {
DatastoreClientCert string `yaml:"datastore-client-crt,omitempty"`
DatastoreClientKey string `yaml:"datastore-client-key,omitempty"`
ExtraSANs []string `yaml:"extrasans,omitempty"`

CACert string `yaml:"ca-crt,omitempty"`
CAKey string `yaml:"ca-key,omitempty"`
FrontProxyCACert string `yaml:"front-proxy-ca-crt"`
FrontProxyCAKey string `yaml:"front-proxy-ca-key"`
APIServerKubeletClientCert string `yaml:"apiserver-kubelet-client-crt"`
APIServerKubeletClientKey string `yaml:"apiserver-kubelet-client-key"`
ServiceAccountKey string `yaml:"service-account-key"`

APIServerCert string `yaml:"apiserver-crt,omitempty"`
APIServerKey string `yaml:"apiserver-key,omitempty"`
KubeletCert string `yaml:"kubelet-crt,omitempty"`
KubeletKey string `yaml:"kubelet-key,omitempty"`
}

// SetDefaults sets the fields to default values.
Expand Down Expand Up @@ -58,6 +71,32 @@ func BootstrapConfigFromMap(m map[string]string) (*BootstrapConfig, error) {
return config, nil
}

type JoinClusterConfig struct {
APIServerCert string `yaml:"apiserver-crt,omitempty"`
APIServerKey string `yaml:"apiserver-key,omitempty"`
KubeletCert string `yaml:"kubelet-crt,omitempty"`
KubeletKey string `yaml:"kubelet-key,omitempty"`
}

func (j *JoinClusterConfig) ToMap() (map[string]string, error) {
config, err := yaml.Marshal(j)
if err != nil {
return nil, fmt.Errorf("failed to marshal config map: %w", err)
}
return map[string]string{
"joinClusterConfig": string(config),
}, nil
}

func JoinClusterConfigFromMap(m map[string]string) (*JoinClusterConfig, error) {
config := &JoinClusterConfig{}
err := yaml.Unmarshal([]byte(m["joinClusterConfig"]), config)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal join config: %w", err)
}
return config, nil
}

type ClusterRole string

const (
Expand Down
35 changes: 32 additions & 3 deletions src/k8s/cmd/k8s/k8s_join_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/canonical/k8s/pkg/config"
"github.com/canonical/lxd/lxd/util"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)

type JoinClusterResult struct {
Expand All @@ -21,8 +22,9 @@ func (b JoinClusterResult) String() string {

func newJoinClusterCmd(env cmdutil.ExecutionEnvironment) *cobra.Command {
var opts struct {
name string
address string
name string
address string
configFile string
}
cmd := &cobra.Command{
Use: "join-cluster <join-token>",
Expand Down Expand Up @@ -62,8 +64,18 @@ func newJoinClusterCmd(env cmdutil.ExecutionEnvironment) *cobra.Command {
return
}

joinClusterConfig := apiv1.JoinClusterConfig{}
if opts.configFile != "" {
joinClusterConfig, err = getJoinClusterConfigFromYaml(opts.configFile)
if err != nil {
cmd.PrintErrf("Error: Failed to read join configuration from %q.\n\nThe error was: %v\n", opts.configFile, err)
env.Exit(1)
return
}
}

cmd.PrintErrln("Joining the cluster. This may take a few seconds, please wait.")
if err := client.JoinCluster(cmd.Context(), apiv1.JoinClusterRequest{Name: opts.name, Address: opts.address, Token: token}); err != nil {
if err := client.JoinCluster(cmd.Context(), apiv1.JoinClusterRequest{Name: opts.name, Address: opts.address, Token: token, Config: joinClusterConfig}); err != nil {
cmd.PrintErrf("Error: Failed to join the cluster using the provided token.\n\nThe error was: %v\n", err)
env.Exit(1)
return
Expand All @@ -76,5 +88,22 @@ func newJoinClusterCmd(env cmdutil.ExecutionEnvironment) *cobra.Command {
}
cmd.Flags().StringVar(&opts.name, "name", "", "node name, defaults to hostname")
cmd.Flags().StringVar(&opts.address, "address", "", "microcluster address, defaults to the node IP address")
cmd.PersistentFlags().StringVar(&opts.configFile, "config", "", "path to the YAML file containing your custom cluster join configuration")
return cmd
}

func getJoinClusterConfigFromYaml(filePath string) (apiv1.JoinClusterConfig, error) {
config := apiv1.JoinClusterConfig{}

yamlContent, err := os.ReadFile(filePath)
if err != nil {
return config, fmt.Errorf("failed to read YAML config file: %w", err)
}

err = yaml.Unmarshal(yamlContent, &config)
if err != nil {
return config, fmt.Errorf("failed to parse YAML config file: %w", err)
}

return config, nil
}
10 changes: 8 additions & 2 deletions src/k8s/pkg/k8sd/api/cluster_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func (e *Endpoints) postClusterJoin(s *state.State, r *http.Request) response.Re
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err))
}

config, err := req.Config.ToMap()
if err != nil {
return response.BadRequest(fmt.Errorf("failed to convert join config to map: %w", err))
}

hostname, err := utils.CleanHostname(req.Name)
if err != nil {
return response.BadRequest(fmt.Errorf("invalid hostname %q: %w", req.Name, err))
Expand All @@ -28,12 +33,13 @@ func (e *Endpoints) postClusterJoin(s *state.State, r *http.Request) response.Re
if internalToken.Decode(req.Token) == nil {
// valid worker node token - let's join the cluster
// The validation of the token is done when fetching the cluster information.
if err := e.provider.MicroCluster().NewCluster(hostname, req.Address, map[string]string{"workerToken": req.Token}, 0); err != nil {
config["workerToken"] = req.Token
if err := e.provider.MicroCluster().NewCluster(hostname, req.Address, config, 0); err != nil {
return response.InternalError(fmt.Errorf("failed to join k8sd cluster as worker: %w", err))
}
} else {
// Is not a worker token. let microcluster check if it is a valid control-plane token.
if err := e.provider.MicroCluster().JoinCluster(hostname, req.Address, req.Token, nil, 0); err != nil {
if err := e.provider.MicroCluster().JoinCluster(hostname, req.Address, req.Token, config, 0); err != nil {
return response.InternalError(fmt.Errorf("failed to join k8sd cluster as control plane: %w", err))
}
}
Expand Down
32 changes: 30 additions & 2 deletions src/k8s/pkg/k8sd/app/hooks_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,25 @@ import (
// onBootstrap configures local services then writes the cluster config on the database.
func (a *App) onBootstrap(s *state.State, initConfig map[string]string) error {
if workerToken, ok := initConfig["workerToken"]; ok {
return a.onBootstrapWorkerNode(s, workerToken)
return a.onBootstrapWorkerNode(s, workerToken, initConfig)
}

return a.onBootstrapControlPlane(s, initConfig)
}

func (a *App) onBootstrapWorkerNode(s *state.State, encodedToken string) error {
func (a *App) onBootstrapWorkerNode(s *state.State, encodedToken string, initConfig map[string]string) error {
snap := a.Snap()

token := &types.InternalWorkerNodeToken{}
if err := token.Decode(encodedToken); err != nil {
return fmt.Errorf("failed to parse worker token: %w", err)
}

joinClusterConfig, err := apiv1.JoinClusterConfigFromMap(initConfig)
if err != nil {
return fmt.Errorf("failed to unmarshal join config: %w", err)
}

if len(token.JoinAddresses) == 0 {
return fmt.Errorf("empty list of control plane addresses")
}
Expand Down Expand Up @@ -102,6 +107,12 @@ func (a *App) onBootstrapWorkerNode(s *state.State, encodedToken string) error {
KubeletCert: response.KubeletCert,
KubeletKey: response.KubeletKey,
}

if !certificates.IsKubeletPresent() {
certificates.KubeletCert = joinClusterConfig.KubeletCert
certificates.KubeletKey = joinClusterConfig.KubeletKey
}

if err := certificates.CompleteCertificates(); err != nil {
return fmt.Errorf("failed to initialize worker node certificates: %w", err)
}
Expand Down Expand Up @@ -220,6 +231,23 @@ func (a *App) onBootstrapControlPlane(s *state.State, initConfig map[string]stri
AllowSelfSignedCA: true,
IncludeMachineAddressSANs: true,
})

certificates.CACert = bootstrapConfig.CACert
certificates.CAKey = bootstrapConfig.CAKey

certificates.FrontProxyCACert = bootstrapConfig.FrontProxyCACert
certificates.FrontProxyCAKey = bootstrapConfig.FrontProxyCAKey

certificates.ServiceAccountKey = bootstrapConfig.ServiceAccountKey

certificates.APIServerKubeletClientCert = bootstrapConfig.APIServerKubeletClientCert
certificates.APIServerKubeletClientKey = bootstrapConfig.APIServerKubeletClientKey

certificates.APIServerCert = bootstrapConfig.APIServerCert
certificates.APIServerKey = bootstrapConfig.APIServerKey
certificates.KubeletCert = bootstrapConfig.KubeletCert
certificates.KubeletKey = bootstrapConfig.KubeletKey

if err := certificates.CompleteCertificates(); err != nil {
return fmt.Errorf("failed to initialize control plane certificates: %w", err)
}
Expand Down
13 changes: 13 additions & 0 deletions src/k8s/pkg/k8sd/app/hooks_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"net"

apiv1 "github.com/canonical/k8s/api/v1"

"github.com/canonical/k8s/pkg/k8sd/pki"
"github.com/canonical/k8s/pkg/k8sd/setup"
"github.com/canonical/k8s/pkg/utils"
Expand All @@ -16,6 +18,11 @@ import (
func (a *App) onPostJoin(s *state.State, initConfig map[string]string) error {
snap := a.Snap()

joinClusterConfig, err := apiv1.JoinClusterConfigFromMap(initConfig)
if err != nil {
return fmt.Errorf("failed to unmarshal join config: %w", err)
}

cfg, err := utils.GetClusterConfig(s.Context, s)
if err != nil {
return fmt.Errorf("failed to get cluster config: %w", err)
Expand Down Expand Up @@ -84,6 +91,12 @@ func (a *App) onPostJoin(s *state.State, initConfig map[string]string) error {
certificates.APIServerKubeletClientKey = cfg.Certificates.GetAPIServerKubeletClientKey()
certificates.ServiceAccountKey = cfg.Certificates.GetServiceAccountKey()

// load certificates from joinClusterConfig
certificates.APIServerCert = joinClusterConfig.APIServerCert
certificates.APIServerKey = joinClusterConfig.APIServerKey
certificates.KubeletCert = joinClusterConfig.KubeletCert
certificates.KubeletKey = joinClusterConfig.KubeletKey

if err := certificates.CompleteCertificates(); err != nil {
return fmt.Errorf("failed to initialize control plane certificates: %w", err)
}
Expand Down
4 changes: 4 additions & 0 deletions src/k8s/pkg/k8sd/pki/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ func (c *WorkerNodePKI) CompleteCertificates() error {
}
return nil
}

func (c *WorkerNodePKI) IsKubeletPresent() bool {
return c.KubeletCert != "" && c.KubeletKey != ""
}
10 changes: 9 additions & 1 deletion src/k8s/pkg/k8sd/types/cluster_config_certificates.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package types

import "reflect"

type Certificates struct {
CACert *string `json:"ca-crt,omitempty"`
CAKey *string `json:"ca-key,omitempty"`
Expand All @@ -22,5 +24,11 @@ func (c Certificates) GetAPIServerKubeletClientKey() string {
return getField(c.APIServerKubeletClientKey)
}
func (c Certificates) Empty() bool {
return c.CACert == nil && c.CAKey == nil && c.FrontProxyCACert == nil && c.FrontProxyCAKey == nil && c.ServiceAccountKey == nil && c.APIServerKubeletClientCert == nil && c.APIServerKubeletClientKey == nil
val := reflect.ValueOf(c)
for i := 0; i < val.NumField(); i++ {
if val.Field(i).Kind() == reflect.Pointer && !val.Field(i).IsNil() {
return false
}
}
return true
}

0 comments on commit 62925dd

Please sign in to comment.