| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 4 | package main |
| 5 | |
| 6 | import ( |
| 7 | "context" |
| Tim Windelschmidt | d5f851b | 2024-04-23 14:59:37 +0200 | [diff] [blame] | 8 | "errors" |
| Tim Windelschmidt | 0b4fb8c | 2024-09-18 17:34:23 +0200 | [diff] [blame] | 9 | "fmt" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 10 | "log" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 11 | "os" |
| Lorenz Brun | 20d1dd1 | 2022-07-01 12:21:42 +0000 | [diff] [blame] | 12 | "os/exec" |
| Tim Windelschmidt | b765f24 | 2024-05-08 01:40:02 +0200 | [diff] [blame] | 13 | "os/signal" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 14 | |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 15 | "github.com/spf13/cobra" |
| Serge Bazanski | 925ec3d | 2024-02-05 14:38:20 +0100 | [diff] [blame] | 16 | "google.golang.org/grpc" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 17 | |
| Mateusz Zalega | 18a67b0 | 2022-08-02 13:37:50 +0200 | [diff] [blame] | 18 | "source.monogon.dev/metropolis/cli/metroctl/core" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 19 | "source.monogon.dev/metropolis/node/core/rpc" |
| Serge Bazanski | 925ec3d | 2024-02-05 14:38:20 +0100 | [diff] [blame] | 20 | "source.monogon.dev/metropolis/node/core/rpc/resolver" |
| Serge Bazanski | dc1bec4 | 2021-12-16 17:38:53 +0100 | [diff] [blame] | 21 | apb "source.monogon.dev/metropolis/proto/api" |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 22 | ) |
| 23 | |
| 24 | var takeownershipCommand = &cobra.Command{ |
| Mateusz Zalega | b1e7ee4 | 2022-07-08 12:19:02 +0200 | [diff] [blame] | 25 | Use: "takeownership", |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 26 | Short: "Takes ownership of a new Metropolis cluster", |
| 27 | Long: `This takes ownership of a new Metropolis cluster by asking the new |
| 28 | cluster to issue an owner certificate to for the owner key generated by a |
| Mateusz Zalega | b1e7ee4 | 2022-07-08 12:19:02 +0200 | [diff] [blame] | 29 | previous invocation of metroctl install on this machine. A single cluster |
| 30 | endpoint must be provided with the --endpoints parameter.`, |
| Tim Windelschmidt | fc6e1cf | 2024-09-18 17:34:07 +0200 | [diff] [blame] | 31 | Args: PrintUsageOnWrongArgs(cobra.ExactArgs(0)), |
| Tim Windelschmidt | 0b4fb8c | 2024-09-18 17:34:23 +0200 | [diff] [blame] | 32 | RunE: func(cmd *cobra.Command, _ []string) error { |
| 33 | ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) |
| 34 | if len(flags.clusterEndpoints) != 1 { |
| 35 | return fmt.Errorf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter") |
| Lorenz Brun | 20d1dd1 | 2022-07-01 12:21:42 +0000 | [diff] [blame] | 36 | } |
| Tim Windelschmidt | 0b4fb8c | 2024-09-18 17:34:23 +0200 | [diff] [blame] | 37 | |
| 38 | contextName, err := cmd.Flags().GetString("context") |
| 39 | if err != nil || contextName == "" { |
| 40 | return fmt.Errorf("takeownership requires a valid context name to be provided with the --context parameter") |
| 41 | } |
| 42 | |
| 43 | ca, err := core.GetClusterCAWithTOFU(ctx, connectOptions()) |
| 44 | if err != nil { |
| 45 | return fmt.Errorf("could not retrieve cluster CA: %w", err) |
| 46 | } |
| 47 | |
| 48 | // Retrieve the cluster owner's private key, and use it to construct |
| 49 | // ephemeral credentials. Then, dial the cluster. |
| 50 | opk, err := core.GetOwnerKey(flags.configPath) |
| 51 | if errors.Is(err, core.ErrNoCredentials) { |
| 52 | 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") |
| 53 | } |
| 54 | if err != nil { |
| 55 | return fmt.Errorf("couldn't get owner's key: %w", err) |
| 56 | } |
| 57 | opts, err := core.DialOpts(ctx, connectOptions()) |
| 58 | if err != nil { |
| 59 | return fmt.Errorf("while configuring cluster dial opts: %w", err) |
| 60 | } |
| 61 | creds, err := rpc.NewEphemeralCredentials(opk, rpc.WantRemoteCluster(ca)) |
| 62 | if err != nil { |
| 63 | return fmt.Errorf("while generating ephemeral credentials: %w", err) |
| 64 | } |
| 65 | opts = append(opts, grpc.WithTransportCredentials(creds)) |
| 66 | |
| Tim Windelschmidt | 9bd9bd4 | 2025-02-14 17:08:52 +0100 | [diff] [blame] | 67 | cc, err := grpc.NewClient(resolver.MetropolisControlAddress, opts...) |
| Tim Windelschmidt | 0b4fb8c | 2024-09-18 17:34:23 +0200 | [diff] [blame] | 68 | if err != nil { |
| Tim Windelschmidt | 9bd9bd4 | 2025-02-14 17:08:52 +0100 | [diff] [blame] | 69 | return fmt.Errorf("while creating client: %w", err) |
| Tim Windelschmidt | 0b4fb8c | 2024-09-18 17:34:23 +0200 | [diff] [blame] | 70 | } |
| 71 | aaa := apb.NewAAAClient(cc) |
| 72 | |
| 73 | ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk) |
| 74 | if err != nil { |
| 75 | return fmt.Errorf("failed to retrive owner certificate from cluster: %w", err) |
| 76 | } |
| 77 | |
| 78 | if err := core.WriteOwnerCertificate(flags.configPath, ownerCert.Certificate[0]); err != nil { |
| 79 | log.Printf("Failed to store retrieved owner certificate: %v", err) |
| 80 | 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") |
| 81 | } |
| 82 | log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...") |
| 83 | |
| 84 | // If the user has metroctl in their path, use the metroctl from path as |
| 85 | // a credential plugin. Otherwise use the path to the currently-running |
| 86 | // metroctl. |
| 87 | metroctlPath := "metroctl" |
| 88 | if _, err := exec.LookPath("metroctl"); err != nil { |
| 89 | metroctlPath, err = os.Executable() |
| 90 | if err != nil { |
| 91 | return fmt.Errorf("failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %w", err) |
| 92 | } |
| 93 | } |
| 94 | // TODO(q3k, issues/144): this only works as long as all nodes are kubernetes controller |
| 95 | // nodes. This won't be the case for too long. Figure this out. |
| 96 | configName := "metroctl" |
| 97 | if err := core.InstallKubeletConfig(ctx, metroctlPath, connectOptions(), configName, flags.clusterEndpoints[0]); err != nil { |
| 98 | return fmt.Errorf("failed to install metroctl/k8s integration: %w", err) |
| 99 | } |
| 100 | log.Printf("Success! kubeconfig is set up. You can now run kubectl --context=%s ... to access the Kubernetes cluster.", configName) |
| 101 | return nil |
| 102 | }, |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | func init() { |
| Tim Windelschmidt | 0a8797d | 2024-03-04 18:58:47 +0100 | [diff] [blame] | 106 | takeownershipCommand.Flags().String("context", "metroctl", "The name for the kubernetes context to configure") |
| Serge Bazanski | beec27c | 2024-10-31 12:27:08 +0000 | [diff] [blame] | 107 | clusterCmd.AddCommand(takeownershipCommand) |
| Lorenz Brun | a9b455f | 2021-12-07 03:53:22 +0100 | [diff] [blame] | 108 | } |