@zowe/imperative
Version:
framework for building configurable CLIs
373 lines • 18.2 kB
JavaScript
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Censor = void 0;
const lodash = require("lodash");
const CliUtils_1 = require("../../utilities/src/CliUtils");
const ImperativeConfig_1 = require("../../utilities/src/ImperativeConfig");
const EnvironmentalVariableSettings_1 = require("../../imperative/src/env/EnvironmentalVariableSettings");
class Censor {
// A set of default censored options.
static get DEFAULT_CENSORED_OPTIONS() {
const censoredList = new Set();
for (const option of this.MAIN_CENSORED_OPTIONS) {
censoredList.add(option);
censoredList.add(CliUtils_1.CliUtils.getOptionFormat(option).kebabCase);
}
return Array.from(censoredList);
}
// Return a customized list of secure prompt options
static get SECURE_PROMPT_OPTIONS() {
const censoredList = new Set();
for (const option of this.MAIN_SECURE_PROMPT_OPTIONS) {
censoredList.add(option);
censoredList.add(CliUtils_1.CliUtils.getOptionFormat(option).kebabCase);
}
return Array.from(censoredList);
}
// Return a customized list of censored options (or just the defaults if not set).
static get CENSORED_OPTIONS() {
return Array.from(this.mCensoredOptions);
}
/**
* Helper method to get an internal reference to the loaded profiles
*/
static get profileSchemas() {
var _b, _c;
if (this.mSchema == null)
this.mSchema = (_c = (_b = ImperativeConfig_1.ImperativeConfig.instance.loadedConfig) === null || _b === void 0 ? void 0 : _b.profiles) !== null && _c !== void 0 ? _c : [];
return this.mSchema;
}
/**
* Helper method to set an internal reference to loaded profiles
* @param _schemas - The schmas to pass in to set to the logger
*/
static setProfileSchemas(_schemas) {
if (this.mSchema == null) {
this.mSchema = [];
}
if (_schemas instanceof Map) {
_schemas.forEach((v) => {
this.mSchema.push({ type: v.type, schema: v });
});
}
else if (Array.isArray(_schemas)) {
_schemas.forEach((v) => {
this.mSchema.push({ type: v.type, schema: v.schema });
});
}
}
/****************************************************
* Helper functions for more advanced functionality *
****************************************************/
/**
* Helper function to handle profile schemas when setting the censored options
* @param {IProfileTypeConfiguration | ICommandProfileTypeConfiguration} profileType - the profile type configuration to iterate over
*/
static handleSchema(profileType) {
const secureOptions = new Set();
/* eslint-disable-next-line no-unused-vars */
for (const [key, cmdProp] of Object.entries(profileType.schema.properties)) {
const prop = cmdProp;
// Add censored options from the schema if the option is secure
if (prop.secure) {
// Handle the case of a single option definition
if (prop.optionDefinition) {
secureOptions.add(prop.optionDefinition.name);
for (const alias of prop.optionDefinition.aliases || []) {
// Remember to add the alias
secureOptions.add(alias);
}
}
else if (prop.optionDefinitions) {
// Handle the case of multiple option definitions
prop.optionDefinitions.forEach(opDef => {
secureOptions.add(opDef.name);
for (const alias of opDef.aliases || []) {
// Remember to add the alias
secureOptions.add(alias);
}
});
}
else {
secureOptions.add(key);
}
}
}
secureOptions.forEach(prop => this.addCensoredOption(prop));
}
/**
* Add a censored option, including it's camelCase and kebabCase versions
* @param {string} option - The option to censor
*/
static addCensoredOption(option) {
// This option is required, but we do not want to ever allow null or undefined itself into the censored options
if (option != null) {
this.mCensoredOptions.add(option);
this.mCensoredOptions.add(CliUtils_1.CliUtils.getOptionFormat(option).camelCase);
this.mCensoredOptions.add(CliUtils_1.CliUtils.getOptionFormat(option).kebabCase);
}
}
/**
* Specifies whether a given property path (e.g. "profiles.lpar1.properties.host") is a special value or not.
* Special value: Refers to any value defined as secure in the schema definition.
* These values should be already masked by the application (and/or plugin) developer.
* @param prop Property path to determine if it is a special value
* @returns True - if the given property is to be treated as a special value; False - otherwise
*/
static isSpecialValue(prop) {
let specialValues = this.SECURE_PROMPT_OPTIONS;
const getPropertyNames = (prop) => {
var _b, _c;
const ret = [];
ret.push((_b = prop.optionDefinition) === null || _b === void 0 ? void 0 : _b.name);
(_c = prop.optionDefinitions) === null || _c === void 0 ? void 0 : _c.map(opDef => ret.push(opDef.name));
return ret;
};
for (const profile of this.profileSchemas) {
for (const prop of Object.values(profile.schema.properties)) {
if (prop.secure)
specialValues = lodash.union(specialValues, getPropertyNames(prop));
}
}
return specialValues.some((v) => prop.endsWith(`.${v}`));
}
/**
* Identifies if a property is a secure property
* @param {string} prop - The property to check
* @returns {boolean} - True if the property is secure; False otherwise
*/
static isSecureValue(prop) {
return this.CENSORED_OPTIONS.includes(prop);
}
/****************************************************************************************
* Bread and butter functions, setting up the class and performing censorship of values *
****************************************************************************************/
/**
* Generate and set the list of censored options.
* Attempt to source the censored options from the schema, config, and/or command being executed.
* @param {ICensorOptions} censorOpts - The objects to use to gather options that should be censored
*/
static setCensoredOptions(censorOpts) {
var _b, _c, _d, _e, _f, _g, _h, _j;
this.mCensoredOptions = new Set(this.DEFAULT_CENSORED_OPTIONS);
if (censorOpts) {
// Save off the config object
this.mConfig = censorOpts.config;
// If we have a ProfileTypeConfiguration (i.e. ImperativeConfig.instance.loadedConfig.profiles)
if (censorOpts.profiles) {
this.mSchema = [];
this.setProfileSchemas(censorOpts.profiles);
}
for (const profileType of (_b = this.profileSchemas) !== null && _b !== void 0 ? _b : []) {
// If we know the command we are running, and we know the profile types that the command uses
// we should only use those profiles to determine what should be censored. If we do not, we should
// add everything
if (censorOpts.commandDefinition == null ||
((_d = (_c = censorOpts.commandDefinition.profile) === null || _c === void 0 ? void 0 : _c.optional) === null || _d === void 0 ? void 0 : _d.includes(profileType.type)) ||
((_f = (_e = censorOpts.commandDefinition.profile) === null || _e === void 0 ? void 0 : _e.required) === null || _f === void 0 ? void 0 : _f.includes(profileType.type))) {
this.handleSchema(profileType);
}
}
// Include any secure options from the config
if (censorOpts.config) {
const secureOptions = new Set();
// Try to use the command and inputs to find the profiles being loaded
if (censorOpts.commandDefinition && censorOpts.commandArguments) {
const profiles = [];
for (const prof of ((_g = censorOpts.commandDefinition.profile) === null || _g === void 0 ? void 0 : _g.required) || []) {
profiles.push(prof);
}
for (const prof of ((_h = censorOpts.commandDefinition.profile) === null || _h === void 0 ? void 0 : _h.optional) || []) {
profiles.push(prof);
}
for (const prof of profiles) {
// If the profile exists, append all of the secure props to the censored list
let profName = (_j = censorOpts.commandArguments) === null || _j === void 0 ? void 0 : _j[`${prof}-profile`];
if (!profName) {
profName = this.mConfig.mProperties.defaults[`${prof}`];
}
if (profName && censorOpts.config.api.profiles.get(profName)) {
censorOpts.config.api.secure.securePropsForProfile(profName).forEach(prop => secureOptions.add(prop));
}
}
}
else {
// We only have a configuration file, assume every property that is secured should be censored
censorOpts.config.api.secure.findSecure(censorOpts.config.mProperties.profiles, "profiles").forEach(prop => secureOptions.add(prop.split(".").pop()));
}
secureOptions.forEach(prop => this.addCensoredOption(prop));
}
}
else if (this.profileSchemas) {
for (const profileType of this.profileSchemas) {
this.handleSchema(profileType);
}
}
}
/**
* Copy and censor any sensitive CLI arguments before logging/printing
* @param {string[]} args - The args list to censor
* @returns {string[]}
*/
static censorCLIArgs(args) {
const newArgs = JSON.parse(JSON.stringify(args));
const censoredValues = this.CENSORED_OPTIONS.map(CliUtils_1.CliUtils.getDashFormOfOption);
for (const value of censoredValues) {
if (args.indexOf(value) >= 0) {
const valueIndex = args.indexOf(value);
if (valueIndex < args.length - 1) {
newArgs[valueIndex + 1] = this.CENSOR_RESPONSE; // censor the argument after the option name
}
}
}
return newArgs;
}
/**
* Copy and censor any sensitive CLI arguments before logging/printing
* @param {string} data - the data to censor
* @returns {string} - the censored data
*/
static censorRawData(data, category = "") {
var _b, _c, _d;
const config = (_b = this.mConfig) !== null && _b !== void 0 ? _b : (_c = ImperativeConfig_1.ImperativeConfig.instance) === null || _c === void 0 ? void 0 : _c.config;
// Return the data if not using config
if (!(config === null || config === void 0 ? void 0 : config.exists))
return data;
// Return the data if we are printing to the console and masking is disabled
if ((_d = ImperativeConfig_1.ImperativeConfig.instance) === null || _d === void 0 ? void 0 : _d.envVariablePrefix) {
const envMaskOutput = EnvironmentalVariableSettings_1.EnvironmentalVariableSettings.read(ImperativeConfig_1.ImperativeConfig.instance.envVariablePrefix).maskOutput.value;
const envShowSecureArgs = EnvironmentalVariableSettings_1.EnvironmentalVariableSettings.read(ImperativeConfig_1.ImperativeConfig.instance.envVariablePrefix).showSecureArgs.value;
// Hardcoding "console" instead of using Logger.DEFAULT_CONSOLE_NAME because of circular dependencies
if ((category === "console" || category === "json") &&
(envMaskOutput.toUpperCase() === "FALSE" || envShowSecureArgs.toUpperCase() === "TRUE")) {
return data;
}
}
let newData = data;
const secureFields = config.api.secure.findSecure(config.mProperties.profiles, "profiles");
for (const prop of secureFields) {
const sec = lodash.get(config.mProperties, prop);
if (sec && typeof sec !== "object" && !this.isSpecialValue(prop) && this.isSecureValue(prop.split(".").pop())) {
newData = newData.replace(new RegExp(sec, "gi"), this.CENSOR_RESPONSE);
}
}
return newData;
}
/**
* Copy and censor a yargs argument object before logging
* @param {yargs.Arguments} args - the args to censor
* @returns {yargs.Arguments} - a censored copy of the arguments
*/
static censorYargsArguments(args) {
const newArgs = JSON.parse(JSON.stringify(args));
for (const optionName of Object.keys(newArgs)) {
if (this.CENSORED_OPTIONS.indexOf(optionName) >= 0) {
const valueToCensor = newArgs[optionName];
newArgs[optionName] = this.CENSOR_RESPONSE;
for (const checkAliasKey of Object.keys(newArgs)) {
if (newArgs[checkAliasKey] === valueToCensor) {
newArgs[checkAliasKey] = this.CENSOR_RESPONSE;
}
}
}
}
return newArgs;
}
// ***********************************************************************
/**
* Censor sensitive data from an session object or a sub-object of a session.
* The intent is to create a copy of the object that is suitable for logging.
*
* @param sessObj - A Session object (or ISession, or availableCreds) to be censored.
*
* @returns - The censored object as a string.
*/
static censorSession(sessObj) {
if (!sessObj) {
return _a.NULL_SESS_OBJ_MSG + " censorSession";
}
return _a.replaceValsInSess(sessObj, true);
}
// ***********************************************************************
/**
* Recursively replace sensitive data in an session-related object
* and any relevant sub-objects.
*
* @param sessObj - A Session object (or ISession, or the availableCreds) to be censored.
*
* @returns - The censored object as a string.
*/
static replaceValsInSess(sessObj, createCopy) {
var _b;
if (!sessObj) {
return _a.NULL_SESS_OBJ_MSG + " replaceValsInSess";
}
const propsToBeCensored = [..._a.SECURE_PROMPT_OPTIONS, ..._a.DEFAULT_CENSORED_OPTIONS];
// create copy of sessObj so that we can replace values in a censored object
let censoredSessObj;
if (createCopy) {
try {
censoredSessObj = JSON.parse(JSON.stringify(sessObj));
}
catch (error) {
return "Invalid session object was passed to API replaceValsInSess. Reason = " + error.toString();
}
}
else {
censoredSessObj = sessObj;
}
// Censor values in the top level of the supplied object
for (const censoredProp of propsToBeCensored) {
if (censoredSessObj[censoredProp] != null) {
censoredSessObj[censoredProp] = _a.CENSOR_RESPONSE;
}
}
if (censoredSessObj.mISession) {
// the object has an ISession sub-object, so censor those values
_a.replaceValsInSess(censoredSessObj.mISession, false);
}
if ((_b = censoredSessObj._authCache) === null || _b === void 0 ? void 0 : _b.availableCreds) {
// the object has an availableCreds sub-object, so censor those values
_a.replaceValsInSess(censoredSessObj._authCache.availableCreds, false);
}
return JSON.stringify(censoredSessObj, null, 2);
}
}
exports.Censor = Censor;
_a = Censor;
/*********************************************************************
* Basic censorship items - list definitions & initialiazations, etc. *
**********************************************************************/
/*
* NOTE(Kelosky): Ideally we might have a consolidated list for secure fields, but for now we'll just
* make sure they're collocated within the same class.
*
* NOTE(Harn): This list should be kept in sync with the base profile secure definitions and MUST be in camel case.
*/
Censor.MAIN_CENSORED_OPTIONS = ["auth", "authentication", "basicAuth", "base64EncodedAuth", "certFilePassphrase", "credentials",
"pw", "pass", "password", "passphrase", "tv", "tokenValue"];
Censor.MAIN_SECURE_PROMPT_OPTIONS = ["keyPassphrase", "password", "passphrase", "tokenValue", "user"];
// The censor response.
Censor.CENSOR_RESPONSE = "****";
// The censor response.
Censor.NULL_SESS_OBJ_MSG = "Null session object was passed to API";
// Set a censored options list that can be set and retrieved for each command.
Censor.mCensoredOptions = new Set(_a.DEFAULT_CENSORED_OPTIONS);
//Singleton caches of the Config, Command Definition and Command Arguments
Censor.mConfig = null;
/**
* Singleton implementation of an internal reference to the schema
*/
Censor.mSchema = null;
//# sourceMappingURL=Censor.js.map
;