UNPKG

k2hr3-api

Version:

K2HR3 REST API is K2hdkc based Resource and Roles and policy Rules

668 lines (667 loc) 27.4 kB
"use strict"; /* * K2HR3 REST API * * Copyright 2017 Yahoo Japan Corporation. * * K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers * common management information for the cloud. * K2HR3 can dynamically manage information as "who", "what", "operate". * These are stored as roles, resources, policies in K2hdkc, and the * client system can dynamically read and modify these information. * * For the full copyright and license information, please view * the license file that was distributed with this source code. * * AUTHOR: Takeshi Nakatani * CREATE: Wed Jun 8 2017 * REVISION: * */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.openstackep = void 0; const https = __importStar(require("https")); const http = __importStar(require("http")); const k2hr3apiutil_1 = __importDefault(require("./k2hr3apiutil")); const dbglogging_1 = __importDefault(require("./dbglogging")); const k2hr3keys_1 = require("./k2hr3keys"); const cacerts_1 = require("./cacerts"); ; // // Type checking // const rawIsValTypeKeystoneEndpoint = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } const _obj = val; const _isUrl = (key) => k2hr3apiutil_1.default.isString(_obj[key]); const _isChecked = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]); const _isStatus = (key) => k2hr3apiutil_1.default.isSafeNumber(_obj[key]); return (_isUrl('url') && _isChecked('checked') && _isStatus('status')); }; const rawIsValTypeKeystoneEndpointMap = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } for (const [, value] of Object.entries(val)) { if (!k2hr3apiutil_1.default.isSafeString(value) && !rawIsValTypeKeystoneEndpoint(value)) { return false; } } return true; }; const rawIsValTypeGetKeystoneEndpointArgs = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } const _obj = val; const _isIsV3 = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]); const _isIsTest = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]); const _isTimeout = (key) => k2hr3apiutil_1.default.isSafeNumber(_obj[key]); const _isCallback = (key) => { const func = _obj[key]; if (!k2hr3apiutil_1.default.isPlainObject(func) || !k2hr3apiutil_1.default.isFunction(func)) { return false; } if (!k2hr3apiutil_1.default.isSafeEntity(func.length) || 3 !== func.length) { return false; } return true; }; return (_isIsV3('is_v3') && _isIsTest('is_test') && _isTimeout('timeout') && _isCallback('callback')); }; const rawIsValTypeUrlKeystoneEndpoint = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } // check UrlWithStringQuery if ((k2hr3apiutil_1.default.isSafeEntity(val.query) && !k2hr3apiutil_1.default.isString(val.query)) || (k2hr3apiutil_1.default.isSafeEntity(val.href) && !k2hr3apiutil_1.default.isString(val.href)) || (k2hr3apiutil_1.default.isSafeEntity(val.protocol) && !k2hr3apiutil_1.default.isString(val.protocol)) || (k2hr3apiutil_1.default.isSafeEntity(val.auth) && !k2hr3apiutil_1.default.isString(val.auth)) || (k2hr3apiutil_1.default.isSafeEntity(val.host) && !k2hr3apiutil_1.default.isString(val.host)) || (k2hr3apiutil_1.default.isSafeEntity(val.hostname) && !k2hr3apiutil_1.default.isString(val.hostname)) || (k2hr3apiutil_1.default.isSafeEntity(val.port) && !k2hr3apiutil_1.default.isString(val.port)) || (k2hr3apiutil_1.default.isSafeEntity(val.pathname) && !k2hr3apiutil_1.default.isString(val.pathname)) || (k2hr3apiutil_1.default.isSafeEntity(val.search) && !k2hr3apiutil_1.default.isString(val.search)) || (k2hr3apiutil_1.default.isSafeEntity(val.path) && !k2hr3apiutil_1.default.isString(val.path)) || (k2hr3apiutil_1.default.isSafeEntity(val.hash) && !k2hr3apiutil_1.default.isString(val.hash)) || (k2hr3apiutil_1.default.isSafeEntity(val.query) && !k2hr3apiutil_1.default.isSafeEntity(val.query)) || (k2hr3apiutil_1.default.isSafeEntity(val.slashes) && !k2hr3apiutil_1.default.isBoolean(val.slashes))) { return false; } // valTypeUrlKeystoneEndpoint if (k2hr3apiutil_1.default.isSafeEntity(val.region) && !k2hr3apiutil_1.default.isString(val.region)) { return false; } return true; }; const rawIsDynamicOpenstackEpModule = (mod) => { if (!k2hr3apiutil_1.default.isPlainObject(mod)) { return false; } if (!('getDynamicKeystoneEndpoints' in mod)) { return false; } if (!k2hr3apiutil_1.default.isPlainObject(mod.getDynamicKeystoneEndpoints) || !k2hr3apiutil_1.default.isFunction(mod.getDynamicKeystoneEndpoints)) { return false; } if (!k2hr3apiutil_1.default.isSafeEntity(mod.getDynamicKeystoneEndpoints.length) || 2 !== mod.getDynamicKeystoneEndpoints.length) { // getDynamicKeystoneEndpoints must have 2 arguments. return false; } return true; }; //--------------------------------------------------------- // Callback function for testing one keystone endpoint //--------------------------------------------------------- // Input parameter: // err : if error is occurred, error object is set(if no error, this is null). // callback : specify the callback to be called when all epallmap is checked. // epallmap : all of keystone endpoint // region : checked region // status_code : check result // // Result: callback(error, epallmap) // error : if error is occurred, error object is set(if no error, this is null). // epallmap : all endpoint mapping // // [NOTE] // The callback function is only called when all checks of // epallmap are complete. // This assumes that the caller will call this function on all // epallmaps. // const rawTestKeystoneEpCallback = (err, callback, epallmap, region, status_code) => { if (!k2hr3apiutil_1.default.isSafeEntity(callback) || !k2hr3apiutil_1.default.isSafeString(region) || !k2hr3apiutil_1.default.isPlainObject(epallmap)) { const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region)); dbglogging_1.default.elog(error.message); if (k2hr3apiutil_1.default.isSafeEntity(callback)) { callback(error); } return; } const tmpEpRegin = epallmap[region]; if (!rawIsValTypeKeystoneEndpoint(tmpEpRegin)) { const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region)); dbglogging_1.default.elog(error.message); if (k2hr3apiutil_1.default.isSafeEntity(callback)) { callback(error); } return; } if (!k2hr3apiutil_1.default.isSafeNumber(status_code)) { dbglogging_1.default.elog('parameter is wrong : status_code=' + JSON.stringify(status_code) + ', but continue with status_code(500).'); status_code = 500; // status code = internal error } if (null !== err) { dbglogging_1.default.elog('failed test about keystone endpoint(' + tmpEpRegin.url + ') for region(' + region + ') by ' + err.message + ', but continue with status_code(500).'); status_code = 500; // status code = internal error } // set checked flag and status result // // [NOTE][TODO] // Under control by this flag, exclusion control is not perfect. // In other words, the callback function may be called multiple times. // (However, the callback function will be called at least once.) // Currently, exclusion control here is not performed, and the caller // recognizes multiple calls. Here is the code to fix. // epallmap[region].checked = true; epallmap[region].status = status_code; // check for finish let is_finish = true; for (const test_region in epallmap) { const tmpOneRegion = epallmap[test_region]; if (!rawIsValTypeKeystoneEndpoint(tmpOneRegion)) { continue; } if (!tmpOneRegion.checked) { is_finish = false; break; } } if (is_finish) { // checked all of endpoints, then call original callback. // // [NOTE] // Perhaps this function passes here multiple times. // callback(null); } }; //--------------------------------------------------------- // Test one keystone endpoint //--------------------------------------------------------- // Input parameter // epallmap : all of keystone endpoint // region : target one region for checking // endpoint : target one endpoint for checking // is_v3 : keystone v3 or v2 // timeout : timeout for checking one endpoint // lastest_callback : callback function which is called end of last checking endpoint. // // Result: lastest_callback(error, orgcb, epallmap, region, status_code, orgcb) // error : error object // orgcb : original callback // epallmap : all endpoint mapping // region : region // status_code : status code for request(timeout = 504) // // [NOTE] // The lastest_callback callback function is only called when // all checks of epallmap are complete. // This assumes that the caller will call this function on all // epallmaps. // const rawTestKeystoneEndpoint = (epallmap, region, endpoint, is_v3, timeout, lastest_callback) => { if (!k2hr3apiutil_1.default.isSafeEntity(epallmap) || !k2hr3apiutil_1.default.isSafeEntity(lastest_callback) || !k2hr3apiutil_1.default.isSafeString(region) || !k2hr3apiutil_1.default.isSafeString(endpoint)) { const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region) + ', endpoint=' + JSON.stringify(endpoint)); dbglogging_1.default.elog(error.message); rawTestKeystoneEpCallback(error, lastest_callback, epallmap, region, 500); // return result code = 500 return; } if (!k2hr3apiutil_1.default.isSafeNumber(timeout)) { const error = new Error('parameter is wrong : timeout=' + JSON.stringify(timeout)); dbglogging_1.default.elog(error.message); rawTestKeystoneEpCallback(error, lastest_callback, epallmap, region, 500); // return result code = 500 return; } if (!k2hr3apiutil_1.default.isBoolean(is_v3)) { is_v3 = true; } const _epallmap = epallmap; const _region = region; const _endpoint = endpoint; const _is_v3 = is_v3; const _timeout = timeout; const _lastest_cb = lastest_callback; // Make request body data // This body failed authorization.(wrong user/passwd) // let strbody; if (!_is_v3) { const body = { 'auth': { 'tenantName': '', // unscoped token for test 'passwordCredentials': { 'username': '', // user name is empty for testing 'password': '' // unauthorized passwd } } }; strbody = JSON.stringify(body); } else { const body = { 'auth': { 'identity': { 'password': { 'user': { 'domain': { 'id': 'default' }, 'name': '', // user name is empty for testing 'password': '' // unauthorized passwd } }, 'methods': ['password'] } } }; strbody = JSON.stringify(body); } const ep = k2hr3apiutil_1.default.urlParse(_endpoint); const is_sec = k2hr3apiutil_1.default.compareCaseString('https:', ep.protocol); const agent = is_sec ? https : http; const headers = { 'Content-Type': 'application/json', 'Content-Length': strbody.length }; const options = { 'host': k2hr3apiutil_1.default.getSafeString(ep.hostname), 'port': k2hr3apiutil_1.default.isSafeNumber(ep.port) ? ep.port : 0, 'path': _is_v3 ? '/v3/auth/tokens' : '/v2.0/tokens', 'method': 'POST', 'headers': headers, 'ca': (is_sec && null !== cacerts_1.ca) ? ((0, cacerts_1.ca)() ?? undefined) : undefined }; // send request const req = agent.request(options, (res) => { let _body = ''; const _status = res.statusCode; dbglogging_1.default.dlog('response status: ' + res.statusCode); dbglogging_1.default.dlog('response header: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', (chunk) => { //r3logger.dlog('response chunk: ' + chunk); _body += chunk; }); res.on('end', () => { dbglogging_1.default.dlog('response body: ' + _body.slice(0, 36) + '...'); // for eslint to use _body. rawTestKeystoneEpCallback(null, _lastest_cb, _epallmap, _region, _status ?? 500); // return result code }); }); req.on('error', (err) => { rawTestKeystoneEpCallback(err, _lastest_cb, _epallmap, _region, 504); // return result code = 500 }); req.on('socket', (socket) => { socket.setTimeout(_timeout, () => { req.abort(); rawTestKeystoneEpCallback(null, _lastest_cb, _epallmap, _region, 504); // return result code = 504 }); }); // write data to request body req.write(strbody); req.end(); }; //--------------------------------------------------------- // Callback function for dynamic/static endpoint list //--------------------------------------------------------- // Input parameter: // cbargs : parent function parameter in this object // timeout : rawGetKeystoneEndpoint function argument // is_test : rawGetKeystoneEndpoint function argument // is_v3 : rawGetKeystoneEndpoint function argument // callback : rawGetKeystoneEndpoint function argument // err : if error is occurred, error object is set(if no error, this is null). // epmap : keystone endpoint map which is result of dynamic/static list. // // Result: callback(error, keystone_ep) // error : error object // keystone_ep : url object with region string element // const rawGetKeystoneEndpointsCallback = (cbargs, err, epmap) => { const _cbargs = cbargs; if (null !== err) { const error = new Error('failed to get dynamic keystone endpoints : ' + err.message); dbglogging_1.default.elog(error.message); _cbargs.callback(error, null); return; } if (!rawIsValTypeKeystoneEndpointMap(epmap)) { const error = new Error('getting dynamic keystone endpoints is something wrong.'); dbglogging_1.default.elog(error.message); _cbargs.callback(error, null); return; } // // check and register endpoints to k2hdkc // const keys = (0, k2hr3keys_1.getK2hr3Keys)(); const epallmap = {}; for (const region in epmap) { const tmpRegionEp = epmap[region]; if (!k2hr3apiutil_1.default.isSafeString(tmpRegionEp)) { // wrong data dbglogging_1.default.wlog('dynamic keystone endpoint for ' + region + ' is something wrong(' + JSON.stringify(tmpRegionEp) + '), thus skip it.'); continue; } const region_url = tmpRegionEp; // register only https! const ep = k2hr3apiutil_1.default.urlParse(region_url); if (!k2hr3apiutil_1.default.compareCaseString('https:', ep.protocol)) { // not https dbglogging_1.default.wlog('dynamic keystone endpoint for ' + region + ' is not https(' + ep.protocol + '), it is not good endpoint.'); } // add to temporary const tmpmap = { 'url': region_url, 'checked': false, 'status': 0 }; epallmap[region] = tmpmap; } if (!_cbargs.is_test) { // not need to test(only updates), finish here // // [NOTE] // keystone endpoint is null // _cbargs.callback(null, null); return; } // test all endpoints(asynchronous) for (const region in epallmap) { // check each endpoint // // [NOTE] // The callback function is called only when all the elements of // epallmap are checked. // const tmpRegionEp = epallmap[region]; if (!rawIsValTypeKeystoneEndpoint(tmpRegionEp)) { continue; } rawTestKeystoneEndpoint(epallmap, region, tmpRegionEp.url, _cbargs.is_v3, _cbargs.timeout, (err) => { const _callback = _cbargs.callback; const _epallmap = epallmap; if (null !== err) { const error = new Error('failed to check keystone endpoints : ' + err.message); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // set all endpoint map for (const region2 in _epallmap) { // register endpoint(type should be VALUE_KEYSTONE_NORMAL) // // [NOTE][TODO] // Now, we do not distinguish v2 and v3 keystone, we are registering it into k2hdkc. // This may possibly cause problems. // const tmpRegionEp2 = _epallmap[region2]; if (!rawIsValTypeKeystoneEndpoint(tmpRegionEp2)) { continue; } // [NOTE] // k2hr3dkc is loaded here using require to lazily load it. // This prevents errors caused by circular loading. // // eslint-disable-next-line @typescript-eslint/no-require-imports const k2hr3 = require('./k2hr3dkc').default; const res_ep = k2hr3.setKeystoneEndpointAll(region2, tmpRegionEp2.url, keys.VALUE_KEYSTONE_NORMAL, 0); if (!res_ep.result) { dbglogging_1.default.elog('could not set keystone endpoint(' + tmpRegionEp2.url + ') for region2(' + region2 + ') into k2hdkc, but continue...'); } else { dbglogging_1.default.mlog('add new keystone endpoint(' + tmpRegionEp2.url + ') for region2(' + region2 + ') into k2hdkc.'); } } // call ownself with not reentrant flag rawGetKeystoneEndpoint((err, keystone_ep) => { if (null !== err) { const error = new Error('failed to get keystone endpoint : ' + err.message); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // // returns keystone endpoint after remaking all endpoint // _callback(null, keystone_ep); }, _cbargs.is_v3, _cbargs.is_test, _cbargs.timeout, false); }); } }; //--------------------------------------------------------- // Get one of endpoint for keystone from static urls //--------------------------------------------------------- // Input parameter // callback : specify callback function // is_v3 : keystone api v3 or v2(default v3) // is_test : specify whether to test each keystone // endpoint after unable to find a valid // keystone endpoint and re-creating it. // (default true) // timeout : specify the timeout required to check // each keystone endpoint. // (default 30s) // is_remake_keystone_ep : if keystone endpoint is not registered, // it specifies whether to recreate it. // (default false) // // Result: callback(error, keystone_ep) // error : error object // keystone_ep : url object with region string element // let last_region = null; let last_endpoint = null; let ksepinit = false; let kseplist = null; let ksepobj = null; const rawInitializeKeystoneEpList = async () => { if (ksepinit) { // already initialized return; } // eslint-disable-next-line @typescript-eslint/no-require-imports const { r3ApiConfig } = require('./k2hr3config'); const apiConf = new r3ApiConfig(); if (apiConf.isKeystoneEpList()) { kseplist = apiConf.getKeystoneEpList(); } else if (apiConf.isKeystoneEpFile()) { const ksepobjPath = './' + apiConf.getKeystoneEpFile(); const mod = await k2hr3apiutil_1.default.tryLoadModule(ksepobjPath); if (rawIsDynamicOpenstackEpModule(mod)) { ksepobj = mod; } } ksepinit = true; }; const rawGetKeystoneEndpoint = (callback, is_v3, is_test, timeout, is_remake_keystone_ep) => { if (!k2hr3apiutil_1.default.isSafeNumber(timeout)) { timeout = 30000; // default 30s } if (!k2hr3apiutil_1.default.isBoolean(is_test)) { is_test = true; // default true } if (!k2hr3apiutil_1.default.isBoolean(is_v3)) { is_v3 = true; // default v3 } if (!k2hr3apiutil_1.default.isBoolean(is_remake_keystone_ep)) { is_remake_keystone_ep = false; } // [NOTE] // This object is inherited from getDynamicKeystoneEndpoints // to rawGetKeystoneEndpointsCallback. // Ultimately, this function is called recursively and receives // the data of this object as an argument. // const cbargs = { timeout: timeout, is_test: is_test, is_v3: is_v3, callback: callback }; if (k2hr3apiutil_1.default.isSafeString(last_endpoint)) { // there is a cache for endpoint/region const keystone_ep = k2hr3apiutil_1.default.urlParse(last_endpoint); if (rawIsValTypeUrlKeystoneEndpoint(keystone_ep)) { keystone_ep.region = k2hr3apiutil_1.default.isSafeString(last_region) ? last_region : undefined; cbargs.callback(null, keystone_ep); return; } } // there is no cache for endpoint/region // [NOTE] // k2hr3dkc is loaded here using require to lazily load it. // This prevents errors caused by circular loading. // // eslint-disable-next-line @typescript-eslint/no-require-imports const k2hr3 = require('./k2hr3dkc').default; const allres = k2hr3.getKeystoneEndpointAll(); if (!allres.result) { dbglogging_1.default.elog('failed to get all keystone endpoint : ' + k2hr3apiutil_1.default.getSafeString(allres.message) + 'but continue for recovering.'); } let keystones = {}; if (k2hr3apiutil_1.default.isSafeEntity(allres.keystones)) { keystones = allres.keystones; } // search OK status from all const keys = (0, k2hr3keys_1.getK2hr3Keys)(); for (const region in keystones) { const tmpKSRegion = keystones[region]; const tmpStatus = k2hr3apiutil_1.default.isSafeNumeric(tmpKSRegion.status) ? k2hr3apiutil_1.default.cvtToNumber(tmpKSRegion.status) : null; if (k2hr3apiutil_1.default.isPlainObject(tmpKSRegion) && k2hr3apiutil_1.default.isSafeString(tmpKSRegion.url) && k2hr3apiutil_1.default.isSafeString(tmpKSRegion.type) && (keys.VALUE_KEYSTONE_NORMAL === tmpKSRegion.type) && k2hr3apiutil_1.default.isSafeNumeric(tmpKSRegion.status) && k2hr3apiutil_1.default.isSafeNumber(tmpStatus) && (tmpStatus < 500)) // allow 0, 2xx, 3xx, 4xx { // found reachable endpoint, then set cache and result last_region = region; last_endpoint = tmpKSRegion.url; const keystone_ep = k2hr3apiutil_1.default.urlParse(last_endpoint); if (rawIsValTypeUrlKeystoneEndpoint(keystone_ep)) { keystone_ep.region = last_region; cbargs.callback(null, keystone_ep); return; } } } // // There are no available endpoints // if (!is_remake_keystone_ep) { const error = new Error('there is no safe keystone endpoints.'); dbglogging_1.default.elog(error.message); cbargs.callback(error, null); return; } // // Initialize endpoint list(object) // rawInitializeKeystoneEpList(); // // try to remake keystone endpoint // if (rawIsValTypeKeystoneEndpointMap(kseplist)) { // // Get a static keystone endpoint. // rawGetKeystoneEndpointsCallback(cbargs, null, kseplist); } else if (k2hr3apiutil_1.default.isSafeEntity(ksepobj)) { // // Get keystone endpoint list dynamically. // ksepobj.getDynamicKeystoneEndpoints(cbargs, rawGetKeystoneEndpointsCallback); } else { // // Wrong configuration // const error = new Error('keystone endpoint configuration is something wrong.'); dbglogging_1.default.elog(error.message); cbargs.callback(error, null); return; } }; //--------------------------------------------------------- // Exports //--------------------------------------------------------- // // Functions // exports.openstackep = { isValTypeKeystoneEndpoint: rawIsValTypeKeystoneEndpoint, isValTypeKeystoneEndpointMap: rawIsValTypeKeystoneEndpointMap, isValTypeGetKeystoneEndpointArgs: rawIsValTypeGetKeystoneEndpointArgs, getKeystoneEndpoint: rawGetKeystoneEndpoint }; // // Default // exports.default = exports.openstackep; /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noexpandtab sw=4 ts=4 fdm=marker * vim<600: noexpandtab sw=4 ts=4 */