Skip to content

Commit

Permalink
Add a SSH manager for image-based deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
rationalsa committed Jan 25, 2022
1 parent 74ddc22 commit ce655d9
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 4 deletions.
117 changes: 114 additions & 3 deletions belaUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ revisions['srtla'] = getRevision(`${srtlaSendExec} -v`);
console.log(revisions);

let config;
let sshPasswordHash;
try {
config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
console.log(config);
sshPasswordHash = config.ssh_pass_hash;
delete config.ssh_pass_hash;
} catch (err) {
console.log(`Failed to open the config file: ${err.message}. Creating an empty config`);
config = {};
Expand All @@ -112,7 +115,10 @@ try {
}

function saveConfig() {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config));
config.ssh_pass_hash = sshPasswordHash;
const c = JSON.stringify(config);
delete config.ssh_pass_hash;
fs.writeFileSync(CONFIG_FILE, c);
}

function savePersistentTokens() {
Expand Down Expand Up @@ -580,7 +586,7 @@ function handleWifiCommand(conn, type) {
};

/* Remote */
const remoteProtocolVersion = 3;
const remoteProtocolVersion = 4;
const remoteEndpoint = 'wss://remote.belabox.net/ws/remote';
const remoteTimeout = 5000;
const remoteConnectTimeout = 10000;
Expand Down Expand Up @@ -937,6 +943,13 @@ function command(conn, cmd) {
case 'update':
doSoftwareUpdate();
break;
case 'start_ssh':
case 'stop_ssh':
startStopSsh(conn, cmd);
break;
case 'reset_ssh_pass':
resetSshPassword(conn);
break;
}
}

Expand Down Expand Up @@ -1113,6 +1126,103 @@ function doSoftwareUpdate() {
}


/* SSH control */
let sshStatus;
function handleSshStatus(s) {
if (s.user !== undefined && s.active !== undefined && s.user_pass !== undefined) {
if (!sshStatus ||
s.user != sshStatus.user ||
s.active != sshStatus.active ||
s.user_pass != sshStatus.user_pass) {
sshStatus = s;
broadcastMsg('status', {ssh: sshStatus});
}
}
}

function getSshUserHash(callback) {
if (!setup.ssh_user) return;

const cmd = `grep "^${setup.ssh_user}:" /etc/shadow`;
exec(cmd, function(err, stdout, stderr) {
if (err === null && stdout.length) {
callback(stdout);
} else {
console.log(`Error getting the password hash for ${setup.ssh_user}: ${err}`);
}
});
}

function getSshStatus(conn) {
if (!setup.ssh_user) return undefined;

let s = {};
s.user = setup.ssh_user;

// Check is the SSH server is running
exec('systemctl is-active ssh', function(err, stdout, stderr) {
if (err === null) {
s.active = true;
} else {
if (stdout == "inactive\n") {
s.active = false;
} else {
console.log('Error running systemctl is-active ssh: ' + err.message);
return;
}
}

handleSshStatus(s);
});

// Check if the user's password has been changed
getSshUserHash(function(hash) {
s.user_pass = (hash != sshPasswordHash);
handleSshStatus(s);
});

// If an immediate result is expected, send the cached status
return sshStatus;
}
getSshStatus();

function startStopSsh(conn, cmd) {
if (!setup.ssh_user) return;

switch(cmd) {
case 'start_ssh':
if (config.ssh_pass === undefined) {
resetSshPassword(conn);
}
case 'stop_ssh':
const action = cmd.split('_')[0];
spawnSync('systemctl', [action, 'ssh'], {detached: true});
getSshStatus();
break;
}
}

function resetSshPassword(conn) {
if (!setup.ssh_user) return;

const password = crypto.randomBytes(24).toString('base64').
replace(/\+|\/|=/g, '').substring(0,20);
const cmd = `printf "${password}\n${password}" | passwd ${setup.ssh_user}`;
exec(cmd, function(err, stdout, stderr) {
if (err) {
sendError(conn, `Failed to reset the SSH password for ${setup.ssh_user}`);
return;
}
getSshUserHash(function(hash) {
config.ssh_pass = password;
sshPasswordHash = hash;
saveConfig();
broadcastMsg('config', config);
getSshStatus();
});
});
}

/* Authentication */
function setPassword(conn, password, isRemote) {
if (conn.isAuthed || (!isRemote && !config.password_hash)) {
Expand Down Expand Up @@ -1141,7 +1251,8 @@ function genAuthToken(isPersistent) {
function sendStatus(conn) {
conn.send(buildMsg('status', {is_streaming: isStreaming,
available_updates: availableUpdates,
updating: softUpdateStatus}));
updating: softUpdateStatus,
ssh: getSshStatus(conn)}));
}

function sendInitialStatus(conn) {
Expand Down
34 changes: 34 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,40 @@ <h5 id="wifiModalTitle" class="modal-title"></h5>
</div> <!-- .collapse -->
</div> <!-- .card -->

<div class="card mb-2 d-none" id="advancedSettings">
<div class="card-header bg-success text-center" type="button"
data-toggle="collapse" data-target="#collapseFour">
<button class="btn btn-link text-white" type="button" data-toggle="collapse"
data-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Advanced / developer settings
</button>
</div> <!-- card-header -->

<div class="collapse" id="collapseFour">
<div class="card-body">

<div class="form-group">
<label for="sshPassword">SSH login</label>
<div class="input-group">
<input type="password" class="form-control click-copy" id="sshPassword" readonly/>
<div class="input-group-append">
<button class="btn btn-outline-secondary showHidePassword" type="button">Show</button>
<button class="btn btn-outline-danger btn-netact" id="resetSshPass" type="button">Reset</button>
</div>
</div>
</div>

<button type="button" id="startSsh" class="btn btn-block btn-success command-btn btn-netact d-none">
Start SSH server
</button>
<button type="button" id="stopSsh" class="btn btn-block btn-danger command-btn btn-netact d-none">
Stop SSH server
</button>

</div> <!-- .card-body -->
</div> <!-- .collapse -->
</div> <!-- .card -->

<div class="card mb-2">
<div class="card-header bg-success text-center" type="button"
data-toggle="collapse" data-target="#collapseThree">
Expand Down
91 changes: 90 additions & 1 deletion public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,39 @@ $('#softwareUpdate').click(function() {
}
});


/* SSH status / control */
let sshStatus;
function showSshStatus(s) {
if (s !== undefined) {
sshStatus = s;
}

if (!sshStatus) return;

const pass = !config.ssh_pass ? 'password not set' : (sshStatus.user_pass ? 'user-set password' : config.ssh_pass)
$('label[for=sshPassword]').text(`SSH password (username: ${sshStatus.user})`);

$('#sshPassword').val(pass);
if (sshStatus.active) {
$('#startSsh').addClass('d-none');
$('#stopSsh').removeClass('d-none');
} else {
$('#stopSsh').addClass('d-none');
$('#startSsh').removeClass('d-none');
}
$('#advancedSettings').removeClass('d-none');
}

$('#resetSshPass').click(function() {
const msg = 'Are you sure you want to reset the SSH password?';

if (confirm(msg)) {
send_command('reset_ssh_pass');
}
});


/* status updates */
function updateStatus(status) {
if (status.is_streaming !== undefined) {
Expand Down Expand Up @@ -314,6 +347,10 @@ function updateStatus(status) {
if (status.updating !== undefined) {
showSoftwareUpdateStatus(status.updating);
}

if (status.ssh) {
showSshStatus(status.ssh);
}
}


Expand All @@ -332,6 +369,10 @@ function loadConfig(c) {

$('#remoteDeviceKey').val(config.remote_key);
$('#remoteKeyForm button[type=submit]').prop('disabled', true);

if (config.ssh_pass && sshStatus) {
showSshStatus();
}
}


Expand Down Expand Up @@ -902,7 +943,9 @@ $('#logout').click(function() {
});

$('.command-btn').click(function() {
send_command(this.id);
// convert to snake case
const cmd = this.id.split(/(?=[A-Z])/).join('_').toLowerCase();
send_command(cmd);
});

$('button.showHidePassword').click(function() {
Expand All @@ -915,3 +958,49 @@ $('button.showHidePassword').click(function() {
$(this).text('Show');
}
});

/* Input fields automatically copied to clipboard when clicked */
function copyInputValToClipboard(obj) {
if (!document.queryCommandSupported || !document.queryCommandSupported("copy")) {
return false;
}

let input = $(obj);
let valField = input;

valField = $('<input>');
valField.css('position', 'fixed');
valField.css('top', '100000px');
valField.val(input.val());
$('body').append(valField);

let success = false;
try {
valField.select();
document.execCommand("copy");
success = true;
} catch (err) {
console.log("Copying failed: " + err.message);
}

valField.remove();

return success;
}

$('input.click-copy').tooltip({title: 'Copied', trigger: 'manual'});
$('input.click-copy').click(function(ev) {
const target = ev.target;
let input = $(ev.target);

if (copyInputValToClipboard(target)) {
input.tooltip('show');
if (target.copiedTooltipTimer) {
clearTimeout(target.copiedTooltipTimer);
}
target.copiedTooltipTimer = setTimeout(function() {
input.tooltip('hide');
delete target.copiedTooltipTimer;
}, 3000);
}
});

0 comments on commit ce655d9

Please sign in to comment.