package server import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io/ioutil" net2 "net" "os" "path/filepath" "strconv" "time" "github.com/pkg/errors" "github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/control" "github.com/rancher/k3s/pkg/datadir" "github.com/rancher/k3s/pkg/deploy" "github.com/rancher/k3s/pkg/helm" "github.com/rancher/k3s/pkg/servicelb" "github.com/rancher/k3s/pkg/tls" appsv1 "github.com/rancher/k3s/types/apis/apps/v1" batchv1 "github.com/rancher/k3s/types/apis/batch/v1" corev1 "github.com/rancher/k3s/types/apis/core/v1" "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1" rbacv1 "github.com/rancher/k3s/types/apis/rbac.authorization.k8s.io/v1" "github.com/rancher/norman" "github.com/rancher/norman/pkg/clientaccess" "github.com/rancher/norman/pkg/dynamiclistener" "github.com/rancher/norman/pkg/resolvehome" "github.com/rancher/norman/types" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/net" ) func resolveDataDir(dataDir string) (string, error) { if dataDir == "" { if os.Getuid() == 0 { dataDir = "/var/lib/rancher/k3s" } else { dataDir = "${HOME}/.rancher/k3s" } } dataDir = filepath.Join(dataDir, "server") return resolvehome.Resolve(dataDir) } func StartServer(ctx context.Context, config *Config) (string, error) { if err := setupDataDirAndChdir(&config.ControlConfig); err != nil { return "", err } if err := control.Server(ctx, &config.ControlConfig); err != nil { return "", errors.Wrap(err, "starting kubernetes") } certs, err := startNorman(ctx, config) if err != nil { return "", errors.Wrap(err, "starting tls server") } ip, err := net.ChooseHostInterface() if err != nil { ip = net2.ParseIP("127.0.0.1") } printTokens(certs, ip.String(), &config.TLSConfig, &config.ControlConfig) writeKubeConfig(certs, &config.TLSConfig, &config.ControlConfig) return certs, nil } func startNorman(ctx context.Context, config *Config) (string, error) { var ( err error tlsServer dynamiclistener.ServerInterface tlsConfig = &config.TLSConfig controlConfig = &config.ControlConfig ) tlsConfig.Handler = router(controlConfig, controlConfig.Runtime.Tunnel, func() (string, error) { if tlsServer == nil { return "", nil } return tlsServer.CACert() }) normanConfig := &norman.Config{ Name: "k3s", KubeConfig: controlConfig.Runtime.KubeConfigSystem, Clients: []norman.ClientFactory{ v1.Factory, appsv1.Factory, corev1.Factory, batchv1.Factory, rbacv1.Factory, }, Schemas: []*types.Schemas{ v1.Schemas, }, CRDs: map[*types.APIVersion][]string{ &v1.APIVersion: { v1.ListenerConfigGroupVersionKind.Kind, v1.AddonGroupVersionKind.Kind, v1.HelmChartGroupVersionKind.Kind, }, }, IgnoredKubeConfigEnv: true, GlobalSetup: func(ctx context.Context) (context.Context, error) { tlsServer, err = tls.NewServer(ctx, v1.ClientsFrom(ctx).ListenerConfig, *tlsConfig) return ctx, err }, DisableLeaderElection: true, MasterControllers: []norman.ControllerRegister{ helm.Register, func(ctx context.Context) error { return servicelb.Register(ctx, norman.GetServer(ctx).K8sClient, !config.DisableServiceLB) }, func(ctx context.Context) error { dataDir := filepath.Join(controlConfig.DataDir, "manifests") templateVars := map[string]string{"%{CLUSTER_DNS}%": controlConfig.ClusterDNS.String()} if err := deploy.Stage(dataDir, templateVars); err != nil { return err } if err := deploy.WatchFiles(ctx, controlConfig.Skips, dataDir); err != nil { return err } return nil }, }, } ctx, _, err = normanConfig.Build(ctx, nil) if err != nil { return "", err } for { certs, err := tlsServer.CACert() if err != nil { logrus.Infof("waiting to generate CA certs") time.Sleep(time.Second) continue } return certs, nil } } func HomeKubeConfig(write bool) (string, error) { if write { if os.Getuid() == 0 { return datadir.GlobalConfig, nil } return resolvehome.Resolve(datadir.HomeConfig) } if _, err := os.Stat(datadir.GlobalConfig); err == nil { return datadir.GlobalConfig, nil } return resolvehome.Resolve(datadir.HomeConfig) } func printTokens(certs, advertiseIP string, tlsConfig *dynamiclistener.UserConfig, config *config.Control) { var ( nodeFile string ) if advertiseIP == "" { advertiseIP = "localhost" } if len(config.Runtime.NodeToken) > 0 { p := filepath.Join(config.DataDir, "node-token") if err := writeToken(config.Runtime.NodeToken, p, certs); err == nil { logrus.Infof("Node token is available at %s", p) nodeFile = p } } if len(nodeFile) > 0 { printToken(tlsConfig.HTTPSPort, advertiseIP, "To join node to cluster:", "agent") } } func writeKubeConfig(certs string, tlsConfig *dynamiclistener.UserConfig, config *config.Control) { clientToken := FormatToken(config.Runtime.ClientToken, certs) url := fmt.Sprintf("https://localhost:%d", tlsConfig.HTTPSPort) kubeConfig, err := HomeKubeConfig(true) def := true if err != nil { kubeConfig = filepath.Join(config.DataDir, "kubeconfig-k3s.yaml") def = false } if config.KubeConfigOutput != "" { kubeConfig = config.KubeConfigOutput } if err = clientaccess.AgentAccessInfoToKubeConfig(kubeConfig, url, clientToken); err != nil { logrus.Errorf("Failed to generate kubeconfig: %v", err) } if config.KubeConfigMode != "" { mode, err := strconv.ParseInt(config.KubeConfigMode, 8, 0) if err == nil { os.Chmod(kubeConfig, os.FileMode(mode)) } else { logrus.Errorf("failed to set %s to mode %s: %v", kubeConfig, os.FileMode(mode), err) } } else { os.Chmod(kubeConfig, os.FileMode(0644)) } logrus.Infof("Wrote kubeconfig %s", kubeConfig) if def { logrus.Infof("Run: %s kubectl", filepath.Base(os.Args[0])) } } func setupDataDirAndChdir(config *config.Control) error { var ( err error ) config.DataDir, err = resolveDataDir(config.DataDir) if err != nil { return err } dataDir := config.DataDir if err := os.MkdirAll(dataDir, 0700); err != nil { return errors.Wrapf(err, "can not mkdir %s", dataDir) } if err := os.Chdir(dataDir); err != nil { return errors.Wrapf(err, "can not chdir %s", dataDir) } return nil } func printToken(httpsPort int, advertiseIP, prefix, cmd string) { ip := advertiseIP if ip == "" { hostIP, err := net.ChooseHostInterface() if err != nil { logrus.Error(err) } ip = hostIP.String() } logrus.Infof("%s k3s %s -s https://%s:%d -t ${NODE_TOKEN}", prefix, cmd, ip, httpsPort) } func FormatToken(token string, certs string) string { if len(token) == 0 { return token } prefix := "K10" if len(certs) > 0 { digest := sha256.Sum256([]byte(certs)) prefix = "K10" + hex.EncodeToString(digest[:]) + "::" } return prefix + token } func writeToken(token, file, certs string) error { if len(token) == 0 { return nil } token = FormatToken(token, certs) return ioutil.WriteFile(file, []byte(token+"\n"), 0600) }