blob: a1a205aea630959e129decd1030b5194938f44ea [file] [log] [blame]
Lorenz Bruna9b455f2021-12-07 03:53:22 +01001package main
2
3import (
4 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02005 "errors"
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +02006 "fmt"
Lorenz Bruna9b455f2021-12-07 03:53:22 +01007 "log"
Lorenz Bruna9b455f2021-12-07 03:53:22 +01008 "os"
Lorenz Brun20d1dd12022-07-01 12:21:42 +00009 "os/exec"
Tim Windelschmidtb765f242024-05-08 01:40:02 +020010 "os/signal"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010011
Lorenz Bruna9b455f2021-12-07 03:53:22 +010012 "github.com/spf13/cobra"
Serge Bazanski925ec3d2024-02-05 14:38:20 +010013 "google.golang.org/grpc"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010014
Mateusz Zalega18a67b02022-08-02 13:37:50 +020015 "source.monogon.dev/metropolis/cli/metroctl/core"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010016 "source.monogon.dev/metropolis/node/core/rpc"
Serge Bazanski925ec3d2024-02-05 14:38:20 +010017 "source.monogon.dev/metropolis/node/core/rpc/resolver"
Serge Bazanskidc1bec42021-12-16 17:38:53 +010018 apb "source.monogon.dev/metropolis/proto/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010019)
20
21var takeownershipCommand = &cobra.Command{
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020022 Use: "takeownership",
Lorenz Bruna9b455f2021-12-07 03:53:22 +010023 Short: "Takes ownership of a new Metropolis cluster",
24 Long: `This takes ownership of a new Metropolis cluster by asking the new
25cluster to issue an owner certificate to for the owner key generated by a
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020026previous invocation of metroctl install on this machine. A single cluster
27endpoint must be provided with the --endpoints parameter.`,
Tim Windelschmidtfc6e1cf2024-09-18 17:34:07 +020028 Args: PrintUsageOnWrongArgs(cobra.ExactArgs(0)),
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +020029 RunE: func(cmd *cobra.Command, _ []string) error {
30 ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
31 if len(flags.clusterEndpoints) != 1 {
32 return fmt.Errorf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter")
Lorenz Brun20d1dd12022-07-01 12:21:42 +000033 }
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +020034
35 contextName, err := cmd.Flags().GetString("context")
36 if err != nil || contextName == "" {
37 return fmt.Errorf("takeownership requires a valid context name to be provided with the --context parameter")
38 }
39
40 ca, err := core.GetClusterCAWithTOFU(ctx, connectOptions())
41 if err != nil {
42 return fmt.Errorf("could not retrieve cluster CA: %w", err)
43 }
44
45 // Retrieve the cluster owner's private key, and use it to construct
46 // ephemeral credentials. Then, dial the cluster.
47 opk, err := core.GetOwnerKey(flags.configPath)
48 if errors.Is(err, core.ErrNoCredentials) {
49 return fmt.Errorf("owner key does not exist. takeownership needs to be executed on the same system that has previously installed the cluster using metroctl install")
50 }
51 if err != nil {
52 return fmt.Errorf("couldn't get owner's key: %w", err)
53 }
54 opts, err := core.DialOpts(ctx, connectOptions())
55 if err != nil {
56 return fmt.Errorf("while configuring cluster dial opts: %w", err)
57 }
58 creds, err := rpc.NewEphemeralCredentials(opk, rpc.WantRemoteCluster(ca))
59 if err != nil {
60 return fmt.Errorf("while generating ephemeral credentials: %w", err)
61 }
62 opts = append(opts, grpc.WithTransportCredentials(creds))
63
64 cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
65 if err != nil {
66 return fmt.Errorf("while dialing the cluster: %w", err)
67 }
68 aaa := apb.NewAAAClient(cc)
69
70 ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
71 if err != nil {
72 return fmt.Errorf("failed to retrive owner certificate from cluster: %w", err)
73 }
74
75 if err := core.WriteOwnerCertificate(flags.configPath, ownerCert.Certificate[0]); err != nil {
76 log.Printf("Failed to store retrieved owner certificate: %v", err)
77 return fmt.Errorf("sorry, the cluster has been lost as taking ownership cannot be repeated. Fix the reason the file couldn't be written and reinstall the node")
78 }
79 log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
80
81 // If the user has metroctl in their path, use the metroctl from path as
82 // a credential plugin. Otherwise use the path to the currently-running
83 // metroctl.
84 metroctlPath := "metroctl"
85 if _, err := exec.LookPath("metroctl"); err != nil {
86 metroctlPath, err = os.Executable()
87 if err != nil {
88 return fmt.Errorf("failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %w", err)
89 }
90 }
91 // TODO(q3k, issues/144): this only works as long as all nodes are kubernetes controller
92 // nodes. This won't be the case for too long. Figure this out.
93 configName := "metroctl"
94 if err := core.InstallKubeletConfig(ctx, metroctlPath, connectOptions(), configName, flags.clusterEndpoints[0]); err != nil {
95 return fmt.Errorf("failed to install metroctl/k8s integration: %w", err)
96 }
97 log.Printf("Success! kubeconfig is set up. You can now run kubectl --context=%s ... to access the Kubernetes cluster.", configName)
98 return nil
99 },
Lorenz Bruna9b455f2021-12-07 03:53:22 +0100100}
101
102func init() {
Tim Windelschmidt0a8797d2024-03-04 18:58:47 +0100103 takeownershipCommand.Flags().String("context", "metroctl", "The name for the kubernetes context to configure")
Lorenz Bruna9b455f2021-12-07 03:53:22 +0100104 rootCmd.AddCommand(takeownershipCommand)
105}