joyent/node-triton#54 first pass at 'triton rbac key' and 'triton rbac keys' (with feeling, aka the new files actually added)
This commit is contained in:
parent
dd0a70820b
commit
74b8f3e42e
323
lib/do_rbac/do_key.js
Normal file
323
lib/do_rbac/do_key.js
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton rbac key ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert-plus');
|
||||||
|
var format = require('util').format;
|
||||||
|
var fs = require('fs');
|
||||||
|
var sshpk = require('sshpk');
|
||||||
|
var strsplit = require('strsplit');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
function _showUserKey(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.string(opts.userId, 'opts.userId');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
cli.tritonapi.cloudapi.getUserKey({
|
||||||
|
userId: opts.userId,
|
||||||
|
// Currently `cloudapi.getUserKey` isn't picky about the `name` being
|
||||||
|
// passed in as the `opts.fingerprint` arg.
|
||||||
|
fingerprint: opts.id
|
||||||
|
}, function onUserKey(err, userKey) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(userKey));
|
||||||
|
} else {
|
||||||
|
console.log(common.chomp(userKey.key));
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _deleteUserKeys(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.string(opts.userId, 'opts.userId');
|
||||||
|
assert.arrayOfString(opts.ids, 'opts.ids');
|
||||||
|
assert.optionalBool(opts.yes, 'opts.yes');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
if (opts.ids.length === 0) {
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function confirm(_, next) {
|
||||||
|
if (opts.yes) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var msg;
|
||||||
|
if (opts.ids.length === 1) {
|
||||||
|
msg = 'Delete user key "' + opts.ids[0] + '"? [y/n] ';
|
||||||
|
} else {
|
||||||
|
msg = format('Delete %d user keys (%s)? [y/n] ',
|
||||||
|
opts.ids.length, opts.ids.join(', '));
|
||||||
|
}
|
||||||
|
common.promptYesNo({msg: msg}, function (answer) {
|
||||||
|
if (answer !== 'y') {
|
||||||
|
console.error('Aborting');
|
||||||
|
next(true); // early abort signal
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function deleteThem(_, next) {
|
||||||
|
vasync.forEachPipeline({
|
||||||
|
inputs: opts.ids,
|
||||||
|
func: function deleteOne(id, nextId) {
|
||||||
|
var delOpts = {
|
||||||
|
userId: opts.userId,
|
||||||
|
fingerprint: id
|
||||||
|
};
|
||||||
|
cli.tritonapi.cloudapi.deleteUserKey(delOpts,
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
nextId(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Deleted user %s key "%s"',
|
||||||
|
opts.userId, id);
|
||||||
|
nextId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, next);
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
if (err === true) {
|
||||||
|
err = null;
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _addUserKey(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.string(opts.userId, 'opts.userId');
|
||||||
|
assert.string(opts.file, 'opts.file');
|
||||||
|
assert.optionalString(opts.name, 'opts.name');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {}, funcs: [
|
||||||
|
function gatherDataStdin(ctx, next) {
|
||||||
|
if (opts.file !== '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var stdin = '';
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
|
ctx.data = stdin;
|
||||||
|
ctx.from = '<stdin>';
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function gatherDataFile(ctx, next) {
|
||||||
|
if (!opts.file || opts.file === '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
ctx.data = fs.readFileSync(opts.file);
|
||||||
|
ctx.from = opts.file;
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function validateData(ctx, next) {
|
||||||
|
try {
|
||||||
|
sshpk.parseKey(ctx.data, 'ssh', ctx.from);
|
||||||
|
} catch (keyErr) {
|
||||||
|
next(keyErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function createIt(ctx, next) {
|
||||||
|
var createOpts = {
|
||||||
|
userId: opts.userId,
|
||||||
|
key: ctx.data.toString('utf8')
|
||||||
|
};
|
||||||
|
if (opts.name) {
|
||||||
|
createOpts.name = opts.name;
|
||||||
|
}
|
||||||
|
cli.tritonapi.cloudapi.createUserKey(createOpts,
|
||||||
|
function (err, userKey) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userKey.name) {
|
||||||
|
console.log('Added user %s key "%s" (%s)',
|
||||||
|
opts.userId, userKey.name, userKey.fingerprint);
|
||||||
|
} else {
|
||||||
|
console.log('Added user %s key %s',
|
||||||
|
opts.userId, userKey.fingerprint);
|
||||||
|
}
|
||||||
|
var extra = '';
|
||||||
|
if (userKey.name) {
|
||||||
|
extra = format(' (%s)', userKey.name);
|
||||||
|
}
|
||||||
|
console.log('Added user %s key "%s"%s',
|
||||||
|
opts.userId, userKey.fingerprint, extra);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function do_key(subcmd, opts, args, cb) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which action?
|
||||||
|
var actions = [];
|
||||||
|
if (opts.add) { actions.push('add'); }
|
||||||
|
if (opts['delete']) { actions.push('delete'); }
|
||||||
|
var action;
|
||||||
|
if (actions.length === 0) {
|
||||||
|
action = 'show';
|
||||||
|
} else if (actions.length > 1) {
|
||||||
|
return cb(new errors.UsageError(
|
||||||
|
'only one action option may be used at once'));
|
||||||
|
} else {
|
||||||
|
action = actions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg count validation.
|
||||||
|
if (action === 'show') {
|
||||||
|
if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('missing USER and KEY arguments'));
|
||||||
|
return;
|
||||||
|
} else if (args.length === 1) {
|
||||||
|
cb(new errors.UsageError('missing KEY argument'));
|
||||||
|
return;
|
||||||
|
} else if (args.length > 2) {
|
||||||
|
cb(new errors.UsageError('incorrect number of arguments'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (action === 'delete') {
|
||||||
|
if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('missing USER argument'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (action === 'add') {
|
||||||
|
if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('missing USER and FILE arguments'));
|
||||||
|
return;
|
||||||
|
} else if (args.length === 1) {
|
||||||
|
cb(new errors.UsageError('missing FILE argument'));
|
||||||
|
return;
|
||||||
|
} else if (args.length > 2) {
|
||||||
|
cb(new errors.UsageError('incorrect number of arguments'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'show':
|
||||||
|
_showUserKey({
|
||||||
|
cli: this.top,
|
||||||
|
userId: args[0],
|
||||||
|
id: args[1],
|
||||||
|
json: opts.json
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
_deleteUserKeys({
|
||||||
|
cli: this.top,
|
||||||
|
userId: args[0],
|
||||||
|
ids: args.slice(1),
|
||||||
|
yes: opts.yes
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
_addUserKey({
|
||||||
|
cli: this.top,
|
||||||
|
name: opts.name,
|
||||||
|
userId: args[0],
|
||||||
|
file: args[1]
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return cb(new errors.InternalError('unknown action: ' + action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_key.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON stream output.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['yes', 'y'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Answer yes to confirmation to delete.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['name', 'n'],
|
||||||
|
type: 'string',
|
||||||
|
helpArg: 'NAME',
|
||||||
|
help: 'An optional name for an added key.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Action Options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['add', 'a'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Add a new key.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['delete', 'd'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Delete the named key.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_key.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Show, upload, and delete RBAC user SSH keys.',
|
||||||
|
'',
|
||||||
|
'Usage:',
|
||||||
|
' {{name}} key USER KEY # show USER\'s KEY',
|
||||||
|
' {{name}} key -d|--delete USER [KEY...] # delete USER\'s KEY',
|
||||||
|
' {{name}} key -a|--add [-n NAME] USER FILE',
|
||||||
|
' # Add a new role. FILE must be a file path to an SSH public',
|
||||||
|
' # key or "-" to pass the public key in on stdin.',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where "USER" is a full RBAC user "id", "login" name or a "shortid"; and',
|
||||||
|
'KEY is an SSH key "name" or "fingerprint".'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
module.exports = do_key;
|
79
lib/do_rbac/do_keys.js
Normal file
79
lib/do_rbac/do_keys.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton rbac keys ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function do_keys(subcmd, opts, args, cb) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
|
return;
|
||||||
|
} else if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('no USER argument given'));
|
||||||
|
return;
|
||||||
|
} else if (args.length !== 1) {
|
||||||
|
cb(new errors.UsageError('invalid args: ' + args));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.top.tritonapi.cloudapi.listUserKeys({userId: args[0]},
|
||||||
|
function (err, userKeys) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
common.jsonStream(userKeys);
|
||||||
|
} else {
|
||||||
|
userKeys.forEach(function (key) {
|
||||||
|
console.log(common.chomp(key.key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_keys.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON output.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_keys.help = (
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'List RBAC user SSH keys.\n' +
|
||||||
|
'\n' +
|
||||||
|
'Usage:\n' +
|
||||||
|
' {{name}} keys [<options>] USER\n' +
|
||||||
|
'\n' +
|
||||||
|
'{{options}}' +
|
||||||
|
'\n' +
|
||||||
|
'Where "USER" is an RBAC user id, login or short id. By default this\n' +
|
||||||
|
'lists just the key content for each key -- in other words, content\n' +
|
||||||
|
'appropriate for a "~/.ssh/authorized_keys" file.\n' +
|
||||||
|
'Use `{{name}} keys -j USER` to see all fields.\n'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = do_keys;
|
Reference in New Issue
Block a user