blob: 6102cf1c489c465d9407755ffa5e270ec4440216 [file] [log] [blame]
Lorenz Bruna9b455f2021-12-07 03:53:22 +01001package main
2
3import (
4 "context"
Lorenz Bruna9b455f2021-12-07 03:53:22 +01005 "encoding/pem"
6 "log"
7 "net"
8 "os"
Lorenz Brun20d1dd12022-07-01 12:21:42 +00009 "os/exec"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010010 "path/filepath"
11
Lorenz Bruna9b455f2021-12-07 03:53:22 +010012 "github.com/spf13/cobra"
Lorenz Brun20d1dd12022-07-01 12:21:42 +000013 clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1"
14 "k8s.io/client-go/tools/clientcmd"
15 clientapi "k8s.io/client-go/tools/clientcmd/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010016
Mateusz Zalega18a67b02022-08-02 13:37:50 +020017 "source.monogon.dev/metropolis/cli/metroctl/core"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010018 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
19 "source.monogon.dev/metropolis/node"
20 "source.monogon.dev/metropolis/node/core/rpc"
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.`,
31 Args: cobra.ExactArgs(0),
32 Run: doTakeOwnership,
Lorenz Bruna9b455f2021-12-07 03:53:22 +010033}
34
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020035func doTakeOwnership(cmd *cobra.Command, _ []string) {
36 if len(flags.clusterEndpoints) != 1 {
37 log.Fatalf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter.")
38 }
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020039
Mateusz Zalega18464502022-07-14 16:18:26 +020040 // Retrieve the cluster owner's private key, and use it to construct
41 // ephemeral credentials. Then, dial the cluster.
42 opk, err := getOwnerKey()
43 if err == noCredentialsError {
Lorenz Bruna9b455f2021-12-07 03:53:22 +010044 log.Fatalf("Owner key does not exist. takeownership needs to be executed on the same system that has previously installed the cluster using metroctl install.")
Lorenz Bruna9b455f2021-12-07 03:53:22 +010045 }
Mateusz Zalega18464502022-07-14 16:18:26 +020046 if err != nil {
47 log.Fatalf("Couldn't get owner's key: %v", err)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010048 }
Mateusz Zalega18464502022-07-14 16:18:26 +020049 ctx := clicontext.WithInterrupt(context.Background())
Mateusz Zalega18a67b02022-08-02 13:37:50 +020050 cc, err := core.DialCluster(ctx, opk, nil, flags.proxyAddr, flags.clusterEndpoints, rpcLogger)
Mateusz Zalega18464502022-07-14 16:18:26 +020051 if err != nil {
52 log.Fatalf("While dialing the cluster: %v", err)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010053 }
Mateusz Zalega18464502022-07-14 16:18:26 +020054 aaa := apb.NewAAAClient(cc)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010055
Mateusz Zalega18464502022-07-14 16:18:26 +020056 ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010057 if err != nil {
58 log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
59 }
60 ownerCertPEM := pem.Block{
61 Type: "CERTIFICATE",
62 Bytes: ownerCert.Certificate[0],
63 }
Mateusz Zalega8234c162022-07-08 17:05:50 +020064 if err := os.WriteFile(filepath.Join(flags.configPath, "owner.pem"), pem.EncodeToMemory(&ownerCertPEM), 0644); err != nil {
Lorenz Bruna9b455f2021-12-07 03:53:22 +010065 log.Printf("Failed to store retrieved owner certificate: %v", err)
66 log.Fatalln("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.")
67 }
Lorenz Brun20d1dd12022-07-01 12:21:42 +000068 log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
69
70 ca := clientcmd.NewDefaultPathOptions()
71 config, err := ca.GetStartingConfig()
72 if err != nil {
73 log.Fatalf("Failed to get initial kubeconfig to add Metropolis cluster: %v", err)
74 }
75 // If the user has metroctl in their path, use the metroctl from path as
76 // a credential plugin. Otherwise use the path to the currently-running
77 // metroctl.
78 metroctlPath := "metroctl"
79 if _, err := exec.LookPath("metroctl"); err != nil {
80 metroctlPath, err = os.Executable()
81 if err != nil {
82 log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
83 }
84 }
85
86 config.AuthInfos["metropolis"] = &clientapi.AuthInfo{
87 Exec: &clientapi.ExecConfig{
88 APIVersion: clientauthentication.SchemeGroupVersion.String(),
89 Command: metroctlPath,
90 Args: []string{k8scredpluginCmd.Use},
91 InstallHint: `Authenticating to Metropolis clusters requires metroctl to be present.
92Running metroctl takeownership creates this entry and either points to metroctl as a command in
93PATH if metroctl is in PATH at that time or to the absolute path to metroctl at that time.
94If you moved metroctl afterwards or want to switch to PATH resolution, edit $HOME/.kube/config and
95change users.metropolis.exec.command to the required path (or just metroctl if using PATH resolution).`,
96 InteractiveMode: clientapi.NeverExecInteractiveMode,
97 },
98 }
99
100 config.Clusters["metropolis"] = &clientapi.Cluster{
101 // MVP: This is insecure, but making this work would be wasted effort
102 // as all of it will be replaced by the identity system.
Mateusz Zalega18464502022-07-14 16:18:26 +0200103 // TODO(issues/144): adjust cluster endpoints once have functioning roles
104 // implemented.
Lorenz Brun20d1dd12022-07-01 12:21:42 +0000105 InsecureSkipTLSVerify: true,
Mateusz Zalega18464502022-07-14 16:18:26 +0200106 Server: "https://" + net.JoinHostPort(flags.clusterEndpoints[0], node.KubernetesAPIWrappedPort.PortString()),
Lorenz Brun20d1dd12022-07-01 12:21:42 +0000107 }
108
109 config.Contexts["metropolis"] = &clientapi.Context{
110 AuthInfo: "metropolis",
111 Cluster: "metropolis",
112 Namespace: "default",
113 }
114
115 // Only set us as the current context if no other exists. Changing that
116 // unprompted would be kind of rude.
117 if config.CurrentContext == "" {
118 config.CurrentContext = "metropolis"
119 }
120
121 if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
122 log.Fatalf("Failed to modify kubeconfig to add Metropolis cluster: %v", err)
123 }
124 log.Println("Success! kubeconfig is set up. You can now run kubectl --context=metropolis ... to access the Kubernetes cluster.")
Lorenz Bruna9b455f2021-12-07 03:53:22 +0100125}
126
127func init() {
128 rootCmd.AddCommand(takeownershipCommand)
129}