Skip to content

Commit

Permalink
- id_rsa reusable as discussed:
Browse files Browse the repository at this point in the history
#290 (comment)
- haves as discussed:
https://github.com/tessel/t2-cli/pull/290/files#r39119278
- home / ~ conversion as discussed:
https://github.com/tessel/t2-cli/pull/290/files#r39067831
- controller.closeTesselConnections changed Tessel object to use
lanConnection instead connectionType === Lan
- FIXME: The check whether a connection is authorized or not has to be
handled different due to security. USB connecting easily makes it
possible to hack IoT devices what isn’t acceptable.
- FIXME: If I use -i ~/.tessel/id_rsa_backup (a copy of erased id_rsa I
the t2 root menu shows „not authorized“ Tessels … but I can select and
connect !!! )
  • Loading branch information
Student007 committed Sep 14, 2015
1 parent 9336b39 commit c6e687b
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 16 deletions.
29 changes: 29 additions & 0 deletions bin/tessel-2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var key = require('../lib/key');
var init = require('../lib/init');
var logs = require('../lib/logs');


var nameOption = {
metavar: 'NAME',
help: 'The name of the tessel on which the command will be executed'
Expand Down Expand Up @@ -174,6 +175,34 @@ parser.command('list')
})
.option('timeout', timeoutOption)
.help('Lists all connected Tessels and their authorization status.');
// accessing the root shell of your tessels
// Fixes issue https://github.com/tessel/t2-cli/issues/80
/**
$ t2 root --help
> Usage: tessel root [-i <path>] [--help]
> -i <path>: provide a path to the desired ssh key
$
$ t2 root
> Accessing root...
root@192.168.128.124 #
*/
var functional_msg = '\nGain SSH root access to one of your authorized tessels (menu listing if multiple targets)';
parser.command('root')
.usage(functional_msg + '\n\nUsage: t2 root [-i PATH] [--help]\n\n-i PATH: Optional targeting a different Private Key \n\n(Note: default target created by "t2 key generate" is ' + controller.TESSEL_AUTH_KEY + ')\n')
.option('path', {
abbr: 'i',
full: 'path',
metavar: 'PATH',
default: controller.TESSEL_AUTH_KEY,
help: 'Private Key (Note: created by "t2 key generate")'
})
.callback(function(opts) {
controller.root(opts)
.then(closeSuccessfulCommand, closeFailedCommand);
})
.help(functional_msg);


parser.command('init')
.callback(init)
Expand Down
254 changes: 242 additions & 12 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ var sprintf = require('sprintf-js').sprintf;
var cp = require('child_process');
var async = require('async');
var updates = require('./update-fetch');
var debug = require('debug')('controller');
var Menu = require('terminal-menu');
var controller = {};
controller.TESSEL_AUTH_KEY = Tessel.TESSEL_AUTH_PATH + '/id_rsa';

Tessel.list = function(opts) {

Expand Down Expand Up @@ -92,7 +95,7 @@ Tessel.list = function(opts) {

Tessel.get = function(opts) {
return new Promise(function(resolve, reject) {
logs.info('Looking for your Tessel...');
logs.info('Searching nearby LAN (netmask) and USB for Tessels...');
// Store the amount of time to look for Tessel in seconds
var timeout = (opts.timeout || 2) * 1000;
// Collection variable as more Tessels are found
Expand All @@ -118,9 +121,22 @@ Tessel.get = function(opts) {
controller.reconcileTessels(tessels)
.then(function(reconciledTessels) {
tessels = reconciledTessels;

// Run the heuristics to pick which Tessel to use
return controller.runHeuristics(opts, tessels)
.then(logAndFinish);
.then(function(tessel){
if(!tessel){
controller.menu.prepareMenu(opts, tessels, resolve, reject,'Please select the default Tessel 2', function(name,index,resolve,reject){
if(!name && name === 'EXIT'){
reject('No Tessel selected!');
}
logs.info('Selected Tessel: '+name);
logAndFinish(tessels[index]);
});
} else {
logAndFinish(tessel);
}
});
});
}
}
Expand Down Expand Up @@ -150,15 +166,22 @@ Tessel.get = function(opts) {
function logAndFinish(tessel) {
// The Tessels that we won't be using should have their connections closed
var connectionsToClose = tessels;

if (tessel) {
logs.info(sprintf('Connected to %s over %s', tessel.name, tessel.connection.connectionType));
connectionsToClose.splice(tessels.indexOf(tessel), 1);
if(opts.root){

controller.closeTesselConnections(connectionsToClose)
.then(function() {
controller.ssh.runSSH(0, opts, tessels, resolve, reject);
});
} else {
logs.info(sprintf('Connected to %s over %s', tessel.name, tessel.connection.connectionType));
connectionsToClose.splice(tessels.indexOf(tessel), 1);

controller.closeTesselConnections(connectionsToClose)
.then(function() {
return resolve(tessel);
});
controller.closeTesselConnections(connectionsToClose)
.then(function() {
return resolve(tessel);
});
}
} else {
logs.info('Please specify a Tessel by name [--name <tessel name>]');
controller.closeTesselConnections(connectionsToClose)
Expand All @@ -180,9 +203,10 @@ may or may not be open and closes them
controller.closeTesselConnections = function(tessels) {
return new Promise(function(resolve, reject) {
async.each(tessels, function closeThem(tessel, done) {
console.log('error: ',tessel);
// If not an unauthorized LAN Tessel, it's connected
if (!(tessel.connection.connectionType === 'LAN' &&
!tessel.connection.authorized)) {
if (!(tessel.lanConnection &&
!tessel.lanConnection.authorized)) {
// Close the connection
return tessel.close()
.then(done, done);
Expand Down Expand Up @@ -333,7 +357,6 @@ controller.runHeuristics = function(opts, tessels) {
}
}
}

// At this point, we know that no name option or env variable was set
// and we know that there is only one USB and/or on LAN Tessel
// We'll return the highest priority available
Expand Down Expand Up @@ -641,6 +664,213 @@ controller.tesselFirmwareVerion = function(opts) {
});
});
};
controller.menu = {

prepareMenu: function(opts, tessels, resolve, reject, title, callback) {
//return new Promise(function(resolve, reject) {

var rtm = new Menu({
// width: process.stdout.columns - 4,
width: 55,
x: 1,
y: 2,
bg: 'red'
});
rtm.reset();
rtm.write(title + '\n');
rtm.write(' \n');

// create menu entries
for (var i in tessels) {
controller.menu.makeMenu(tessels[i], i, rtm);
}

rtm.add('EXIT\n');
rtm._draw();
if (!opts.menu) {
// if this is no test, starting the menu as child process
controller.menu.showMenu(rtm, resolve, reject);
} else {
// because the test needs to resolve the promise ...
resolve({
opts: opts,
tessels: tessels
});
}

rtm.once('select', function(label, index) {
rtm.close();
controller.menu.clear();

// Identify the Exit command by first letter (all other entries start with numbers/index)
if (label[0] !== 'E') {
callback(tessels[index].name, index, resolve, reject);
// controller.ssh.runSSH(index, opts, tessels, resolve, reject);
} else {
// going to clear screen and calling exit to resolve promise
controller.menu.clear();
controller.menu.exit(resolve);
}
});
// });
},
makeMenu: function(tessel, index, rtm) {
if (tessel.lanConnection) {
if (!tessel.lanConnection.authorized) {
rtm.add(index + ') ' + tessel.name + ': ' + tessel.lanConnection.ip + ' (not authorized) \n');
} else {
rtm.add(index + ') ' + tessel.name + ': ' + tessel.lanConnection.ip + ' (authorized) \n');
}

} else if (tessel.usbConnection) {
if (!tessel.usbConnection.authorized) {
rtm.add(index + ') ' + tessel.name + ': [USB] (not authorized) \n');
} else {
rtm.add(index + ') ' + tessel.name + ': [USB] (authorized) \n');
}
}
// FIXME: Workaround for trouble with dynamic added menu elements
rtm._draw();
},
showMenu: function(rtm, resolve, reject) {
// Menu navigation
process.stdin.pipe(rtm.createStream()).pipe(process.stdout);

// raw mode for gain ability to navigate up and down within the menu
process.stdin.setRawMode(true);

// cb
rtm.on('close', function() {
process.stdin.setRawMode(false);
process.stdin.end();
});
rtm.on('error', function(e) {
process.stdin.setRawMode(false);
reject(e);
});
},
seek: function(opts) {
// to be able to replace the seeker in testing mode
// return Tessel.seekTessels(opts);
return Tessel.get(opts);
},
clear: function() {
// selected exit!
process.stdout.write('\u001b[2J\u001b[0;0H');
},
exit: function(resolve) {
resolve();
//process.exit();
}
};
controller.ssh = {
runSSH: function(id, opts, tessels, resolve, reject) {
// clear console
debug('runSSH...\n root@' + tessels[id].lanConnection.host);
if (opts.menu) {
// because the test needs to resolve the promise ...
resolve({
opts: opts,
tessels: tessels
});
} else {
// if this is no test, starting the menu as child process
controller.menu.clear();
var ch = require('child_process')
.spawn('ssh', ['-i',
opts.path,
'root@' + tessels[id].lanConnection.ip
], {
stdio: 'inherit'
});
logs.info('Connect to ' + tessels[id].lanConnection.host + '...');

// FIXME: There is no handler for poweroff Tessel ! Needs to be fixed within the firmware...
// (Terminal freezes imediatelly after poweroff the Tessel)

ch.once('error', function(e) {
if (e === 255) {
controller.menu.clear();
controller.ssh.notAuthorized();
reject();
} else {
logs.warn('Error while connected to ' + tessels[id].lanConnection.host + ':', e);
reject(e);
}

});
ch.once('exit', function(e) {

if (e === 255) {
controller.menu.clear();
controller.ssh.notAuthorized();
reject();
} else if (e === 127) { // exit by user
// clear console
controller.menu.clear();
logs.warn('Connection to ' + tessels[id].lanConnection.host + ' closed!\n');
resolve();
} else if (e === 0) {
// everything works fine ... now lets clear the terminal
controller.menu.clear();
logs.info('Connection to ' + tessels[id].lanConnection.host + ' closed!\n');
resolve();
} else {
logs.warn('Connection to ' + tessels[id].lanConnection.host + ' closed due to reason:\n', e);
reject(e);
}
});

ch.once('close', function(e) {
// tessel powers off
if (e === 0) {
// everything works fine...
resolve();
} else if (e === 255) {
//reject();
process.exit();
} else if (e === 127) {
reject();
} else {
logs.warn('Connection to ' + tessels[id].lanConnection.host + ' closed due to reason:\n', e);
reject();
}
});
}
},
notAuthorized: function() {
logs.warn('Sorry, you are not authorized!');
logs.info('Maybe this Tessel didn\'t got "t2 provision" until now?');
}
};

/*
The T2 root command is used to login into the firmware and gaining superuser access.
The security is provided by RSA - your Tessel get the keys while provisioning via USB.
If you have only one Tessel, the T2 root command will login directly else there is
a ncurses like menu you can select the Tessel you like to gain root access...
The structure of code and maybe unusual parts are paid due being testable.
The lightweight terminal-menu isn't written testable what needs a little bit hacking.
Finally some parts are causing from my personnal learning curve about writing unit tests using sinon!
*/
controller.root = function(opts) {
// ~ conversion to home because spawn isn't able to handle this right
if (opts.path && opts.path.substring(0, 1) === '~') {
var home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
if (/^~/.test(opts.path)) {
opts.path = opts.path.replace(/~/, home);
}
}
opts.root = true;
return new Promise(function(resolve, reject) {
controller.menu.seek(opts)
.then(resolve)
.catch(reject);
});
};

module.exports = controller;

Expand Down
10 changes: 6 additions & 4 deletions lib/usb_connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,12 @@ util.inherits(USB.Scanner, EventEmitter);

USB.Scanner.prototype.start = function() {
var self = this;

usb.getDeviceList().forEach(deviceInspector);

usb.on('attach', deviceInspector);
if (haveusb) {
usb.getDeviceList().forEach(deviceInspector);
usb.on('attach', deviceInspector);
} else {
logs.warn('Skipping USB discovering! (No USB controller)');
}

function deviceInspector(device) {
if ((device.deviceDescriptor.idVendor === TESSEL_VID) && (device.deviceDescriptor.idProduct === TESSEL_PID)) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"stream-to-buffer": "^0.1.0",
"tar": "^2.1.1",
"tar-stream": "^1.2.1",
"terminal-menu": "^2.1.1",
"url-join": "0.0.1",
"usb": "^1.0.5",
"usb-daemon-parser": "0.0.1"
Expand Down

0 comments on commit c6e687b

Please sign in to comment.