blob: d6d9df6f61251901e33fa3b3abc124c38eafeadf [file] [log] [blame]
Lorenz Bruna9b455f2021-12-07 03:53:22 +01001package main
2
3import (
4 "context"
5 "crypto/ed25519"
6 "encoding/pem"
7 "log"
8 "net"
9 "os"
Lorenz Brun20d1dd12022-07-01 12:21:42 +000010 "os/exec"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010011 "path/filepath"
12
13 "github.com/adrg/xdg"
14 "github.com/spf13/cobra"
Serge Bazanski399ce552022-03-29 12:52:42 +020015 "google.golang.org/grpc"
Lorenz Brun20d1dd12022-07-01 12:21:42 +000016 clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1"
17 "k8s.io/client-go/tools/clientcmd"
18 clientapi "k8s.io/client-go/tools/clientcmd/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010019
Lorenz Bruna9b455f2021-12-07 03:53:22 +010020 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
21 "source.monogon.dev/metropolis/node"
22 "source.monogon.dev/metropolis/node/core/rpc"
Serge Bazanskidc1bec42021-12-16 17:38:53 +010023 apb "source.monogon.dev/metropolis/proto/api"
Lorenz Bruna9b455f2021-12-07 03:53:22 +010024)
25
26var takeownershipCommand = &cobra.Command{
27 Use: "takeownership <node-addr>",
28 Short: "Takes ownership of a new Metropolis cluster",
29 Long: `This takes ownership of a new Metropolis cluster by asking the new
30cluster to issue an owner certificate to for the owner key generated by a
31previous invocation of metroctl install on this machine.`,
32 Example: "takeownership 192.0.2.1",
33 Args: cobra.ExactArgs(1), // One positional argument: the node address
34 Run: doTakeOwnership,
35}
36
37func doTakeOwnership(cmd *cobra.Command, args []string) {
38 ctx := clicontext.WithInterrupt(context.Background())
39 ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
40 if os.IsNotExist(err) {
41 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.")
42 } else if err != nil {
43 log.Fatalf("Failed to load owner private key: %v", err)
44 }
45 block, _ := pem.Decode(ownerPrivateKeyPEM)
46 if block == nil {
47 log.Fatalf("owner-key.pem contains invalid PEM")
48 }
49 if block.Type != ownerKeyType {
50 log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType)
51 }
52 if len(block.Bytes) != ed25519.PrivateKeySize {
53 log.Fatal("owner-key.pem contains non-Ed25519 key")
54 }
55 ownerPrivateKey := ed25519.PrivateKey(block.Bytes)
56
Serge Bazanski399ce552022-03-29 12:52:42 +020057 ephCreds, err := rpc.NewEphemeralCredentials(ownerPrivateKey, nil)
58 if err != nil {
59 log.Fatalf("Failed to create ephemeral credentials: %v", err)
60 }
61 client, err := grpc.Dial(net.JoinHostPort(args[0], node.CuratorServicePort.PortString()), grpc.WithTransportCredentials(ephCreds))
Lorenz Bruna9b455f2021-12-07 03:53:22 +010062 if err != nil {
63 log.Fatalf("Failed to create client to given node address: %v", err)
64 }
65 defer client.Close()
66 aaa := apb.NewAAAClient(client)
67 ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, ownerPrivateKey)
68 if err != nil {
69 log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
70 }
71 ownerCertPEM := pem.Block{
72 Type: "CERTIFICATE",
73 Bytes: ownerCert.Certificate[0],
74 }
75 if err := os.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner.pem"), pem.EncodeToMemory(&ownerCertPEM), 0644); err != nil {
76 log.Printf("Failed to store retrieved owner certificate: %v", err)
77 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.")
78 }
Lorenz Brun20d1dd12022-07-01 12:21:42 +000079 log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
80
81 ca := clientcmd.NewDefaultPathOptions()
82 config, err := ca.GetStartingConfig()
83 if err != nil {
84 log.Fatalf("Failed to get initial kubeconfig to add Metropolis cluster: %v", err)
85 }
86 // If the user has metroctl in their path, use the metroctl from path as
87 // a credential plugin. Otherwise use the path to the currently-running
88 // metroctl.
89 metroctlPath := "metroctl"
90 if _, err := exec.LookPath("metroctl"); err != nil {
91 metroctlPath, err = os.Executable()
92 if err != nil {
93 log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
94 }
95 }
96
97 config.AuthInfos["metropolis"] = &clientapi.AuthInfo{
98 Exec: &clientapi.ExecConfig{
99 APIVersion: clientauthentication.SchemeGroupVersion.String(),
100 Command: metroctlPath,
101 Args: []string{k8scredpluginCmd.Use},
102 InstallHint: `Authenticating to Metropolis clusters requires metroctl to be present.
103Running metroctl takeownership creates this entry and either points to metroctl as a command in
104PATH if metroctl is in PATH at that time or to the absolute path to metroctl at that time.
105If you moved metroctl afterwards or want to switch to PATH resolution, edit $HOME/.kube/config and
106change users.metropolis.exec.command to the required path (or just metroctl if using PATH resolution).`,
107 InteractiveMode: clientapi.NeverExecInteractiveMode,
108 },
109 }
110
111 config.Clusters["metropolis"] = &clientapi.Cluster{
112 // MVP: This is insecure, but making this work would be wasted effort
113 // as all of it will be replaced by the identity system.
114 InsecureSkipTLSVerify: true,
115 Server: "https://" + net.JoinHostPort(args[0], node.KubernetesAPIWrappedPort.PortString()),
116 }
117
118 config.Contexts["metropolis"] = &clientapi.Context{
119 AuthInfo: "metropolis",
120 Cluster: "metropolis",
121 Namespace: "default",
122 }
123
124 // Only set us as the current context if no other exists. Changing that
125 // unprompted would be kind of rude.
126 if config.CurrentContext == "" {
127 config.CurrentContext = "metropolis"
128 }
129
130 if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
131 log.Fatalf("Failed to modify kubeconfig to add Metropolis cluster: %v", err)
132 }
133 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 +0100134}
135
136func init() {
137 rootCmd.AddCommand(takeownershipCommand)
138}