blob: 38b82bf1bf3acd7f7d3fd4a71657ea1f81076a94 [file] [log] [blame]
Lorenz Brune6573102021-11-02 14:15:37 +01001package main
2
3import (
Serge Bazanski97783222021-12-14 16:04:26 +01004 "bytes"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +02005 "context"
Lorenz Brune6573102021-11-02 14:15:37 +01006 "crypto/ed25519"
7 "crypto/rand"
Lorenz Brun7a510192022-07-04 15:31:38 +00008 _ "embed"
Lorenz Brune6573102021-11-02 14:15:37 +01009 "encoding/pem"
Lorenz Brun7a510192022-07-04 15:31:38 +000010 "io"
Lorenz Brune6573102021-11-02 14:15:37 +010011 "log"
12 "os"
13 "path/filepath"
14
Lorenz Brune6573102021-11-02 14:15:37 +010015 "github.com/spf13/cobra"
16
17 "source.monogon.dev/metropolis/cli/metroctl/core"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020018 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
Serge Bazanski97783222021-12-14 16:04:26 +010019 "source.monogon.dev/metropolis/cli/pkg/datafile"
Lorenz Brune6573102021-11-02 14:15:37 +010020 "source.monogon.dev/metropolis/proto/api"
21)
22
23var installCmd = &cobra.Command{
Lorenz Brun7a510192022-07-04 15:31:38 +000024 Short: "Contains subcommands to install Metropolis via different media.",
Lorenz Brune6573102021-11-02 14:15:37 +010025 Use: "install",
26}
27
Lorenz Brun7a510192022-07-04 15:31:38 +000028var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
29
Lorenz Brune6573102021-11-02 14:15:37 +010030var genusbCmd = &cobra.Command{
Serge Bazanski97783222021-12-14 16:04:26 +010031 Use: "genusb target",
Lorenz Brune6573102021-11-02 14:15:37 +010032 Short: "Generates a Metropolis installer disk or image.",
Lorenz Brun7a510192022-07-04 15:31:38 +000033 Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
Lorenz Brune6573102021-11-02 14:15:37 +010034 Args: cobra.ExactArgs(1), // One positional argument: the target
35 Run: doGenUSB,
36}
37
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020038// bootstrap is a flag controlling node parameters included in the installer
39// image. If set, the installed node will bootstrap a new cluster. Otherwise,
40// it will try to connect to the cluster which endpoints were provided with
41// the --endpoints flag.
42var bootstrap bool
43
Lorenz Brune6573102021-11-02 14:15:37 +010044// A PEM block type for a Metropolis initial owner private key
45const ownerKeyType = "METROPOLIS INITIAL OWNER PRIVATE KEY"
46
Lorenz Brun7a510192022-07-04 15:31:38 +000047//go:embed metropolis/installer/kernel.efi
48var installer []byte
49
Lorenz Brune6573102021-11-02 14:15:37 +010050func doGenUSB(cmd *cobra.Command, args []string) {
Lorenz Brun7a510192022-07-04 15:31:38 +000051 var bundleReader io.Reader
52 var bundleSize uint64
53 if bundlePath == nil || *bundlePath == "" {
54 // Attempt Bazel runfile bundle if not explicitly set
55 bundle, err := datafile.Get("metropolis/node/bundle.zip")
56 if err != nil {
57 log.Fatalf("No bundle specified and fallback to runfiles failed: %v", err)
58 }
59 bundleReader = bytes.NewReader(bundle)
60 bundleSize = uint64(len(bundle))
61 } else {
62 // Load bundle from specified path
63 bundle, err := os.Open(*bundlePath)
64 if err != nil {
65 log.Fatalf("Failed to open specified bundle: %v", err)
66 }
67 bundleStat, err := bundle.Stat()
68 if err != nil {
69 log.Fatalf("Failed to stat specified bundle: %v", err)
70 }
71 bundleReader = bundle
72 bundleSize = uint64(bundleStat.Size())
73 }
Lorenz Brune6573102021-11-02 14:15:37 +010074
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020075 ctx := clicontext.WithInterrupt(context.Background())
76
Lorenz Brune6573102021-11-02 14:15:37 +010077 // TODO(lorenz): Have a key management story for this
Mateusz Zalega8234c162022-07-08 17:05:50 +020078 if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
Lorenz Brune6573102021-11-02 14:15:37 +010079 log.Fatalf("Failed to create config directory: %v", err)
80 }
Lorenz Brune6573102021-11-02 14:15:37 +010081
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020082 var params *api.NodeParameters
83 if bootstrap {
84 var ownerPublicKey ed25519.PublicKey
Mateusz Zalega8234c162022-07-08 17:05:50 +020085 ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(flags.configPath, "owner-key.pem"))
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020086 if os.IsNotExist(err) {
87 pub, priv, err := ed25519.GenerateKey(rand.Reader)
88 if err != nil {
89 log.Fatalf("Failed to generate owner private key: %v", err)
90 }
91 pemPriv := pem.EncodeToMemory(&pem.Block{Type: ownerKeyType, Bytes: priv})
Mateusz Zalega8234c162022-07-08 17:05:50 +020092 if err := os.WriteFile(filepath.Join(flags.configPath, "owner-key.pem"), pemPriv, 0600); err != nil {
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020093 log.Fatalf("Failed to store owner private key: %v", err)
94 }
95 ownerPublicKey = pub
96 } else if err != nil {
97 log.Fatalf("Failed to load owner private key: %v", err)
98 } else {
99 block, _ := pem.Decode(ownerPrivateKeyPEM)
100 if block == nil {
101 log.Fatalf("owner-key.pem contains invalid PEM")
102 }
103 if block.Type != ownerKeyType {
104 log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType)
105 }
106 if len(block.Bytes) != ed25519.PrivateKeySize {
107 log.Fatal("owner-key.pem contains non-Ed25519 key")
108 }
109 ownerPrivateKey := ed25519.PrivateKey(block.Bytes)
110 ownerPublicKey = ownerPrivateKey.Public().(ed25519.PublicKey)
111 }
112
113 params = &api.NodeParameters{
114 Cluster: &api.NodeParameters_ClusterBootstrap_{
115 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
116 OwnerPublicKey: ownerPublicKey,
117 },
Lorenz Brune6573102021-11-02 14:15:37 +0100118 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200119 }
120 } else {
121 ocert, opkey, err := getCredentials()
122 if err == noCredentialsError {
123 log.Fatalf("In order to create a non-bootstrap node installer, you have to take ownership of the cluster first: %v", err)
124 }
125 if err != nil {
126 log.Fatalf("While retrieving owner credentials: %v", err)
127 }
128 if len(flags.clusterEndpoints) == 0 {
129 log.Fatal("At least one cluster endpoint is required while generating non-bootstrap installer images.")
130 }
Mateusz Zalegaf7774962022-07-08 12:26:55 +0200131 cc, err := dialCluster(ctx, opkey, ocert, flags.proxyAddr, flags.clusterEndpoints)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200132 if err != nil {
133 log.Fatalf("While dialing the cluster: %v", err)
134 }
135 mgmt := api.NewManagementClient(cc)
136 resT, err := mgmt.GetRegisterTicket(ctx, &api.GetRegisterTicketRequest{})
137 if err != nil {
138 log.Fatalf("While receiving register ticket: %v", err)
139 }
140 resI, err := mgmt.GetClusterInfo(ctx, &api.GetClusterInfoRequest{})
141 if err != nil {
142 log.Fatalf("While receiving cluster directory: %v", err)
143 }
144
145 params = &api.NodeParameters{
146 Cluster: &api.NodeParameters_ClusterRegister_{
147 ClusterRegister: &api.NodeParameters_ClusterRegister{
148 RegisterTicket: resT.Ticket,
149 ClusterDirectory: resI.ClusterDirectory,
150 CaCertificate: resI.CaCertificate,
151 },
152 },
153 }
Lorenz Brune6573102021-11-02 14:15:37 +0100154 }
155
156 installerImageArgs := core.MakeInstallerImageArgs{
157 TargetPath: args[0],
Lorenz Brun7a510192022-07-04 15:31:38 +0000158 Installer: bytes.NewReader(installer),
Serge Bazanski97783222021-12-14 16:04:26 +0100159 InstallerSize: uint64(len(installer)),
Lorenz Brune6573102021-11-02 14:15:37 +0100160 NodeParams: params,
Lorenz Brun7a510192022-07-04 15:31:38 +0000161 Bundle: bundleReader,
162 BundleSize: bundleSize,
Lorenz Brune6573102021-11-02 14:15:37 +0100163 }
164
Serge Bazanski97783222021-12-14 16:04:26 +0100165 log.Printf("Generating installer image (this can take a while, see issues/92).")
Lorenz Brune6573102021-11-02 14:15:37 +0100166 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
167 log.Fatalf("Failed to create installer: %v", err)
168 }
169}
170
171func init() {
172 rootCmd.AddCommand(installCmd)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200173
174 genusbCmd.Flags().BoolVar(&bootstrap, "bootstrap", false, "Create a bootstrap installer image.")
Lorenz Brune6573102021-11-02 14:15:37 +0100175 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100176}