package main import ( "crypto/rand" "encoding/hex" "errors" "fmt" "isle/admin" "isle/bootstrap" "isle/nebula" "net/netip" "os" ) func randStr(l int) string { b := make([]byte, l) if _, err := rand.Read(b); err != nil { panic(err) } return hex.EncodeToString(b) } func readAdmin(path string) (admin.Admin, error) { if path == "-" { adm, err := admin.FromReader(os.Stdin) if err != nil { return admin.Admin{}, fmt.Errorf("parsing admin.json from stdin: %w", err) } return adm, nil } f, err := os.Open(path) if err != nil { return admin.Admin{}, fmt.Errorf("opening file: %w", err) } defer f.Close() return admin.FromReader(f) } var subCmdAdminCreateBootstrap = subCmd{ name: "create-bootstrap", descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout", do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) hostName nebula.HostName ip netip.Addr ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to generate bootstrap.json for", ) ipF := flags.VarPF( textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", ) adminPath := flags.StringP( "admin-path", "a", "", `Path to admin.json file. If the given path is "-" then stdin is used.`, ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed || !ipF.Changed || *adminPath == "" { return errors.New("--hostname, --ip, and --admin-path are required") } adm, err := readAdmin(*adminPath) if err != nil { return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err) } garageBootstrap := bootstrap.Garage{ RPCSecret: adm.Garage.RPCSecret, AdminToken: randStr(32), GlobalBucketS3APICredentials: adm.Garage.GlobalBucketS3APICredentials, } newHostBootstrap, err := bootstrap.New( adm.Nebula.CACredentials, adm.CreationParams, garageBootstrap, hostName, ip, ) if err != nil { return fmt.Errorf("initializing bootstrap data: %w", err) } hostsRes, err := subCmdCtx.getHosts() if err != nil { return fmt.Errorf("getting hosts: %w", err) } for _, host := range hostsRes.Hosts { newHostBootstrap.Hosts[host.Name] = host } return newHostBootstrap.WriteTo(os.Stdout) }, } var subCmdAdminCreateNebulaCert = subCmd{ name: "create-nebula-cert", descr: "Creates a signed nebula certificate file and writes it to stdout", do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) hostName nebula.HostName ip netip.Addr ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to generate a certificate for", ) ipF := flags.VarPF( textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", ) adminPath := flags.StringP( "admin-path", "a", "", `Path to admin.json file. If the given path is "-" then stdin is used.`, ) pubKeyPath := flags.StringP( "public-key-path", "p", "", `Path to PEM file containing public key which will be embedded in the cert.`, ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed || !ipF.Changed || *adminPath == "" || *pubKeyPath == "" { return errors.New("--hostname, --ip, --admin-path, and --pub-key-path are required") } adm, err := readAdmin(*adminPath) if err != nil { return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err) } hostPubPEM, err := os.ReadFile(*pubKeyPath) if err != nil { return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err) } var hostPub nebula.EncryptingPublicKey if err := hostPub.UnmarshalNebulaPEM(hostPubPEM); err != nil { return fmt.Errorf("unmarshaling public key as PEM: %w", err) } nebulaHostCert, err := nebula.NewHostCert( adm.Nebula.CACredentials, hostPub, hostName, ip, ) if err != nil { return fmt.Errorf("creating cert: %w", err) } nebulaHostCertPEM, err := nebulaHostCert.Unwrap().MarshalToPEM() if err != nil { return fmt.Errorf("marshaling cert to PEM: %w", err) } if _, err := os.Stdout.Write([]byte(nebulaHostCertPEM)); err != nil { return fmt.Errorf("writing to stdout: %w", err) } return nil }, } var subCmdAdmin = subCmd{ name: "admin", descr: "Sub-commands which only admins can run", do: func(subCmdCtx subCmdCtx) error { return subCmdCtx.doSubCmd( subCmdAdminCreateBootstrap, subCmdAdminCreateNebulaCert, ) }, }