From cdf5c5eb0003ca168a5cb636110e3ba9ba9bf4cf Mon Sep 17 00:00:00 2001 From: Fedor Korotkov Date: Wed, 22 Mar 2023 15:43:37 -0400 Subject: [PATCH] Simplified bootstrapping of a cluster (#40) * Simplified bootstrapping of a cluster Introduced a new convention about a pre-defined `bootstrap-admin` account for `orchard controller run`. Providing `ORCHARD_BOOTSTRAP_ADMIN_TOKEN` will auto-create such user for easier configuration. `bootstrap-admin` can be used for creating other service accounts on the first run and after that can be disposed. Also change `orchard worker run` to expect controller URL as the only parameter and a bootstrap token passed via an argument instead of using a context that might not be created. * Missing error check --- internal/command/context/create.go | 24 ++--------------- internal/command/controller/init.go | 2 -- internal/command/controller/run.go | 17 ++++++------ internal/command/worker/run.go | 39 ++++++++++++++++++++++++--- internal/controller/controller.go | 6 +++++ internal/netconstants/netconstants.go | 22 +++++++++++++++ internal/worker/option.go | 11 +++++++- pkg/client/option.go | 18 ++++++++++--- 8 files changed, 100 insertions(+), 39 deletions(-) diff --git a/internal/command/context/create.go b/internal/command/context/create.go index ebba4ee..b3ef690 100644 --- a/internal/command/context/create.go +++ b/internal/command/context/create.go @@ -48,21 +48,11 @@ func newCreateCommand() *cobra.Command { } func runCreate(cmd *cobra.Command, args []string) error { - addr := args[0] - - if !strings.HasPrefix(addr, "https://") && !strings.HasPrefix(addr, "http://") { - addr = "https://" + addr - } - - controllerURL, err := url.Parse(addr) + controllerURL, err := netconstants.NormalizeAddress(args[0]) if err != nil { return err } - if controllerURL.Port() == "" { - controllerURL.Host += fmt.Sprintf(":%d", netconstants.DefaultControllerPort) - } - // Establish trust var trustedControllerCertificate *x509.Certificate @@ -82,19 +72,9 @@ func runCreate(cmd *cobra.Command, args []string) error { } } - // Check that the API is accessible - privatePool := x509.NewCertPool() - privatePool.AddCert(trustedControllerCertificate) - - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS13, - RootCAs: privatePool, - ServerName: netconstants.DefaultControllerServerName, - } - client, err := client.New( client.WithAddress(controllerURL.String()), - client.WithTLSConfig(tlsConfig), + client.WithTrustedCertificate(trustedControllerCertificate), client.WithCredentials(serviceAccountName, serviceAccountToken), ) if err != nil { diff --git a/internal/command/controller/init.go b/internal/command/controller/init.go index 5e8021b..b3547e5 100644 --- a/internal/command/controller/init.go +++ b/internal/command/controller/init.go @@ -20,8 +20,6 @@ var ErrInitFailed = errors.New("controller initialization failed") var controllerCertPath string var controllerKeyPath string -var serviceAccountName string -var serviceAccountToken string func FindControllerCertificate(dataDir *controller.DataDir) (controllerCert tls.Certificate, err error) { if controllerCertPath != "" || controllerKeyPath != "" { diff --git a/internal/command/controller/run.go b/internal/command/controller/run.go index 2446c1a..b03b251 100644 --- a/internal/command/controller/run.go +++ b/internal/command/controller/run.go @@ -14,6 +14,7 @@ import ( ) var ErrRunFailed = errors.New("failed to run controller") +var BootstrapAdminAccountName = "bootstrap-admin" var address string @@ -39,11 +40,6 @@ func newRunCommand() *cobra.Command { cmd.PersistentFlags().StringVar(&controllerKeyPath, "controller-key", "", "use the controller certificate key from the specified path instead of the auto-generated one"+ " (requires --controller-cert)") - cmd.PersistentFlags().StringVar(&serviceAccountName, "superuser-account-name", "", - "optional name of a service account with maximum privileges to auto-create") - cmd.PersistentFlags().StringVar(&serviceAccountToken, "superuser-account-token", "", - "token to use when creating a service account with maximum privileges "+ - "(required when --admin-account-name is specified)") return cmd } @@ -94,14 +90,19 @@ func runController(cmd *cobra.Command, args []string) (err error) { return err } - if serviceAccountName != "" { + if adminToken, ok := os.LookupEnv("ORCHARD_BOOTSTRAP_ADMIN_TOKEN"); ok { err = controllerInstance.EnsureServiceAccount(&v1.ServiceAccount{ Meta: v1.Meta{ - Name: serviceAccountName, + Name: BootstrapAdminAccountName, }, - Token: serviceAccountToken, + Token: adminToken, Roles: v1.AllServiceAccountRoles(), }) + } else { + err = controllerInstance.DeleteServiceAccount(BootstrapAdminAccountName) + } + if err != nil { + return err } return controllerInstance.Run(cmd.Context()) diff --git a/internal/command/worker/run.go b/internal/command/worker/run.go index 8eba75e..1d45dc4 100644 --- a/internal/command/worker/run.go +++ b/internal/command/worker/run.go @@ -1,19 +1,52 @@ package worker import ( + "errors" + "github.com/cirruslabs/orchard/internal/bootstraptoken" + "github.com/cirruslabs/orchard/internal/netconstants" "github.com/cirruslabs/orchard/internal/worker" + "github.com/cirruslabs/orchard/pkg/client" "github.com/spf13/cobra" "go.uber.org/zap" ) +var ErrBootstrapTokenNotProvided = errors.New("no bootstrap token provided") + +var bootstrapTokenRaw string + func newRunCommand() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "run", RunE: runWorker, + Args: cobra.ExactArgs(1), } + cmd.PersistentFlags().StringVar(&bootstrapTokenRaw, "bootstrap-token", "", + "a bootstrap token retrieved via `orchard get bootstrap-token `") + return cmd } func runWorker(cmd *cobra.Command, args []string) (err error) { + controllerURL, err := netconstants.NormalizeAddress(args[0]) + if err != nil { + return err + } + if bootstrapTokenRaw == "" { + return ErrBootstrapTokenNotProvided + } + bootstrapToken, err := bootstraptoken.NewFromString(bootstrapTokenRaw) + if err != nil { + return err + } + + controllerClient, err := client.New( + client.WithAddress(controllerURL.String()), + client.WithTrustedCertificate(bootstrapToken.Certificate()), + client.WithCredentials(bootstrapToken.ServiceAccountName(), bootstrapToken.ServiceAccountToken()), + ) + if err != nil { + return err + } + // Initialize the logger logger, err := zap.NewProduction() if err != nil { @@ -25,10 +58,10 @@ func runWorker(cmd *cobra.Command, args []string) (err error) { } }() - worker, err := worker.New(worker.WithDataDirPath(dataDirPath), worker.WithLogger(logger)) + workerInstance, err := worker.New(worker.WithClient(controllerClient), worker.WithLogger(logger)) if err != nil { return err } - return worker.Run(cmd.Context()) + return workerInstance.Run(cmd.Context()) } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 66bc048..980f96a 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -129,6 +129,12 @@ func (controller *Controller) EnsureServiceAccount(serviceAccount *v1.ServiceAcc }) } +func (controller *Controller) DeleteServiceAccount(name string) error { + return controller.store.Update(func(txn storepkg.Transaction) error { + return txn.DeleteServiceAccount(name) + }) +} + func (controller *Controller) Run(ctx context.Context) error { // Run the scheduler so that each VM will eventually // be assigned to a specific WorkerUID diff --git a/internal/netconstants/netconstants.go b/internal/netconstants/netconstants.go index da9b764..e629ff8 100644 --- a/internal/netconstants/netconstants.go +++ b/internal/netconstants/netconstants.go @@ -1,6 +1,28 @@ package netconstants +import ( + "fmt" + "net/url" + "strings" +) + const ( DefaultControllerPort = 6120 DefaultControllerServerName = "orchard-controller" ) + +func NormalizeAddress(addr string) (*url.URL, error) { + if !strings.HasPrefix(addr, "https://") && !strings.HasPrefix(addr, "http://") { + addr = "https://" + addr + } + + controllerURL, err := url.Parse(addr) + if err != nil { + return nil, err + } + + if controllerURL.Port() == "" { + controllerURL.Host += fmt.Sprintf(":%d", DefaultControllerPort) + } + return controllerURL, nil +} diff --git a/internal/worker/option.go b/internal/worker/option.go index ea288ba..ae2957d 100644 --- a/internal/worker/option.go +++ b/internal/worker/option.go @@ -1,6 +1,9 @@ package worker -import "go.uber.org/zap" +import ( + "github.com/cirruslabs/orchard/pkg/client" + "go.uber.org/zap" +) type Option func(*Worker) @@ -15,3 +18,9 @@ func WithLogger(logger *zap.Logger) Option { worker.logger = logger.Sugar() } } + +func WithClient(client *client.Client) Option { + return func(worker *Worker) { + worker.client = client + } +} diff --git a/pkg/client/option.go b/pkg/client/option.go index 6199dc6..78d9611 100644 --- a/pkg/client/option.go +++ b/pkg/client/option.go @@ -1,6 +1,10 @@ package client -import "crypto/tls" +import ( + "crypto/tls" + "crypto/x509" + "github.com/cirruslabs/orchard/internal/netconstants" +) type Option func(*Client) @@ -10,9 +14,17 @@ func WithAddress(address string) Option { } } -func WithTLSConfig(tlsConfig *tls.Config) Option { +func WithTrustedCertificate(cert *x509.Certificate) Option { return func(client *Client) { - client.tlsConfig = tlsConfig + // Check that the API is accessible + privatePool := x509.NewCertPool() + privatePool.AddCert(cert) + + client.tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS13, + RootCAs: privatePool, + ServerName: netconstants.DefaultControllerServerName, + } } }