UNPKG

k2hr3-api

Version:

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

1,313 lines (1,206 loc) 101 kB
/* * 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: * */ 'use strict'; var k2hr3 = require('./k2hr3dkc'); var r3keys = require('./k2hr3keys').getK2hr3Keys; var apiutil = require('./k2hr3apiutil'); // Debug logging objects var r3logger = require('../lib/dbglogging'); //--------------------------------------------------------- // Configuration // * Keystone api wrapper // * Get expiration for role tokens //--------------------------------------------------------- // [NOTE] // We use config which has keystone.type value. // Default value is "keystone_v3". // var osapi = null; var expire_rtoken = 0; var expire_reg_rtoken = 0; (function() { var r3Conf = require('./k2hr3config').r3ApiConfig; var apiConf = new r3Conf(); var keystone_type = './' + apiConf.getKeystoneType(); osapi = require(keystone_type); expire_rtoken = apiConf.getExpireTimeRoleToken(); expire_reg_rtoken = apiConf.getExpireTimeRegRoleToken(); }()); //--------------------------------------------------------- // Get User Token //--------------------------------------------------------- // // Get scoped/unscoped user token from keystone // function rawGetUserToken(user, passwd, tenant, callback) { var _callback = callback; if(!apiutil.isSafeString(user)){ var error = new Error('user parameter is wrong'); r3logger.elog(error.message); _callback(error, null); return; } var _tenant = apiutil.isSafeString(tenant) ? tenant.toLowerCase() : ''; var _user = user; var _passwd = apiutil.isSafeString(passwd) ? passwd : ''; // get unscoped token rawGetUserUnscopedTokenWrap(_user, _passwd, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get user access token by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } // save to local val _user = jsonres.user; // over write var _userid = jsonres.userid; var _username = jsonres.user; var _unscopedtoken = jsonres.token; var _tokenexpire = jsonres.expire; // eslint-disable-line no-unused-vars var _region = jsonres.region; // eslint-disable-line no-unused-vars // break when unscoped token if(!apiutil.isSafeString(_tenant)){ _callback(null, _unscopedtoken); return; } // get scoped user token return rawGetScopedUserToken(_unscopedtoken, _username, _userid, _tenant, _callback); }); } //--------------------------------------------------------- // Get User Token from token issued by another authentication system //--------------------------------------------------------- // // Get scoped/unscoped user token from token issued by another authentication system // (ex. openstack identity token which is not registered in k2hr3 yet.) // function rawGetUserTokenByToken(token, tenant, callback) { var _callback = callback; if(!apiutil.isSafeString(token)){ var error = new Error('token parameter is wrong'); r3logger.elog(error.message); _callback(error, null); return; } var _tenant = apiutil.isSafeString(tenant) ? tenant.toLowerCase() : ''; var _orgtoken = token; // get unscoped token rawGetUserUnscopedTokenbyTokenWrap(_orgtoken, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get user access token by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } // save to local val var _userid = jsonres.userid; var _username = jsonres.user; var _unscopedtoken = jsonres.token; var _tokenexpire = jsonres.expire; // eslint-disable-line no-unused-vars var _region = jsonres.region; // eslint-disable-line no-unused-vars // break when unscoped token if(!apiutil.isSafeString(_tenant)){ _callback(null, _unscopedtoken); return; } // get scoped user token return rawGetScopedUserToken(_unscopedtoken, _username, _userid, _tenant, _callback); }); } // // Get scoped user token from unscoped user token // function rawGetScopedUserTokenByUnscoped(unscopedtoken, username, tenant, callback) { var error; if(!apiutil.isSafeStrings(unscopedtoken, username, tenant)){ error = new Error('unscopedtoken or username or tenant parameters are wrong'); r3logger.elog(error.message); callback(error, null); return; } // user id from user name var _tenant = tenant.toLowerCase(); var user_info = k2hr3.getUserId(username); // user id from user name if(null === user_info || !apiutil.isSafeEntity(user_info.name) || !apiutil.isSafeEntity(user_info.id)){ error = new Error('could not find username(' + username + ') from unscoped token in k2hdkc.'); r3logger.elog(error.message); callback(error, null); return; } // get scoped user token return rawGetScopedUserToken(unscopedtoken, user_info.name, user_info.id, _tenant, callback); } // // Get scoped user token from keystone // function rawGetScopedUserToken(unscopedtoken, username, userid, tenant, callback) { if(!apiutil.isSafeStrings(unscopedtoken, username, userid, tenant)){ var error = new Error('unscopedtoken or username or userid or tenant parameters are wrong'); r3logger.elog(error.message); _callback(error, null); return; } var _unscopedtoken = unscopedtoken; var _username = username; var _userid = userid; var _tenant = apiutil.isSafeString(tenant) ? tenant.toLowerCase() : ''; var _callback = callback; // get tenant list for check osapi.getUserTenantList(_unscopedtoken, _userid, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get tenant list for user ' + _username + '(token=' + _unscopedtoken + ') by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } //r3logger.dlog('get user tenant list jsonres=\n' + JSON.stringify(jsonres)); // check tenants(and initialize tenants) var _tenant_id = null; var _tenant_name = null; var _tenant_desc = null; var _tenant_display = null; var _tenant_list = new Array(0); for(var cnt = 0; cnt < jsonres.length; ++cnt){ if(!apiutil.isSafeEntity(jsonres[cnt])){ continue; } // over write var resobj = k2hr3.initUserTenant(_username, _userid, _username, jsonres[cnt].name, jsonres[cnt].id, jsonres[cnt].description, jsonres[cnt].display); if(!resobj.result){ error = new Error(resobj.message); r3logger.elog(error.message); _callback(error, null); return; } if(apiutil.compareCaseString(jsonres[cnt].name, _tenant)){ // find target tenant _tenant_id = jsonres[cnt].id; _tenant_name = jsonres[cnt].name; _tenant_desc = jsonres[cnt].description; _tenant_display = jsonres[cnt].display; } _tenant_list.push(jsonres[cnt].name); } // get and add local tenants var tmpresobj = k2hr3.listLocalTenant(_username, true); if(!apiutil.isSafeEntity(tmpresobj) || !apiutil.isSafeEntity(tmpresobj.result) || false === tmpresobj.result){ if(apiutil.isSafeEntity(tmpresobj) && apiutil.isSafeString(tmpresobj.message)){ r3logger.wlog('failed to get local tenant list by ' + tmpresobj.message); }else{ r3logger.wlog('failed to get local tenant list.'); } }else{ if(!apiutil.isEmptyArray(tmpresobj.tenants)){ for(var cnt2 = 0; cnt2 < tmpresobj.tenants.length; ++cnt2){ var one_tenant = tmpresobj.tenants[cnt2]; if(!apiutil.isSafeEntity(tmpresobj.tenants[cnt2])){ continue; } if(apiutil.compareCaseString(tmpresobj.tenants[cnt2].name, _tenant)){ // find target tenant _tenant_id = tmpresobj.tenants[cnt2].id; _tenant_name = tmpresobj.tenants[cnt2].name; _tenant_desc = tmpresobj.tenants[cnt2].desc; // eslint-disable-line no-unused-vars _tenant_display = tmpresobj.tenants[cnt2].display; // eslint-disable-line no-unused-vars } _tenant_list.push(one_tenant); } } } // check and remove old tenant for user if(!k2hr3.removeComprehensionByNewTenants(_username, _tenant_list)){ r3logger.elog('failed to remove some tenant for user, but continue...'); } // get scoped token osapi.getUserScopedToken(_unscopedtoken, _tenant_name, _tenant_id, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get scoped user token for user ' + _username + ' by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } //r3logger.dlog('get user scoped token jsonres=\n' + JSON.stringify(jsonres)); var token_seed = apiutil.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if(!rawSetUserToken(_username, _tenant_name, jsonres.token, jsonres.expire, jsonres.region, token_seed)){ error = new Error('failed to set unscoped/scoped user token'); r3logger.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres.token); return; }); }); } //--------------------------------------------------------- // set user/tenant token //--------------------------------------------------------- // tenant undefined(or null) thus token must be unscoped. // expire UTC ISO 8601 format // // [NOTE] Must initialize User/Tenant before calling this function. // function rawSetUserToken(user, tenant, token, expire, region, seed) { if(!apiutil.isSafeStrings(user, token, expire, region)){ // allow tenant/seed is null r3logger.elog('some parameters are wrong : user=' + JSON.stringify(user) + ', tenant=' + JSON.stringify(tenant) + ', token=' + JSON.stringify(token) + ', expire=' + JSON.stringify(expire) + ', region=' + JSON.stringify(region)); return false; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var keys = r3keys(user, tenant); var subkeylist; if(!apiutil.isSafeEntity(dkcobj)){ return false; } // // Check user key exists and create these. // var expire_limit = apiutil.calcExpire(expire); // UTC ISO 8601 to unixtime var token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" var user_token_key = keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + '/' + token; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" var user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" // [NOTE] // If tenant is null, following keys have not tenant keyword in that key string. // [ not have tenant name ] vs [ have tenant name ] // keys.USER_TENANT_AMBIGUOUS_KEY ---> "yrn:yahoo::::user:<user>:tenant/" or "yrn:yahoo::::user:<user>:tenant/<tenant>" // keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY ---> "yrn:yahoo::::user:<user>:tenant//token" or "yrn:yahoo::::user:<user>:tenant/<tenant>/token" // // token top subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(keys.USER_TENANT_AMBIGUOUS_KEY, true)); if(apiutil.tryAddStringToArray(subkeylist, keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY)){ if(!dkcobj.setSubkeys(keys.USER_TENANT_AMBIGUOUS_KEY, subkeylist)){ // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token -> yrn:yahoo::::user:<user>:tenant/{<tenant>} r3logger.elog('could not add ' + keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + ' subkey under ' + keys.USER_TENANT_AMBIGUOUS_KEY + ' key'); dkcobj.clean(); return false; } } // to token key subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY, true)); if(apiutil.tryAddStringToArray(subkeylist, user_token_key)){ if(!dkcobj.setSubkeys(keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY, subkeylist)){ // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token r3logger.elog('could not add ' + user_token_key + ' subkey under ' + keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + ' key'); dkcobj.clean(); return false; } } // get/set token value var old_value = dkcobj.getValue(user_token_key, null, true, null); if(old_value != region){ if(!dkcobj.setValue(user_token_key, region, null, null, expire_limit)){ // update new token key(value with region and expire) -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> r3logger.elog('could not set ' + region + '(expire=' + expire + ') to ' + user_token_key + ' key'); dkcobj.clean(); return false; } } // get/set seed value if(apiutil.isSafeString(seed)){ old_value = dkcobj.getValue(user_token_seed_key, null, true, null); if(old_value != seed){ if(!dkcobj.setValue(user_token_seed_key, seed, null, null, expire_limit)){ // update new token seed key(value with expire) -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed r3logger.elog('could not set ' + seed + '(expire=' + expire + ') to ' + user_token_seed_key + ' key'); dkcobj.clean(); return false; } } subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(user_token_key, true)); if(apiutil.tryAddStringToArray(subkeylist, user_token_seed_key)){ if(!dkcobj.setSubkeys(user_token_key, subkeylist)){ // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> r3logger.elog('could not add ' + user_token_seed_key + ' subkey under ' + user_token_key + ' key'); dkcobj.clean(); return false; } } } // create new token under token top key // // [NOTE] // This key is not set expire limit. if you need to check expire, // you look up key under user key. // if(!dkcobj.setValue(token_value_key, user_token_key)){ // create(over write) value(="yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>") without expire -> yrn:yahoo::::token/<token> r3logger.elog('could not set ' + user_token_key + ' value without expire to ' + token_value_key + ' key'); dkcobj.clean(); return false; } subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(keys.TOKEN_USER_TOP_KEY, true)); if(apiutil.tryAddStringToArray(subkeylist, token_value_key)){ if(!dkcobj.setSubkeys(keys.TOKEN_USER_TOP_KEY, subkeylist)){ // add subkey yrn:yahoo::::token:user/<token> -> yrn:yahoo::::token:user r3logger.elog('could not add ' + token_value_key + ' subkey under ' + keys.TOKEN_USER_TOP_KEY + ' key'); dkcobj.clean(); return false; } } dkcobj.clean(); return true; } //--------------------------------------------------------- // get user unscoped token //--------------------------------------------------------- // // Get user token from keystone // // result null or object // { // user: user name(if existed, from parameter) // userid: user id(if existed, from "yrn:yahoo::::user:<user name>:id") // scoped: false(always false) // token: token string(id) // expire: expire string(if existed, null) // region: region string // } // // [NOTE] // This function is wrapper for osapi.getUserUnscopedToken(). // This function checks existing unscoped token before call it. // function rawGetUserUnscopedTokenWrap(user, passwd, callback) { var _user = user; var _passwd = apiutil.isSafeString(passwd) ? passwd : ''; var _callback = callback; // get unscoped token osapi.getUserUnscopedToken(_user, _passwd, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get user access token by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } // init user var resobj = k2hr3.initUser(jsonres.user, jsonres.userid, jsonres.user, null); if(!resobj.result){ error = new Error(resobj.message); r3logger.elog(error.message); _callback(error, null); return; } // set unscoped token var token_seed = apiutil.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if(!rawSetUserToken(jsonres.user, null, jsonres.token, jsonres.expire, jsonres.region, token_seed)){ error = new Error('failed to set unscoped/scoped user token'); r3logger.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres); return; }); } //--------------------------------------------------------- // get user unscoped token from token issued by another authentication system //--------------------------------------------------------- // // Get user token from token issued by another authentication system // // result null or object // { // user: user name(if existed, from parameter) // userid: user id(if existed, from "yrn:yahoo::::user:<user name>:id") // scoped: false(always false) // token: token string(id) // expire: expire string(if existed, null) // region: region string // } // // [NOTE] // This function is wrapper for osapi.getUserUnscopedTokenByToken(). // This function checks existing unscoped token before call it. // function rawGetUserUnscopedTokenbyTokenWrap(token, callback) { var _orgtoken = token; var _callback = callback; // get unscoped token osapi.getUserUnscopedTokenByToken(_orgtoken, function(err, jsonres) { var error; if(null !== err){ error = new Error('could not get user access token by ' + err.message); r3logger.elog(error.message); _callback(error, null); return; } // init user var resobj = k2hr3.initUser(jsonres.user, jsonres.userid, jsonres.user, null); if(!resobj.result){ error = new Error(resobj.message); r3logger.elog(error.message); _callback(error, null); return; } // set unscoped token var token_seed = apiutil.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if(!rawSetUserToken(jsonres.user, null, jsonres.token, jsonres.expire, jsonres.region, token_seed)){ error = new Error('failed to set unscoped/scoped user token'); r3logger.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres); return; }); } //--------------------------------------------------------- // remove scoped user token //--------------------------------------------------------- // // token scoped user token // // [NOTE] This removes(force expire) scoped user token for // using ACR API. // The token used by ACR must be removed after checking // it, because this case allows using token one time. // function rawRemoveScopedUserToken(token) { if(!apiutil.isSafeString(token)){ r3logger.elog('parameter is wrong : token=' + JSON.stringify(token)); return false; } // // Check token // if(0 === token.indexOf('R=')){ r3logger.elog('token(' + JSON.stringify(token) + ') is role token.'); return false; }else if(0 === token.indexOf('U=')){ token = token.substr(2); // cut 'U=' } var token_info = rawCheckUserToken(token); if( null === token_info || !apiutil.isSafeString(token_info.user) || !apiutil.isSafeString(token_info.tenant) || !apiutil.isSafeEntity(token_info.scoped) || 'boolean' !== typeof token_info.scoped || true !== token_info.scoped ) { r3logger.elog('token(' + JSON.stringify(token) + ') is something wrong.'); return false; } // // Remove token // var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var subkeylist; var errmsg = ''; if(!apiutil.isSafeEntity(dkcobj)){ return false; } // // Keys // var keys = r3keys(token_info.user, token_info.tenant); var token_top_key = keys.TOKEN_USER_TOP_KEY; // "yrn:yahoo::::token:user" var token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" var utoken_top_key = keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY; // "yrn:yahoo::::user:<user>:tenant/<tenant>/token" var utoken_token_key= keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + '/' + token; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" var utoken_seed_key = utoken_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" // // check under token top // subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(token_top_key, true)); if(apiutil.removeStringFromArray(subkeylist, token_value_key)){ // remove subkeys "yrn:yahoo::::token:user/<token>" -> "yrn:yahoo::::token:user" if(!dkcobj.setSubkeys(token_top_key, subkeylist)){ errmsg += 'could not remove ' + token_value_key + ' subkey under ' + token_top_key + ' key, '; } } if(!dkcobj.remove(token_value_key, false)){ // remove key "yrn:yahoo::::token:user/<token>" errmsg += 'could not remove ' + token_value_key + 'key, probably it is not existed, '; } // // check under user top // subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(utoken_top_key, true)); if(apiutil.removeStringFromArray(subkeylist, utoken_token_key)){ // remove subkeys "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" -> "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token" if(!dkcobj.setSubkeys(utoken_top_key, subkeylist)){ errmsg += 'could not remove ' + utoken_token_key + ' subkey under ' + utoken_top_key + ' key, '; } } subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(utoken_token_key, true)); if(apiutil.removeStringFromArray(subkeylist, utoken_seed_key)){ // remove subkeys "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" -> "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" if(!dkcobj.setSubkeys(utoken_token_key, subkeylist)){ errmsg += 'could not remove ' + utoken_seed_key + ' subkey under ' + utoken_token_key + ' key, '; } } if(!dkcobj.remove(utoken_seed_key, false)){ // remove key "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" errmsg += 'could not remove ' + utoken_seed_key + 'key, probably it is not existed, '; } if(!dkcobj.remove(utoken_token_key, false)){ // remove key "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" errmsg += 'could not remove ' + utoken_token_key + 'key, probably it is not existed, '; } if(apiutil.isSafeString(errmsg)){ r3logger.elog(errmsg); } dkcobj.clean(); return true; // Returns true even if there is an error in deletion processing } //--------------------------------------------------------- // Check User Token //--------------------------------------------------------- // // Check User Token // // result : null or token information // { // role: role name // user: user name // hostname: always null // ip: always null // port: always 0 // cuk: always null // extra: always null // tenant: tenant name // display: display alias name for tenant // id: tenant id string // description: description for tenant // scoped: role token is always scoped(true) // region: when user token, the creator region name of the token // } // function rawCheckUserToken(token) { if(!apiutil.isSafeString(token)){ r3logger.elog('token parameter is wrong'); return null; } // get user/tenant from token var token_info = rawGetUserTenantByToken(token); if(null === token_info || !apiutil.isSafeEntity(token_info.user)){ r3logger.elog('token is not any user/tenant(expired or wrong token)'); return null; } // add scoped flag if(apiutil.isSafeEntity(token_info.tenant)){ token_info.scoped = true; }else{ token_info.scoped = false; } token_info.role = null; token_info.ip = null; token_info.hostname = null; token_info.port = 0; token_info.cuk = null; token_info.extra = null; return token_info; } //--------------------------------------------------------- // [Role Token] //--------------------------------------------------------- // // Token: Token Id(################) // X-Auth-Token: R=Token Id // Token Id: The "Token Id" is a unique hex number string for 128bit. // "Token Id" = "(<base id(64bit:8byte)> ^ <crypt id(64bit:8byte)>)" + "(<role id(64bit:8byte)> ^ <crypt id(64bit:8byte)>)" // Role Token Key: "yrn:yahoo::::token:role/<Token Id>" // Role Token Value: { // role: "role full yrn" // date: "UTC ISO 8601 time at create" // expire: "UTC ISO 8601 time at expire" // creator: "Host full yrn" or "User full yrn" // base: "id from host" or "last 64bit string in UserToken" // user: "user name", if creator is user, this value is user name.(if not, this is null) // ip: "ip address", if creator is host, this value is ip address.(if not, this is null) // hostname: "hostname", if creator is host, this value is hostname.(if not, this is null) // port: "port number", if creator is host, this value is port number.(if not, this is 0) // cuk: "cuk", if creator is host on iaas, this value is cuk.(allowed null) // extra: "extra", if creator is host on iaas, this value is extra.(allowed null) // tenant: "tenant name" for this role token(this mean token is scoped) // verify: "random 64bit id for verify token" // } // // [NOTE] // "role id" which is in "Token Id" is included from role key(yrn:yahoo:<Tenant>:::role:<role name>/id). // This value is secret, any API could not get this value directly. // //--------------------------------------------------------- // Get Role Token From user(token) //--------------------------------------------------------- // // user : this value is parsed from user token // tenant : this value is parsed from user token // role : target role name(full yrn or only role name) // expire_limit : specify expire second(default 24H = 24 * 60 * 60 sec, 0 means no expire time.) // // result : { // result: true/false // message: null or error message string // token: undefined(error) or role token string // } // // [NOTE] // set role token value is following // { // role: token's role yrn("yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}") // date: create date(UTC ISO 8601) // expire: expire date(UTC ISO 8601) // creator: creator yrn("yrn:yahoo::::user:<user>" or "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/hosts/ip/<ip:port>") // user: if creator is user, this value is user name. // hostname: always null // ip: always null // port: if creator is host, this value is port number.(if not, this is 0) // cuk: if creator is host on iaas, this value is cuk.(allowed null) // extra: if creator is host on iaas, this value is extra.(allowed null) // tenant: tenant name for this role token(this mean token is scoped) // base: 32bytes hex string // } // function rawGetRoleTokenByUser(user, tenant, role, expire_limit) { var resobj = {result: true, message: null}; if(!apiutil.isSafeStrings(user, tenant, role)){ // allow other argument is empty resobj.result = false; resobj.message = 'some parameters are wrong : user=' + JSON.stringify(user) + ', tenant=' + JSON.stringify(tenant) + ', role=' + JSON.stringify(role); r3logger.elog(resobj.message); return resobj; } if(!apiutil.isSafeEntity(expire_limit) || isNaN(expire_limit)){ // expire_limit must be number or null(undefined) expire_limit = expire_rtoken; // default 24H }else{ if(0 == expire_limit){ // [NOTE] // If 0, set the maximum value to 10 years. // Disable expire by setting a period of time within which this // application is guaranteed not to survive, as permitted by ISO8601. // expire_limit = expire_reg_rtoken; } } // check role name is only name or full yrn path var keys = r3keys(user, tenant); role = role.toLowerCase(); var roleptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/ var rolematchs = role.match(roleptn); if(apiutil.isEmptyArray(rolematchs) || rolematchs.length < 4){ // role is not matched role(maybe not full yrn), then we need check it is another yrn path roleptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/ if(role.match(roleptn)){ resobj.result = false; resobj.message = 'role(' + role + ') is not role yrn path)'; r3logger.elog(resobj.message); return resobj; } // role is only role name, then we do not modify it. }else{ // check tenant name if(tenant !== rolematchs[2]){ resobj.result = false; resobj.message = 'role(' + role + ') yrn has tenant(' + rolematchs[2] + '), but it is not specified tenant(' + tenant + ')'; r3logger.elog(resobj.message); return resobj; } // role is set only role name role = rolematchs[3]; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var subkeylist; if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } // // keys // var role_key = keys.ROLE_TOP_KEY + ':' + role; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}" var role_id_key = role_key + '/' + keys.ID_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/id" var role_tokens_key = role_key + '/' + keys.ROLE_TOKEN_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens" // user id var user_id = dkcobj.getValue(keys.USER_ID_KEY, null, true, null); // get user id from "yrn:yahoo::::user:<user name>:id" if(!apiutil.isSafeString(user_id)){ resobj.result = false; resobj.message = 'could not get user id(' + keys.USER_ID_KEY + ') value, or it is wrong value(' + JSON.stringify(user_id) + ').'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // role id var role_id = dkcobj.getValue(role_id_key, null, true, null); if(!apiutil.isSafeStrUuid4(role_id)){ var isError = false; var old_role_id = apiutil.parseJSON(role_id); if(!apiutil.isEmptyArray(old_role_id) && 4 == old_role_id.length){ // old version id, so reset new valus(UUID4) here. role_id = apiutil.getStrUuid4(); if(!dkcobj.setValue(role_id_key, role_id)){ // set value -> yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/id isError = true; resobj.message = 'could not get role id(' + role_id_key + ') value, because failed to reset new role id instead of old id.'; } }else{ isError = true; resobj.message = 'could not get role id(' + role_id_key + ') value, or it is wrong value(' + JSON.stringify(role_id) + ').'; } if(isError){ resobj.result = false; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } } // make token value var now_unixtime = apiutil.getUnixtime(); var token_value = {}; token_value.role = role_key; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}" token_value.date = (new Date(now_unixtime * 1000)).toISOString(); // now date(UTC ISO 8601) token_value.expire = (new Date((now_unixtime + expire_limit) * 1000)).toISOString(); // expire date(UTC ISO 8601) token_value.creator = keys.USER_KEY; // "yrn:yahoo::::user:<user>" token_value.user = user; // user(creator) token_value.hostname= null; // hostname(creator) token_value.ip = null; // ip(creator) token_value.port = 0; // port(creator) token_value.cuk = null; // cuk(creator) token_value.extra = null; // extra(creator) token_value.tenant = tenant; // tenant(scope) // role token and yrn key var role_token = ''; var token_role_key = null; // create key for(var is_loop = true; is_loop; ){ // for eslint // make role token var token_elements = apiutil.makeStringToken256(user_id, role_id); if(!apiutil.isSafeEntity(token_elements)){ resobj.result = false; resobj.message = 'could not make token from ' + JSON.stringify(user_id) + ' and ' + JSON.stringify(role_id); r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } role_token = token_elements.str_token; token_value.base = token_elements.str_base; // token base // role token key token_role_key = keys.TOKEN_ROLE_TOP_KEY + '/' + role_token; // "yrn:yahoo::::token:role/<role token>" // get role token for existing check var value = dkcobj.getValue(token_role_key, null, true, null); if(!apiutil.isSafeEntity(value)){ // set value to role token if(!dkcobj.setValue(token_role_key, JSON.stringify(token_value), null, null, expire_limit)){ // set token value -> yrn:yahoo::::token:role/<role token> resobj.result = false; resobj.message = 'could not set ' + JSON.stringify(token_value) + ' value to ' + token_role_key + ' key'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } break; }else{ r3logger.dlog('conflict role token(' + role_token + ') which already is used, so remake token for uniq.'); } } // Add token to role token top key's subkey list subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(keys.TOKEN_ROLE_TOP_KEY, true)); if(apiutil.tryAddStringToArray(subkeylist, token_role_key)){ if(!dkcobj.setSubkeys(keys.TOKEN_ROLE_TOP_KEY, subkeylist)){ // add subkey yrn:yahoo::::token:role/<role token> -> yrn:yahoo::::token:role resobj.result = false; resobj.message = 'could not add ' + token_role_key + ' subkey under ' + keys.TOKEN_ROLE_TOP_KEY + ' key'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } } // Add token to role tokens key's subkey list subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(role_tokens_key, true)); if(apiutil.tryAddStringToArray(subkeylist, token_role_key)){ if(!dkcobj.setSubkeys(role_tokens_key, subkeylist)){ // add subkey yrn:yahoo::::token:role/<role token> -> yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens resobj.result = false; resobj.message = 'could not add ' + token_role_key + ' subkey under ' + role_tokens_key + ' key'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } } // Add role token into result object. resobj.token = role_token; dkcobj.clean(); return resobj; } //--------------------------------------------------------- // Get Role Token From ip address //--------------------------------------------------------- // ip : ip address // port : port number(0, undefined, null means any port) // cuk : container unique key(undefined, null means any) // tenant : tenant name // role : target role name(full yrn or only role name) // expire_limit : specify expire second(default 24H = 24 * 60 * 60 sec) // // result : { // result: true/false // message: null or error message string // token: undefined(error) or role token string // } // // [NOTE] // set role token value is following // { // role: token's role yrn("yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}") // date: create date(UTC ISO 8601) // expire: expire date(UTC ISO 8601) // creator: creator yrn("yrn:yahoo::::user:<user>" or "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/hosts/ip/<ip:port>") // user: always null // hostname: always null // ip: if creator is host, this value is ip address. // port: if creator is host, this value is port number.(if not, this is 0) // cuk: if creator is host on iaas, this value is cuk.(allowed null) // extra: if creator is host on iaas, this value is extra.(allowed null) // tenant: tenant name for this role token(this mean token is scoped) // base: 32bytes hex string // } // // The role token which is built by host is made by only IP address. // Hostname can not build a role token, because it is not secure.(= Hostname can easily be disguised.) // Then only IP address can build a role token without UserToken. // function rawGetRoleTokenByIP(ip, port, cuk, tenant, role, expire_limit) { var resobj = {result: true, message: null}; if(!apiutil.isSafeStrings(tenant, role, ip)){ // allow other argument is empty resobj.result = false; resobj.message = 'some parameters are wrong : tenant=' + JSON.stringify(tenant) + ', role=' + JSON.stringify(role) + ', ip' + JSON.stringify(ip); r3logger.elog(resobj.message); return resobj; } if(!apiutil.isSafeString(ip)){ resobj.result = false; resobj.message = 'ip parameter is empty.'; r3logger.elog(resobj.message); return resobj; } if(!apiutil.isSafeEntity(port) || isNaN(port)){ // port must be number or null(undefined) port = 0; // force set port 0(any). } if(!apiutil.isSafeString(cuk)){ // cuk is string if spacified cuk = null; // force set null. } if(!apiutil.isSafeEntity(expire_limit) || isNaN(expire_limit)){ // expire_limit must be number or null(undefined) expire_limit = expire_rtoken; // default 24H } // check role name is only name or full yrn path var keys = r3keys(null, tenant); role = role.toLowerCase(); var roleptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/ var rolematchs = role.match(roleptn); if(apiutil.isEmptyArray(rolematchs) || rolematchs.length < 4){ // role is not matched role(maybe not full yrn), then we need check it is another yrn path roleptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/ if(role.match(roleptn)){ resobj.result = false; resobj.message = 'role(' + role + ') is not role yrn path)'; r3logger.elog(resobj.message); return resobj; } // role is only role name, then we do not modify it. }else{ // check tenant name if(tenant !== rolematchs[2]){ resobj.result = false; resobj.message = 'role(' + role + ') yrn has tenant(' + rolematchs[2] + '), but it is not specified tenant(' + tenant + ')'; r3logger.elog(resobj.message); return resobj; } // role is set only role name role = rolematchs[3]; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var subkeylist; if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } // // keys // var role_key = keys.ROLE_TOP_KEY + ':' + role; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}" var role_id_key = role_key + '/' + keys.ID_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/id" var role_tokens_key = role_key + '/' + keys.ROLE_TOKEN_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens" // find host key and get host id for base id var host_info = k2hr3.findRoleHost(dkcobj, role_key, null, ip, port, cuk); if(apiutil.isEmptyArray(host_info)){ resobj.result = false; resobj.message = 'Not found ip(' + ip + ') with port(' + String(port) + ') and cuk(' + JSON.stringify(cuk) + ') in tenant(' + tenant + ') + role(' + role + ')'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } var host_value = null; for(var pos = 0; pos < host_info.length; ++pos){ if(!apiutil.isSafeEntity(host_info[pos]) || !apiutil.isSafeString(host_info[pos].key)){ r3logger.dlog('Found ip(' + ip + ') with port(' + String(port) + ') in tenant(' + tenant + ') + role(' + role + '), but this host info(' + JSON.stringify(host_info[pos]) + ') is something wrong, skip it'); continue; } host_value = dkcobj.getValue(host_info[pos].key, null, true, null); host_value = apiutil.parseJSON(host_value); if( !apiutil.isSafeEntity(host_value) || (!apiutil.isSafeString(host_value.hostname) && !apiutil.isSafeString(host_value.ip)) || // hostname or ip is existed !apiutil.isSafeEntity(host_value.port) || !apiutil.isSafeString(host_value[keys.ID_KW]) ) { r3logger.dlog('Found ip(' + ip + ') with port(' + String(port) + ') in tenant(' + tenant + ') + role(' + role + '), but this host value(' + JSON.stringify(host_value) + ') is something wrong, skip it'); }else{ // found safe host info, we use this. host_value.key = host_info[pos].key; host_value.cuk = host_info[pos].cuk; host_value.extra= host_info[pos].extra; break; } host_value = null; } if(null === host_value){ resobj.result = false; resobj.message = 'Found ip(' + ip + ') with port(' + String(port) + ') in tenant(' + tenant + ') + role(' + role + '), but there is not safe any host info'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // role id var role_id = dkcobj.getValue(role_id_key, null, true, null); if(!apiutil.isSafeStrUuid4(role_id)){ var isError = false; var old_role_id = apiutil.parseJSON(role_id); if(!apiutil.isEmptyArray(old_role_id) && 4 == old_role_id.length){ // old version id, so reset new valus(UUID4) here. role_id = apiutil.getStrUuid4(); if(!dkcobj.setValue(role_id_key, role_id)){ // set value -> yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/id isError = true; resobj.message = 'could not get role id(' + role_id_key + ') value, because failed to reset new role id instead of old id.'; } }else{ isError = true; resobj.message = 'could not get role id(' + role_id_key + ') value, or it is wrong value(' + JSON.stringify(role_id) + ').'; } if(isError){ resobj.result = false; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } } // make token value var now_unixtime = apiutil.getUnixtime(); var token_value = {}; token_value.role = role_key; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}" token_value.date = (new Date(now_unixtime * 1000)).toISOString(); // now date(UTC ISO 8601) token_value.expire = (new Date((now_unixtime + expire_limit) * 1000)).toISOString(); // expire date(UTC ISO 8601) token_value.creator = host_value.key; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/hosts/ip/<ip:port>" token_value.user = null; // user(creator) token_value.hostname= null; // hostname(null) token_value.ip = host_value.ip; // ip(creator) token_value.port = host_value.port; // port(creator) token_value.cuk = host_value.cuk; // cuk(creator) token_value.extra = host_value.extra; // extra(creator) token_value.tenant = tenant; // tenant(scope) // role token and yrn key var role_token = ''; var token_role_key = null; // create key for(var is_loop = true; is_loop; ){ // for eslint // make role token var token_elements = apiutil.makeStringToken256(host_value[keys.ID_KW], role_id); if(!apiutil.isSafeEntity(token_elements)){ resobj.result = false; resobj.message = 'could not make token from ' + JSON.stringify(host_value[keys.ID_KW]) + ' and ' + JSON.stringify(role_id); r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } role_token = token_elements.str_token; token_value.base = token_elements.str_base; // token base // role token key token_role_key = keys.TOKEN_ROLE_TOP_KEY + '/' + role_token; // "yrn:yahoo::::token:role/<role token>" // get role token for existing check var value = dkcobj.getValue(token_role_key, null, true, null); if(!apiutil.isSafeEntity(value)){ // set value to role token if(!dkcobj.setValue(token_role_key, JSON.stringify(token_value), null, null, expire_limit)){ // set token value -> yrn:yahoo::::token:role/<role token> resobj.result = false; resobj.message = 'could not set ' + JSON.stringify(token_value) + ' value to ' + token_role_key + ' key'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } break; }else{ r3logger.dlog('conflict role token(' + role_token + ') which already is used, so remake token for uniq.'); } } // Add token to role token top key's subkey list subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(keys.TOKEN_ROLE_TOP_KEY, true)); if(apiutil.tryAddStringToArray(subkeylist, token_role_key)){ if(!dkcobj.setSubkeys(keys.TOKEN_ROLE_TOP_KEY, subkeylist)){ // add subkey yrn:yahoo::::token:role/<role token> -> yrn:yahoo::::token:role r3logger.elog('could not add ' + token_role_key + ' subkey under ' + keys.TOKEN_ROLE_TOP_KEY + ' key'); dkcobj.clean(); return resobj; } } // Add token to role tokens key's subkey list subkeylist = apiutil.getSafeArray(dkcobj.getSubkeys(role_tokens_key, true)); if(apiutil.tryAddStringToArray(subkeylist, token_role_key)){ if(!dkcobj.setSubkeys(role_tokens_key, subkeylist)){ // add subkey yrn:yahoo::::token:role/<role token> -> yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens resobj.result = false; resobj.message = 'could not add ' + token_role_key + ' subkey under ' + role_tokens_key + ' key'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } } // Add role token into result object. resobj.token = role_token; dkcobj.clean(); return resobj; } //--------------------------------------------------------- // Remove Role Token by User/IP //--------------------------------------------------------- // token : role token // user : this value is parsed from user token(or null) // tenant : this value is parsed from user token(or null) // ip : client ip address(or null) // port : port number when ip address parameter exists // cuk : cuk value when ip address parameter exists // // result : { // result: true/false // message: null or error message string // } // function rawRemoveRoleToken(token, user, tenant, ip, port, cuk) { var resobj = {result: true, message: null}; if(!apiutil.isSafeString(token)){ // allow other argument is empty resobj.result = false; resobj.message = 'token parameter is empty'; r3logger.elog(resobj.message); return resobj; } if( (apiutil.isSafeString(ip) && (apiutil.isSafeString(user) || apiutil.isSafeString(tenant))) || (!apiutil.isSafeString(ip) && (!apiutil.isSafeString(user) || !apiutil.isSafeString(tenant))) ) { resobj.result = false; resobj.message = 'tenant(' + JSON.stringify(tenant) + '), user(' + JSON.stringify(user) + '), ip(' + JSON.stringify(ip) + ') parameters are wrong pattern.'; r3logger.elog(resobj.message); return resobj; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var subkeylist; var value; if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } // // keys // var keys = (apiutil.isSafeString(ip) ? r3keys() : r3keys(user, tenant)); var role_token_key = keys.TOKEN_ROLE_TOP_KEY + '/' + token; // "yrn:yahoo::::token:role/<role token>" // get role token value value = dkcobj.getValue(role_token_key, null, true, null); value = apiutil.parseJSON(value); if(!apiutil.isSafeEntity(value)){ // already role token is removed or expired. r3logger.dlog('could not get role token(' + role_token_key + ') value, or it is wrong value(' + JSON.stringify(value) + '). We check all role token for expire here.'); dkcobj.clean(); // // check and remove old role token under token top key("yrn:yahoo::::token:role") if old token is expired // // [NOTE] // This processing is taking time, so it runs asynchronously. // And for notes on this processing, refer to NOTE of rawCleanupRoleToken function. // rawCleanupRoleToken(function(result) { if(!result){ r3logger.wlog('Failed to cleanup expired role tokens under ' + keys.TOKEN_ROLE_TOP_KEY + ' key, but continue...'); } }); // return success return resobj; } // check token value if( !apiutil.isSafeString(value.role) || !apiutil.isSafeString(value.date) || !apiutil.isSafeString(value.expire) || !apiutil.isSafeString(value.creator) || (!apiutil.isSafeString(value.user) && !apiutil.isSafeString(value.hostname) && !apiutil.isSafeString(value.ip)) || isNaN(value.port) || !apiutil.isSafeString(value.tenant) || !apiutil.isSafeString(value.base) ) { // // Not check expired token and ip address is empty here. // resobj.result = false; resobj.message = 'could not get role token(' + role_token_key + ') value, or it is wrong value(' + JSON.stringify(value) + ').'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // check expire if(apiutil.isExpired(value.expire)){ resobj.result = false; resobj.message = 'role token(' + role_token_key + ') is expired by expire date(' + value.expire + ').'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // keep role tokens key for removing key in its subkey list. var role_tokens_key = value.role + '/' + keys.ROLE_TOKEN_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens" // check requester if(apiutil.isSafeString(ip)){ // check ip address bases // // port if(!isNaN(port)){ port = parseInt(port); }else{ port = 0; } // cuk if(apiutil.isSafeString(cuk) && apiutil.isSafeString(cuk.trim())){ cuk = cuk.trim(); }else{ cuk = null; } // check if( value.port != port || apiutil.isSafeString(value.cuk) != apiutil.isSafeString(cuk) || (apiutil.isSafeString(value.cuk) && value.cuk != cuk) ) { resobj.result = false; resobj.message = 'could not remove role token(' + token + '), because port(' + JSON.stringify(port) + ' vs ' + JSON.stringify(value.port) + ')/cuk(' + JSON.stringify(cuk) + ' vs