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_apply.js

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 Spearhead 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 `spearhead 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 ',
'Spearhead CLI profile for each user (named "$currprofile-user-$login"). This ',
'simplies using the CLI as that user:',
' spearhead -p coal-user-bob create ...',
' spearhead -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;