blob: 64cdb26b2d1cb0d1a14d8b5a9ecd81ded22f3ef9 [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
Tim Windelschmidt2a1d1b22024-02-06 07:07:42 +010013 "github.com/bazelbuild/rules_go/go/runfiles"
Lorenz Brune6573102021-11-02 14:15:37 +010014 "github.com/spf13/cobra"
15
Tim Windelschmidt2a1d1b22024-02-06 07:07:42 +010016 "source.monogon.dev/metropolis/proto/api"
17 cpb "source.monogon.dev/metropolis/proto/common"
18
Lorenz Brune6573102021-11-02 14:15:37 +010019 "source.monogon.dev/metropolis/cli/metroctl/core"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020020 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
Lorenz Brunad131882023-06-28 16:42:20 +020021 "source.monogon.dev/metropolis/pkg/blkio"
22 "source.monogon.dev/metropolis/pkg/fat32"
Lorenz Brune6573102021-11-02 14:15:37 +010023)
24
25var installCmd = &cobra.Command{
Lorenz Brun7a510192022-07-04 15:31:38 +000026 Short: "Contains subcommands to install Metropolis via different media.",
Lorenz Brune6573102021-11-02 14:15:37 +010027 Use: "install",
28}
29
Lorenz Brun7a510192022-07-04 15:31:38 +000030var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
Serge Bazanski46a45f92023-07-19 17:09:52 +020031var installerPath = installCmd.PersistentFlags().StringP("installer", "i", "", "Path to the Metropolis installer to use when installing")
Lorenz Brun7a510192022-07-04 15:31:38 +000032
Lorenz Brune6573102021-11-02 14:15:37 +010033var genusbCmd = &cobra.Command{
Serge Bazanski97783222021-12-14 16:04:26 +010034 Use: "genusb target",
Lorenz Brune6573102021-11-02 14:15:37 +010035 Short: "Generates a Metropolis installer disk or image.",
Lorenz Brun7a510192022-07-04 15:31:38 +000036 Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
Lorenz Brune6573102021-11-02 14:15:37 +010037 Args: cobra.ExactArgs(1), // One positional argument: the target
38 Run: doGenUSB,
39}
40
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020041// bootstrap is a flag controlling node parameters included in the installer
42// image. If set, the installed node will bootstrap a new cluster. Otherwise,
43// it will try to connect to the cluster which endpoints were provided with
44// the --endpoints flag.
45var bootstrap bool
46
Serge Bazanskibdc803b2023-03-27 10:55:09 +020047var bootstrapTPMMode string
Tim Windelschmidtc09327c2023-06-14 17:48:56 +020048var bootstrapStorageSecurityPolicy string
Serge Bazanskibdc803b2023-03-27 10:55:09 +020049
Serge Bazanski46a45f92023-07-19 17:09:52 +020050type externalFile struct {
51 reader io.Reader
52 size uint64
53}
54
Lorenz Brunad131882023-06-28 16:42:20 +020055func external(name, datafilePath string, flag *string) fat32.SizedReader {
Serge Bazanski46a45f92023-07-19 17:09:52 +020056 if flag == nil || *flag == "" {
Tim Windelschmidt2a1d1b22024-02-06 07:07:42 +010057 rPath, err := runfiles.Rlocation(datafilePath)
Serge Bazanski46a45f92023-07-19 17:09:52 +020058 if err != nil {
59 log.Fatalf("No %s specified", name)
60 }
Tim Windelschmidt2a1d1b22024-02-06 07:07:42 +010061 df, err := os.ReadFile(rPath)
62 if err != nil {
63 log.Fatalf("Cant read file: %v", err)
64 }
Lorenz Brunad131882023-06-28 16:42:20 +020065 return bytes.NewReader(df)
Serge Bazanski46a45f92023-07-19 17:09:52 +020066 }
67
Lorenz Brunad131882023-06-28 16:42:20 +020068 f, err := blkio.NewFileReader(*bundlePath)
Serge Bazanski46a45f92023-07-19 17:09:52 +020069 if err != nil {
70 log.Fatalf("Failed to open specified %s: %v", name, err)
71 }
Lorenz Brunad131882023-06-28 16:42:20 +020072
73 return f
Serge Bazanski46a45f92023-07-19 17:09:52 +020074}
Lorenz Brun7a510192022-07-04 15:31:38 +000075
Lorenz Brune6573102021-11-02 14:15:37 +010076func doGenUSB(cmd *cobra.Command, args []string) {
Serge Bazanskibdc803b2023-03-27 10:55:09 +020077 var tpmMode cpb.ClusterConfiguration_TPMMode
78 switch strings.ToLower(bootstrapTPMMode) {
79 case "required", "require":
80 tpmMode = cpb.ClusterConfiguration_TPM_MODE_REQUIRED
81 case "best-effort", "besteffort":
82 tpmMode = cpb.ClusterConfiguration_TPM_MODE_BEST_EFFORT
83 case "disabled", "disable":
84 tpmMode = cpb.ClusterConfiguration_TPM_MODE_DISABLED
85 default:
86 log.Fatalf("Invalid --bootstrap-tpm-mode (must be one of: required, best-effort, disabled)")
87 }
88
Tim Windelschmidtc09327c2023-06-14 17:48:56 +020089 var bootstrapStorageSecurity cpb.ClusterConfiguration_StorageSecurityPolicy
90 switch strings.ToLower(bootstrapStorageSecurityPolicy) {
91 case "permissive":
92 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_PERMISSIVE
93 case "needs-encryption":
94 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_ENCRYPTION
95 case "needs-encryption-and-authentication":
96 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_ENCRYPTION_AND_AUTHENTICATION
97 case "needs-insecure":
98 bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE
99 default:
100
101 log.Fatalf("Invalid --bootstrap-storage-security (must be one of: permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
102 }
103
Serge Bazanski46a45f92023-07-19 17:09:52 +0200104 bundle := external("bundle", "metropolis/node/bundle.zip", bundlePath)
105 installer := external("installer", "metropolis/installer/kernel.efi", installerPath)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200106 ctx := clicontext.WithInterrupt(context.Background())
107
Lorenz Brune6573102021-11-02 14:15:37 +0100108 // TODO(lorenz): Have a key management story for this
Mateusz Zalega8234c162022-07-08 17:05:50 +0200109 if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
Lorenz Brune6573102021-11-02 14:15:37 +0100110 log.Fatalf("Failed to create config directory: %v", err)
111 }
Lorenz Brune6573102021-11-02 14:15:37 +0100112
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200113 var params *api.NodeParameters
114 if bootstrap {
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100115 priv, err := core.GetOrMakeOwnerKey(flags.configPath)
116 if err != nil {
117 log.Fatalf("Failed to generate or get owner key: %v", err)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200118 }
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100119 pub := priv.Public().(ed25519.PublicKey)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200120 params = &api.NodeParameters{
121 Cluster: &api.NodeParameters_ClusterBootstrap_{
122 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100123 OwnerPublicKey: pub,
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200124 InitialClusterConfiguration: &cpb.ClusterConfiguration{
Tim Windelschmidtc09327c2023-06-14 17:48:56 +0200125 StorageSecurityPolicy: bootstrapStorageSecurity,
126 TpmMode: tpmMode,
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200127 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200128 },
Lorenz Brune6573102021-11-02 14:15:37 +0100129 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200130 }
131 } else {
Mateusz Zalegadb75e212022-08-04 17:31:34 +0200132 cc := dialAuthenticated(ctx)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200133 mgmt := api.NewManagementClient(cc)
134 resT, err := mgmt.GetRegisterTicket(ctx, &api.GetRegisterTicketRequest{})
135 if err != nil {
136 log.Fatalf("While receiving register ticket: %v", err)
137 }
138 resI, err := mgmt.GetClusterInfo(ctx, &api.GetClusterInfoRequest{})
139 if err != nil {
140 log.Fatalf("While receiving cluster directory: %v", err)
141 }
142
143 params = &api.NodeParameters{
144 Cluster: &api.NodeParameters_ClusterRegister_{
145 ClusterRegister: &api.NodeParameters_ClusterRegister{
146 RegisterTicket: resT.Ticket,
147 ClusterDirectory: resI.ClusterDirectory,
148 CaCertificate: resI.CaCertificate,
149 },
150 },
151 }
Lorenz Brune6573102021-11-02 14:15:37 +0100152 }
153
154 installerImageArgs := core.MakeInstallerImageArgs{
Lorenz Brunad131882023-06-28 16:42:20 +0200155 TargetPath: args[0],
156 Installer: installer,
157 NodeParams: params,
158 Bundle: bundle,
Lorenz Brune6573102021-11-02 14:15:37 +0100159 }
160
Serge Bazanski97783222021-12-14 16:04:26 +0100161 log.Printf("Generating installer image (this can take a while, see issues/92).")
Lorenz Brune6573102021-11-02 14:15:37 +0100162 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
163 log.Fatalf("Failed to create installer: %v", err)
164 }
165}
166
167func init() {
168 rootCmd.AddCommand(installCmd)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200169
170 genusbCmd.Flags().BoolVar(&bootstrap, "bootstrap", false, "Create a bootstrap installer image.")
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200171 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 +0200172 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 +0100173 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100174}