2019-10-04 10:22:53 +00:00
/ *
Copyright © 2019 Devan Carpenter < mail @ dvn . me >
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 < http : //www.gnu.org/licenses/>.
* /
/ *
TODO : Check if device_name already exists in DB , and quit , if so .
* /
package cmd
import (
"fmt"
"log"
"os"
"os/exec"
2020-10-19 22:37:01 +00:00
"io"
2019-10-04 10:22:53 +00:00
"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 ( )
2020-10-19 22:37:01 +00:00
//if verbose {
logWriter := io . MultiWriter ( os . Stdout , logFile )
log . SetOutput ( logWriter )
//} else {
// log.SetOutput(logFile)
//}
2019-10-04 10:22:53 +00:00
// Generate SSH keys if they don't already exist
if _ , err := os . Stat ( config . SshPrivKeyFile ) ; os . IsNotExist ( err ) {
2020-10-19 22:37:01 +00:00
log . Printf ( "Generating ssh private key '%s'..." , config . SshPrivKeyFile )
2019-10-04 10:22:53 +00:00
utils . GenerateSSHKeys ( config . SshPrivKeyFile , config . SshPubKeyFile )
2020-10-19 22:37:01 +00:00
log . Print ( "Done" )
2019-10-04 10:22:53 +00:00
}
// 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 )
}
2020-10-19 22:37:01 +00:00
log . Printf ( "Connecting to %s ...\n" , host )
2019-10-04 10:22:53 +00:00
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 ) {
2020-10-19 22:37:01 +00:00
log . Printf ( "Setting up remote service..." )
2019-10-04 10:22:53 +00:00
// Copy over the gnunet.conf from the remote device.
// TODO: Verify config path
2020-10-19 22:37:01 +00:00
log . Printf ( "... SSH setup" )
2019-10-04 10:22:53 +00:00
sshApi , err := sshwrapper . DefaultSshApiSetup ( host , port , user , privkey )
if err != nil {
log . Fatal ( err )
}
2020-10-19 22:37:01 +00:00
log . Printf ( "... copy .config/gnunet.conf" )
2019-10-04 10:22:53 +00:00
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
2020-10-19 22:37:01 +00:00
log . Printf ( "... load /tmp/gnunet.conf" )
2019-10-04 10:22:53 +00:00
cfg , err := ini . LoadSources ( ini . LoadOptions {
IgnoreInlineComment : true ,
} , "/tmp/gnunet.conf" )
if err != nil {
2020-10-19 22:37:01 +00:00
log . Panicf ( "Fail to read file: %v" , err )
2019-10-04 10:22:53 +00:00
}
2020-10-19 22:37:01 +00:00
log . Printf ( "... update /tmp/gnunet.conf" )
2019-10-04 10:22:53 +00:00
serviceName := fmt . Sprintf ( "%s.gnunet." , serviceSecret )
cfg . Section ( serviceName ) . Key ( "TCP_REDIRECTS" ) . SetValue ( "22:169.254.86.1:22" )
cfg . SaveTo ( "/tmp/gnunet.conf" )
2020-10-19 22:37:01 +00:00
log . Printf ( "... upload .config/gnunet.conf" )
2019-10-04 10:22:53 +00:00
err = sshApi . CopyToRemote ( "/tmp/gnunet.conf" , ".config/gnunet.conf" )
if err != nil {
log . Fatal ( err )
}
// Restart GNUnet
2020-10-19 22:37:01 +00:00
log . Printf ( "... restart gnunet" )
stdout , stderr , err := sshApi . Run ( "gnunet-arm -s ; gnunet-arm -r && sleep 20" )
2019-10-04 10:22:53 +00:00
if err != nil {
log . Print ( stdout )
log . Print ( stderr )
log . Fatal ( err )
}
2020-10-19 22:37:01 +00:00
log . Printf ( "...Done!" )
2019-10-04 10:22:53 +00:00
}