blob: 9ad79536cc0908673c6a0d7dc684c9fc395432b2 [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"
Lorenz Brun7a510192022-07-04 15:31:38 +00007 _ "embed"
Lorenz Brun7a510192022-07-04 15:31:38 +00008 "io"
Lorenz Brune6573102021-11-02 14:15:37 +01009 "log"
10 "os"
Serge Bazanskibdc803b2023-03-27 10:55:09 +020011 "strings"
Lorenz Brune6573102021-11-02 14:15:37 +010012
Lorenz Brune6573102021-11-02 14:15:37 +010013 "github.com/spf13/cobra"
14
15 "source.monogon.dev/metropolis/cli/metroctl/core"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020016 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
Serge Bazanski97783222021-12-14 16:04:26 +010017 "source.monogon.dev/metropolis/cli/pkg/datafile"
Lorenz Brune6573102021-11-02 14:15:37 +010018 "source.monogon.dev/metropolis/proto/api"
Serge Bazanskibdc803b2023-03-27 10:55:09 +020019 cpb "source.monogon.dev/metropolis/proto/common"
Lorenz Brune6573102021-11-02 14:15:37 +010020)
21
22var installCmd = &cobra.Command{
Lorenz Brun7a510192022-07-04 15:31:38 +000023 Short: "Contains subcommands to install Metropolis via different media.",
Lorenz Brune6573102021-11-02 14:15:37 +010024 Use: "install",
25}
26
Lorenz Brun7a510192022-07-04 15:31:38 +000027var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
Serge Bazanski46a45f92023-07-19 17:09:52 +020028var installerPath = installCmd.PersistentFlags().StringP("installer", "i", "", "Path to the Metropolis installer to use when installing")
Lorenz Brun7a510192022-07-04 15:31:38 +000029
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
Serge Bazanskibdc803b2023-03-27 10:55:09 +020044var bootstrapTPMMode string
Tim Windelschmidtc09327c2023-06-14 17:48:56 +020045var bootstrapStorageSecurityPolicy string
Serge Bazanskibdc803b2023-03-27 10:55:09 +020046
Serge Bazanski46a45f92023-07-19 17:09:52 +020047type externalFile struct {
48 reader io.Reader
49 size uint64
50}
51
52func external(name, datafilePath string, flag *string) *externalFile {
53 if flag == nil || *flag == "" {
54 df, err := datafile.Get(datafilePath)
55 if err != nil {
56 log.Fatalf("No %s specified", name)
57 }
58 return &externalFile{
59 reader: bytes.NewReader(df),
60 size: uint64(len(df)),
61 }
62 }
63
64 f, err := os.Open(*bundlePath)
65 if err != nil {
66 log.Fatalf("Failed to open specified %s: %v", name, err)
67 }
68 st, err := f.Stat()
69 if err != nil {
70 log.Fatalf("Failed to stat specified %s: %v", name, err)
71 }
72 return &externalFile{
73 reader: f,
74 size: uint64(st.Size()),
75 }
76}
Lorenz Brun7a510192022-07-04 15:31:38 +000077
Lorenz Brune6573102021-11-02 14:15:37 +010078func doGenUSB(cmd *cobra.Command, args []string) {
Serge Bazanskibdc803b2023-03-27 10:55:09 +020079 var tpmMode cpb.ClusterConfiguration_TPMMode
80 switch strings.ToLower(bootstrapTPMMode) {
81 case "required", "require":
82 tpmMode = cpb.ClusterConfiguration_TPM_MODE_REQUIRED
83 case "best-effort", "besteffort":
84 tpmMode = cpb.ClusterConfiguration_TPM_MODE_BEST_EFFORT
85 case "disabled", "disable":
86 tpmMode = cpb.ClusterConfiguration_TPM_MODE_DISABLED
87 default:
88 log.Fatalf("Invalid --bootstrap-tpm-mode (must be one of: required, best-effort, disabled)")
89 }
90
Tim Windelschmidtc09327c2023-06-14 17:48:56 +020091 var bootstrapStorageSecurity cpb.ClusterConfiguration_StorageSecurityPolicy
92 switch strings.ToLower(bootstrapStorageSecurityPolicy) {
93 case "permissive":
94 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_PERMISSIVE
95 case "needs-encryption":
96 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_ENCRYPTION
97 case "needs-encryption-and-authentication":
98 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_ENCRYPTION_AND_AUTHENTICATION
99 case "needs-insecure":
100 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE
101 default:
102
103 log.Fatalf("Invalid --bootstrap-storage-security (must be one of: permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
104 }
105
Serge Bazanski46a45f92023-07-19 17:09:52 +0200106 bundle := external("bundle", "metropolis/node/bundle.zip", bundlePath)
107 installer := external("installer", "metropolis/installer/kernel.efi", installerPath)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200108 ctx := clicontext.WithInterrupt(context.Background())
109
Lorenz Brune6573102021-11-02 14:15:37 +0100110 // TODO(lorenz): Have a key management story for this
Mateusz Zalega8234c162022-07-08 17:05:50 +0200111 if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
Lorenz Brune6573102021-11-02 14:15:37 +0100112 log.Fatalf("Failed to create config directory: %v", err)
113 }
Lorenz Brune6573102021-11-02 14:15:37 +0100114
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200115 var params *api.NodeParameters
116 if bootstrap {
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100117 priv, err := core.GetOrMakeOwnerKey(flags.configPath)
118 if err != nil {
119 log.Fatalf("Failed to generate or get owner key: %v", err)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200120 }
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100121 pub := priv.Public().(ed25519.PublicKey)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200122 params = &api.NodeParameters{
123 Cluster: &api.NodeParameters_ClusterBootstrap_{
124 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100125 OwnerPublicKey: pub,
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200126 InitialClusterConfiguration: &cpb.ClusterConfiguration{
Tim Windelschmidtc09327c2023-06-14 17:48:56 +0200127 StorageSecurityPolicy: bootstrapStorageSecurity,
128 TpmMode: tpmMode,
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200129 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200130 },
Lorenz Brune6573102021-11-02 14:15:37 +0100131 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200132 }
133 } else {
Mateusz Zalegadb75e212022-08-04 17:31:34 +0200134 cc := dialAuthenticated(ctx)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200135 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],
Serge Bazanski46a45f92023-07-19 17:09:52 +0200158 Installer: installer.reader,
159 InstallerSize: installer.size,
Lorenz Brune6573102021-11-02 14:15:37 +0100160 NodeParams: params,
Serge Bazanski46a45f92023-07-19 17:09:52 +0200161 Bundle: bundle.reader,
162 BundleSize: bundle.size,
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.")
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200175 genusbCmd.Flags().StringVar(&bootstrapTPMMode, "bootstrap-tpm-mode", "required", "TPM mode to set on cluster (required, best-effort, disabled)")
Tim Windelschmidtc09327c2023-06-14 17:48:56 +0200176 genusbCmd.Flags().StringVar(&bootstrapStorageSecurityPolicy, "bootstrap-storage-security", "needs-encryption-and-authentication", "Storage security policy to set on cluster (permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
Lorenz Brune6573102021-11-02 14:15:37 +0100177 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100178}