/* Copyright © 2019 Devan Carpenter This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /* TODO: Check if device_name already exists in DB, and quit, if so. */ package cmd import ( "fmt" "log" "os" "os/exec" "io" "strings" "boxen/config" "boxen/db" "boxen/utils" "github.com/eugenmayer/go-sshclient/sshwrapper" "github.com/sethvargo/go-password/password" "github.com/spf13/cobra" "gopkg.in/ini.v1" ) var ( host string user string port int name string privkey string sshKeyPath string = fmt.Sprintf("%sid_rsa_boxen", config.SshPath) ) // initCmd represents the init command var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a new device", Long: `Initialize a new device. You will name it and authorize yourself as the owner.`, Run: initProcess, } func init() { RootCmd.AddCommand(initCmd) initCmd.Flags().StringVar(&host, "host", "", "Required: The hostname or IP of the device you want to initialize") initCmd.Flags().StringVar(&user, "user", "root", "Optional: The SSH user") initCmd.Flags().IntVar(&port, "port", 22, "Optional: SSH port") initCmd.Flags().StringVar(&privkey, "key", sshKeyPath, "Optional: Your SSH private key") initCmd.Flags().StringVar(&name, "name", "", "Required: The name to assign the device") } func initProcess(_ *cobra.Command, _ []string) { logFile, err := os.OpenFile(config.LogsPath+"init.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) if err != nil { log.Fatalf("error opening log file: %v", err) } defer logFile.Close() //if verbose { logWriter := io.MultiWriter(os.Stdout, logFile) log.SetOutput(logWriter) //} else { // log.SetOutput(logFile) //} // Generate SSH keys if they don't already exist if _, err := os.Stat(config.SshPrivKeyFile); os.IsNotExist(err) { log.Printf("Generating ssh private key '%s'...", config.SshPrivKeyFile) utils.GenerateSSHKeys(config.SshPrivKeyFile, config.SshPubKeyFile) log.Print("Done") } // Generate a password that is 50 characters long with 10 digits, 0 symbols, // allowing upper and lower case letters, allowing repeat characters. adminServiceSecret, err := password.Generate(50, 10, 0, false, true) if err != nil { log.Fatal(err) } log.Printf("Connecting to %s ...\n", host) setupRemoteService(adminServiceSecret) createIdentityCmd := fmt.Sprintf("gnunet-identity -C %s && gnunet-identity -d | grep %s", name, name) namestoreCmd := fmt.Sprintf("gnunet-namestore -z %s -t vpn -ap -e \"1 hour\" -n boxen -V \"6 $(gnunet-peerinfo -s -q) %s\"", name, adminServiceSecret) // It seems like doing an initial loiokup can help with DHT propagation, // but this is effectively a myth at this point, as I have not created // a test to verify this observation. gnsLookupCmd := fmt.Sprintf("gnunet-gns -u boxen.%s -t any", name) insertRecordCmd := namestoreCmd + "&&" + gnsLookupCmd if host == "" { log.Fatal("please set the --host option") } if name == "" { log.Fatal("please set the --name option") } sshApi, err := sshwrapper.DefaultSshApiSetup(host, port, user, privkey) if err != nil { log.Fatal(err) } var ( stdout string stderr string ) stdout, stderr, err = sshApi.Run(createIdentityCmd) if err != nil { log.Print(stdout) log.Print(stderr) log.Fatal(err) } s := stdout[strings.LastIndex(stdout, " ")+1:] deviceEgo := strings.TrimSpace(s) fmt.Printf(" The device is now named: '%s'\n\n Its administrative ego's PKEY is:\n %s\n\n Share with caution, or not at all.\n It can allow access to the administrative services on this device.\n", name, deviceEgo) stdout, stderr, err = sshApi.Run(insertRecordCmd) if err != nil { log.Print(stdout) log.Print(stderr) log.Fatal(err) } addZoneToLocalConfig(deviceEgo) db.InsertDevice(name, deviceEgo, "admin", fmt.Sprintf("boxen.%s", name), 1) log.Print(fmt.Sprintf("%s has returned:\n %s", name, stdout)) } func addZoneToLocalConfig(deviceEgo string) { // TODO: allow to specify gnunet config path linkZone := exec.Command("gnunet-config", "--section=gns", fmt.Sprintf("--option=.%s", name), fmt.Sprintf("--value=%s", deviceEgo)) err := linkZone.Run() log.Printf("Command finished with error: %v", err) } func setupRemoteService(serviceSecret string) { log.Printf("Setting up remote service...") // Copy over the gnunet.conf from the remote device. // TODO: Verify config path log.Printf("... SSH setup") sshApi, err := sshwrapper.DefaultSshApiSetup(host, port, user, privkey) if err != nil { log.Fatal(err) } log.Printf("... copy .config/gnunet.conf") err = sshApi.CopyFromRemote(".config/gnunet.conf", "/tmp/gnunet.conf") if err != nil { log.Fatal(err) } // Add the admin service to the Exit section of config log.Printf("... load /tmp/gnunet.conf") cfg, err := ini.LoadSources(ini.LoadOptions{ IgnoreInlineComment: true, }, "/tmp/gnunet.conf") if err != nil { log.Panicf("Fail to read file: %v", err) } log.Printf("... update /tmp/gnunet.conf") serviceName := fmt.Sprintf("%s.gnunet.", serviceSecret) cfg.Section(serviceName).Key("TCP_REDIRECTS").SetValue("22:169.254.86.1:22") cfg.SaveTo("/tmp/gnunet.conf") log.Printf("... upload .config/gnunet.conf") err = sshApi.CopyToRemote("/tmp/gnunet.conf", ".config/gnunet.conf") if err != nil { log.Fatal(err) } // Restart GNUnet log.Printf("... restart gnunet") stdout, stderr, err := sshApi.Run("gnunet-arm -s ; gnunet-arm -r && sleep 20") if err != nil { log.Print(stdout) log.Print(stderr) log.Fatal(err) } log.Printf("...Done!") }