UNPKG

k2hr3-api

Version:

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

1,013 lines (922 loc) 32.9 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: Hirotaka Wakabayashi * CREATE: Fri, Aug 20 2021 * REVISION: * */ //------------------------------------------------------------------------ // Usage //------------------------------------------------------------------------ // To enable this module, make the following settings in the K2HR3 API // configuration file(ex, production.json/local.json/etc). // // { // 'keystone': { // 'type': 'k8soidc' // } // } // // Set the value of the 'keystone'->'type' object to 'k8soidc'. // // Next, this module requires its own information, so the following // settings in configuration file are required. // // { // 'k8soidc': { // 'audience': '<client id for open id connect>', // 'issuer': '<issue url for open id connect>', // 'usernamekey': '<user name key name in token>', // 'k8sapi_url': '<kubernetes api url>', // 'k8s_ca_path': '<CA cert file path for kubernetes api url>', // 'k8s_sa_token': '<Service account token for kubernetes>' // 'unscopedtoken_exp':'<Expire limit for unscoped Token created from oidc>' // } // } // // Set the 'k8soidc' object as above. This object should contain the // following keys(objects). The contents of each setting are explained. // // [audience] // Set the client id for Open id connect. This key and value are // required. // [issuer] // Set the issuer URL of Open id connect. This key and value are // required. // [usernamekey] // Specify the key name that is the Username set in the Token of // Open id connect. If there is no key representing Username in // Token, it can be omitted. If omitted, the value of the 'sub' // key is treated as the Username. // [k8sapi_url] // Specify the URL of the Kubernetes API. This module accesses // the Kubernetes API to get the list of Kubernetes Namespaces. // For example, that is 'https://kubernetes.default.svc'. This key // and value are required. // [k8s_ca_path] // Specify the path of the CA certificate to access the Kubernetes // API. If you're running the K2HR3 API inside a Kubernetes pod, // it's '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'. // This key and value are required. // [k8s_sa_token] // Specify the Token of the Service Account to access the Kubernetes // API. If you're running the K2HR3 API inside a Kubernetes pod, // it's '/var/run/secrets/kubernetes.io/serviceaccount/token'. // This key and value are required. // [unscopedtoken_exp] // Specifies the expiration date of the Unscoped token created by // OIDC. This value is specified in seconds(s). // If this value does not exist or is less than or equal to 0, // the default value will be used. The default value is the same // as the OIDC token expiration date. // //------------------------------------------------------------------------ 'use strict'; var apiutil = require('./k2hr3apiutil'); var k2hr3 = require('./k2hr3dkc'); var r3keys = require('./k2hr3keys').getK2hr3Keys; // Debug logging objects var r3logger = require('../lib/dbglogging'); // decode oidc token libraries var { decode } = require('jose').base64url; var { jwtVerify } = require('jose'); var { createRemoteJWKSet } = require('jose'); // kubernetes client api var k8sclientapi = require('@kubernetes/client-node'); var fs = require('fs'); // https library var https = require('https'); // const variables var K8S_PUBLISHER_NAME = 'K8SOIDC'; var K8S_REGION_NAME = 'K8sCluster'; var OIDC_JWKS_URI_KEYNAME = 'jwks_uri'; // // Global variables from configuration file // var oidc_config = null; var oidc_audience = null; var oidc_issuer = null; var oidc_jwks_uri = null; var oidc_username = null; var k8s_api_url = null; var k8s_ca_cert = null; var k2hr3_k8s_sa_token = null; var unscopedtoken_exp = 0; // Expire limit for unscoped Token created from oidc(default is 0 means as same as oidc limit) (function() { var r3Conf = require('./k2hr3config').r3ApiConfig; var apiConf = new r3Conf(); oidc_config = apiConf.getOtherObject('k8soidc'); if(apiutil.isSafeEntity(oidc_config)){ oidc_audience = oidc_config.audience; oidc_issuer = oidc_config.issuer; oidc_username = oidc_config.usernamekey; k8s_api_url = oidc_config.k8sapi_url; k8s_ca_cert = oidc_config.k8s_ca_path; k2hr3_k8s_sa_token = fs.readFileSync(oidc_config.k8s_sa_token, 'utf8'); // unscopedtoken_exp must be number if(apiutil.isSafeEntity(oidc_config.unscopedtoken_exp) && !isNaN(oidc_config.unscopedtoken_exp) && 0 < oidc_config.unscopedtoken_exp){ unscopedtoken_exp = oidc_config.unscopedtoken_exp; } } }()); //--------------------------------------------------------- // User Token for k8s oidc //--------------------------------------------------------- // // user : user name which is verified authentication // user_id : user id which is verified authentication // expire_limit : specify expire second(default 24H = 24 * 60 * 60 sec), and allow empty // // result : { // result: true/false // message: null or error message string // token: undefined(error) or user token string // expire_at: expire date(UTC ISO 8601) // token_seed: JSON token seed data // userid: user id // } // // [NOTE] // user token seed value is following // { // publisher: "K8SOIDC" // userexid: "user extra id(a part of seed uuid4)" // date: "UTC ISO 8601 time at create" // expire: "UTC ISO 8601 time at expire" // creator: "User full yrn" // base: "32byte hex string" // user: "user name" // ip: always null // hostname: always null // port: always 0 // cuk: always null // extra: always null // tenant: if scoped token, this is "tenant name". if not, this is null // } // function rawCreateUserTokenByK8sUser(user, user_id, tenant, expire_limit) { var resobj = {result: true, message: null}; if(!apiutil.isSafeString(user)){ // allow another parameter is null resobj.result = false; resobj.message = 'parameter is wrong : user=' + JSON.stringify(user); r3logger.elog(resobj.message); return resobj; } if(!apiutil.isSafeStrUuid4(user_id)){ // user_id is uuid4 resobj.result = false; resobj.message = 'parameter is wrong : user_id(must be uuid4)=' + JSON.stringify(user_id); r3logger.elog(resobj.message); return resobj; } if(!apiutil.isSafeString(tenant)){ tenant = null; } if(0 < unscopedtoken_exp){ expire_limit = unscopedtoken_exp; // override expire limit by config }else{ if(!apiutil.isSafeEntity(expire_limit) || isNaN(expire_limit) || expire_limit <= 0){ expire_limit = 24 * 60 * 60; // default 24H } } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) user = user.toLowerCase(); var keys = r3keys(user, null); if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } var user_id_uuid4 = user_id; // user id must be UUID4 var user_ex_id = apiutil.getStrUuid4(); // set seed(uuid4) // make token seed value var now_unixtime = apiutil.getUnixtime(); var token_seed = {}; token_seed.publisher= K8S_PUBLISHER_NAME; // "K8SOIDC" token_seed.userexid = user_ex_id; // seed(uuid4) token_seed.date = (new Date(now_unixtime * 1000)).toISOString(); // now date(UTC ISO 8601) token_seed.expire = (new Date((now_unixtime + expire_limit) * 1000)).toISOString(); // expire date(UTC ISO 8601) token_seed.creator = keys.USER_KEY; // "yrn:yahoo::::user:<user>" token_seed.user = user; // user(creator) token_seed.hostname = null; // hostname(creator) token_seed.ip = null; // ip(creator) token_seed.port = 0; // port(creator) token_seed.cuk = null; // cuk(creator) token_seed.extra = null; // extra(creator) token_seed.tenant = tenant; // tenant(if scope, not null) // user token and yrn key var user_token = ''; var token_user_key = null; // create key for(var is_loop = true; is_loop; ){ // for eslint // make user token var token_elements = apiutil.makeStringToken256(user_ex_id, user_id_uuid4); if(!apiutil.isSafeEntity(token_elements)){ resobj.result = false; resobj.message = 'could not make token from ' + JSON.stringify(user_ex_id) + ' and ' + JSON.stringify(user_id); r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } user_token = token_elements.str_token; token_seed.base = token_elements.str_base; // token base // user token key token_user_key = keys.TOKEN_USER_TOP_KEY + '/' + user_token; // "yrn:yahoo::::token:user/<user token>" // get user token for existing check var value = dkcobj.getValue(token_user_key, null, true, null); if(!apiutil.isSafeEntity(value)){ // succeed uniq token break; } r3logger.dlog('conflict user token(' + user_token + ') which already is used, so remake token for uniq.'); } // Add user token/expire/seed into result object. resobj.token = user_token; resobj.expire_at = token_seed.expire; resobj.token_seed = JSON.stringify(token_seed); resobj.userid = user_id; dkcobj.clean(); return resobj; } //--------------------------------------------------------- // Verify User Token Publisher For k8s oidc //--------------------------------------------------------- // // token_seed : token seed data // // result : { // result: true/false // message: null or error message string // } // function rawWrapVerifyUserTokenPublisherForK8s(token_seed) { var resobj = {result: true, message: null}; var seed = apiutil.parseJSON(token_seed); if( !apiutil.isSafeEntity(seed) || !apiutil.isSafeString(seed.publisher) || (seed.publisher != K8S_PUBLISHER_NAME) ) // publisher must be 'K8SOIDC' { resobj.result = false; resobj.message = 'token_seed(not printable) is not safe entity.'; r3logger.elog(resobj.message); return resobj; } return resobj; } //--------------------------------------------------------- // Verify User Token (OIDC Token) //--------------------------------------------------------- // // dkcobj_permanent : dkcobj object // user : target user name for token // tenant : target tenant name for token(if token is scoped) // token : check token // token_seed : token seed data // // result : { // result: true/false // message: null or error message string // } // function rawVerifyUserTokenByK8sUser(dkcobj_permanent, user, tenant, token, token_seed) { var resobj = {result: true, message: null}; if(!apiutil.isSafeStrings(token, token_seed, user)){ resobj.result = false; resobj.message = 'some parameters are wrong : token=' + JSON.stringify(token) + ', token_seed=<not printable>, user=' + JSON.stringify(user); r3logger.elog(resobj.message); return resobj; } // check all seed values var seed = apiutil.parseJSON(token_seed); if( !apiutil.isSafeEntity(seed) || !apiutil.isSafeString(seed.publisher) || (seed.publisher != K8S_PUBLISHER_NAME) || // publisher must be 'K8SOIDC' !apiutil.isSafeString(seed.userexid) || !apiutil.isSafeString(seed.date) || !apiutil.isSafeString(seed.expire) || !apiutil.isSafeString(seed.creator) || !apiutil.isSafeString(seed.base) || !apiutil.isSafeString(seed.user) || !apiutil.compareCaseString(seed.user, user)) { resobj.result = false; resobj.message = 'token_seed(not printable) is not safe entity.'; r3logger.elog(resobj.message); return resobj; } // check expire if(apiutil.isExpired(seed.expire)){ resobj.result = false; resobj.message = 'token is expired by expire date(' + seed.expire + ') in token_seed.'; r3logger.elog(resobj.message); return resobj; } // check tenant name(if tenant is specified, seed must have same tenant name) if(apiutil.isSafeString(seed.tenant) !== apiutil.isSafeString(tenant) || (apiutil.isSafeString(seed.tenant) && !apiutil.compareCaseString(seed.tenant, tenant))){ resobj.result = false; resobj.message = 'token_seed(not printable) is (un)scoped, but tenant name is (not) specified. Then unmatched.'; r3logger.elog(resobj.message); return resobj; } // k2hdkc var keys = r3keys(seed.user, seed.tenant); var dkcobj = dkcobj_permanent; if(!apiutil.isSafeEntity(dkcobj)){ dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } } // get user id var userid = dkcobj.getValue(keys.USER_ID_KEY, null, true, null); // get user id from "yrn:yahoo::::user:<user>:id" if(!apiutil.isSafeEntity(dkcobj_permanent)){ dkcobj.clean(); } if(!apiutil.isSafeString(userid)){ resobj.result = false; resobj.message = 'could not get user id for user(' + seed.user + ').'; r3logger.elog(resobj.message); return resobj; } // make verify token var token_elements = apiutil.makeStringToken256(seed.userexid, userid, seed.base); if(!apiutil.isSafeEntity(token_elements)){ resobj.result = false; resobj.message = 'could not make verify token from ' + JSON.stringify(seed.userexid) + ' and ' + JSON.stringify(userid) + ' and ' + JSON.stringify(seed.base); r3logger.elog(resobj.message); return resobj; } if(token !== token_elements.str_token){ resobj.result = false; resobj.message = 'token(' + token + ') verify is failure, verify token is ' + token_elements.str_token + '.'; r3logger.elog(resobj.message); return resobj; } return resobj; } //--------------------------------------------------------- // Get User/Tenant information by User Token //--------------------------------------------------------- // // Result: { // result: true/false // message: null or error message string // user: user name // userid: user id // tenant: if token is scoped token, this value is set tenant name. // } // function rawGetUserTenantInfoByUserToken(token) { var resobj = {result: true, message: null}; if(!apiutil.isSafeString(token)){ resobj.result = false; resobj.message = 'parameter is wrong : token=' + JSON.stringify(token); r3logger.elog(resobj.message); return resobj; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var keys = r3keys(); if(!apiutil.isSafeEntity(dkcobj)){ resobj.result = false; resobj.message = 'Not initialize yet.'; r3logger.elog(resobj.message); return resobj; } // get token key under user key var token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" var user_token_key = dkcobj.getValue(token_value_key, null, true, null); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" if(!apiutil.isSafeString(user_token_key)){ resobj.result = false; resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') is not existed.'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // get user name and tenant name from token key yrn path var pattern = new RegExp('^' + keys.MATCH_ANY_USER_TOKEN); // regex = /^yrn:yahoo::::user:(.*):tenant\/(.*)\/token\/(.*)/ var matches = user_token_key.match(pattern); // reverse to user/tenant names if(apiutil.isEmptyArray(matches) || matches.length < 4 || '' === apiutil.getSafeString(matches[1])){ resobj.result = false; resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') has wrong format value(' + user_token_key + ')'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } var token_user = apiutil.getSafeString(matches[1]); var token_tenant= apiutil.getSafeString(matches[2]); if('' === token_tenant){ token_tenant= null; } // get token seed var user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" var token_seed = dkcobj.getValue(user_token_seed_key, null, true, null); if(!apiutil.isSafeString(token_seed)){ resobj.result = false; resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') does not have token seed data.'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // verify token var vres = rawVerifyUserTokenByK8sUser(dkcobj, token_user, token_tenant, token, token_seed); if(!vres.result){ resobj.result = false; resobj.message = 'failed to verify token(' + token + ') with seed by ' + vres.message; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } // get user id keys = r3keys(token_user, null); // remake keys var userid = dkcobj.getValue(keys.USER_ID_KEY, null, true, null); // get user id from "yrn:yahoo::::user:<user>:id" if(!apiutil.isSafeString(userid)){ resobj.result = false; resobj.message = 'could not get user id for user(' + token_user + ').'; r3logger.elog(resobj.message); dkcobj.clean(); return resobj; } dkcobj.clean(); // make result resobj.user = token_user; resobj.userid = userid; resobj.tenant = token_tenant; return resobj; } //--------------------------------------------------------- // Verify (Un)scoped Token //--------------------------------------------------------- // // token : unscoped/scoped token // function rawVerifyUnscopedToken(token) { if(!apiutil.isSafeString(token)){ r3logger.elog('token(' + JSON.stringify(token) + ') parameter is wrong.'); return false; } var dkcobj = k2hr3.getK2hdkc(true, false); // use permanent object(need to clean) var keys = r3keys(); if(!apiutil.isSafeEntity(dkcobj)){ r3logger.elog('K2hdkc client is not initialized yet.'); return false; } // get token key under user key var token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" var user_token_key = dkcobj.getValue(token_value_key, null, true, null); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" if(!apiutil.isSafeString(user_token_key)){ r3logger.elog('token key(' + token_value_key + ') for token(' + token + ') is not existed.'); dkcobj.clean(); return false; } // get user name and tenant name from token key yrn path var pattern = new RegExp('^' + keys.MATCH_ANY_USER_TOKEN); // regex = /^yrn:yahoo::::user:(.*):tenant\/(.*)\/token\/(.*)/ var matches = user_token_key.match(pattern); // reverse to user/tenant names if(apiutil.isEmptyArray(matches) || matches.length < 4 || '' === apiutil.getSafeString(matches[1])){ r3logger.elog('token key(' + token_value_key + ') for token(' + token + ') has wrong format value(' + user_token_key + ')'); dkcobj.clean(); return false; } var token_user = apiutil.getSafeString(matches[1]); var token_tenant= apiutil.getSafeString(matches[2]); if('' === token_tenant){ token_tenant= null; } // get token seed var user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" var token_seed = dkcobj.getValue(user_token_seed_key, null, true, null); if(!apiutil.isSafeString(token_seed)){ r3logger.elog('token key(' + token_value_key + ') for token(' + token + ') does not have token seed data.'); dkcobj.clean(); return false; } // verify token var vres = rawVerifyUserTokenByK8sUser(dkcobj, token_user, token_tenant, token, token_seed); if(!vres.result){ r3logger.elog('failed to verify token(' + token + ') with seed by ' + vres.message); dkcobj.clean(); return false; } dkcobj.clean(); return true; } //--------------------------------------------------------- // Get Scoped token from k8s user token //--------------------------------------------------------- // // callback(error, result): // result = { // user: user name // userid: user id // scoped: always true // token: token string // expire: expire string(UTC ISO 8601) // region: region string // token_seed: JSON token seed data // } // // [NOTE] // The token is allowed scoped token, but it must be same tenant token. // function rawGetUserScopedTokenK8s(token, tenant, callback) { var error; if(!apiutil.isSafeStrings(token, tenant)){ error = new Error('some parameters are wrong : token=' + JSON.stringify(token) + ', tenant=' + JSON.stringify(tenant)); r3logger.elog(error.message); callback(error, null); return; } // verify and get user/tenant information var token_info = rawGetUserTenantInfoByUserToken(token); if(!token_info.result){ error = new Error('could not get any information from token(' + token + '), result : ' + token_info.message); r3logger.elog(error.message); callback(error, null); return; } // check tenant name if(apiutil.isSafeString(token_info.tenant) && token_info.tenant !== tenant){ error = new Error('token(' + token + ') has scoped(' + token_info.tenant + '), but it is not as same as the request tenant(' + tenant + ').'); r3logger.elog(error.message); callback(error, null); return; } // create scoped token var resobj = rawCreateUserTokenByK8sUser(token_info.user, token_info.userid, tenant); // not specify expire limit now(using default). if(!resobj.result){ error = new Error('could not create user scoped token for uname(' + token_info.user + ')/user id(' + token_info.userid + ') for tenant(' + tenant + ').'); r3logger.elog(error.message); callback(error, null); return; } // make result var result = {}; result.user = token_info.user; result.userid = token_info.userid; result.scoped = true; result.token = resobj.token; result.expire = resobj.expire_at; result.region = K8S_REGION_NAME; result.token_seed = resobj.token_seed; callback(null, result); } //--------------------------------------------------------- // Get Unscoped token by oidc token //--------------------------------------------------------- // // callback(error, result): // result = { // user: user name User name in token: set user name if specified user name key name in config. if not specified, set user id // userid: user id User id in token: payload in token has 'sub' key, it is user id. // scoped: false (always false) // token: token string(id) OIDC Token // expire: expire string expire in token: payload in token has 'exp' key, it is expire unix time. // region: region string (always n/a) // token_seed: seed ({publisher: 'K8SOIDC'}) // } // // // Utility - Verify OIDC token and get user name // // token: oidc token // async function rawVerifyTokenAndGetUsername(token) { var jwtParam = { issuer: oidc_issuer, audience: oidc_audience }; var myPromise = function(issuer_url, conf_key){ return new Promise(function(resolve, reject){ https.get(oidc_issuer + '/.well-known/openid-configuration', function(res){ if(res.statusCode !== 200){ res.resume(); reject('statusCode should be 200, not ', res.statusCode); } res.setEncoding('utf8'); let rawData = ''; res.on('data', function(chunk){ rawData += chunk; }); res.on('end', function(){ var parsedData = apiutil.parseJSON(rawData); if(apiutil.isSafeEntity(parsedData[conf_key])){ resolve(parsedData[conf_key]); }else{ var errorMsg = ('the ' + conf_key + ' key should exist, but no such a key'); r3logger.elog(errorMsg); reject(errorMsg); } }); }).on('error', function(err){ r3logger.elog(err.message); reject(err.message); }); }); }; // 1. Calls async here. async function asyncFunction(){ // 2. Calls await() here. try{ oidc_jwks_uri = await myPromise(oidc_issuer, OIDC_JWKS_URI_KEYNAME); if(!apiutil.isSafeString(oidc_jwks_uri)){ var error = new Error('oidc_jwks_uri should be defined, but no oidc_jwks_uri.'); r3logger.elog(error.message); throw error; } }catch(err){ r3logger.elog(err.message); throw err; } var JWKS = createRemoteJWKSet(new URL(oidc_jwks_uri)); var { payload, protectedHeader } = await jwtVerify(token, JWKS, jwtParam).catch(function(err){ // eslint-disable-line no-unused-vars r3logger.elog(err.message); throw err; }); var userName = null; if(apiutil.isSafeString(oidc_username)){ userName = payload[oidc_username]; }else{ if(apiutil.isSafeString(payload.sub)){ userName = payload.sub; } } if(!apiutil.isSafeString(userName)){ error = new Error('failed to verify token for getting user name.'); r3logger.elog(error.message); throw error; } return userName; } return asyncFunction(); } function rawGetUserUnscopedTokenK8s(token, callback) { if(!apiutil.isSafeString(token)){ var error = new Error('oidc token parameter is not string or empty.'); r3logger.elog(error.message); callback(error, null); return; } // // Check the id_token. // // see. https://openid.net/specs/openid-connect-core-1_0.html#IDToken // var parts = token.split('.', 2); if(2 !== parts.length){ error = new Error('oidc token must have two parts, but it has ' + parts.length + ' parts.'); r3logger.elog(error.message); callback(error, null); return; } // // decode part[1] to payload // var payload = apiutil.parseJSON(new TextDecoder().decode(decode(parts[1]))); if(!apiutil.isSafeEntity(payload)){ error = new Error('could not decode json from the part[1] in oidc token.'); r3logger.elog(error.message); callback(error, null); return; } // // payload must have 'sub' key for user id // var userid; if(apiutil.isSafeString(payload.sub)){ userid = payload.sub; }else{ error = new Error('token payload should contain sub(userid), but not find sub(userid).'); r3logger.elog(error.message); callback(error, null); return; } // // get user name from payload // var username; if(apiutil.isSafeString(oidc_username)){ if(apiutil.isSafeString(payload[oidc_username])){ username = payload[oidc_username]; }else{ error = new Error('token payload should contain user name(' + oidc_username + '), but not find it.'); r3logger.elog(error.message); callback(error, null); return; } }else{ // If user name key is not specified, user id will be used instead. username = userid; } // // verify token by JWT library and get user name // var _callback = callback; var lower_username = username.toLowerCase(); // to lower case var verified_username; rawVerifyTokenAndGetUsername(token).then(function(result){ verified_username = result; // // compare user name // if(!apiutil.compareCaseString(lower_username, verified_username)){ error = new Error('oidc token has ' + lower_username + ' username, but verified username(' + verified_username + ') is different.'); r3logger.elog(error.message); _callback(error, null); return; } // core seed var user_id_uuid4 = apiutil.cvtNumberStringToUuid4(userid, 10); // payload.sub is decimal string var expire_limit; if(apiutil.isSafeEntity(payload['exp']) && !isNaN(payload['exp'])){ expire_limit = payload['exp'] - apiutil.getUnixtime(); if(expire_limit <= 0){ expire_limit= 24 * 60 * 60; // default 24H } }else{ expire_limit = 24 * 60 * 60; // default 24H } // create token var resobj = rawCreateUserTokenByK8sUser(lower_username, user_id_uuid4, null, expire_limit); if(!resobj.result){ error = new Error('could not create user token for uname(' + lower_username + ') or something wrong result : ' + resobj.message); r3logger.elog(error.message); _callback(error, null); return; } // make result var resultobj = {}; resultobj.user = lower_username; resultobj.userid = resobj.userid; resultobj.scoped = false; resultobj.token = resobj.token; resultobj.expire = resobj.expire_at; resultobj.region = K8S_REGION_NAME; resultobj.token_seed = resobj.token_seed; _callback(null, resultobj); }).catch(function(err){ r3logger.elog(err.message); _callback(err, null); return; }); return; } //--------------------------------------------------------- // Get tenant list by unscoped token //--------------------------------------------------------- // // unscopedtoken: oidc token(Not use) // function rawGetUserTenantListK8s(unscopedtoken, callback) { if(!apiutil.isSafeString(unscopedtoken)){ var error = new Error('unscopedtoken parameter is wrong.'); r3logger.elog(error.message); callback(error, null); return; } // Verify unscoped token if(!rawVerifyUnscopedToken(unscopedtoken)){ error = new Error('unscopedtoken is not safe, varidation is failed.'); r3logger.elog(error.message); callback(error, null); return; } var _callback = callback; // // Get Namespaces by Service Access token // var cluster = { server: k8s_api_url, caFile: k8s_ca_cert, skipTLSVerify: false }; var user = { token: k2hr3_k8s_sa_token // [NOTE] k2hr3_k8s_sa_token is global variable }; var kubeconfig = new k8sclientapi.KubeConfig(); kubeconfig.loadFromClusterAndUser(cluster, user); var k8sApi = kubeconfig.makeApiClient(k8sclientapi.CoreV1Api); k8sApi.setDefaultAuthentication({ applyToRequest: function(opts){ // [NOTE] Make this an arrow function in the future. opts.headers.Authorization = 'Bearer ' + k2hr3_k8s_sa_token, // [NOTE] k2hr3_k8s_sa_token is global variable opts.rejectUnauthorized = false; } }); var k8s_ns = ['kube-node-lease', 'kube-public', 'kube-system', 'kubernetes-dashboard']; var resarr = new Array; k8sApi.listNamespace().then(function(response){ // [NOTE] Using Promise for(var pos = 0; pos < response.body.items.length; ++pos){ // check body...items if( !apiutil.isSafeEntity(response.body) || !apiutil.isArray(response.body.items) || !apiutil.isSafeEntity(response.body.items[pos].metadata) || !apiutil.isSafeString(response.body.items[pos].metadata.name) ) { r3logger.wlog('one of response for project(tenant) list is something wrong : ' + JSON.stringify(response.body)); continue; } // Is the k8s cluster namespace in kubernetes system namespaces? if(apiutil.findStringInArray(k8s_ns, response.body.items[pos].metadata.name)){ continue; } var tenant = {}; tenant.name = response.body.items[pos].metadata.name; tenant.id = response.body.items[pos].metadata.uid; tenant.description = response.body.items[pos].metadata.name; tenant.display = response.body.items[pos].metadata.name; resarr.push(tenant); } if(0 === resarr.length){ error = new Error('no tenant exists'); r3logger.elog(error.message); _callback(error, null); return; } _callback(null, resarr); }).catch (function(error) { r3logger.elog(error.message); _callback(error, null); }); return; } //--------------------------------------------------------- // Exports //--------------------------------------------------------- // // uname: username // passwd: passwd // exports.getUserUnscopedToken = function(uname, passwd, callback) { var error = new Error('getUserUnscopedToken is not implemented'); r3logger.elog(error.message); callback(error, null); }; // // Get Unscoped Token // // token: OIDC token // exports.getUserUnscopedTokenByToken = function(token, callback) { return rawGetUserUnscopedTokenK8s(token, callback); }; // // Get Scoped Token // // tenantid: not used // exports.getUserScopedToken = function(unscopedtoken, tenantname, tenantid, callback) { return rawGetUserScopedTokenK8s(unscopedtoken, tenantname, callback); }; // // Verify publisher type in seed // exports.verifyUserTokenPublisher = function(token_seed) { return rawWrapVerifyUserTokenPublisherForK8s(token_seed); }; // // Verify token // exports.verifyUserToken = function(user, tenant, token, token_seed) { return rawVerifyUserTokenByK8sUser(null, user, tenant, token, token_seed); }; // // Get tenant list // exports.getUserTenantList = function(unscopedtoken, userid, callback) { return rawGetUserTenantListK8s(unscopedtoken, callback); }; /* * 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 */