joyent/node-triton#54 first pass at 'triton rbac key' and 'triton rbac keys'

This commit is contained in:
Trent Mick 2015-11-05 15:13:14 -08:00
parent 4491a55093
commit dd0a70820b
10 changed files with 191 additions and 32 deletions

View File

@ -16,6 +16,8 @@
- `triton rbac role ...` to show, create, edit and delete roles. - `triton rbac role ...` to show, create, edit and delete roles.
- `triton rbac policies` to list all policies. - `triton rbac policies` to list all policies.
- `triton rbac policy ...` to show, create, edit and delete policies. - `triton rbac policy ...` to show, create, edit and delete policies.
- `triton rbac keys` to list all RBAC user SSH keys.
- `triton rbac key ...` to show, create, edit and delete user keys.
## 2.1.4 ## 2.1.4

View File

@ -294,7 +294,7 @@ CloudApi.prototype._passThrough = function _passThrough(endpoint, opts, cb) {
/** /**
* Get network information * Get network information
* *
* @param {Function} callback of the form `function (err, networks, response)` * @param {Function} callback of the form `function (err, networks, res)`
*/ */
CloudApi.prototype.listNetworks = function listNetworks(opts, cb) { CloudApi.prototype.listNetworks = function listNetworks(opts, cb) {
var endpoint = format('/%s/networks', this.account); var endpoint = format('/%s/networks', this.account);
@ -324,7 +324,7 @@ CloudApi.prototype.getNetwork = function getNetwork(id, cb) {
/** /**
* Get services information * Get services information
* *
* @param {Function} callback of the form `function (err, services, response)` * @param {Function} callback of the form `function (err, services, res)`
*/ */
CloudApi.prototype.listServices = function listServices(opts, cb) { CloudApi.prototype.listServices = function listServices(opts, cb) {
var endpoint = format('/%s/services', this.account); var endpoint = format('/%s/services', this.account);
@ -334,8 +334,7 @@ CloudApi.prototype.listServices = function listServices(opts, cb) {
/** /**
* Get datacenters information * Get datacenters information
* *
* @param {Function} callback of the form * @param {Function} callback of the form `function (err, datacenters, res)`
* `function (err, datacenters, response)`
*/ */
CloudApi.prototype.listDatacenters = function listDatacenters(opts, cb) { CloudApi.prototype.listDatacenters = function listDatacenters(opts, cb) {
var endpoint = format('/%s/datacenters', this.account); var endpoint = format('/%s/datacenters', this.account);
@ -348,7 +347,7 @@ CloudApi.prototype.listDatacenters = function listDatacenters(opts, cb) {
/** /**
* Get account information * Get account information
* *
* @param {Function} callback of the form `function (err, account, response)` * @param {Function} callback of the form `function (err, account, res)`
*/ */
CloudApi.prototype.getAccount = function getAccount(opts, cb) { CloudApi.prototype.getAccount = function getAccount(opts, cb) {
var endpoint = format('/%s', this.account); var endpoint = format('/%s', this.account);
@ -356,9 +355,9 @@ CloudApi.prototype.getAccount = function getAccount(opts, cb) {
}; };
/** /**
* Get public key information * List account's SSH keys.
* *
* @param {Function} callback of the form `function (err, keys, response)` * @param {Function} callback of the form `function (err, keys, res)`
*/ */
CloudApi.prototype.listKeys = function listKeys(opts, cb) { CloudApi.prototype.listKeys = function listKeys(opts, cb) {
var endpoint = format('/%s/keys', this.account); var endpoint = format('/%s/keys', this.account);
@ -444,7 +443,7 @@ CloudApi.prototype.getPackage = function getPackage(opts, cb) {
* XXX cloudapi docs don't doc the credentials=true option * XXX cloudapi docs don't doc the credentials=true option
* *
* @param {String} uuid (required) The machine id. * @param {String} uuid (required) The machine id.
* @param {Function} callback of the form `function (err, machine, response)` * @param {Function} callback of the form `function (err, machine, res)`
*/ */
CloudApi.prototype.getMachine = function getMachine(id, cb) { CloudApi.prototype.getMachine = function getMachine(id, cb) {
assert.uuid(id, 'id'); assert.uuid(id, 'id');
@ -480,7 +479,7 @@ CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) {
* start a machine by id. * start a machine by id.
* *
* @param {String} uuid (required) The machine id. * @param {String} uuid (required) The machine id.
* @param {Function} callback of the form `function (err, machine, response)` * @param {Function} callback of the form `function (err, machine, res)`
*/ */
CloudApi.prototype.startMachine = function startMachine(uuid, callback) { CloudApi.prototype.startMachine = function startMachine(uuid, callback) {
return this._doMachine('start', uuid, callback); return this._doMachine('start', uuid, callback);
@ -490,7 +489,7 @@ CloudApi.prototype.startMachine = function startMachine(uuid, callback) {
* stop a machine by id. * stop a machine by id.
* *
* @param {String} uuid (required) The machine id. * @param {String} uuid (required) The machine id.
* @param {Function} callback of the form `function (err, machine, response)` * @param {Function} callback of the form `function (err, machine, res)`
*/ */
CloudApi.prototype.stopMachine = function stopMachine(uuid, callback) { CloudApi.prototype.stopMachine = function stopMachine(uuid, callback) {
return this._doMachine('stop', uuid, callback); return this._doMachine('stop', uuid, callback);
@ -500,7 +499,7 @@ CloudApi.prototype.stopMachine = function stopMachine(uuid, callback) {
* reboot a machine by id. * reboot a machine by id.
* *
* @param {String} uuid (required) The machine id. * @param {String} uuid (required) The machine id.
* @param {Function} callback of the form `function (err, machine, response)` * @param {Function} callback of the form `function (err, machine, res)`
*/ */
CloudApi.prototype.rebootMachine = function rebootMachine(uuid, callback) { CloudApi.prototype.rebootMachine = function rebootMachine(uuid, callback) {
return this._doMachine('reboot', uuid, callback); return this._doMachine('reboot', uuid, callback);
@ -658,7 +657,7 @@ CloudApi.prototype.createMachine = function createMachine(options, callback) {
* XXX IMO this endpoint should be called ListMachineAudit in cloudapi. * XXX IMO this endpoint should be called ListMachineAudit in cloudapi.
* *
* @param {String} id (required) The machine id. * @param {String} id (required) The machine id.
* @param {Function} callback of the form `function (err, audit, response)` * @param {Function} callback of the form `function (err, audit, res)`
*/ */
CloudApi.prototype.machineAudit = function machineAudit(id, cb) { CloudApi.prototype.machineAudit = function machineAudit(id, cb) {
assert.uuid(id, 'id'); assert.uuid(id, 'id');
@ -827,6 +826,112 @@ CloudApi.prototype.deleteUser = function deleteUser(opts, cb) {
}; };
/**
* List RBAC user's SSH keys.
*
* @param {Object} opts (object)
* - {String} userId (required) The user id or login.
* @param {Function} callback of the form `function (err, userKeys, res)`
*/
CloudApi.prototype.listUserKeys = function listUserKeys(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.userId, 'opts.userId');
assert.func(cb, 'cb');
var endpoint = format('/%s/users/%s/keys', this.account, opts.userId);
this._passThrough(endpoint, opts, cb);
};
/**
* Get a RBAC user's SSH key.
*
* @param {Object} opts (object)
* - {String} userId (required) The user id or login.
* - {String} fingerprint (required*) The SSH key fingerprint. One of
* 'fingerprint' or 'name' is required.
* - {String} name (required*) The SSH key name. One of 'fingerprint'
* or 'name' is required.
* @param {Function} callback of the form `function (err, userKey, res)`
*/
CloudApi.prototype.getUserKey = function getUserKey(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.userId, 'opts.userId');
assert.optionalString(opts.fingerprint, 'opts.fingerprint');
assert.optionalString(opts.name, 'opts.name');
assert.ok(opts.fingerprint || opts.name,
'one of "fingerprint" or "name" is require');
assert.func(cb, 'cb');
var endpoint = format('/%s/users/%s/keys/%s', this.account, opts.userId,
encodeURIComponent(opts.fingerprint || opts.name));
this._passThrough(endpoint, {}, cb);
};
/**
* Create/upload a new RBAC user SSH public key.
*
* @param {Object} opts (object)
* - {String} userId (required) The user id or login.
* - {String} key (required) The SSH public key content.
* - {String} name (optional) A name for the key. If not given, the
* key fingerprint will be used.
* @param {Function} callback of the form `function (err, userKey, res)`
*/
CloudApi.prototype.createUserKey = function createUserKey(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.userId, 'opts.userId');
assert.string(opts.key, 'opts.key');
assert.optionalString(opts.name, 'opts.name');
assert.func(cb, 'cb');
var data = {
name: opts.name,
key: opts.key
};
this._request({
method: 'POST',
path: format('/%s/users/%s/keys', this.account, opts.userId),
data: data
}, function (err, req, res, body) {
cb(err, body, res);
});
};
/**
* Delete a RBAC user's SSH key.
*
* @param {Object} opts (object)
* - {String} userId (required) The user id or login.
* - {String} fingerprint (required*) The SSH key fingerprint. One of
* 'fingerprint' or 'name' is required.
* - {String} name (required*) The SSH key name. One of 'fingerprint'
* or 'name' is required.
* @param {Function} callback of the form `function (err, res)`
*/
CloudApi.prototype.deleteUserKey = function deleteUserKey(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.userId, 'opts.userId');
assert.optionalString(opts.fingerprint, 'opts.fingerprint');
assert.optionalString(opts.name, 'opts.name');
assert.ok(opts.fingerprint || opts.name,
'one of "fingerprint" or "name" is require');
assert.func(cb, 'cb');
this._request({
method: 'DELETE',
path: format('/%s/users/%s/keys/%s', this.account, opts.userId,
encodeURIComponent(opts.fingerprint || opts.name))
}, function (err, req, res) {
cb(err, res);
});
};
/** /**
* <http://apidocs.joyent.com/cloudapi/#ListRoles> * <http://apidocs.joyent.com/cloudapi/#ListRoles>
* *

View File

@ -695,6 +695,17 @@ function indent(s, indentation) {
} }
// http://perldoc.perl.org/functions/chomp.html
function chomp(s) {
if (s.length) {
while (s.slice(-1) === '\n') {
s = s.slice(0, -1);
}
}
return s;
}
//---- exports //---- exports
@ -719,6 +730,7 @@ module.exports = {
promptField: promptField, promptField: promptField,
editInEditor: editInEditor, editInEditor: editInEditor,
ansiStylize: ansiStylize, ansiStylize: ansiStylize,
indent: indent indent: indent,
chomp: chomp
}; };
// vim: set softtabstop=4 shiftwidth=4: // vim: set softtabstop=4 shiftwidth=4:

View File

@ -11,19 +11,21 @@
*/ */
var common = require('./common'); var common = require('./common');
var errors = require('./errors');
function do_keys(subcmd, opts, args, callback) {
function do_keys(subcmd, opts, args, cb) {
if (opts.help) { if (opts.help) {
this.do_help('help', {}, [subcmd], callback); this.do_help('help', {}, [subcmd], cb);
return; return;
} else if (args.length !== 0) { } else if (args.length !== 0) {
callback(new Error('invalid args: ' + args)); cb(new errors.UsageError('invalid args: ' + args));
return; return;
} }
this.tritonapi.cloudapi.listKeys(function (err, keys) { this.tritonapi.cloudapi.listKeys(function (err, keys) {
if (err) { if (err) {
callback(err); cb(err);
return; return;
} }
@ -31,13 +33,10 @@ function do_keys(subcmd, opts, args, callback) {
common.jsonStream(keys); common.jsonStream(keys);
} else { } else {
keys.forEach(function (key) { keys.forEach(function (key) {
process.stdout.write(key.key); console.log(common.chomp(key.key));
if (key.key && key.key.slice(-1) !== '\n') {
process.stdout.write('\n');
}
}); });
} }
callback(); cb();
}); });
} }

View File

@ -55,9 +55,8 @@ function do_policies(subcmd, opts, args, cb) {
if (opts.json) { if (opts.json) {
common.jsonStream(policies); common.jsonStream(policies);
} else { } else {
var i, j;
// Add some convenience fields // Add some convenience fields
for (i = 0; i < policies.length; i++) { for (var i = 0; i < policies.length; i++) {
var role = policies[i]; var role = policies[i];
role.shortid = role.id.split('-', 1)[0]; role.shortid = role.id.split('-', 1)[0];
role.nrules = role.rules.length; role.nrules = role.rules.length;

View File

@ -98,10 +98,11 @@ function _stripYamlishLine(line) {
function _policyFromYamlish(yamlish) { function _policyFromYamlish(yamlish) {
assert.string(yamlish, 'yamlish'); assert.string(yamlish, 'yamlish');
var line;
var policy = {}; var policy = {};
var lines = yamlish.split(/\n/g); var lines = yamlish.split(/\n/g);
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
var line = _stripYamlishLine(lines[i]); line = _stripYamlishLine(lines[i]);
if (!line) { if (!line) {
continue; continue;
} }
@ -109,13 +110,13 @@ function _policyFromYamlish(yamlish) {
var key = parts[0].trim(); var key = parts[0].trim();
var value = parts[1].trim(); var value = parts[1].trim();
if (key === 'rules') { if (key === 'rules') {
rules = []; var rules = [];
if (value) { if (value) {
rules.push(value); rules.push(value);
} }
// Remaining lines are rules. // Remaining lines are rules.
for (var j = i+1; j < lines.length; j++) { for (var j = i+1; j < lines.length; j++) {
var line = _stripYamlishLine(lines[j]); line = _stripYamlishLine(lines[j]);
if (!line) { if (!line) {
continue; continue;
} }
@ -185,7 +186,7 @@ function _editPolicy(opts, cb) {
cli.tritonapi.cloudapi.updatePolicy(editedPolicy, cli.tritonapi.cloudapi.updatePolicy(editedPolicy,
function (uErr, updated) { function (uErr, updated) {
if (uErr) { if (uErr) {
var prefix = 'Error updating policy with your changes:' var prefix = 'Error updating policy with your changes:';
var errmsg = uErr.toString(); var errmsg = uErr.toString();
if (errmsg.indexOf('\n') !== -1) { if (errmsg.indexOf('\n') !== -1) {
console.error(prefix + '\n' + common.indent(errmsg)); console.error(prefix + '\n' + common.indent(errmsg));
@ -526,7 +527,7 @@ do_policy.help = [
' # Or exclude FILE to interactively add.', ' # Or exclude FILE to interactively add.',
'', '',
'{{options}}', '{{options}}',
'Where "POLICY" is a full policy "id", the policy "login" name or a "shortid", i.e.', 'Where "POLICY" is a full policy "id", the policy "name" or a "shortid", i.e.',
'an id prefix.', 'an id prefix.',
'', '',
'Fields for creating a policy:', 'Fields for creating a policy:',

View File

@ -488,7 +488,7 @@ do_role.help = [
' # Or exclude FILE to interactively add.', ' # Or exclude FILE to interactively add.',
'', '',
'{{options}}', '{{options}}',
'Where "ROLE" is a full role "id", the role "login" name or a "shortid", i.e.', 'Where "ROLE" is a full role "id", the role "name" or a "shortid", i.e.',
'an id prefix.', 'an id prefix.',
'', '',
'Fields for creating a role:', 'Fields for creating a role:',

View File

@ -53,12 +53,14 @@ function _showUser(opts, cb) {
assert.object(opts.cli, 'opts.cli'); assert.object(opts.cli, 'opts.cli');
assert.string(opts.id, 'opts.id'); assert.string(opts.id, 'opts.id');
assert.optionalBool(opts.roles, 'opts.roles'); assert.optionalBool(opts.roles, 'opts.roles');
assert.optionalBool(opts.keys, 'opts.keys');
assert.func(cb, 'cb'); assert.func(cb, 'cb');
var cli = opts.cli; var cli = opts.cli;
cli.tritonapi.getUser({ cli.tritonapi.getUser({
id: opts.id, id: opts.id,
roles: opts.roles roles: opts.roles,
keys: opts.keys
}, function onUser(err, user) { }, function onUser(err, user) {
if (err) { if (err) {
return cb(err); return cb(err);
@ -68,8 +70,20 @@ function _showUser(opts, cb) {
console.log(JSON.stringify(user)); console.log(JSON.stringify(user));
} else { } else {
Object.keys(user).forEach(function (key) { Object.keys(user).forEach(function (key) {
if (key === 'keys') {
return;
}
console.log('%s: %s', key, user[key]); console.log('%s: %s', key, user[key]);
}); });
if (opts.keys) {
console.log('keys:');
user.keys.forEach(function (key) {
process.stdout.write(' ' + key.key);
if (key.key && key.key.slice(-1) !== '\n') {
process.stdout.write('\n');
}
});
}
} }
cb(); cb();
}); });
@ -397,6 +411,7 @@ function do_user(subcmd, opts, args, cb) {
cli: this.top, cli: this.top,
id: args[0], id: args[0],
roles: opts.roles || opts.membership, roles: opts.roles || opts.membership,
keys: opts.keys,
json: opts.json json: opts.json
}, cb); }, cb);
break; break;
@ -436,7 +451,7 @@ do_user.options = [
{ {
names: ['roles', 'r'], names: ['roles', 'r'],
type: 'bool', type: 'bool',
help: 'Include "roles" and "default_roles" this user has.' help: 'Include "roles" and "default_roles" fields for this user.'
}, },
{ {
names: ['membership'], names: ['membership'],
@ -446,6 +461,11 @@ do_user.options = [
'node-smartdc.', 'node-smartdc.',
hidden: true hidden: true
}, },
{
names: ['keys', 'k'],
type: 'bool',
help: 'Include SSH keys (the "keys" field) for this user.'
},
{ {
names: ['yes', 'y'], names: ['yes', 'y'],
type: 'bool', type: 'bool',

View File

@ -50,4 +50,7 @@ RbacCLI.prototype.do_role = require('./do_role');
RbacCLI.prototype.do_policies = require('./do_policies'); RbacCLI.prototype.do_policies = require('./do_policies');
RbacCLI.prototype.do_policy = require('./do_policy'); RbacCLI.prototype.do_policy = require('./do_policy');
RbacCLI.prototype.do_keys = require('./do_keys');
RbacCLI.prototype.do_key = require('./do_key');
module.exports = RbacCLI; module.exports = RbacCLI;

View File

@ -596,6 +596,8 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) {
* - id {UUID|String} The user ID (a UUID), login or short id. * - id {UUID|String} The user ID (a UUID), login or short id.
* - roles {Boolean} Optional. Whether to includes roles of which this * - roles {Boolean} Optional. Whether to includes roles of which this
* user is a member. Default false. * user is a member. Default false.
* - keys {Boolean} Optional. Set to `true` to also (with a separate
* request) retrieve the `keys` for this user. Default is false.
* @param {Function} callback of the form `function (err, user)` * @param {Function} callback of the form `function (err, user)`
*/ */
TritonApi.prototype.getUser = function getUser(opts, cb) { TritonApi.prototype.getUser = function getUser(opts, cb) {
@ -603,6 +605,7 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
assert.object(opts, 'opts'); assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id'); assert.string(opts.id, 'opts.id');
assert.optionalBool(opts.roles, 'opts.roles'); assert.optionalBool(opts.roles, 'opts.roles');
assert.optionalBool(opts.keys, 'opts.keys');
assert.func(cb, 'cb'); assert.func(cb, 'cb');
/* /*
@ -705,6 +708,21 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
next(); next();
} }
}); });
},
function getKeys(ctx, next) {
if (!opts.keys) {
next();
return;
}
self.cloudapi.listUserKeys({id: ctx.user.id}, function (err, keys) {
if (err) {
next(err);
return;
}
ctx.user.keys = keys;
next();
});
} }
]}, function (err) { ]}, function (err) {