blob: ab31610e8c183b12b0a2fed7b0f32082b0a21bd6 [file] [log] [blame]
Lorenz Brune6573102021-11-02 14:15:37 +01001package main
2
3import (
4 "crypto/ed25519"
5 "crypto/rand"
6 "encoding/pem"
7 "io/ioutil"
8 "log"
9 "os"
10 "path/filepath"
11
12 "github.com/adrg/xdg"
13 "github.com/spf13/cobra"
14
15 "source.monogon.dev/metropolis/cli/metroctl/core"
16 "source.monogon.dev/metropolis/proto/api"
17)
18
19var installCmd = &cobra.Command{
20 Short: "Contains subcommands to install Metropolis over different mediums.",
21 Use: "install",
22}
23
24// install flags
25var installer *string
26var bundle *string
27
28var genusbCmd = &cobra.Command{
29 Use: "genusb target --installer=inst.efi --bundle=bundle.bin",
30 Short: "Generates a Metropolis installer disk or image.",
31 Example: "metroctl install genusb /dev/sdx --installer=installer_x86_64.efi --bundle=metropolis_dev_x86_64.tar.xz",
32 Args: cobra.ExactArgs(1), // One positional argument: the target
33 Run: doGenUSB,
34}
35
36// A PEM block type for a Metropolis initial owner private key
37const ownerKeyType = "METROPOLIS INITIAL OWNER PRIVATE KEY"
38
39func doGenUSB(cmd *cobra.Command, args []string) {
40 installerFile, err := os.Open(*installer)
41 if err != nil {
42 log.Fatalf("Failed to open installer: %v", err)
43 }
44 installerFileStat, err := installerFile.Stat()
45 if err != nil {
46 log.Fatalf("Failed to stat installer: %v", err)
47 }
48 var bundleFile *os.File
49 var bundleFileStat os.FileInfo
50 if bundle != nil && *bundle != "" {
51 bundleFile, err = os.Open(*bundle)
52 if err != nil {
53 log.Fatalf("Failed to open bundle: %v", err)
54 }
55 bundleFileStat, err = bundleFile.Stat()
56 if err != nil {
57 log.Fatalf("Failed to stat bundle: %v", err)
58 }
59 }
60
61 // TODO(lorenz): Have a key management story for this
62 if err := os.MkdirAll(filepath.Join(xdg.ConfigHome, "metroctl"), 0700); err != nil {
63 log.Fatalf("Failed to create config directory: %v", err)
64 }
65 var ownerPublicKey ed25519.PublicKey
66 ownerPrivateKeyPEM, err := ioutil.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
67 if os.IsNotExist(err) {
68 pub, priv, err := ed25519.GenerateKey(rand.Reader)
69 if err != nil {
70 log.Fatalf("Failed to generate owner private key: %v", err)
71 }
72 pemPriv := pem.EncodeToMemory(&pem.Block{Type: ownerKeyType, Bytes: priv})
73 if err := ioutil.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"), pemPriv, 0600); err != nil {
74 log.Fatalf("Failed to store owner private key: %v", err)
75 }
76 ownerPublicKey = pub
77 } else if err != nil {
78 log.Fatalf("Failed to load owner private key: %v", err)
79 } else {
80 block, _ := pem.Decode(ownerPrivateKeyPEM)
81 if block == nil {
82 log.Fatalf("owner-key.pem contains invalid PEM")
83 }
84 if block.Type != ownerKeyType {
85 log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType)
86 }
87 if len(block.Bytes) != ed25519.PrivateKeySize {
88 log.Fatal("owner-key.pem contains non-Ed25519 key")
89 }
90 ownerPrivateKey := ed25519.PrivateKey(block.Bytes)
91 ownerPublicKey = ownerPrivateKey.Public().(ed25519.PublicKey)
92 }
93
94 // TODO(lorenz): This can only bootstrap right now. As soon as @serge's role
95 // management has stabilized we can replace this with a proper
96 // implementation.
97 params := &api.NodeParameters{
98 Cluster: &api.NodeParameters_ClusterBootstrap_{
99 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
100 OwnerPublicKey: ownerPublicKey,
101 },
102 },
103 }
104
105 installerImageArgs := core.MakeInstallerImageArgs{
106 TargetPath: args[0],
107 Installer: installerFile,
108 InstallerSize: uint64(installerFileStat.Size()),
109 NodeParams: params,
110 }
111
112 if bundleFile != nil {
113 installerImageArgs.Bundle = bundleFile
114 installerImageArgs.BundleSize = uint64(bundleFileStat.Size())
115 }
116
117 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
118 log.Fatalf("Failed to create installer: %v", err)
119 }
120}
121
122func init() {
123 rootCmd.AddCommand(installCmd)
124 installCmd.AddCommand(genusbCmd)
125
126 bundle = installCmd.PersistentFlags().StringP("bundle", "b", "", "Metropolis bundle file to use")
127 installer = installCmd.PersistentFlags().StringP("installer", "i", "", "Metropolis installer file to use")
128}