UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

332 lines 13 kB
"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