blob: 3566ea077251b78e37298e817908cd86f2a189ba [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Bruna9b455f2021-12-07 03:53:22 +01004package main
5
6import (
7 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02008 "errors"
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +02009 "fmt"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010010 "log"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010011 "os"
Lorenz Brun20d1dd12022-07-01 12:21:42 +000012 "os/exec"
Tim Windelschmidtb765f242024-05-08 01:40:02 +020013 "os/signal"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010014
Lorenz Bruna9b455f2021-12-07 03:53:22 +010015 "github.com/spf13/cobra"
Serge Bazanski925ec3d2024-02-05 14:38:20 +010016 "google.golang.org/grpc"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010017
Mateusz Zalega18a67b02022-08-02 13:37:50 +020018 "source.monogon.dev/metropolis/cli/metroctl/core"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010019 "source.monogon.dev/metropolis/node/core/rpc"
Serge Bazanski925ec3d2024-02-05 14:38:20 +010020 "source.monogon.dev/metropolis/node/core/rpc/resolver"
Serge Bazanskidc1bec42021-12-16 17:38:53 +010021 apb "source.monogon.dev/metropolis/proto/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010022)
23
24var takeownershipCommand = &cobra.Command{
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020025 Use: "takeownership",
Lorenz Bruna9b455f2021-12-07 03:53:22 +010026 Short: "Takes ownership of a new Metropolis cluster",
27 Long: `This takes ownership of a new Metropolis cluster by asking the new
28cluster to issue an owner certificate to for the owner key generated by a
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020029previous invocation of metroctl install on this machine. A single cluster
30endpoint must be provided with the --endpoints parameter.`,
Tim Windelschmidtfc6e1cf2024-09-18 17:34:07 +020031 Args: PrintUsageOnWrongArgs(cobra.ExactArgs(0)),
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +020032 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 Brun20d1dd12022-07-01 12:21:42 +000036 }
Tim Windelschmidt0b4fb8c2024-09-18 17:34:23 +020037
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
67 cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
68 if err != nil {
69 return fmt.Errorf("while dialing the cluster: %w", err)
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 Bruna9b455f2021-12-07 03:53:22 +0100103}
104
105func init() {
Tim Windelschmidt0a8797d2024-03-04 18:58:47 +0100106 takeownershipCommand.Flags().String("context", "metroctl", "The name for the kubernetes context to configure")
Serge Bazanskibeec27c2024-10-31 12:27:08 +0000107 clusterCmd.AddCommand(takeownershipCommand)
Lorenz Bruna9b455f2021-12-07 03:53:22 +0100108}