'use strict'; exports = module.exports = { SshError: SshError, getAuthorizedKeys: getAuthorizedKeys, getAuthorizedKey: getAuthorizedKey, addAuthorizedKey: addAuthorizedKey, delAuthorizedKey: delAuthorizedKey, _clear: clear }; var assert = require('assert'), config = require('./config.js'), debug = require('debug')('box:ssh'), fs = require('fs'), path = require('path'), safe = require('safetydance'), shell = require('./shell.js'), util = require('util'); var AUTHORIZED_KEYS_FILEPATH = config.TEST ? path.join(config.baseDir(), 'authorized_keys') : ((config.provider() === 'ec2' || config.provider() === 'lightsail' || config.provider() === 'ami') ? '/home/ubuntu/.ssh/authorized_keys' : '/root/.ssh/authorized_keys'); var AUTHORIZED_KEYS_TMP_FILEPATH = '/tmp/.authorized_keys'; var AUTHORIZED_KEYS_CMD = path.join(__dirname, 'scripts/authorized_keys.sh'); var VALID_KEY_TYPES = ['ssh-rsa']; // TODO add all supported ones var VALID_MIN_KEY_LENGTH = 370; // TODO verify this length requirement function SshError(reason, errorOrMessage) { assert.strictEqual(typeof reason, 'string'); assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); Error.call(this); Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; this.reason = reason; if (typeof errorOrMessage === 'undefined') { this.message = reason; } else if (typeof errorOrMessage === 'string') { this.message = errorOrMessage; } else { this.message = 'Internal error'; this.nestedError = errorOrMessage; } } util.inherits(SshError, Error); SshError.NOT_FOUND = 'Not found'; SshError.INVALID_KEY = 'Invalid key'; SshError.INTERNAL_ERROR = 'Internal Error'; function clear(callback) { assert.strictEqual(typeof callback, 'function'); safe.fs.unlinkSync(AUTHORIZED_KEYS_FILEPATH); callback(); } function saveKeys(keys) { assert(Array.isArray(keys)); if (!safe.fs.writeFileSync(AUTHORIZED_KEYS_TMP_FILEPATH, keys.map(function (k) { return k.key; }).join('\n'))) { debug('Error writing to temporary file', safe.error); return false; } try { // 600 = rw------- fs.chmodSync(AUTHORIZED_KEYS_TMP_FILEPATH, '600'); } catch (e) { debug('Failed to adjust permissions of %s %j', AUTHORIZED_KEYS_TMP_FILEPATH, e); return false; } var user = config.TEST ? process.env.USER : ((config.provider() === 'ec2' || config.provider() === 'lightsail' || config.provider() === 'ami') ? 'ubuntu' : 'root'); shell.sudoSync('authorized_keys', util.format('%s %s %s %s', AUTHORIZED_KEYS_CMD, user, AUTHORIZED_KEYS_TMP_FILEPATH, AUTHORIZED_KEYS_FILEPATH)); return true; } function getKeys() { shell.sudoSync('authorized_keys', util.format('%s %s %s %s', AUTHORIZED_KEYS_CMD, process.env.USER, AUTHORIZED_KEYS_FILEPATH, AUTHORIZED_KEYS_TMP_FILEPATH)); var content = safe.fs.readFileSync(AUTHORIZED_KEYS_TMP_FILEPATH, 'utf8'); if (!content) return []; var keys = content.split('\n') .filter(function (k) { return !!k.trim(); }) .map(function (k) { return { identifier: k.split(' ')[2], key: k }; }) .filter(function (k) { return k.identifier && k.key; }); safe.fs.unlinkSync(AUTHORIZED_KEYS_TMP_FILEPATH); return keys; } function getAuthorizedKeys(callback) { assert.strictEqual(typeof callback, 'function'); return callback(null, getKeys().sort(function (a, b) { return a.identifier.localeCompare(b.identifier); })); } function getAuthorizedKey(identifier, callback) { assert.strictEqual(typeof identifier, 'string'); assert.strictEqual(typeof callback, 'function'); var keys = getKeys(); if (keys.length === 0) return callback(new SshError(SshError.NOT_FOUND)); var key = keys.find(function (k) { return k.identifier === identifier; }); if (!key) return callback(new SshError(SshError.NOT_FOUND)); callback(null, key); } function addAuthorizedKey(key, callback) { assert.strictEqual(typeof key, 'string'); assert.strictEqual(typeof callback, 'function'); var tmp = key.split(' '); if (tmp.length !== 3) return callback(new SshError(SshError.INVALID_KEY)); if (!VALID_KEY_TYPES.some(function (t) { return tmp[0] === t; })) return callback(new SshError(SshError.INVALID_KEY, 'Invalid key type')); if (tmp[1].length < VALID_MIN_KEY_LENGTH) return callback(new SshError(SshError.INVALID_KEY)); var identifier = tmp[2]; var keys = getKeys(); var index = keys.findIndex(function (k) { return k.identifier === identifier; }); if (index !== -1) keys[index] = { identifier: identifier, key: key }; else keys.push({ identifier: identifier, key: key }); if (!saveKeys(keys)) return callback(new SshError(SshError.INTERNAL_ERROR)); callback(); } function delAuthorizedKey(identifier, callback) { assert.strictEqual(typeof identifier, 'string'); assert.strictEqual(typeof callback, 'function'); var keys = getKeys(); var index = keys.findIndex(function (k) { return k.identifier === identifier; }); if (index === -1) return callback(new SshError(SshError.NOT_FOUND)); // now remove the key keys.splice(index, 1); if (!saveKeys(keys)) return callback(new SshError(SshError.INTERNAL_ERROR)); callback(); }