k2hr3-api 1.0.42 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/k2hr3-init.sh.templ +4 -4
- package/dist/.gitkeep +0 -0
- package/dist/src/app.js +262 -0
- package/{bin → dist/src/bin}/run.sh +1 -1
- package/dist/src/bin/watcher.js +113 -0
- package/dist/src/bin/www.js +217 -0
- package/dist/src/lib/basicipcheck.js +392 -0
- package/dist/src/lib/cacerts.js +106 -0
- package/dist/src/lib/dbglogging.js +190 -0
- package/dist/src/lib/dummyuserapi.js +719 -0
- package/dist/src/lib/ipwatch.js +354 -0
- package/dist/src/lib/k2hr3acrutil.js +532 -0
- package/dist/src/lib/k2hr3apiutil.js +1444 -0
- package/dist/src/lib/k2hr3cliutil.js +183 -0
- package/dist/src/lib/k2hr3config.js +832 -0
- package/dist/src/lib/k2hr3cryptutil.js +258 -0
- package/dist/src/lib/k2hr3dkc.js +12121 -0
- package/dist/src/lib/k2hr3extdata.js +198 -0
- package/dist/src/lib/k2hr3keys.js +207 -0
- package/dist/src/lib/k2hr3resutil.js +111 -0
- package/dist/src/lib/k2hr3template.js +6546 -0
- package/dist/src/lib/k2hr3tokens.js +2643 -0
- package/dist/src/lib/k2hr3userdata.js +296 -0
- package/dist/src/lib/k8soidc.js +1000 -0
- package/dist/src/lib/openstackapiv2.js +695 -0
- package/dist/src/lib/openstackapiv3.js +932 -0
- package/dist/src/lib/openstackep.js +667 -0
- package/{tests/auto_common.js → dist/src/lib/types.js} +4 -38
- package/dist/src/routes/acr.js +704 -0
- package/dist/src/routes/debugVerify.js +294 -0
- package/dist/src/routes/extdata.js +219 -0
- package/dist/src/routes/list.js +264 -0
- package/dist/src/routes/policy.js +840 -0
- package/dist/src/routes/resource.js +1489 -0
- package/dist/src/routes/role.js +2627 -0
- package/dist/src/routes/service.js +908 -0
- package/dist/src/routes/tenant.js +1141 -0
- package/dist/src/routes/userTokens.js +482 -0
- package/dist/src/routes/userdata.js +212 -0
- package/dist/src/routes/version.js +103 -0
- package/package.json +152 -121
- package/ChangeLog +0 -378
- package/app.js +0 -292
- package/bin/watcher +0 -122
- package/bin/www +0 -180
- package/eslint.config.mjs +0 -68
- package/lib/basicipcheck.js +0 -376
- package/lib/cacerts.js +0 -71
- package/lib/dbglogging.js +0 -151
- package/lib/dummyuserapi.js +0 -766
- package/lib/ipwatch.js +0 -379
- package/lib/k2hr3acrutil.js +0 -516
- package/lib/k2hr3apiutil.js +0 -1494
- package/lib/k2hr3cliutil.js +0 -191
- package/lib/k2hr3config.js +0 -826
- package/lib/k2hr3cryptutil.js +0 -254
- package/lib/k2hr3dkc.js +0 -12632
- package/lib/k2hr3extdata.js +0 -198
- package/lib/k2hr3keys.js +0 -234
- package/lib/k2hr3resutil.js +0 -100
- package/lib/k2hr3template.js +0 -6925
- package/lib/k2hr3tokens.js +0 -2799
- package/lib/k2hr3userdata.js +0 -312
- package/lib/k8soidc.js +0 -1012
- package/lib/openstackapiv2.js +0 -764
- package/lib/openstackapiv3.js +0 -1032
- package/lib/openstackep.js +0 -553
- package/routes/acr.js +0 -738
- package/routes/debugVerify.js +0 -263
- package/routes/extdata.js +0 -232
- package/routes/list.js +0 -270
- package/routes/policy.js +0 -869
- package/routes/resource.js +0 -1441
- package/routes/role.js +0 -2664
- package/routes/service.js +0 -894
- package/routes/tenant.js +0 -1095
- package/routes/userTokens.js +0 -511
- package/routes/userdata.js +0 -218
- package/routes/version.js +0 -108
- package/templ/Dockerfile.templ +0 -71
- package/tests/auto_acr.js +0 -1101
- package/tests/auto_acr_spec.js +0 -79
- package/tests/auto_all_spec.js +0 -142
- package/tests/auto_control_subprocess.sh +0 -243
- package/tests/auto_extdata.js +0 -220
- package/tests/auto_extdata_spec.js +0 -79
- package/tests/auto_init_config_json.sh +0 -275
- package/tests/auto_k2hdkc_server.ini +0 -109
- package/tests/auto_k2hdkc_slave.ini +0 -83
- package/tests/auto_list.js +0 -439
- package/tests/auto_list_spec.js +0 -79
- package/tests/auto_policy.js +0 -1579
- package/tests/auto_policy_spec.js +0 -79
- package/tests/auto_resource.js +0 -10956
- package/tests/auto_resource_spec.js +0 -79
- package/tests/auto_role.js +0 -6150
- package/tests/auto_role_spec.js +0 -79
- package/tests/auto_service.js +0 -770
- package/tests/auto_service_spec.js +0 -79
- package/tests/auto_subprocesses.js +0 -114
- package/tests/auto_template.sh +0 -126
- package/tests/auto_tenant.js +0 -1100
- package/tests/auto_tenant_spec.js +0 -79
- package/tests/auto_token_util.js +0 -219
- package/tests/auto_userdata.js +0 -292
- package/tests/auto_userdata_spec.js +0 -79
- package/tests/auto_usertokens.js +0 -565
- package/tests/auto_usertokens_spec.js +0 -79
- package/tests/auto_version.js +0 -127
- package/tests/auto_version_spec.js +0 -79
- package/tests/auto_watcher.js +0 -157
- package/tests/auto_watcher_spec.js +0 -79
- package/tests/k2hdkc_test.data +0 -986
- package/tests/k2hdkc_test_load.sh +0 -255
- package/tests/k2hr3template_test.js +0 -187
- package/tests/k2hr3template_test.sh +0 -339
- package/tests/k2hr3template_test_async.js +0 -216
- package/tests/k2hr3template_test_template.result +0 -7117
- package/tests/k2hr3template_test_template.txt +0 -3608
- package/tests/k2hr3template_test_vars.js +0 -194
- package/tests/manual_acr_delete.js +0 -143
- package/tests/manual_acr_get.js +0 -297
- package/tests/manual_acr_postput.js +0 -215
- package/tests/manual_allusertenant_get.js +0 -113
- package/tests/manual_extdata_get.js +0 -191
- package/tests/manual_k2hr3keys_get.js +0 -84
- package/tests/manual_list_gethead.js +0 -230
- package/tests/manual_policy_delete.js +0 -132
- package/tests/manual_policy_gethead.js +0 -275
- package/tests/manual_policy_postput.js +0 -297
- package/tests/manual_resource_delete.js +0 -433
- package/tests/manual_resource_gethead.js +0 -423
- package/tests/manual_resource_postput.js +0 -487
- package/tests/manual_role_delete.js +0 -404
- package/tests/manual_role_gethead.js +0 -547
- package/tests/manual_role_postput.js +0 -544
- package/tests/manual_service_delete.js +0 -153
- package/tests/manual_service_gethead.js +0 -178
- package/tests/manual_service_postput.js +0 -348
- package/tests/manual_tenant_delete.js +0 -186
- package/tests/manual_tenant_gethead.js +0 -268
- package/tests/manual_tenant_postput.js +0 -293
- package/tests/manual_test.sh +0 -352
- package/tests/manual_userdata_get.js +0 -173
- package/tests/manual_usertoken_gethead.js +0 -136
- package/tests/manual_usertoken_postput.js +0 -310
- package/tests/manual_version_get.js +0 -127
- package/tests/run_local_test_k2hdkc.sh +0 -174
- package/tests/test.sh +0 -333
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* K2HR3 REST API
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2017 Yahoo Japan Corporation.
|
|
6
|
+
*
|
|
7
|
+
* K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers
|
|
8
|
+
* common management information for the cloud.
|
|
9
|
+
* K2HR3 can dynamically manage information as "who", "what", "operate".
|
|
10
|
+
* These are stored as roles, resources, policies in K2hdkc, and the
|
|
11
|
+
* client system can dynamically read and modify these information.
|
|
12
|
+
*
|
|
13
|
+
* For the full copyright and license information, please view
|
|
14
|
+
* the license file that was distributed with this source code.
|
|
15
|
+
*
|
|
16
|
+
* AUTHOR: Hirotaka Wakabayashi
|
|
17
|
+
* CREATE: Fri, Aug 20 2021
|
|
18
|
+
* REVISION:
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
54
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
55
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
56
|
+
};
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.k8soidc = void 0;
|
|
59
|
+
//------------------------------------------------------------------------
|
|
60
|
+
// Usage
|
|
61
|
+
//------------------------------------------------------------------------
|
|
62
|
+
// To enable this module, make the following settings in the K2HR3 API
|
|
63
|
+
// configuration file(ex, production.json/local.json/etc).
|
|
64
|
+
//
|
|
65
|
+
// {
|
|
66
|
+
// 'keystone': {
|
|
67
|
+
// 'type': 'k8soidc'
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
|
+
//
|
|
71
|
+
// Set the value of the 'keystone'->'type' object to 'k8soidc'.
|
|
72
|
+
//
|
|
73
|
+
// Next, this module requires its own information, so the following
|
|
74
|
+
// settings in configuration file are required.
|
|
75
|
+
//
|
|
76
|
+
// {
|
|
77
|
+
// 'k8soidc': {
|
|
78
|
+
// 'audience': '<client id for open id connect>',
|
|
79
|
+
// 'issuer': '<issue url for open id connect>',
|
|
80
|
+
// 'usernamekey': '<user name key name in token>',
|
|
81
|
+
// 'k8sapi_url': '<kubernetes api url>',
|
|
82
|
+
// 'k8s_ca_path': '<CA cert file path for kubernetes api url>',
|
|
83
|
+
// 'k8s_sa_token': '<Service account token for kubernetes>'
|
|
84
|
+
// 'unscopedtoken_exp':'<Expire limit for unscoped Token created from oidc>'
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
//
|
|
88
|
+
// Set the 'k8soidc' object as above. This object should contain the
|
|
89
|
+
// following keys(objects). The contents of each setting are explained.
|
|
90
|
+
//
|
|
91
|
+
// [audience]
|
|
92
|
+
// Set the client id for Open id connect. This key and value are
|
|
93
|
+
// required.
|
|
94
|
+
// [issuer]
|
|
95
|
+
// Set the issuer URL of Open id connect. This key and value are
|
|
96
|
+
// required.
|
|
97
|
+
// [usernamekey]
|
|
98
|
+
// Specify the key name that is the Username set in the Token of
|
|
99
|
+
// Open id connect. If there is no key representing Username in
|
|
100
|
+
// Token, it can be omitted. If omitted, the value of the 'sub'
|
|
101
|
+
// key is treated as the Username.
|
|
102
|
+
// [k8sapi_url]
|
|
103
|
+
// Specify the URL of the Kubernetes API. This module accesses
|
|
104
|
+
// the Kubernetes API to get the list of Kubernetes Namespaces.
|
|
105
|
+
// For example, that is 'https://kubernetes.default.svc'. This key
|
|
106
|
+
// and value are required.
|
|
107
|
+
// [k8s_ca_path]
|
|
108
|
+
// Specify the path of the CA certificate to access the Kubernetes
|
|
109
|
+
// API. If you're running the K2HR3 API inside a Kubernetes pod,
|
|
110
|
+
// it's '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'.
|
|
111
|
+
// This key and value are required.
|
|
112
|
+
// [k8s_sa_token]
|
|
113
|
+
// Specify the Token of the Service Account to access the Kubernetes
|
|
114
|
+
// API. If you're running the K2HR3 API inside a Kubernetes pod,
|
|
115
|
+
// it's '/var/run/secrets/kubernetes.io/serviceaccount/token'.
|
|
116
|
+
// This key and value are required.
|
|
117
|
+
// [unscopedtoken_exp]
|
|
118
|
+
// Specifies the expiration date of the Unscoped token created by
|
|
119
|
+
// OIDC. This value is specified in seconds(s).
|
|
120
|
+
// If this value does not exist or is less than or equal to 0,
|
|
121
|
+
// the default value will be used. The default value is the same
|
|
122
|
+
// as the OIDC token expiration date.
|
|
123
|
+
//
|
|
124
|
+
//------------------------------------------------------------------------
|
|
125
|
+
const k2hr3apiutil_1 = __importDefault(require("./k2hr3apiutil"));
|
|
126
|
+
const k2hr3dkc_1 = __importDefault(require("./k2hr3dkc"));
|
|
127
|
+
const dbglogging_1 = __importDefault(require("./dbglogging"));
|
|
128
|
+
const fs = __importStar(require("fs"));
|
|
129
|
+
const https = __importStar(require("https"));
|
|
130
|
+
const k2hr3keys_1 = require("./k2hr3keys");
|
|
131
|
+
const client_node_1 = require("@kubernetes/client-node");
|
|
132
|
+
const jose_1 = require("jose");
|
|
133
|
+
const { decode } = jose_1.base64url;
|
|
134
|
+
const k2hr3config_1 = require("./k2hr3config");
|
|
135
|
+
const apiConf = new k2hr3config_1.r3ApiConfig();
|
|
136
|
+
//---------------------------------------------------------
|
|
137
|
+
// Variables and Initializer
|
|
138
|
+
//---------------------------------------------------------
|
|
139
|
+
//
|
|
140
|
+
// Const Variables
|
|
141
|
+
//
|
|
142
|
+
const K8S_PUBLISHER_NAME = 'K8SOIDC';
|
|
143
|
+
const K8S_REGION_NAME = 'K8sCluster';
|
|
144
|
+
const OIDC_JWKS_URI_KEYNAME = 'jwks_uri';
|
|
145
|
+
//
|
|
146
|
+
// Global variables from configuration file
|
|
147
|
+
//
|
|
148
|
+
let oidc_audience = null;
|
|
149
|
+
let oidc_issuer = null;
|
|
150
|
+
let oidc_username = null;
|
|
151
|
+
let k8s_api_url = null;
|
|
152
|
+
let k8s_ca_cert = null;
|
|
153
|
+
let k2hr3_k8s_sa_token = null;
|
|
154
|
+
let unscopedtoken_exp = 0; // Expire limit for unscoped Token created from oidc(default is 0 means as same as oidc limit)
|
|
155
|
+
(() => {
|
|
156
|
+
const tmp_oidc_config = apiConf.getOtherObject('k8soidc');
|
|
157
|
+
if (k2hr3apiutil_1.default.isPlainObject(tmp_oidc_config)) {
|
|
158
|
+
oidc_audience = k2hr3apiutil_1.default.isString(tmp_oidc_config.audience) ? tmp_oidc_config.audience : null;
|
|
159
|
+
oidc_issuer = k2hr3apiutil_1.default.isString(tmp_oidc_config.issuer) ? tmp_oidc_config.issuer : null;
|
|
160
|
+
oidc_username = k2hr3apiutil_1.default.isString(tmp_oidc_config.usernamekey) ? tmp_oidc_config.usernamekey : null;
|
|
161
|
+
k8s_api_url = k2hr3apiutil_1.default.isString(tmp_oidc_config.k8sapi_url) ? tmp_oidc_config.k8sapi_url : null;
|
|
162
|
+
k8s_ca_cert = k2hr3apiutil_1.default.isString(tmp_oidc_config.k8s_ca_path) ? tmp_oidc_config.k8s_ca_path : null;
|
|
163
|
+
try {
|
|
164
|
+
k2hr3_k8s_sa_token = fs.readFileSync(k2hr3apiutil_1.default.getSafeString(tmp_oidc_config.k8s_sa_token), 'utf8');
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
k2hr3_k8s_sa_token = null;
|
|
168
|
+
}
|
|
169
|
+
// unscopedtoken_exp must be number
|
|
170
|
+
if (k2hr3apiutil_1.default.isSafeNumber(tmp_oidc_config.unscopedtoken_exp) && 0 < tmp_oidc_config.unscopedtoken_exp) {
|
|
171
|
+
unscopedtoken_exp = tmp_oidc_config.unscopedtoken_exp;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
//---------------------------------------------------------
|
|
176
|
+
// User Token for k8s oidc
|
|
177
|
+
//---------------------------------------------------------
|
|
178
|
+
//
|
|
179
|
+
// user : user name which is verified authentication
|
|
180
|
+
// user_id : user id which is verified authentication
|
|
181
|
+
// expire_limit : specify expire second(default 24H = 24 * 60 * 60 sec), and allow empty
|
|
182
|
+
//
|
|
183
|
+
// result : {
|
|
184
|
+
// result: true/false
|
|
185
|
+
// message: null or error message string
|
|
186
|
+
// token: undefined(error) or user token string
|
|
187
|
+
// expire_at: expire date(UTC ISO 8601)
|
|
188
|
+
// token_seed: JSON token seed data
|
|
189
|
+
// userid: user id
|
|
190
|
+
// }
|
|
191
|
+
//
|
|
192
|
+
// [NOTE]
|
|
193
|
+
// user token seed value is following
|
|
194
|
+
// {
|
|
195
|
+
// publisher: "K8SOIDC"
|
|
196
|
+
// userexid: "user extra id(a part of seed uuid4)"
|
|
197
|
+
// date: "UTC ISO 8601 time at create"
|
|
198
|
+
// expire: "UTC ISO 8601 time at expire"
|
|
199
|
+
// creator: "User full yrn"
|
|
200
|
+
// base: "32byte hex string"
|
|
201
|
+
// user: "user name"
|
|
202
|
+
// ip: always null
|
|
203
|
+
// hostname: always null
|
|
204
|
+
// port: always 0
|
|
205
|
+
// cuk: always null
|
|
206
|
+
// extra: always null
|
|
207
|
+
// tenant: if scoped token, this is "tenant name". if not, this is null
|
|
208
|
+
// }
|
|
209
|
+
//
|
|
210
|
+
const rawCreateUserTokenByK8sUser = (user, user_id, tenant, expire_limit) => {
|
|
211
|
+
const resobj = { result: true, message: null };
|
|
212
|
+
if (!k2hr3apiutil_1.default.isSafeString(user)) { // allow another parameter is null
|
|
213
|
+
resobj.result = false;
|
|
214
|
+
resobj.message = 'parameter is wrong : user=' + JSON.stringify(user);
|
|
215
|
+
dbglogging_1.default.elog(resobj.message);
|
|
216
|
+
return resobj;
|
|
217
|
+
}
|
|
218
|
+
const _user = user.toLowerCase();
|
|
219
|
+
if (!k2hr3apiutil_1.default.isString(user_id) || !k2hr3apiutil_1.default.isSafeStrUuid4(user_id)) { // user_id is uuid4
|
|
220
|
+
resobj.result = false;
|
|
221
|
+
resobj.message = 'parameter is wrong : user_id(must be uuid4)=' + JSON.stringify(user_id);
|
|
222
|
+
dbglogging_1.default.elog(resobj.message);
|
|
223
|
+
return resobj;
|
|
224
|
+
}
|
|
225
|
+
const _user_id = user_id;
|
|
226
|
+
let _tenant = null;
|
|
227
|
+
if (k2hr3apiutil_1.default.isSafeString(tenant)) {
|
|
228
|
+
_tenant = tenant;
|
|
229
|
+
}
|
|
230
|
+
let _expire_limit = 24 * 60 * 60; // default 24H
|
|
231
|
+
if (0 < unscopedtoken_exp) {
|
|
232
|
+
_expire_limit = unscopedtoken_exp; // override expire limit by config
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
if (k2hr3apiutil_1.default.isSafeNumber(expire_limit) && 0 < expire_limit) {
|
|
236
|
+
_expire_limit = expire_limit;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean)
|
|
240
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)(_user, null);
|
|
241
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) {
|
|
242
|
+
resobj.result = false;
|
|
243
|
+
resobj.message = 'Not initialize yet.';
|
|
244
|
+
dbglogging_1.default.elog(resobj.message);
|
|
245
|
+
return resobj;
|
|
246
|
+
}
|
|
247
|
+
const user_id_uuid4 = _user_id; // user id must be UUID4
|
|
248
|
+
const user_ex_id = k2hr3apiutil_1.default.getStrUuid4(); // set seed(uuid4)
|
|
249
|
+
// make token seed value
|
|
250
|
+
const now_unixtime = k2hr3apiutil_1.default.getUnixtime();
|
|
251
|
+
// user token and yrn key
|
|
252
|
+
let user_token = '';
|
|
253
|
+
let user_token_base = '';
|
|
254
|
+
// create key
|
|
255
|
+
for (let is_loop = true; is_loop;) {
|
|
256
|
+
// make user token
|
|
257
|
+
const token_elements = k2hr3apiutil_1.default.makeStringToken256(user_ex_id, user_id_uuid4);
|
|
258
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(token_elements)) {
|
|
259
|
+
resobj.result = false;
|
|
260
|
+
resobj.message = 'could not make token from ' + JSON.stringify(user_ex_id) + ' and ' + JSON.stringify(_user_id);
|
|
261
|
+
dbglogging_1.default.elog(resobj.message);
|
|
262
|
+
dkcobj.clean();
|
|
263
|
+
return resobj;
|
|
264
|
+
}
|
|
265
|
+
user_token = token_elements.str_token;
|
|
266
|
+
user_token_base = token_elements.str_base; // token base
|
|
267
|
+
// user token key
|
|
268
|
+
const token_user_key = keys.TOKEN_USER_TOP_KEY + '/' + user_token; // "yrn:yahoo::::token:user/<user token>"
|
|
269
|
+
// get user token for existing check
|
|
270
|
+
const value = dkcobj.getValue(token_user_key, null, true, null);
|
|
271
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(value)) {
|
|
272
|
+
// succeed uniq token
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
dbglogging_1.default.dlog('conflict user token(' + user_token + ') which already is used, so remake token for uniq.');
|
|
276
|
+
}
|
|
277
|
+
const token_seed = {
|
|
278
|
+
publisher: K8S_PUBLISHER_NAME, // "K8SOIDC"
|
|
279
|
+
userexid: user_ex_id, // seed(uuid4)
|
|
280
|
+
date: (new Date(now_unixtime * 1000)).toISOString(), // now date(UTC ISO 8601)
|
|
281
|
+
expire: (new Date((now_unixtime + _expire_limit) * 1000)).toISOString(), // expire date(UTC ISO 8601)
|
|
282
|
+
creator: keys.USER_KEY, // "yrn:yahoo::::user:<user>"
|
|
283
|
+
base: user_token_base, // token bease
|
|
284
|
+
user: _user, // user(creator)
|
|
285
|
+
ip: null, // ip(creator)
|
|
286
|
+
hostname: null, // hostname(creator)
|
|
287
|
+
port: 0, // port(creator)
|
|
288
|
+
cuk: null, // cuk(creator)
|
|
289
|
+
extra: null, // extra(creator)
|
|
290
|
+
tenant: _tenant // tenant(if scope, not null)
|
|
291
|
+
};
|
|
292
|
+
// Add user token/expire/seed into result object.
|
|
293
|
+
resobj.token = user_token;
|
|
294
|
+
resobj.expire_at = token_seed.expire;
|
|
295
|
+
resobj.token_seed = JSON.stringify(token_seed);
|
|
296
|
+
resobj.userid = _user_id;
|
|
297
|
+
dkcobj.clean();
|
|
298
|
+
return resobj;
|
|
299
|
+
};
|
|
300
|
+
//---------------------------------------------------------
|
|
301
|
+
// Verify User Token Publisher For k8s oidc
|
|
302
|
+
//---------------------------------------------------------
|
|
303
|
+
//
|
|
304
|
+
// token_seed : token seed data
|
|
305
|
+
//
|
|
306
|
+
// result : {
|
|
307
|
+
// result: true/false
|
|
308
|
+
// message: null or error message string
|
|
309
|
+
// }
|
|
310
|
+
//
|
|
311
|
+
const rawWrapVerifyUserTokenPublisherForK8s = (token_seed) => {
|
|
312
|
+
const resobj = { result: true, message: null };
|
|
313
|
+
// parse seed
|
|
314
|
+
if (!k2hr3apiutil_1.default.checkSimpleJSON(token_seed)) {
|
|
315
|
+
resobj.result = false;
|
|
316
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
317
|
+
dbglogging_1.default.elog(resobj.message);
|
|
318
|
+
return resobj;
|
|
319
|
+
}
|
|
320
|
+
const tmpseed = k2hr3apiutil_1.default.parseJSON(token_seed);
|
|
321
|
+
if (!k2hr3apiutil_1.default.isValTypeTokenSeed(tmpseed)) {
|
|
322
|
+
resobj.result = false;
|
|
323
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
324
|
+
dbglogging_1.default.elog(resobj.message);
|
|
325
|
+
return resobj;
|
|
326
|
+
}
|
|
327
|
+
const seed = tmpseed;
|
|
328
|
+
if (!k2hr3apiutil_1.default.isSafeString(seed.publisher) ||
|
|
329
|
+
(seed.publisher != K8S_PUBLISHER_NAME)) // publisher must be 'K8SOIDC'
|
|
330
|
+
{
|
|
331
|
+
resobj.result = false;
|
|
332
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
333
|
+
dbglogging_1.default.elog(resobj.message);
|
|
334
|
+
return resobj;
|
|
335
|
+
}
|
|
336
|
+
return resobj;
|
|
337
|
+
};
|
|
338
|
+
//---------------------------------------------------------
|
|
339
|
+
// Verify User Token (OIDC Token)
|
|
340
|
+
//---------------------------------------------------------
|
|
341
|
+
//
|
|
342
|
+
// dkcobj_permanent : dkcobj object
|
|
343
|
+
// user : target user name for token
|
|
344
|
+
// tenant : target tenant name for token(if token is scoped)
|
|
345
|
+
// token : check token
|
|
346
|
+
// token_seed : token seed data
|
|
347
|
+
//
|
|
348
|
+
// result : {
|
|
349
|
+
// result: true/false
|
|
350
|
+
// message: null or error message string
|
|
351
|
+
// }
|
|
352
|
+
//
|
|
353
|
+
const rawVerifyUserTokenByK8sUser = (dkcobj_permanent, user, tenant, token, token_seed) => {
|
|
354
|
+
const resobj = { result: true, message: null };
|
|
355
|
+
if (!k2hr3apiutil_1.default.isSafeString(token) || !k2hr3apiutil_1.default.isSafeString(token_seed) || !k2hr3apiutil_1.default.isSafeString(user)) {
|
|
356
|
+
resobj.result = false;
|
|
357
|
+
resobj.message = 'some parameters are wrong : token=' + JSON.stringify(token) + ', token_seed=<not printable>, user=' + JSON.stringify(user);
|
|
358
|
+
dbglogging_1.default.elog(resobj.message);
|
|
359
|
+
return resobj;
|
|
360
|
+
}
|
|
361
|
+
// parse seed
|
|
362
|
+
if (!k2hr3apiutil_1.default.checkSimpleJSON(token_seed)) {
|
|
363
|
+
resobj.result = false;
|
|
364
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
365
|
+
dbglogging_1.default.elog(resobj.message);
|
|
366
|
+
return resobj;
|
|
367
|
+
}
|
|
368
|
+
const tmpseed = k2hr3apiutil_1.default.parseJSON(token_seed);
|
|
369
|
+
if (!k2hr3apiutil_1.default.isValTypeTokenSeed(tmpseed)) {
|
|
370
|
+
resobj.result = false;
|
|
371
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
372
|
+
dbglogging_1.default.elog(resobj.message);
|
|
373
|
+
return resobj;
|
|
374
|
+
}
|
|
375
|
+
// check all seed values
|
|
376
|
+
const seed = tmpseed;
|
|
377
|
+
if (!k2hr3apiutil_1.default.isSafeString(seed.publisher) ||
|
|
378
|
+
(seed.publisher != K8S_PUBLISHER_NAME) || // publisher must be 'K8SOIDC'
|
|
379
|
+
!k2hr3apiutil_1.default.isSafeString(seed.userexid) ||
|
|
380
|
+
!k2hr3apiutil_1.default.isSafeString(seed.date) ||
|
|
381
|
+
!k2hr3apiutil_1.default.isSafeString(seed.expire) ||
|
|
382
|
+
!k2hr3apiutil_1.default.isSafeString(seed.creator) ||
|
|
383
|
+
!k2hr3apiutil_1.default.isSafeString(seed.base) ||
|
|
384
|
+
!k2hr3apiutil_1.default.isSafeString(seed.user) ||
|
|
385
|
+
!k2hr3apiutil_1.default.compareCaseString(seed.user, user)) {
|
|
386
|
+
resobj.result = false;
|
|
387
|
+
resobj.message = 'token_seed(not printable) is not safe entity.';
|
|
388
|
+
dbglogging_1.default.elog(resobj.message);
|
|
389
|
+
return resobj;
|
|
390
|
+
}
|
|
391
|
+
// check expire
|
|
392
|
+
if (k2hr3apiutil_1.default.isExpired(seed.expire)) {
|
|
393
|
+
resobj.result = false;
|
|
394
|
+
resobj.message = 'token is expired by expire date(' + JSON.stringify(seed.expire) + ') in token_seed.';
|
|
395
|
+
dbglogging_1.default.elog(resobj.message);
|
|
396
|
+
return resobj;
|
|
397
|
+
}
|
|
398
|
+
// check tenant name(if tenant is specified, seed must have same tenant name)
|
|
399
|
+
if (k2hr3apiutil_1.default.isSafeString(seed.tenant) !== k2hr3apiutil_1.default.isSafeString(tenant) || (k2hr3apiutil_1.default.isSafeString(seed.tenant) && !k2hr3apiutil_1.default.compareCaseString(seed.tenant, tenant))) {
|
|
400
|
+
resobj.result = false;
|
|
401
|
+
resobj.message = 'token_seed(not printable) is (un)scoped, but tenant name is (not) specified. Then unmatched.';
|
|
402
|
+
dbglogging_1.default.elog(resobj.message);
|
|
403
|
+
return resobj;
|
|
404
|
+
}
|
|
405
|
+
// k2hdkc
|
|
406
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)(seed.user, seed.tenant);
|
|
407
|
+
let dkcobj = dkcobj_permanent;
|
|
408
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) {
|
|
409
|
+
dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean)
|
|
410
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) {
|
|
411
|
+
resobj.result = false;
|
|
412
|
+
resobj.message = 'Not initialize yet.';
|
|
413
|
+
dbglogging_1.default.elog(resobj.message);
|
|
414
|
+
return resobj;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// get user id
|
|
418
|
+
const userid = dkcobj.getValue(keys.USER_ID_KEY, null, true, null); // get user id from "yrn:yahoo::::user:<user>:id"
|
|
419
|
+
if (!k2hr3apiutil_1.default.isSafeString(userid)) {
|
|
420
|
+
resobj.result = false;
|
|
421
|
+
resobj.message = 'could not get user id for user(' + seed.user + ').';
|
|
422
|
+
dbglogging_1.default.elog(resobj.message);
|
|
423
|
+
return resobj;
|
|
424
|
+
}
|
|
425
|
+
// make verify token
|
|
426
|
+
const token_elements = k2hr3apiutil_1.default.makeStringToken256(seed.userexid, userid, seed.base);
|
|
427
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(token_elements)) {
|
|
428
|
+
resobj.result = false;
|
|
429
|
+
resobj.message = 'could not make verify token from ' + JSON.stringify(seed.userexid) + ' and ' + JSON.stringify(userid) + ' and ' + JSON.stringify(seed.base);
|
|
430
|
+
dbglogging_1.default.elog(resobj.message);
|
|
431
|
+
return resobj;
|
|
432
|
+
}
|
|
433
|
+
if (token !== token_elements.str_token) {
|
|
434
|
+
resobj.result = false;
|
|
435
|
+
resobj.message = 'token(' + token + ') verify is failure, verify token is ' + token_elements.str_token + '.';
|
|
436
|
+
dbglogging_1.default.elog(resobj.message);
|
|
437
|
+
return resobj;
|
|
438
|
+
}
|
|
439
|
+
return resobj;
|
|
440
|
+
};
|
|
441
|
+
//---------------------------------------------------------
|
|
442
|
+
// Get User/Tenant information by User Token
|
|
443
|
+
//---------------------------------------------------------
|
|
444
|
+
//
|
|
445
|
+
// Result: {
|
|
446
|
+
// result: true/false
|
|
447
|
+
// message: null or error message string
|
|
448
|
+
// user: user name
|
|
449
|
+
// userid: user id
|
|
450
|
+
// tenant: if token is scoped token, this value is set tenant name.
|
|
451
|
+
// }
|
|
452
|
+
//
|
|
453
|
+
const rawGetUserTenantInfoByUserToken = (token) => {
|
|
454
|
+
const resobj = { result: true, message: null };
|
|
455
|
+
if (!k2hr3apiutil_1.default.isSafeString(token)) {
|
|
456
|
+
resobj.result = false;
|
|
457
|
+
resobj.message = 'parameter is wrong : token=' + JSON.stringify(token);
|
|
458
|
+
dbglogging_1.default.elog(resobj.message);
|
|
459
|
+
return resobj;
|
|
460
|
+
}
|
|
461
|
+
const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean)
|
|
462
|
+
let keys = (0, k2hr3keys_1.getK2hr3Keys)();
|
|
463
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) {
|
|
464
|
+
resobj.result = false;
|
|
465
|
+
resobj.message = 'Not initialize yet.';
|
|
466
|
+
dbglogging_1.default.elog(resobj.message);
|
|
467
|
+
return resobj;
|
|
468
|
+
}
|
|
469
|
+
// get token key under user key
|
|
470
|
+
const token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>"
|
|
471
|
+
const user_token_key = dkcobj.getValue(token_value_key, null, true, null); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>"
|
|
472
|
+
if (!k2hr3apiutil_1.default.isSafeString(user_token_key)) {
|
|
473
|
+
resobj.result = false;
|
|
474
|
+
resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') is not existed.';
|
|
475
|
+
dbglogging_1.default.elog(resobj.message);
|
|
476
|
+
dkcobj.clean();
|
|
477
|
+
return resobj;
|
|
478
|
+
}
|
|
479
|
+
// get user name and tenant name from token key yrn path
|
|
480
|
+
const pattern = new RegExp('^' + keys.MATCH_ANY_USER_TOKEN); // regex = /^yrn:yahoo::::user:(.*):tenant\/(.*)\/token\/(.*)/
|
|
481
|
+
const matches = user_token_key.match(pattern); // reverse to user/tenant names
|
|
482
|
+
if (!k2hr3apiutil_1.default.isNotEmptyArray(matches) || matches.length < 4 || '' === k2hr3apiutil_1.default.getSafeString(matches[1])) {
|
|
483
|
+
resobj.result = false;
|
|
484
|
+
resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') has wrong format value(' + user_token_key + ')';
|
|
485
|
+
dbglogging_1.default.elog(resobj.message);
|
|
486
|
+
dkcobj.clean();
|
|
487
|
+
return resobj;
|
|
488
|
+
}
|
|
489
|
+
const token_user = k2hr3apiutil_1.default.getSafeString(matches[1]);
|
|
490
|
+
const token_tenant = k2hr3apiutil_1.default.isString(matches[2]) ? matches[2] : null;
|
|
491
|
+
// get token seed
|
|
492
|
+
const user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed"
|
|
493
|
+
const token_seed = dkcobj.getValue(user_token_seed_key, null, true, null);
|
|
494
|
+
if (!k2hr3apiutil_1.default.isSafeString(token_seed)) {
|
|
495
|
+
resobj.result = false;
|
|
496
|
+
resobj.message = 'token key(' + token_value_key + ') for token(' + token + ') does not have token seed data.';
|
|
497
|
+
dbglogging_1.default.elog(resobj.message);
|
|
498
|
+
dkcobj.clean();
|
|
499
|
+
return resobj;
|
|
500
|
+
}
|
|
501
|
+
// verify token
|
|
502
|
+
const vres = rawVerifyUserTokenByK8sUser(dkcobj, token_user, token_tenant, token, token_seed);
|
|
503
|
+
if (!vres.result) {
|
|
504
|
+
resobj.result = false;
|
|
505
|
+
resobj.message = 'failed to verify token(' + token + ') with seed by ' + vres.message;
|
|
506
|
+
dbglogging_1.default.elog(resobj.message);
|
|
507
|
+
dkcobj.clean();
|
|
508
|
+
return resobj;
|
|
509
|
+
}
|
|
510
|
+
// get user id
|
|
511
|
+
keys = (0, k2hr3keys_1.getK2hr3Keys)(token_user, null); // remake keys
|
|
512
|
+
const userid = dkcobj.getValue(keys.USER_ID_KEY, null, true, null); // get user id from "yrn:yahoo::::user:<user>:id"
|
|
513
|
+
if (!k2hr3apiutil_1.default.isSafeString(userid)) {
|
|
514
|
+
resobj.result = false;
|
|
515
|
+
resobj.message = 'could not get user id for user(' + token_user + ').';
|
|
516
|
+
dbglogging_1.default.elog(resobj.message);
|
|
517
|
+
dkcobj.clean();
|
|
518
|
+
return resobj;
|
|
519
|
+
}
|
|
520
|
+
dkcobj.clean();
|
|
521
|
+
// make result
|
|
522
|
+
resobj.user = token_user;
|
|
523
|
+
resobj.userid = userid;
|
|
524
|
+
resobj.tenant = token_tenant;
|
|
525
|
+
return resobj;
|
|
526
|
+
};
|
|
527
|
+
//---------------------------------------------------------
|
|
528
|
+
// Verify (Un)scoped Token
|
|
529
|
+
//---------------------------------------------------------
|
|
530
|
+
//
|
|
531
|
+
// token : unscoped/scoped token
|
|
532
|
+
//
|
|
533
|
+
const rawVerifyUnscopedToken = (token) => {
|
|
534
|
+
if (!k2hr3apiutil_1.default.isSafeString(token)) {
|
|
535
|
+
dbglogging_1.default.elog('token(' + JSON.stringify(token) + ') parameter is wrong.');
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean)
|
|
539
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)();
|
|
540
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) {
|
|
541
|
+
dbglogging_1.default.elog('K2hdkc client is not initialized yet.');
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
// get token key under user key
|
|
545
|
+
const token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>"
|
|
546
|
+
const user_token_key = dkcobj.getValue(token_value_key, null, true, null); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>"
|
|
547
|
+
if (!k2hr3apiutil_1.default.isSafeString(user_token_key)) {
|
|
548
|
+
dbglogging_1.default.elog('token key(' + token_value_key + ') for token(' + token + ') is not existed.');
|
|
549
|
+
dkcobj.clean();
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
// get user name and tenant name from token key yrn path
|
|
553
|
+
const pattern = new RegExp('^' + keys.MATCH_ANY_USER_TOKEN); // regex = /^yrn:yahoo::::user:(.*):tenant\/(.*)\/token\/(.*)/
|
|
554
|
+
const matches = user_token_key.match(pattern); // reverse to user/tenant names
|
|
555
|
+
if (!k2hr3apiutil_1.default.isNotEmptyArray(matches) || matches.length < 4 || '' === k2hr3apiutil_1.default.getSafeString(matches[1])) {
|
|
556
|
+
dbglogging_1.default.elog('token key(' + token_value_key + ') for token(' + token + ') has wrong format value(' + user_token_key + ')');
|
|
557
|
+
dkcobj.clean();
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
const token_user = k2hr3apiutil_1.default.getSafeString(matches[1]);
|
|
561
|
+
let token_tenant = k2hr3apiutil_1.default.getSafeString(matches[2]);
|
|
562
|
+
if ('' === token_tenant) {
|
|
563
|
+
token_tenant = null;
|
|
564
|
+
}
|
|
565
|
+
// get token seed
|
|
566
|
+
const user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed"
|
|
567
|
+
const token_seed = dkcobj.getValue(user_token_seed_key, null, true, null);
|
|
568
|
+
if (!k2hr3apiutil_1.default.isSafeString(token_seed)) {
|
|
569
|
+
dbglogging_1.default.elog('token key(' + token_value_key + ') for token(' + token + ') does not have token seed data.');
|
|
570
|
+
dkcobj.clean();
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
// verify token
|
|
574
|
+
const vres = rawVerifyUserTokenByK8sUser(dkcobj, token_user, token_tenant, token, token_seed);
|
|
575
|
+
if (!vres.result) {
|
|
576
|
+
dbglogging_1.default.elog('failed to verify token(' + token + ') with seed by ' + vres.message);
|
|
577
|
+
dkcobj.clean();
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
dkcobj.clean();
|
|
581
|
+
return true;
|
|
582
|
+
};
|
|
583
|
+
//---------------------------------------------------------
|
|
584
|
+
// Get Scoped token from k8s user token
|
|
585
|
+
//---------------------------------------------------------
|
|
586
|
+
//
|
|
587
|
+
// callback(error, result):
|
|
588
|
+
// result = {
|
|
589
|
+
// user: user name
|
|
590
|
+
// userid: user id
|
|
591
|
+
// scoped: always true
|
|
592
|
+
// token: token string
|
|
593
|
+
// expire: expire string(UTC ISO 8601)
|
|
594
|
+
// region: region string
|
|
595
|
+
// token_seed: JSON token seed data
|
|
596
|
+
// }
|
|
597
|
+
//
|
|
598
|
+
// [NOTE]
|
|
599
|
+
// The token is allowed scoped token, but it must be same tenant token.
|
|
600
|
+
//
|
|
601
|
+
const rawGetUserScopedTokenK8s = (token, tenant, callback) => {
|
|
602
|
+
if (!k2hr3apiutil_1.default.isSafeString(token) || !k2hr3apiutil_1.default.isSafeString(tenant)) {
|
|
603
|
+
const error = new Error('some parameters are wrong : token=' + JSON.stringify(token) + ', tenant=' + JSON.stringify(tenant));
|
|
604
|
+
dbglogging_1.default.elog(error.message);
|
|
605
|
+
callback(error, null);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
// verify and get user/tenant information
|
|
609
|
+
const token_info = rawGetUserTenantInfoByUserToken(token);
|
|
610
|
+
if (!token_info.result) {
|
|
611
|
+
const error = new Error('could not get any information from token(' + token + '), result : ' + token_info.message);
|
|
612
|
+
dbglogging_1.default.elog(error.message);
|
|
613
|
+
callback(error, null);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
// check tenant name
|
|
617
|
+
if (k2hr3apiutil_1.default.isSafeString(token_info.tenant) && token_info.tenant !== tenant) {
|
|
618
|
+
const error = new Error('token(' + token + ') has scoped(' + token_info.tenant + '), but it is not as same as the request tenant(' + tenant + ').');
|
|
619
|
+
dbglogging_1.default.elog(error.message);
|
|
620
|
+
callback(error, null);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
// create scoped token
|
|
624
|
+
const resobj = rawCreateUserTokenByK8sUser(k2hr3apiutil_1.default.getSafeString(token_info.user), k2hr3apiutil_1.default.getSafeString(token_info.userid), tenant); // not specify expire limit now(using default).
|
|
625
|
+
if (!resobj.result) {
|
|
626
|
+
const error = new Error('could not create user scoped token for uname(' + token_info.user + ')/user id(' + token_info.userid + ') for tenant(' + tenant + ').');
|
|
627
|
+
dbglogging_1.default.elog(error.message);
|
|
628
|
+
callback(error, null);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// make result
|
|
632
|
+
const result = {
|
|
633
|
+
user: k2hr3apiutil_1.default.getSafeString(token_info.user),
|
|
634
|
+
userid: k2hr3apiutil_1.default.getSafeString(token_info.userid),
|
|
635
|
+
scoped: true,
|
|
636
|
+
token: k2hr3apiutil_1.default.getSafeString(resobj.token),
|
|
637
|
+
expire: k2hr3apiutil_1.default.isString(resobj.expire_at) ? resobj.expire_at : null,
|
|
638
|
+
region: K8S_REGION_NAME,
|
|
639
|
+
token_seed: k2hr3apiutil_1.default.getSafeString(resobj.token_seed),
|
|
640
|
+
};
|
|
641
|
+
callback(null, result);
|
|
642
|
+
};
|
|
643
|
+
//---------------------------------------------------------
|
|
644
|
+
// Get Unscoped token by oidc token
|
|
645
|
+
//---------------------------------------------------------
|
|
646
|
+
//
|
|
647
|
+
// callback(error, result):
|
|
648
|
+
// result = {
|
|
649
|
+
// user: user name User name in token: set user name if specified user name key name in config. if not specified, set user id
|
|
650
|
+
// userid: user id User id in token: payload in token has 'sub' key, it is user id.
|
|
651
|
+
// scoped: false (always false)
|
|
652
|
+
// token: token string(id) OIDC Token
|
|
653
|
+
// expire: expire string expire in token: payload in token has 'exp' key, it is expire unix time.
|
|
654
|
+
// region: region string (always n/a)
|
|
655
|
+
// token_seed: seed ({publisher: 'K8SOIDC'})
|
|
656
|
+
// }
|
|
657
|
+
//
|
|
658
|
+
//
|
|
659
|
+
// Utility - Verify OIDC token and get user name
|
|
660
|
+
//
|
|
661
|
+
// token: oidc token
|
|
662
|
+
//
|
|
663
|
+
const rawVerifyTokenAndGetUsername = async (token) => {
|
|
664
|
+
const jwtParam = {
|
|
665
|
+
issuer: oidc_issuer ?? undefined,
|
|
666
|
+
audience: oidc_audience ?? undefined
|
|
667
|
+
};
|
|
668
|
+
const myPromise = (issuer_url, conf_key) => {
|
|
669
|
+
return new Promise((resolve, reject) => {
|
|
670
|
+
https.get(issuer_url + '/.well-known/openid-configuration', (res) => {
|
|
671
|
+
if (res.statusCode !== 200) {
|
|
672
|
+
res.resume();
|
|
673
|
+
reject('statusCode should be 200, not ' + String(res.statusCode));
|
|
674
|
+
}
|
|
675
|
+
res.setEncoding('utf8');
|
|
676
|
+
let rawData = '';
|
|
677
|
+
res.on('data', (chunk) => {
|
|
678
|
+
rawData += chunk;
|
|
679
|
+
});
|
|
680
|
+
res.on('end', () => {
|
|
681
|
+
const parsedData = k2hr3apiutil_1.default.parseJSON(rawData);
|
|
682
|
+
if (k2hr3apiutil_1.default.isPlainObject(parsedData) && k2hr3apiutil_1.default.isString(parsedData[conf_key])) {
|
|
683
|
+
resolve(parsedData[conf_key]);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
const errorMsg = ('the ' + conf_key + ' key should exist, but no such a key');
|
|
687
|
+
dbglogging_1.default.elog(errorMsg);
|
|
688
|
+
reject(errorMsg);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}).on('error', (err) => {
|
|
692
|
+
reject(err);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
};
|
|
696
|
+
const asyncFunction = async () => {
|
|
697
|
+
let oidc_jwks_uri;
|
|
698
|
+
try {
|
|
699
|
+
oidc_jwks_uri = await myPromise(k2hr3apiutil_1.default.getSafeString(oidc_issuer), OIDC_JWKS_URI_KEYNAME);
|
|
700
|
+
if (!k2hr3apiutil_1.default.isSafeString(oidc_jwks_uri)) {
|
|
701
|
+
const error = new Error('oidc_jwks_uri should be defined, but no oidc_jwks_uri.');
|
|
702
|
+
dbglogging_1.default.elog(error.message);
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
if (err instanceof Error) {
|
|
708
|
+
dbglogging_1.default.elog(err.message);
|
|
709
|
+
}
|
|
710
|
+
throw err;
|
|
711
|
+
}
|
|
712
|
+
const JWKS = (0, jose_1.createRemoteJWKSet)(new URL(oidc_jwks_uri));
|
|
713
|
+
let payload;
|
|
714
|
+
try {
|
|
715
|
+
const jwtResult = await (0, jose_1.jwtVerify)(token, JWKS, jwtParam);
|
|
716
|
+
payload = jwtResult.payload;
|
|
717
|
+
}
|
|
718
|
+
catch (err) {
|
|
719
|
+
if (err instanceof Error) {
|
|
720
|
+
dbglogging_1.default.elog(err.message);
|
|
721
|
+
}
|
|
722
|
+
throw err;
|
|
723
|
+
}
|
|
724
|
+
let userName = null;
|
|
725
|
+
if (k2hr3apiutil_1.default.isPlainObject(payload) && k2hr3apiutil_1.default.isSafeString(oidc_username) && k2hr3apiutil_1.default.isString(payload[oidc_username])) {
|
|
726
|
+
userName = k2hr3apiutil_1.default.getSafeString(payload[oidc_username]);
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
if (k2hr3apiutil_1.default.isPlainObject(payload) && k2hr3apiutil_1.default.isSafeString(payload.sub)) {
|
|
730
|
+
userName = payload.sub;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (!k2hr3apiutil_1.default.isSafeString(userName)) {
|
|
734
|
+
const error = new Error('failed to verify token for getting user name.');
|
|
735
|
+
dbglogging_1.default.elog(error.message);
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
return userName;
|
|
739
|
+
};
|
|
740
|
+
return asyncFunction();
|
|
741
|
+
};
|
|
742
|
+
const rawGetUserUnscopedTokenK8s = (token, callback) => {
|
|
743
|
+
if (!k2hr3apiutil_1.default.isSafeString(token)) {
|
|
744
|
+
const error = new Error('oidc token parameter is not string or empty.');
|
|
745
|
+
dbglogging_1.default.elog(error.message);
|
|
746
|
+
callback(error, null);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
//
|
|
750
|
+
// Check the id_token.
|
|
751
|
+
//
|
|
752
|
+
// see. https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
|
753
|
+
//
|
|
754
|
+
const parts = token.split('.', 2);
|
|
755
|
+
if (2 !== parts.length) {
|
|
756
|
+
const error = new Error('oidc token must have two parts, but it has ' + parts.length + ' parts.');
|
|
757
|
+
dbglogging_1.default.elog(error.message);
|
|
758
|
+
callback(error, null);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
//
|
|
762
|
+
// decode part[1] to payload
|
|
763
|
+
//
|
|
764
|
+
const payload = k2hr3apiutil_1.default.parseJSON(new TextDecoder().decode(decode(parts[1])));
|
|
765
|
+
if (!k2hr3apiutil_1.default.isPlainObject(payload)) {
|
|
766
|
+
const error = new Error('could not decode json from the part[1] in oidc token.');
|
|
767
|
+
dbglogging_1.default.elog(error.message);
|
|
768
|
+
callback(error, null);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
//
|
|
772
|
+
// payload must have 'sub' key for user id
|
|
773
|
+
//
|
|
774
|
+
let userid;
|
|
775
|
+
if (k2hr3apiutil_1.default.isSafeString(payload.sub)) {
|
|
776
|
+
userid = payload.sub;
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
const error = new Error('token payload should contain sub(userid), but not find sub(userid).');
|
|
780
|
+
dbglogging_1.default.elog(error.message);
|
|
781
|
+
callback(error, null);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
//
|
|
785
|
+
// get user name from payload
|
|
786
|
+
//
|
|
787
|
+
let username;
|
|
788
|
+
if (k2hr3apiutil_1.default.isSafeString(oidc_username)) {
|
|
789
|
+
if (k2hr3apiutil_1.default.isSafeString(payload[oidc_username])) {
|
|
790
|
+
username = k2hr3apiutil_1.default.getSafeString(payload[oidc_username]);
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
const error = new Error('token payload should contain user name(' + oidc_username + '), but not find it.');
|
|
794
|
+
dbglogging_1.default.elog(error.message);
|
|
795
|
+
callback(error, null);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
// If user name key is not specified, user id will be used instead.
|
|
801
|
+
username = userid;
|
|
802
|
+
}
|
|
803
|
+
//
|
|
804
|
+
// verify token by JWT library and get user name
|
|
805
|
+
//
|
|
806
|
+
const _callback = callback;
|
|
807
|
+
const lower_username = username.toLowerCase(); // to lower case
|
|
808
|
+
rawVerifyTokenAndGetUsername(token).then((result) => {
|
|
809
|
+
const verified_username = k2hr3apiutil_1.default.getSafeString(result);
|
|
810
|
+
//
|
|
811
|
+
// compare user name
|
|
812
|
+
//
|
|
813
|
+
if (!k2hr3apiutil_1.default.compareCaseString(lower_username, verified_username)) {
|
|
814
|
+
const error = new Error('oidc token has ' + lower_username + ' username, but verified username(' + verified_username + ') is different.');
|
|
815
|
+
dbglogging_1.default.elog(error.message);
|
|
816
|
+
_callback(error, null);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
// core seed
|
|
820
|
+
const user_id_uuid4 = k2hr3apiutil_1.default.cvtNumberStringToUuid4(userid, 10); // payload.sub is decimal string
|
|
821
|
+
let expire_limit;
|
|
822
|
+
if (k2hr3apiutil_1.default.isSafeNumber(payload['exp'])) {
|
|
823
|
+
expire_limit = payload['exp'] - k2hr3apiutil_1.default.getUnixtime();
|
|
824
|
+
if (expire_limit <= 0) {
|
|
825
|
+
expire_limit = 24 * 60 * 60; // default 24H
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
expire_limit = 24 * 60 * 60; // default 24H
|
|
830
|
+
}
|
|
831
|
+
// create token
|
|
832
|
+
const resobj = rawCreateUserTokenByK8sUser(lower_username, user_id_uuid4, null, expire_limit);
|
|
833
|
+
if (!resobj.result) {
|
|
834
|
+
const error = new Error('could not create user token for uname(' + lower_username + ') or something wrong result : ' + resobj.message);
|
|
835
|
+
dbglogging_1.default.elog(error.message);
|
|
836
|
+
_callback(error, null);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
// make result
|
|
840
|
+
const resultobj = {
|
|
841
|
+
user: lower_username,
|
|
842
|
+
userid: k2hr3apiutil_1.default.getSafeString(resobj.userid),
|
|
843
|
+
scoped: false,
|
|
844
|
+
token: k2hr3apiutil_1.default.getSafeString(resobj.token),
|
|
845
|
+
expire: k2hr3apiutil_1.default.isSafeString(resobj.expire_at) ? resobj.expire_at : null,
|
|
846
|
+
region: K8S_REGION_NAME,
|
|
847
|
+
token_seed: k2hr3apiutil_1.default.getSafeString(resobj.token_seed)
|
|
848
|
+
};
|
|
849
|
+
_callback(null, resultobj);
|
|
850
|
+
}).catch((err) => {
|
|
851
|
+
dbglogging_1.default.elog(err.message);
|
|
852
|
+
_callback(err, null);
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
const hasSetDefaultAuthentication = (obj) => {
|
|
856
|
+
return k2hr3apiutil_1.default.isPlainObject(obj) && k2hr3apiutil_1.default.isFunction(obj.setDefaultAuthentication);
|
|
857
|
+
};
|
|
858
|
+
//
|
|
859
|
+
// unscopedtoken: oidc token(Not use)
|
|
860
|
+
//
|
|
861
|
+
const rawGetUserTenantListK8s = (unscopedtoken, callback) => {
|
|
862
|
+
if (!k2hr3apiutil_1.default.isSafeString(unscopedtoken)) {
|
|
863
|
+
const error = new Error('unscopedtoken parameter is wrong.');
|
|
864
|
+
dbglogging_1.default.elog(error.message);
|
|
865
|
+
callback(error, null);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Verify unscoped token
|
|
869
|
+
if (!rawVerifyUnscopedToken(unscopedtoken)) {
|
|
870
|
+
const error = new Error('unscopedtoken is not safe, varidation is failed.');
|
|
871
|
+
dbglogging_1.default.elog(error.message);
|
|
872
|
+
callback(error, null);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const _callback = callback;
|
|
876
|
+
//
|
|
877
|
+
// Get Namespaces by Service Access token
|
|
878
|
+
//
|
|
879
|
+
const cluster = {
|
|
880
|
+
name: 'k2hr3-api-k8soidc-cluster',
|
|
881
|
+
server: k2hr3apiutil_1.default.getSafeString(k8s_api_url),
|
|
882
|
+
caFile: k2hr3apiutil_1.default.getSafeString(k8s_ca_cert),
|
|
883
|
+
skipTLSVerify: false
|
|
884
|
+
};
|
|
885
|
+
const user = {
|
|
886
|
+
name: 'k2hr3-api-k8soidc-name',
|
|
887
|
+
token: k2hr3apiutil_1.default.getSafeString(k2hr3_k8s_sa_token) // [NOTE] k2hr3_k8s_sa_token is global variable
|
|
888
|
+
};
|
|
889
|
+
const kubeconfig = new client_node_1.KubeConfig();
|
|
890
|
+
kubeconfig.loadFromClusterAndUser(cluster, user);
|
|
891
|
+
const k8sApi = kubeconfig.makeApiClient(client_node_1.CoreV1Api);
|
|
892
|
+
if (hasSetDefaultAuthentication(k8sApi)) {
|
|
893
|
+
k8sApi.setDefaultAuthentication({
|
|
894
|
+
applyToRequest: (opts) => {
|
|
895
|
+
if (!k2hr3apiutil_1.default.isPlainObject(opts.headers)) {
|
|
896
|
+
opts.headers = {};
|
|
897
|
+
}
|
|
898
|
+
opts.headers.Authorization = 'Bearer ' + k2hr3_k8s_sa_token; // [NOTE] k2hr3_k8s_sa_token is global variable
|
|
899
|
+
opts.rejectUnauthorized = false;
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
const error = new Error('Not found CoreV1Api.setDefaultAuthentication function.');
|
|
905
|
+
dbglogging_1.default.elog(error.message);
|
|
906
|
+
callback(error, null);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const k8s_ns = ['kube-node-lease', 'kube-public', 'kube-system', 'kubernetes-dashboard'];
|
|
910
|
+
const resarr = [];
|
|
911
|
+
k8sApi.listNamespace().then((response) => {
|
|
912
|
+
if (k2hr3apiutil_1.default.isPlainObject(response) && k2hr3apiutil_1.default.isPlainObject(response.body) && k2hr3apiutil_1.default.isArray(response.body.items)) {
|
|
913
|
+
for (let pos = 0; pos < response.body.items.length; ++pos) {
|
|
914
|
+
// check body...items
|
|
915
|
+
const tmpItem = response.body.items[pos];
|
|
916
|
+
if (!k2hr3apiutil_1.default.isPlainObject(tmpItem) ||
|
|
917
|
+
!k2hr3apiutil_1.default.isPlainObject(tmpItem.metadata) ||
|
|
918
|
+
!k2hr3apiutil_1.default.isSafeString(tmpItem.metadata.name)) {
|
|
919
|
+
dbglogging_1.default.wlog('one of response for project(tenant) list is something wrong : ' + JSON.stringify(response.body));
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
// Is the k8s cluster namespace in kubernetes system namespaces?
|
|
923
|
+
if (k2hr3apiutil_1.default.findStringInArray(k8s_ns, tmpItem.metadata.name)) {
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const tenant = {
|
|
927
|
+
name: tmpItem.metadata.name,
|
|
928
|
+
id: k2hr3apiutil_1.default.getSafeString(tmpItem.metadata.uid),
|
|
929
|
+
description: tmpItem.metadata.name,
|
|
930
|
+
display: tmpItem.metadata.name
|
|
931
|
+
};
|
|
932
|
+
resarr.push(tenant);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (0 === resarr.length) {
|
|
936
|
+
const error = new Error('no tenant exists');
|
|
937
|
+
dbglogging_1.default.elog(error.message);
|
|
938
|
+
_callback(error, null);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
_callback(null, resarr);
|
|
942
|
+
}).catch((error) => {
|
|
943
|
+
dbglogging_1.default.elog(error.message);
|
|
944
|
+
_callback(error, null);
|
|
945
|
+
});
|
|
946
|
+
};
|
|
947
|
+
//---------------------------------------------------------
|
|
948
|
+
// Exports
|
|
949
|
+
//---------------------------------------------------------
|
|
950
|
+
exports.k8soidc = {
|
|
951
|
+
//
|
|
952
|
+
// uname: username
|
|
953
|
+
// passwd: passwd
|
|
954
|
+
//
|
|
955
|
+
getUserUnscopedToken: (uname, passwd, callback) => {
|
|
956
|
+
const error = new Error('getUserUnscopedToken is not implemented');
|
|
957
|
+
dbglogging_1.default.elog(error.message);
|
|
958
|
+
callback(error, null);
|
|
959
|
+
},
|
|
960
|
+
//
|
|
961
|
+
// Get Unscoped Token
|
|
962
|
+
//
|
|
963
|
+
// token: OIDC token
|
|
964
|
+
//
|
|
965
|
+
getUserUnscopedTokenByToken: rawGetUserUnscopedTokenK8s,
|
|
966
|
+
//
|
|
967
|
+
// Get Scoped Token
|
|
968
|
+
//
|
|
969
|
+
// tenantid: not used
|
|
970
|
+
//
|
|
971
|
+
getUserScopedToken: rawGetUserScopedTokenK8s,
|
|
972
|
+
//
|
|
973
|
+
// Verify publisher type in seed
|
|
974
|
+
//
|
|
975
|
+
verifyUserTokenPublisher: rawWrapVerifyUserTokenPublisherForK8s,
|
|
976
|
+
//
|
|
977
|
+
// Verify token
|
|
978
|
+
//
|
|
979
|
+
verifyUserToken: (dkcobj_permanent, user, tenant, token, token_seed) => {
|
|
980
|
+
return rawVerifyUserTokenByK8sUser(dkcobj_permanent, user, tenant, token, token_seed);
|
|
981
|
+
},
|
|
982
|
+
//
|
|
983
|
+
// Get tenant list
|
|
984
|
+
//
|
|
985
|
+
getUserTenantList: (unscopedtoken, userid, callback) => {
|
|
986
|
+
rawGetUserTenantListK8s(unscopedtoken, callback);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
//
|
|
990
|
+
// Default
|
|
991
|
+
//
|
|
992
|
+
exports.default = exports.k8soidc;
|
|
993
|
+
/*
|
|
994
|
+
* Local variables:
|
|
995
|
+
* tab-width: 4
|
|
996
|
+
* c-basic-offset: 4
|
|
997
|
+
* End:
|
|
998
|
+
* vim600: noexpandtab sw=4 ts=4 fdm=marker
|
|
999
|
+
* vim<600: noexpandtab sw=4 ts=4
|
|
1000
|
+
*/
|