fabe0a0841
This builds on cmdln 4.x's "errHelp" facilities.
240 lines
8.0 KiB
JavaScript
240 lines
8.0 KiB
JavaScript
/*
|
|
* 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 apply ...`
|
|
*/
|
|
|
|
var assert = require('assert-plus');
|
|
var format = require('util').format;
|
|
var vasync = require('vasync');
|
|
|
|
var common = require('../common');
|
|
var mod_config = require('../config');
|
|
var errors = require('../errors');
|
|
var rbac = require('../rbac');
|
|
|
|
var ansiStylize = common.ansiStylize;
|
|
|
|
|
|
function do_apply(subcmd, opts, args, cb) {
|
|
var self = this;
|
|
if (opts.help) {
|
|
this.do_help('help', {}, [subcmd], cb);
|
|
return;
|
|
} else if (args.length !== 0) {
|
|
cb(new errors.UsageError('invalid args: ' + args));
|
|
return;
|
|
}
|
|
|
|
var context = {
|
|
log: this.log,
|
|
tritonapi: this.top.tritonapi,
|
|
cloudapi: this.top.tritonapi.cloudapi,
|
|
rbacConfigPath: opts.file || './rbac.json',
|
|
rbacDryRun: opts.dry_run
|
|
};
|
|
vasync.pipeline({arg: context, funcs: [
|
|
rbac.loadRbacConfig,
|
|
rbac.loadRbacState,
|
|
rbac.createRbacUpdatePlan,
|
|
|
|
/*
|
|
* For each user (in the target config):
|
|
* - if they don't have a key and one isn't being added in the plan
|
|
* already, then we want to create an ssh key pair and add it; and
|
|
* - add or replace the '$currprofile-user-$login' Triton CLI
|
|
* profile
|
|
*/
|
|
function devExtendKeys(ctx, next) {
|
|
if (!opts.dev_create_keys_and_profiles) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
ctx.rbacConfig.users.forEach(function devUserKey(user) {
|
|
if (user.keys && user.keys.length > 0) {
|
|
return;
|
|
}
|
|
ctx.rbacUpdatePlan.push({
|
|
action: 'generate',
|
|
type: 'key',
|
|
desc: format('user %s key', user.login),
|
|
currProfile: ctx.tritonapi.profile,
|
|
user: user.login
|
|
});
|
|
});
|
|
next();
|
|
},
|
|
|
|
function devExtendProfiles(ctx, next) {
|
|
if (!opts.dev_create_keys_and_profiles) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
var profiles = mod_config.loadAllProfiles({
|
|
configDir: self.top.configDir,
|
|
log: self.log
|
|
});
|
|
var profileFromName = {};
|
|
profiles.forEach(function (p) {
|
|
profileFromName[p.name] = p;
|
|
});
|
|
|
|
ctx.rbacConfig.users.forEach(function devUserKey(user) {
|
|
var profileName = format('%s-user-%s',
|
|
ctx.tritonapi.profile.name, user.login);
|
|
var wantThing = {
|
|
name: profileName,
|
|
url: ctx.tritonapi.profile.url,
|
|
insecure: ctx.tritonapi.profile.insecure,
|
|
account: ctx.tritonapi.profile.account,
|
|
user: user.login,
|
|
// If we are adding a key, we won't have this fingerprint
|
|
// until after executing that part of the rbacUpdatePlan.
|
|
keyId: user.keys && user.keys[0].fingerprint
|
|
};
|
|
var existing = profileFromName[profileName];
|
|
if (existing) {
|
|
// If it is the same, avoid no-op update.
|
|
if (! common.deepEqual(wantThing, existing)) {
|
|
ctx.rbacUpdatePlan.push({
|
|
action: 'update',
|
|
type: 'profile',
|
|
desc: format('user %s CLI profile', user.login),
|
|
id: profileName,
|
|
haveThing: existing,
|
|
wantThing: wantThing,
|
|
user: user.login,
|
|
configDir: self.top.configDir
|
|
});
|
|
}
|
|
} else {
|
|
ctx.rbacUpdatePlan.push({
|
|
action: 'create',
|
|
type: 'profile',
|
|
desc: format('user %s CLI profile', user.login),
|
|
id: profileName,
|
|
wantThing: wantThing,
|
|
user: user.login,
|
|
configDir: self.top.configDir
|
|
});
|
|
}
|
|
});
|
|
next();
|
|
},
|
|
|
|
function confirmApply(ctx, next) {
|
|
if (opts.yes || ctx.rbacUpdatePlan.length === 0) {
|
|
next();
|
|
return;
|
|
}
|
|
ctx.log.info({rbacUpdatePlan: ctx.rbacUpdatePlan},
|
|
'rbacUpdatePlan');
|
|
var p = console.log;
|
|
p('');
|
|
p('This will make the following RBAC config changes:');
|
|
ctx.rbacUpdatePlan.forEach(function (c) {
|
|
var extra = '';
|
|
if (c.action === 'update' && c.diff) {
|
|
extra = format(' (%s)',
|
|
Object.keys(c.diff).map(function (field) {
|
|
return c.diff[field] + ' ' + field;
|
|
}).join(', '));
|
|
}
|
|
p(' %s %s %s%s',
|
|
{create: 'Create', 'delete': 'Delete',
|
|
update: 'Update', generate: 'Generate'}[c.action],
|
|
c.desc || c.type,
|
|
c.id || '',
|
|
extra);
|
|
});
|
|
p('');
|
|
var msg = format('Would you like to continue%s? [y/N] ',
|
|
opts.dry_run ? ' (dry-run)' : '');
|
|
common.promptYesNo({msg: msg, default: 'n'}, function (answer) {
|
|
if (answer !== 'y') {
|
|
p('Aborting update');
|
|
return cb();
|
|
}
|
|
p('');
|
|
next();
|
|
});
|
|
},
|
|
rbac.executeRbacUpdatePlan
|
|
]}, function (err) {
|
|
cb(err);
|
|
});
|
|
}
|
|
|
|
do_apply.options = [
|
|
{
|
|
names: ['help', 'h'],
|
|
type: 'bool',
|
|
help: 'Show this help.'
|
|
},
|
|
{
|
|
names: ['dry-run', 'n'],
|
|
type: 'bool',
|
|
help: 'Go through the motions without applying changes.'
|
|
},
|
|
{
|
|
names: ['yes', 'y'],
|
|
type: 'bool',
|
|
help: 'Answer "yes" to confirmation.'
|
|
},
|
|
{
|
|
names: ['file', 'f'],
|
|
type: 'string',
|
|
helpArg: 'FILE',
|
|
help: 'RBAC config JSON file.'
|
|
},
|
|
{
|
|
names: ['dev-create-keys-and-profiles'],
|
|
type: 'bool',
|
|
help: 'Convenient option to generate keys and Triton CLI profiles ' +
|
|
'for all users. For experimenting only. See section below.'
|
|
}
|
|
];
|
|
|
|
do_apply.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
|
|
|
do_apply.help = [
|
|
/* BEGIN JSSTYLED */
|
|
'Apply an RBAC configuration.',
|
|
'',
|
|
'{{usage}}',
|
|
'',
|
|
'{{options}}',
|
|
'If "--file FILE" is not specified, this defaults to using "./rbac.json".',
|
|
'The RBAC configuration is loaded from FILE and compared to the live',
|
|
'RBAC state (see `triton rbac info`). It then calculates necessary updates,',
|
|
'confirms, and applies them.',
|
|
'',
|
|
'Warning: Currently, RBAC state updates can take a few seconds to appear',
|
|
'as they are replicated across data centers. This can result in unexpected',
|
|
'no-op updates with consecutive quick re-runs of this command.',
|
|
'',
|
|
'The "--dev-create-keys-and-profiles" option is provided for **experimenting',
|
|
'with, developing, or testing** Triton RBAC. It will create a key and setup a ',
|
|
'Triton CLI profile for each user (named "$currprofile-user-$login"). This ',
|
|
'simplies using the CLI as that user:',
|
|
' triton -p coal-user-bob create ...',
|
|
' triton -p coal-user-sarah imgs',
|
|
'Note that proper production usage of RBAC should have the administrator',
|
|
'never seeing each user\'s private key.',
|
|
'',
|
|
'TODO: Document the rbac.json configuration format.'
|
|
/* END JSSTYLED */
|
|
].join('\n');
|
|
|
|
|
|
|
|
module.exports = do_apply;
|