This repository has been archived on 2020-01-20. You can view files and clone it, but cannot push or open issues or pull requests.
node-spearhead/lib/do_rbac/do_info.js

273 lines
8.6 KiB
JavaScript
Raw Normal View History

/*
* 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 info ...`
*/
/* BEGIN JSSTYLED */
/*
Sample output for development/discussion:
```
users ($numUsers):
bob ($fullname, **no ssh keys**): roles web*
carl (Carl Fogel): roles eng, operator*, cibot*, monbot*
...
roles ($numRoles):
eng: policies write, read # include users here?
ops: policies delete, read, write
support: policies read
policies ($numPolicies):
delete ($desc):
can deletemachine
read (cloudapi read-only actions):
can listmachines, getmachine and listimages
write (cloudapi write (non-delete) actions):
can createmachine, updatemachine, stopmachine and startmachine
resources: # or call this 'resources'? role-tags?
# some dump of all resources (perhaps not default to *all*) and their
# role-tags
instance foo0 ($uuid): role-tags eng
image bar@1.2.3 ($uuid): role-tags ops
```
Ideas:
- red warning about users with no keys
- `triton rbac info -u bob` Show everything from bob's p.o.v.
- `triton rbac info -r readonly` Show everything from this role's p.o.v.
`... --instance foo0`, etc.
- `-t|--role-tags` to include the role tag info. Perhaps with arg for which?
E.g. do we traverse all machines, images, networks? That could too much...
Might need cloudapi support for returning those optionally.
ListImages?fields=*,role_tags # perhaps don't support '*'
*/
/* END JSSTYLED */
var assert = require('assert-plus');
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var ansiStylize = common.ansiStylize;
/*
* Gather RBAC users, policies, roles and add those to the given `ctx` object.
*/
function gatherRbacBasicInfo(ctx, cb) {
assert.object(ctx.cloudapi, 'ctx.cloudapi');
assert.func(cb, 'cb');
vasync.parallel({funcs: [
function listUsers(next) {
ctx.cloudapi.listUsers(function (err, users) {
ctx.users = users;
next(err);
});
},
function listPolicies(next) {
ctx.cloudapi.listPolicies(function (err, policies) {
ctx.policies = policies;
next(err);
});
},
function listRoles(next) {
ctx.cloudapi.listRoles(function (err, roles) {
ctx.roles = roles;
next(err);
});
}
]}, cb);
}
function do_info(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 0) {
cb(new errors.UsageError('invalid args: ' + args));
return;
}
var log = this.log;
var context = {
tritonapi: this.top.tritonapi,
cloudapi: this.top.tritonapi.cloudapi
};
vasync.pipeline({arg: context, funcs: [
gatherRbacBasicInfo,
function gatherUserKeys(ctx, next) {
if (!opts.all) {
next();
return;
}
// XXX Q! or concurrency forEachParallel
vasync.forEachParallel({
inputs: ctx.users,
func: function oneUser(user, nextUser) {
ctx.cloudapi.listUserKeys({userId: user.id},
function (err, userKeys) {
user.keys = userKeys;
nextUser(err);
});
}
}, next);
},
function fillInUserRoles(ctx, next) {
var i;
var userFromLogin = {};
for (i = 0; i < ctx.users.length; i++) {
var user = ctx.users[i];
user.default_roles = [];
user.roles = [];
userFromLogin[user.login] = user;
}
for (i = 0; i < ctx.roles.length; i++) {
var role = ctx.roles[i];
role.default_members.forEach(function (login) {
userFromLogin[login].default_roles.push(role.name);
});
role.members.forEach(function (login) {
userFromLogin[login].roles.push(role.name);
});
}
next();
},
function printInfo(ctx, next) {
var i;
log.trace({
users: ctx.users,
policies: ctx.policies,
roles: ctx.roles
}, 'rbac info data');
console.log('users (%d):', ctx.users.length);
tabula.sortArrayOfObjects(ctx.users, ['name']);
for (i = 0; i < ctx.users.length; i++) {
var user = ctx.users[i];
var userExtra = [];
if (user.firstName || user.lastName) {
userExtra.push(((user.firstName || '') + ' ' +
(user.lastName || '')).trim());
}
if (user.keys && user.keys.length === 0) {
userExtra.push(ansiStylize('no ssh keys', 'red'));
}
if (userExtra.length > 0) {
userExtra = format(' (%s)', userExtra.join(', '));
} else {
userExtra = '';
}
var roleInfo = [];
user.default_roles.sort();
user.roles.sort();
var roleSeen = {};
user.default_roles.forEach(function (r) {
roleSeen[r] = true;
roleInfo.push(r);
});
user.roles.forEach(function (r) {
if (!roleSeen[r]) {
roleInfo.push(r + '*'); // marker for non-default role
}
});
if (roleInfo.length === 1) {
roleInfo = 'role ' + roleInfo.join(', ');
} else if (roleInfo.length > 0) {
roleInfo = 'roles ' + roleInfo.join(', ');
} else {
roleInfo = ansiStylize('no roles', 'red');
}
console.log(' %s%s: %s', ansiStylize(user.login, 'bold'),
userExtra, roleInfo);
}
console.log('roles (%d):', ctx.roles.length);
tabula.sortArrayOfObjects(ctx.roles, ['name']);
for (i = 0; i < ctx.roles.length; i++) {
var role = ctx.roles[i];
var policyInfo;
if (role.policies.length === 1) {
policyInfo = 'policy ' + role.policies.join(', ');
} else if (role.policies.length > 0) {
policyInfo = 'policies ' + role.policies.join(', ');
} else {
policyInfo = ansiStylize('no policies', 'red');
}
console.log(' %s: %s', ansiStylize(role.name, 'bold'),
policyInfo);
}
console.log('policies (%d):', ctx.policies.length);
tabula.sortArrayOfObjects(ctx.policies, ['name']);
for (i = 0; i < ctx.policies.length; i++) {
var policy = ctx.policies[i];
var noRules = '';
if (policy.rules.length === 0) {
noRules = ' ' + ansiStylize('no rules', 'red');
}
if (policy.description) {
console.log(' %s (%s) rules:%s',
ansiStylize(policy.name, 'bold'),
policy.description, noRules);
} else {
console.log(' %s rules:%s',
ansiStylize(policy.name, 'bold'), noRules);
}
policy.rules.forEach(function (r) {
console.log(' %s', r);
});
}
next();
}
]}, function (err) {
cb(err);
});
}
do_info.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['all', 'a'],
type: 'bool',
help: 'Include all info for a more full report. This requires more ' +
'work to gather all info.'
}
];
do_info.help = (
/* BEGIN JSSTYLED */
'Print an account RBAC summary.\n' +
'\n' +
'Usage:\n' +
' {{name}} info [<options>]\n' +
'\n' +
'{{options}}'
/* END JSSTYLED */
);
module.exports = do_info;