@controlplane/cli
Version:
Control Plane Corporation CLI
332 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromBindingOption = exports.PolicyCmd = void 0;
const _ = require("lodash");
const generic_1 = require("./generic");
const command_1 = require("../cli/command");
const query_1 = require("./query");
const resolver_1 = require("./resolver");
const options_1 = require("./options");
const functions_1 = require("../util/functions");
const api_1 = require("../rest/api");
const names_1 = require("../util/names");
const objects_1 = require("../util/objects");
const bindings_1 = require("./bindings");
class PolicyCmd extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'policy';
this.describe = 'Manage access policies';
}
builder(yargs) {
const resolver = (0, resolver_1.kindResolver)('policy');
const policySchema = {
props: [...query_1.defaultProps, 'origin', 'target', 'targetKind'],
};
const targetSchema = {
// TODO schema is different depending on the targetKind of the policy
props: [...query_1.defaultProps],
};
const opts = [options_1.withOrgOptions, options_1.withStandardOptions];
const commandName = 'policy';
const commandNamePlural = 'policies';
const commandNameA = 'a policy';
return (yargs
.demandCommand()
.version(false)
.help()
// generic
.command(new generic_1.Get(commandNamePlural, resolver, ...opts).toYargs())
.command(new generic_1.Edit(commandName, resolver, ...opts).toYargs())
.command(new generic_1.Patch(commandName, resolver, ...opts).toYargs())
.command(new generic_1.Query(commandNamePlural, resolver, policySchema, ...opts).toYargs())
.command(new generic_1.Delete(commandNamePlural, resolver, ...opts).toYargs())
.command(new generic_1.Eventlog(commandName, resolver, ...opts).toYargs())
.command(new generic_1.Tag(commandNamePlural, resolver, ...opts).toYargs())
.command(new generic_1.ListPermissions(commandNameA, resolver, ...opts).toYargs())
.command(new generic_1.ViewAccessReport(commandName, resolver, ...opts).toYargs())
.command(new generic_1.Update(commandName, resolver, [
{
path: 'description',
},
{
path: 'tags.<key>',
},
{
path: 'targetLinks',
array: true,
},
{
path: 'target',
choices: ['all'],
},
], ...opts).toYargs())
// specific
.command(new Create(resolver, targetSchema).toYargs())
.command(new AddBinding(resolver).toYargs())
.command(new RemoveBinding(resolver, targetSchema).toYargs())
.command(new Clone(resolver).toYargs()));
}
handle() { }
}
exports.PolicyCmd = PolicyCmd;
function withBindingOptions(action) {
return function (yargs) {
return yargs.options({
email: {
describe: `User email to ${action} permission`,
requiresArg: true,
multiple: true,
},
serviceaccount: {
describe: `Service account name to ${action} permission`,
requiresArg: true,
multiple: true,
},
group: {
describe: `Group name to ${action} permission`,
requiresArg: true,
multiple: true,
},
identity: {
describe: `Identity name OR link to ${action} permission`,
requiresArg: true,
multiple: true,
},
permission: {
requiresArg: true,
demandOption: true,
describe: `Permission to ${action}`,
multiple: true,
},
});
};
}
class Create extends command_1.Command {
constructor(resolve, schema) {
super();
this.resolve = resolve;
this.schema = schema;
this.command = 'create';
this.describe = 'Create a new policy';
}
withCreateOptions(yargs) {
return yargs.options({
name: {
describe: 'Name of the new policy, type - to generate a valid name',
requiresArg: true,
demandOption: true,
},
description: {
alias: 'desc',
requiresArg: true,
describe: 'Optional description, defaults to the name if not set',
},
'target-kind': {
describe: 'Select a target kind for this policy',
choices: api_1.Kinds.filter((k) => !['deployment', 'permissions', 'event'].includes(k)),
demandOption: true,
requiresArg: true,
},
all: {
describe: 'Apply policy to all instances of the kind',
boolean: true,
},
resource: {
describe: 'Enumerate resource names to add to the policy',
multiple: true,
requiresArg: true,
},
});
}
builder(yargs) {
return (0, functions_1.pipe)(
//
this.withCreateOptions, (0, query_1.withQuerySpecOptions)(this.schema), generic_1.withTagOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
let name = args.name;
let description = args.description;
if (args.name === '-') {
name = (0, names_1.nextName)();
if (!description) {
description = 'Created by cpln';
}
}
const body = {
name: name,
description: description,
targetKind: args.targetKind,
tags: (0, generic_1.fromTagOptions)(args),
};
if (args.all) {
body.target = 'all';
body.targetLinks = [];
}
else {
const targetLinks = this.collectLinks(args.targetKind, (0, objects_1.toArray)(args.resource));
const query = (0, query_1.fromQuerySpecOptions)(args);
if (!query && targetLinks.length < 1) {
this.session.abort({ message: 'Policy must target some items' });
}
body.targetQuery = query;
body.targetLinks = targetLinks;
}
const path = this.resolve.homeLink(this.session.context);
const data = await this.client.create(path, body);
this.session.outFormat(data);
}
collectLinks(kind, names) {
return names.map((x) => (0, resolver_1.resolveToLink)(kind, x, this.session.context));
}
}
async function excludeInvalidEmails(opts) {
const resolve = (0, resolver_1.kindResolver)('user');
const promises = [];
const emails = (0, objects_1.toArray)(opts.email);
for (let email of emails) {
promises.push(this.client.get(`${resolve.homeLink(this.session.context)}/-for/email/${email}`));
}
const responses = await Promise.allSettled(promises);
let validEmails = [];
responses.forEach((response, index) => {
if (response.status === 'rejected') {
this.session.err(`User with email <${emails[index]}> not found`);
}
else {
validEmails.push(response.value.email);
}
});
opts.email = validEmails;
}
function fromBindingOption(session, opts) {
function toLocalLinks(kind, s) {
return _.uniq((0, objects_1.toArray)(s)).map((x) => (0, resolver_1.resolveToLink)(kind, x, session.context));
}
// need to evaluate to full links, otherwise removePermission() wouldn't work correct
function toIdentityLinks(s) {
return _.uniq((0, objects_1.toArray)(s)).map((x) => {
if (x.startsWith('//')) {
const gvc = x.split('/')[3];
const name = x.split('/')[5];
return (0, resolver_1.resolveToLink)('identity', name, { ...session.context, gvc });
}
// Handle identity name
if (!x.includes('/')) {
if (!opts.gvc) {
session.abort({ message: 'ERROR: The --gvc option is required when specifiying an identity name.' });
}
return (0, resolver_1.resolveToLink)('identity', x, { ...session.context, gvc: opts.gvc });
}
return x;
});
}
return {
principalLinks: [
...toLocalLinks('serviceaccount', opts.serviceaccount),
...toLocalLinks('group', opts.group),
...toLocalLinks('user', opts.email),
...toIdentityLinks(opts.identity),
],
permissions: _.uniq((0, objects_1.toArray)(opts.permission)),
};
}
exports.fromBindingOption = fromBindingOption;
class AddBinding extends command_1.Command {
constructor(resolve) {
super();
this.resolve = resolve;
this.excludeInvalidEmails = excludeInvalidEmails;
this.command = 'add-binding <ref>';
this.describe = 'Bind one or more permissions to the referenced policy and associate it with one or more users / service accounts / groups / identities';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
generic_1.withSingleRef, withBindingOptions('add'), options_1.withAllOptions)(yargs);
}
async handle(args) {
const link = this.resolve.resourceLink(args.ref, this.session.context);
const policy = await this.client.get(link);
await this.excludeInvalidEmails(args);
const toAdd = fromBindingOption(this.session, args);
if (toAdd.principalLinks.length < 1) {
this.session.abort({ message: 'At least 1 principal is required' });
}
const newBindings = (0, bindings_1.addPermissions)(policy.bindings, toAdd);
const patchBody = {
bindings: newBindings,
// optimistic locking
version: policy.version,
id: policy.id,
};
const patched = await this.client.patch(link, patchBody);
this.session.outFormat(patched);
}
}
class RemoveBinding extends command_1.Command {
constructor(resolve, schema) {
super();
this.resolve = resolve;
this.schema = schema;
this.excludeInvalidEmails = excludeInvalidEmails;
this.command = 'remove-binding <ref>';
this.describe = 'Remove the bindings of one or more permissions from the referenced policy and disassociate it from one or more users / service accounts / groups / identities';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
generic_1.withSingleRef, withBindingOptions('remove'), options_1.withAllOptions)(yargs);
}
async handle(args) {
const link = this.resolve.resourceLink(args.ref, this.session.context);
const policy = await this.client.get(link);
await this.excludeInvalidEmails(args);
const toRemove = fromBindingOption(this.session, args);
const newBindings = (0, bindings_1.removePermissions)(policy.bindings, toRemove);
const patchBody = {
bindings: newBindings,
// optimistic locking
version: policy.version,
id: policy.id,
};
const patched = await this.client.patch(link, patchBody);
this.session.outFormat(patched);
}
}
class Clone extends command_1.Command {
constructor(resolve) {
super();
this.resolve = resolve;
this.command = 'clone <ref>';
this.describe = 'Clone a policy';
this.aliases = ['copy'];
}
builder(yargs) {
return (0, functions_1.pipe)(
//
generic_1.withSingleRef, generic_1.withCloneOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
const resourceLink = this.resolve.resourceLink(args.ref, this.session.context);
const resource = await this.client.get(resourceLink);
// new description
if (args.description) {
resource.description = args.description;
}
else {
resource.description = (0, generic_1.guessDescription)(resource);
}
if (args.name === '-') {
resource.name = (0, names_1.nextName)();
}
else {
resource.name = args.name;
}
resource.tags = { ...resource.tags, ...(0, generic_1.fromTagOptions)(args) };
const path = this.resolve.parentLink(this.session.context);
const data = await this.client.create(path, resource);
this.session.outFormat(data);
}
}
//# sourceMappingURL=policy.js.map