@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
302 lines • 23.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.signTacoFile = exports.generateCsr = void 0;
var fs_1 = __importDefault(require("fs"));
var fs_extra_1 = __importDefault(require("fs-extra"));
var path_1 = __importDefault(require("path"));
var process_1 = __importDefault(require("process"));
var inquirer_1 = __importDefault(require("inquirer"));
var logging_1 = require("../utils/logging");
var status_code_1 = require("../../../shared/errors/status-code");
var error_source_1 = require("../../../shared/errors/error-source");
var error_codes_1 = require("../../../shared/errors/error-codes");
var connector_json_1 = require("../utils/connector-json");
var connector_1 = require("../utils/connector");
var command_1 = require("../utils/command");
var constants_1 = require("../utils/constants");
var console_1 = require("../utils/console");
/**
* Creates a signing resources folder in the connector folder that contains a newly
* created keypair/keystore, a self-signed certificate, and a certificate signing
* request that can be sent to a trusted CA for signing.
*/
function generateCsr(config, state) {
var spinner = state.spinner;
var tacoRootPath = config.tacoRootPath;
var absRootPath = (0, command_1.resolvePath)(tacoRootPath);
var connectorName = getConnectorName(tacoRootPath);
// Create/locate resource folder
var signingResourcePath = path_1.default.join(absRootPath, 'signing_resources');
if (!fs_1.default.existsSync(signingResourcePath)) {
fs_1.default.mkdirSync(signingResourcePath);
}
else {
// Empty signing_resources folder
fs_extra_1.default.emptyDirSync(signingResourcePath);
}
spinner.info('Generating Certificate Signing Request\n');
// Generate KeyPair
var keystorePath = path_1.default.join(signingResourcePath, 'taco.keystore');
var genkeypairResult = (0, command_1.executeCmd)('keytool', [
'-genkeypair',
'-alias',
connectorName,
'-keystore',
keystorePath,
'-storetype',
'PKCS12',
'-storepass',
'password',
'-validity',
'3650',
'-keyalg',
'RSA',
], { stdio: 'inherit' } // the cmd requires user's input
);
if ((0, command_1.isErrorResult)(genkeypairResult)) {
spinner.fail('Failed to generate key pair.');
var _a = genkeypairResult.error, error = _a.error, stderr = _a.stderr;
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.NotFound,
errorSource: error_source_1.ErrorSources.Content,
errorCode: error_codes_1.ErrorCodes.LNQB4OXC,
message: "Failed to generate key pair.\nError: ".concat(error, ".\nMore info: ").concat(stderr),
error: error,
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
spinner.success("Generated KeyPair with alias '".concat(connectorName, "' at ").concat(keystorePath));
// Export to a .crt file
var certPath = path_1.default.join(signingResourcePath, "".concat(connectorName, ".crt"));
var exportCertResult = (0, command_1.executeCmd)('keytool', [
'-export',
'-alias',
connectorName,
'-file',
certPath,
'-keystore',
keystorePath,
'-storepass',
'password',
]);
if ((0, command_1.isErrorResult)(exportCertResult)) {
spinner.fail('Failed to generate .crt file.');
var error = exportCertResult.error.error;
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.NotFound,
errorSource: error_source_1.ErrorSources.Content,
errorCode: error_codes_1.ErrorCodes.LNQB4OXC,
message: 'Failed to generate .crt file.',
error: error,
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
spinner.success("Exported certificate under ".concat(certPath));
// Generate .csr file
var csrPath = path_1.default.join(signingResourcePath, "".concat(connectorName, ".csr"));
var generateCsrResult = (0, command_1.executeCmd)('keytool', [
'-certreq',
'-alias',
connectorName,
'-keystore',
keystorePath,
'-file',
csrPath,
'-storepass',
'password', // TODO: figure out why it's hardcoded
]);
if ((0, command_1.isErrorResult)(generateCsrResult)) {
spinner.fail('Failed to generate .csr file.');
var error = generateCsrResult.error.error;
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.NotFound,
errorSource: error_source_1.ErrorSources.Content,
errorCode: error_codes_1.ErrorCodes.LNQB4OXC,
message: 'Failed to generate .csr file.',
error: error,
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
spinner.success("Generated CSR at ".concat(csrPath));
spinner.success('A keystore, certificate, and CSR have been generated and are now ' +
"located in ".concat(path_1.default.join(process_1.default.cwd(), 'signing_resources'), ". Please keep the following information ") +
"on hand for the next steps:\n" +
'\n-----------------------------------------------------------------------------------\n' +
"\nKeystore path: ".concat(path_1.default.join(tacoRootPath, 'signing_resources', 'taco.keystore'), "\n") +
"Keystore password: password\n" +
"Alias for your taco: ".concat(connectorName, "\n") +
'\n-----------------------------------------------------------------------------------\n' +
"Once you have signed the CSR with a CA and imported, run " +
'taco sign --keystore=<keystore path> in order to sign your TACO.\n');
spinner.success("Successfully generated CSR.");
}
exports.generateCsr = generateCsr;
function getConnectorName(tacoRootPath) {
try {
var connectorJsonPath = (0, connector_1.getConnectorJsonPath)(tacoRootPath);
var connectorJson = (0, connector_json_1.getConnectorJson)(connectorJsonPath);
return (0, connector_json_1.getConnectorClassName)(connectorJson);
}
catch (error) {
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.NotFound,
errorSource: error_source_1.ErrorSources.Content,
errorCode: error_codes_1.ErrorCodes.NAGGOW13,
message: 'Failed to generate .csr file.',
error: error,
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
}
/**
* Prompts the user for their keystore password, their signature alias and what they
* want their signed TACO to be named, and then signs their .taco file while also
* prompting the user to see if they want to delete the original.
*
* @param keystorePath
*/
function signTacoFile(config, state) {
return __awaiter(this, void 0, void 0, function () {
var currentPath, keystorePath, spinner, connectorName, tacoFilename, tacoFilePath, _a, password, alias, saveAsFilename, deleteOriginal, signingResult, _b, error, stderr;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
currentPath = config.currentPath, keystorePath = config.keystorePath;
spinner = state.spinner;
connectorName = getConnectorName(currentPath);
tacoFilename = "".concat(connectorName, ".taco");
tacoFilePath = path_1.default.join(currentPath, tacoFilename);
// taco path should be from user?
if (!fs_1.default.existsSync(tacoFilePath)) {
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.Cancelled,
errorSource: error_source_1.ErrorSources.Content,
errorCode: error_codes_1.ErrorCodes.IRGS6QUI,
message: "A valid .taco file does not exist in the current directory. Please make sure to run 'taco pack' before continuing.",
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
return [4 /*yield*/, askForSigningInfo(connectorName)];
case 1:
_a = _c.sent(), password = _a.password, alias = _a.alias, saveAsFilename = _a.saveAsFilename, deleteOriginal = _a.deleteOriginal;
spinner.start("Signing ".concat(tacoFilename, "..."));
signingResult = (0, command_1.executeCmd)('jarsigner', [
'-keystore',
keystorePath,
'-storepass',
password,
'-signedjar',
"".concat(saveAsFilename, ".taco"),
tacoFilename,
alias,
]);
if ((0, command_1.isErrorResult)(signingResult)) {
spinner.fail('Signing failed.');
_b = signingResult.error, error = _b.error, stderr = _b.stderr;
(0, logging_1.logFatalError)({
statusCode: status_code_1.StatusCode.Cancelled,
errorSource: error_source_1.ErrorSources.Client,
errorCode: error_codes_1.ErrorCodes.MSAAL1S9,
message: "Signing failed with error:\n".concat(error, ". More information: ").concat(stderr),
});
process_1.default.exit(constants_1.TACO_COMMAND_ERROR_EXIT_CODE);
}
spinner.success("Successfully signed ".concat(tacoFilename, ". A signed taco file '").concat(saveAsFilename, ".taco' has been created."));
if (deleteOriginal) {
try {
fs_1.default.rmSync(tacoFilename);
}
catch (error) {
(0, console_1.consoleError)("Failed to remove the original file ".concat(tacoFilename), error);
process_1.default.exit(1);
}
logging_1.logger.info("".concat(tacoFilename, " was deleted."));
}
return [2 /*return*/];
}
});
});
}
exports.signTacoFile = signTacoFile;
function askForSigningInfo(tacoFilename) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, inquirer_1.default.prompt([
{
name: 'password',
message: 'What is the keystore password?',
type: 'password',
mask: '*',
default: 'password', // TODO: remove this default value when we figure out the password for KeyPair generation
},
{
name: 'alias',
message: 'What is your key alias?',
default: tacoFilename,
},
{
name: 'saveAsFilename',
message: 'Save the signed taco as a separate file? (enter the filename without file extension or press enter to skip)',
default: tacoFilename,
validate: function (saveAsFilename) {
// align with taco filename pattern, number, letter, dash and underscore
return /^[\w\-_]+$/.test(saveAsFilename);
},
},
{
name: 'deleteOriginal',
when: function (_a) {
var saveAsFilename = _a.saveAsFilename;
return saveAsFilename !== tacoFilename;
},
message: 'Do you want to remove the original unsigned taco file?',
type: 'confirm',
default: false,
},
])];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9jbGkvdGFjby1jb21tYW5kcy9zaWduL3NpZ24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsMENBQW1CO0FBQ25CLHNEQUEwQjtBQUMxQiw4Q0FBdUI7QUFDdkIsb0RBQTZCO0FBQzdCLHNEQUErQjtBQUUvQiw0Q0FBd0Q7QUFDeEQsa0VBQStEO0FBQy9ELG9FQUFrRTtBQUNsRSxrRUFBK0Q7QUFDL0QsMERBQWlGO0FBQ2pGLGdEQUF5RDtBQUN6RCw0Q0FBeUY7QUFDekYsZ0RBQWlFO0FBQ2pFLDRDQUErQztBQVcvQzs7OztHQUlHO0FBQ0gsU0FBZ0IsV0FBVyxDQUFDLE1BQXlCLEVBQUUsS0FBcUI7SUFDbEUsSUFBQSxPQUFPLEdBQUssS0FBSyxRQUFWLENBQVU7SUFDakIsSUFBQSxZQUFZLEdBQUssTUFBTSxhQUFYLENBQVc7SUFFL0IsSUFBTSxXQUFXLEdBQUcsSUFBQSxxQkFBVyxFQUFDLFlBQVksQ0FBQyxDQUFBO0lBRTdDLElBQU0sYUFBYSxHQUFHLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxDQUFBO0lBRXBELGdDQUFnQztJQUNoQyxJQUFNLG1CQUFtQixHQUFHLGNBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLG1CQUFtQixDQUFDLENBQUE7SUFDdkUsSUFBSSxDQUFDLFlBQUUsQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQUMsRUFBRTtRQUN2QyxZQUFFLENBQUMsU0FBUyxDQUFDLG1CQUFtQixDQUFDLENBQUE7S0FDbEM7U0FBTTtRQUNMLGlDQUFpQztRQUNqQyxrQkFBRyxDQUFDLFlBQVksQ0FBQyxtQkFBbUIsQ0FBQyxDQUFBO0tBQ3RDO0lBRUQsT0FBTyxDQUFDLElBQUksQ0FBQywwQ0FBMEMsQ0FBQyxDQUFBO0lBQ3hELG1CQUFtQjtJQUNuQixJQUFNLFlBQVks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