blob: 1b055009feb3ec528b13e4549139264700fb93ba [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
Lorenz Bruna9b455f2021-12-07 03:53:22 +010017 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
18 "source.monogon.dev/metropolis/node"
19 "source.monogon.dev/metropolis/node/core/rpc"
Serge Bazanskidc1bec42021-12-16 17:38:53 +010020 apb "source.monogon.dev/metropolis/proto/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010021)
22
23var takeownershipCommand = &cobra.Command{
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020024 Use: "takeownership",
Lorenz Bruna9b455f2021-12-07 03:53:22 +010025 Short: "Takes ownership of a new Metropolis cluster",
26 Long: `This takes ownership of a new Metropolis cluster by asking the new
27cluster to issue an owner certificate to for the owner key generated by a
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020028previous invocation of metroctl install on this machine. A single cluster
29endpoint must be provided with the --endpoints parameter.`,
30 Args: cobra.ExactArgs(0),
31 Run: doTakeOwnership,
Lorenz Bruna9b455f2021-12-07 03:53:22 +010032}
33
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020034func doTakeOwnership(cmd *cobra.Command, _ []string) {
35 if len(flags.clusterEndpoints) != 1 {
36 log.Fatalf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter.")
37 }
Mateusz Zalegab1e7ee42022-07-08 12:19:02 +020038
Mateusz Zalega18464502022-07-14 16:18:26 +020039 // Retrieve the cluster owner's private key, and use it to construct
40 // ephemeral credentials. Then, dial the cluster.
41 opk, err := getOwnerKey()
42 if err == noCredentialsError {
Lorenz Bruna9b455f2021-12-07 03:53:22 +010043 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 +010044 }
Mateusz Zalega18464502022-07-14 16:18:26 +020045 if err != nil {
46 log.Fatalf("Couldn't get owner's key: %v", err)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010047 }
Mateusz Zalega18464502022-07-14 16:18:26 +020048 ctx := clicontext.WithInterrupt(context.Background())
49 cc, err := dialCluster(ctx, opk, nil, flags.proxyAddr, flags.clusterEndpoints)
50 if err != nil {
51 log.Fatalf("While dialing the cluster: %v", err)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010052 }
Mateusz Zalega18464502022-07-14 16:18:26 +020053 aaa := apb.NewAAAClient(cc)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010054
Mateusz Zalega18464502022-07-14 16:18:26 +020055 ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
Lorenz Bruna9b455f2021-12-07 03:53:22 +010056 if err != nil {
57 log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
58 }
59 ownerCertPEM := pem.Block{
60 Type: "CERTIFICATE",
61 Bytes: ownerCert.Certificate[0],
62 }
Mateusz Zalega8234c162022-07-08 17:05:50 +020063 if err := os.WriteFile(filepath.Join(flags.configPath, "owner.pem"), pem.EncodeToMemory(&ownerCertPEM), 0644); err != nil {
Lorenz Bruna9b455f2021-12-07 03:53:22 +010064 log.Printf("Failed to store retrieved owner certificate: %v", err)
65 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.")
66 }
Lorenz Brun20d1dd12022-07-01 12:21:42 +000067 log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
68
69 ca := clientcmd.NewDefaultPathOptions()
70 config, err := ca.GetStartingConfig()
71 if err != nil {
72 log.Fatalf("Failed to get initial kubeconfig to add Metropolis cluster: %v", err)
73 }
74 // If the user has metroctl in their path, use the metroctl from path as
75 // a credential plugin. Otherwise use the path to the currently-running
76 // metroctl.
77 metroctlPath := "metroctl"
78 if _, err := exec.LookPath("metroctl"); err != nil {
79 metroctlPath, err = os.Executable()
80 if err != nil {
81 log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
82 }
83 }
84
85 config.AuthInfos["metropolis"] = &clientapi.AuthInfo{
86 Exec: &clientapi.ExecConfig{
87 APIVersion: clientauthentication.SchemeGroupVersion.String(),
88 Command: metroctlPath,
89 Args: []string{k8scredpluginCmd.Use},
90 InstallHint: `Authenticating to Metropolis clusters requires metroctl to be present.
91Running metroctl takeownership creates this entry and either points to metroctl as a command in
92PATH if metroctl is in PATH at that time or to the absolute path to metroctl at that time.
93If you moved metroctl afterwards or want to switch to PATH resolution, edit $HOME/.kube/config and
94change users.metropolis.exec.command to the required path (or just metroctl if using PATH resolution).`,
95 InteractiveMode: clientapi.NeverExecInteractiveMode,
96 },
97 }
98
99 config.Clusters["metropolis"] = &clientapi.Cluster{
100 // MVP: This is insecure, but making this work would be wasted effort
101 // as all of it will be replaced by the identity system.
Mateusz Zalega18464502022-07-14 16:18:26 +0200102 // TODO(issues/144): adjust cluster endpoints once have functioning roles
103 // implemented.
Lorenz Brun20d1dd12022-07-01 12:21:42 +0000104 InsecureSkipTLSVerify: true,
Mateusz Zalega18464502022-07-14 16:18:26 +0200105 Server: "https://" + net.JoinHostPort(flags.clusterEndpoints[0], node.KubernetesAPIWrappedPort.PortString()),
Lorenz Brun20d1dd12022-07-01 12:21:42 +0000106 }
107
108 config.Contexts["metropolis"] = &clientapi.Context{
109 AuthInfo: "metropolis",
110 Cluster: "metropolis",
111 Namespace: "default",
112 }
113
114 // Only set us as the current context if no other exists. Changing that
115 // unprompted would be kind of rude.
116 if config.CurrentContext == "" {
117 config.CurrentContext = "metropolis"
118 }
119
120 if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
121 log.Fatalf("Failed to modify kubeconfig to add Metropolis cluster: %v", err)
122 }
123 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 +0100124}
125
126func init() {
127 rootCmd.AddCommand(takeownershipCommand)
128}