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