@cloudbase/manager-node 5.6.0 → 5.6.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## @cloudbase/manager-node v5.6.0 版本发布 🚀(2026-07-07)
2
+
3
+ - 新增 integration
4
+ - 修复测试用例
5
+
6
+ ---
7
+
1
8
  ## @cloudbase/manager-node v5.5.7 版本发布 🚀(2026-06-30)
2
9
 
3
10
  - 优化云存储上传逻辑,解决 socket hang up 问题
@@ -250,7 +250,7 @@ class CloudRunService {
250
250
  */
251
251
  async deploy(params) {
252
252
  var _a;
253
- const { serverName, targetPath = process.cwd(), serverConfig } = params;
253
+ const { serverName, targetPath = process.cwd(), serverConfig, vpcInfo } = params;
254
254
  /**
255
255
  * 参数校验和默认值设置
256
256
  */
@@ -335,7 +335,8 @@ class CloudRunService {
335
335
  return this._upsertFunction(true, {
336
336
  name: serverName,
337
337
  deployInfo,
338
- serverConfig: _serverConfig
338
+ serverConfig: _serverConfig,
339
+ vpcInfo
339
340
  });
340
341
  }
341
342
  }
@@ -398,15 +399,20 @@ class CloudRunService {
398
399
  }
399
400
  }
400
401
  _upsertFunction(isNew, data) {
401
- const { name, deployInfo, serverConfig } = data;
402
+ const { name, deployInfo, serverConfig, vpcInfo } = data;
402
403
  const envConfig = this.environment.lazyEnvironmentConfig;
403
404
  const Items = parseObjectToDiffConfigItem(serverConfig);
404
- return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', {
405
+ const basePayload = {
405
406
  EnvId: envConfig.EnvId,
406
407
  ServerName: name,
407
408
  DeployInfo: deployInfo,
408
409
  Items,
409
- });
410
+ };
411
+ // CreateCloudRunServer 支持传入 VpcInfo(仅创建时有效)
412
+ if (isNew && vpcInfo) {
413
+ basePayload.VpcInfo = vpcInfo;
414
+ }
415
+ return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', basePayload);
410
416
  }
411
417
  }
412
418
  exports.CloudRunService = CloudRunService;
@@ -550,6 +556,15 @@ function parseObjectToDiffConfigItem(data) {
550
556
  else if (['TimerScale'].includes(k)) {
551
557
  Items.push({ Key, TimerScale: v });
552
558
  }
559
+ else if (['VpcConf'].includes(k)) {
560
+ Items.push({ Key, VpcConf: v });
561
+ }
562
+ else if (['VolumesConf'].includes(k)) {
563
+ Items.push({ Key, VolumesConf: v });
564
+ }
565
+ else if (['PublicNetConf'].includes(k)) {
566
+ Items.push({ Key, PublicNetConf: v });
567
+ }
553
568
  });
554
569
  return Items;
555
570
  }
package/lib/env/index.js CHANGED
@@ -139,7 +139,7 @@ class EnvService {
139
139
  * @returns {Promise<IEnvInfoRes>}
140
140
  */
141
141
  async getEnvInfo() {
142
- var _a;
142
+ var _a, _b;
143
143
  // 使用 DescribeEnvInfo 进行单环境查询
144
144
  const params = {
145
145
  EnvId: this.envId
@@ -147,6 +147,7 @@ class EnvService {
147
147
  const response = await this.describeEnvInfo(params);
148
148
  const envBaseInfo = (_a = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _a === void 0 ? void 0 : _a.EnvBaseInfo;
149
149
  // 将 EnvBaseInfo 完整映射到 EnvInfo 类型,避免新增字段被丢弃
150
+ // UserInfo 位于 response.EnvInfo 顶层(非 EnvBaseInfo),单独映射
150
151
  const envInfo = {
151
152
  EnvId: envBaseInfo.EnvId,
152
153
  Alias: envBaseInfo.Alias,
@@ -176,7 +177,8 @@ class EnvService {
176
177
  EnvPreferences: envBaseInfo.EnvPreferences,
177
178
  Meta: envBaseInfo.Meta,
178
179
  PostgreSQL: envBaseInfo.PostgreSQL,
179
- EnvStatus: envBaseInfo.EnvStatus
180
+ EnvStatus: envBaseInfo.EnvStatus,
181
+ UserInfo: (_b = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _b === void 0 ? void 0 : _b.UserInfo
180
182
  };
181
183
  return {
182
184
  EnvInfo: envInfo,
@@ -1506,7 +1506,7 @@ class FunctionService {
1506
1506
  const { func, functionPath, functionRootPath } = options;
1507
1507
  const { env, appId } = this.getFunctionConfig();
1508
1508
  if (!appId) {
1509
- throw new error_1.CloudBaseError('无法获取 AppId,请确认环境已开通存储服务');
1509
+ throw new error_1.CloudBaseError('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
1510
1510
  }
1511
1511
  const objectPath = `${appId}/${env}/${func.name}.zip`;
1512
1512
  // 1. 生成存放函数包的临时 Cos 目录
@@ -1742,14 +1742,18 @@ class FunctionService {
1742
1742
  * @memberof FunctionService
1743
1743
  */
1744
1744
  getFunctionConfig() {
1745
- var _a, _b, _c, _d, _e;
1745
+ var _a, _b, _c, _d, _e, _f;
1746
1746
  const envConfig = this.environment.lazyEnvironmentConfig;
1747
1747
  // Functions 可能为空
1748
1748
  const namespace = ((_b = (_a = envConfig.Functions) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Namespace) || '';
1749
- // AppId 可能为空字符串,从 Bucket 名称中提取
1749
+ // AppId 优先从 DescribeEnvInfo 的 UserInfo 获取(账号级标识,不依赖存储服务)
1750
+ // 兜底从 Storages[0] 的 AppId 或 Bucket 名提取
1750
1751
  // COS Bucket 格式: {BucketName}-{APPID},APPID 为纯数字
1751
1752
  const storage = (_c = envConfig.Storages) === null || _c === void 0 ? void 0 : _c[0];
1752
- const appId = (storage === null || storage === void 0 ? void 0 : storage.AppId) || ((_e = (_d = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _d === void 0 ? void 0 : _d.match(/-(\d+)$/)) === null || _e === void 0 ? void 0 : _e[1]) || '';
1753
+ const appId = ((_d = envConfig.UserInfo) === null || _d === void 0 ? void 0 : _d.AppId)
1754
+ || (storage === null || storage === void 0 ? void 0 : storage.AppId)
1755
+ || ((_f = (_e = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _e === void 0 ? void 0 : _e.match(/-(\d+)$/)) === null || _f === void 0 ? void 0 : _f[1])
1756
+ || '';
1753
1757
  const { proxy } = this.environment.cloudBaseContext;
1754
1758
  return {
1755
1759
  proxy,
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AESCrypto = void 0;
37
+ exports.aesEncryptWithIv = aesEncryptWithIv;
38
+ /**
39
+ * 集成中心 AES 加解密
40
+ *
41
+ * 与 dev-platform 控制台的 AESCrypto(crypto-js)完全对齐,保证密文可互相解出:
42
+ * - 算法:AES-256-CBC
43
+ * - key:32 字节(按 UTF-8 解析)
44
+ * - IV:每次随机 16 字节
45
+ * - padding:PKCS7
46
+ * - 密文格式:base64(IV) + base64(ciphertext)
47
+ * (IV 固定 16 字节 → base64 长度恒为 24,前 24 字符即 IV)
48
+ *
49
+ * 使用 Node 内置 crypto 复刻,无需引入额外依赖。
50
+ */
51
+ const crypto = __importStar(require("crypto"));
52
+ const ALGORITHM = 'aes-256-cbc';
53
+ const KEY_BYTE_LENGTH = 32;
54
+ const IV_BYTE_LENGTH = 16;
55
+ /** base64(16 字节) 的固定长度 */
56
+ const IV_BASE64_LENGTH = 24;
57
+ function assertKey(key) {
58
+ if (Buffer.byteLength(key, 'utf8') !== KEY_BYTE_LENGTH) {
59
+ throw new Error('AES key 必须是 32 字节长度');
60
+ }
61
+ }
62
+ /**
63
+ * 使用指定 IV 加密(供测试与内部使用,保证可重现)
64
+ */
65
+ function aesEncryptWithIv(plaintext, key, iv) {
66
+ assertKey(key);
67
+ if (iv.length !== IV_BYTE_LENGTH) {
68
+ throw new Error('IV 必须是 16 字节长度');
69
+ }
70
+ const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
71
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
72
+ return iv.toString('base64') + encrypted.toString('base64');
73
+ }
74
+ class AESCryptoImpl {
75
+ /**
76
+ * AES-256-CBC 加密,返回 base64(IV)+base64(密文)
77
+ */
78
+ encrypt(plaintext, key) {
79
+ assertKey(key);
80
+ const iv = crypto.randomBytes(IV_BYTE_LENGTH);
81
+ return aesEncryptWithIv(plaintext, key, iv);
82
+ }
83
+ /**
84
+ * 解密 base64(IV)+base64(密文) 格式的密文
85
+ */
86
+ decrypt(encryptedData, key) {
87
+ assertKey(key);
88
+ const ivBase64 = encryptedData.slice(0, IV_BASE64_LENGTH);
89
+ const ciphertextBase64 = encryptedData.slice(IV_BASE64_LENGTH);
90
+ const iv = Buffer.from(ivBase64, 'base64');
91
+ if (iv.length !== IV_BYTE_LENGTH) {
92
+ throw new Error('密文格式错误:IV 长度非法');
93
+ }
94
+ const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
95
+ const decrypted = Buffer.concat([
96
+ decipher.update(Buffer.from(ciphertextBase64, 'base64')),
97
+ decipher.final()
98
+ ]);
99
+ return decrypted.toString('utf8');
100
+ }
101
+ }
102
+ exports.AESCrypto = new AESCryptoImpl();
@@ -24,15 +24,18 @@ exports.IntegrationService = void 0;
24
24
  const error_1 = require("../error");
25
25
  const utils_1 = require("../utils");
26
26
  const constants_1 = require("./constants");
27
+ const crypto_1 = require("./crypto");
28
+ const secretKey_1 = require("./secretKey");
27
29
  __exportStar(require("./types"), exports);
28
30
  /**
29
31
  * 集成中心(UserKey)服务
30
32
  *
31
33
  * 复用现有 tcb 云 API(version 2018-06-08),统一携带 Business='integration'。
32
34
  *
33
- * 本服务只做云 API 的纯封装:不识别模板字段、不做敏感字段加解密、不做
34
- * "绑定资源"这类编排逻辑——这些依赖模板结构 / 七彩石密钥 / 上层交互,
35
- * 由调用方(CLI / 控制台)按各自场景自行编排后,通过 `ext` 传入最终 JSON 字符串。
35
+ * 提供两种使用模式:
36
+ * 1. 透传模式:直接传 `ext`(JSON 字符串),SDK 原样传递给云 API(向后兼容)
37
+ * 2. 自动加解密模式:传 `envVariables`(明文键值对),SDK 自动识别模板中的
38
+ * password 字段、拉取七彩石密钥完成加密后拼装 ext
36
39
  */
37
40
  class IntegrationService {
38
41
  constructor(environment) {
@@ -66,36 +69,50 @@ class IntegrationService {
66
69
  Business: (_c = params.business) !== null && _c !== void 0 ? _c : constants_1.USER_KEY_BUSINESS // 默认 integration
67
70
  });
68
71
  }
69
- /** 查询单个集成实例详情 */
72
+ /**
73
+ * 查询单个集成实例详情。
74
+ *
75
+ * 当 `decryptPwd=true` 时,SDK 自动拉取模板识别 password 字段并从七彩石
76
+ * 获取 AES 密钥解密,Ext 中 password 字段返回明文。解密失败时保留密文原值,
77
+ * 不中断流程。
78
+ */
70
79
  async getUserKey(params) {
71
- var _a, _b;
80
+ var _a, _b, _c;
72
81
  this.validateRequiredParams(params, ['keyId']);
73
82
  const { envId } = this.getEnvInfo();
74
- return this.tcbService.request('GetOneUserKey', {
83
+ const result = await this.tcbService.request('GetOneUserKey', {
75
84
  EnvId: envId,
76
85
  KeyID: params.keyId,
77
86
  NeedPwd: (_a = params.needPwd) !== null && _a !== void 0 ? _a : false,
78
87
  Business: (_b = params.business) !== null && _b !== void 0 ? _b : constants_1.USER_KEY_BUSINESS // 默认 integration
79
88
  });
89
+ // 自动解密 password 字段
90
+ if (params.decryptPwd && ((_c = result.UserKeyInfo) === null || _c === void 0 ? void 0 : _c.Ext)) {
91
+ result.UserKeyInfo = await this.decryptExtIfNeeded(result.UserKeyInfo, envId);
92
+ }
93
+ return result;
80
94
  }
81
95
  // =========== 写 API ===========
82
96
  /**
83
97
  * 创建集成实例。
84
98
  *
85
- * `Ext` 为最终写入的 JSON 字符串,由调用方按模板字段自行拼装(含敏感字段
86
- * 加密),本方法不做任何解析或加工,原样传递给云 API
99
+ * 支持两种传参方式:
100
+ * - 传 `ext`:透传模式,原样传递给云 API(向后兼容,调用方自行加密)
101
+ * - 传 `envVariables`:自动加密模式,SDK 识别模板 password 字段并加密后拼装 ext
102
+ * - 两者都传时以 `ext` 优先
87
103
  */
88
104
  async createUserKey(params) {
89
105
  var _a;
90
106
  this.validateRequiredParams(params, ['name', 'authTypeCode']);
91
107
  const { envId } = this.getEnvInfo();
108
+ const ext = await this.buildExtFromParams(envId, params.authTypeCode, { ext: params.ext, envVariables: params.envVariables });
92
109
  return this.tcbService.request('CreateUserKey', {
93
110
  EnvId: envId,
94
111
  Name: params.name,
95
112
  KeyID: params.keyId,
96
113
  AuthTypeCode: params.authTypeCode,
97
114
  Description: params.description,
98
- Ext: params.ext,
115
+ Ext: ext,
99
116
  TicketID: params.ticketId,
100
117
  Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
101
118
  DemoCodeFunctionName: params.demoCodeFunctionName,
@@ -105,20 +122,28 @@ class IntegrationService {
105
122
  /**
106
123
  * 更新集成实例。
107
124
  *
108
- * `Ext` 为最终写入的 JSON 字符串,由调用方自行完成"与现有 Ext 合并"
109
- * "敏感字段加密"等编排后传入;未传 `ext` 时后端保留原有 Ext 不变。
125
+ * 支持两种传参方式:
126
+ * - `ext`:透传模式,原样传递给云 API(向后兼容)
127
+ * - 传 `envVariables`:自动加密模式,与现有 Ext 合并后加密写入
128
+ * - 两者都传时以 `ext` 优先
129
+ * - 都未传时后端保留原有 Ext 不变
110
130
  */
111
131
  async updateUserKey(params) {
112
132
  var _a;
113
133
  this.validateRequiredParams(params, ['keyId']);
114
134
  const { envId } = this.getEnvInfo();
135
+ let ext = params.ext;
136
+ // 传了 envVariables 且未传 ext:需要合并现有 Ext 后加密
137
+ if (params.envVariables && !params.ext) {
138
+ ext = await this.buildMergedExtForUpdate(envId, params.keyId, { authTypeCode: params.authTypeCode, envVariables: params.envVariables });
139
+ }
115
140
  return this.tcbService.request('UpdateUserKey', {
116
141
  EnvId: envId,
117
142
  KeyID: params.keyId,
118
143
  Name: params.name,
119
144
  AuthTypeCode: params.authTypeCode,
120
145
  Description: params.description,
121
- Ext: params.ext,
146
+ Ext: ext,
122
147
  DemoCodeFunctionName: params.demoCodeFunctionName,
123
148
  Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
124
149
  RuntimeConfig: params.runtimeConfig
@@ -149,6 +174,163 @@ class IntegrationService {
149
174
  }
150
175
  }
151
176
  }
177
+ /**
178
+ * 根据 ext / envVariables 参数构造最终的 Ext JSON 字符串。
179
+ * ext 优先;envVariables 由 SDK 自动加密 password 字段。
180
+ */
181
+ async buildExtFromParams(envId, authTypeCode, opts) {
182
+ // ext 优先(透传模式)
183
+ if (opts.ext !== undefined) {
184
+ return opts.ext;
185
+ }
186
+ // 自动加密模式
187
+ if (opts.envVariables && Object.keys(opts.envVariables).length > 0) {
188
+ return this.encryptAndStringify(envId, authTypeCode, opts.envVariables);
189
+ }
190
+ return undefined;
191
+ }
192
+ /**
193
+ * 更新场景:将 envVariables 与现有实例的 Ext 合并后加密。
194
+ * 需要先查询现有实例获取当前的 authTypeCode 和 Ext。
195
+ */
196
+ async buildMergedExtForUpdate(envId, keyId, opts) {
197
+ var _a, _b;
198
+ const { envVariables, authTypeCode } = opts;
199
+ if (!envVariables || Object.keys(envVariables).length === 0) {
200
+ return undefined;
201
+ }
202
+ // 查询现有实例以合并 Ext
203
+ let existingExtObj = {};
204
+ let resolvedAuthTypeCode = authTypeCode;
205
+ try {
206
+ const existing = await this.getUserKey({
207
+ keyId,
208
+ needPwd: true,
209
+ decryptPwd: true, // 解密后方便合并
210
+ business: constants_1.USER_KEY_BUSINESS
211
+ });
212
+ const extObj = this.parseExt((_a = existing.UserKeyInfo) === null || _a === void 0 ? void 0 : _a.Ext);
213
+ existingExtObj = Object.assign({}, extObj);
214
+ resolvedAuthTypeCode = resolvedAuthTypeCode || ((_b = existing.UserKeyInfo) === null || _b === void 0 ? void 0 : _b.AuthTypeCode);
215
+ }
216
+ catch (_c) {
217
+ // 查询失败时仅使用传入的 envVariables
218
+ }
219
+ if (!resolvedAuthTypeCode) {
220
+ // 无法获取 authTypeCode,无法识别 password 字段,直接 JSON 序列化
221
+ return JSON.stringify(Object.assign(Object.assign({}, existingExtObj), envVariables));
222
+ }
223
+ // 合并:现有 Ext 为基础,envVariables 覆盖
224
+ const merged = Object.assign(Object.assign({}, existingExtObj), envVariables);
225
+ return this.encryptAndStringify(envId, resolvedAuthTypeCode, merged);
226
+ }
227
+ /**
228
+ * 加密 envVariables 中的 password 字段并返回 JSON 字符串。
229
+ * 无 password 字段时直接 JSON.stringify,不请求七彩石。
230
+ */
231
+ async encryptAndStringify(envId, authTypeCode, envVariables) {
232
+ // 1. 拉取模板,识别 password 字段
233
+ const passwordFieldNames = await this.getPasswordFieldNames(authTypeCode);
234
+ // 2. 判断是否有 password 字段需要加密
235
+ const hasPasswordValue = passwordFieldNames.some(name => envVariables[name]);
236
+ if (!hasPasswordValue) {
237
+ return JSON.stringify(envVariables);
238
+ }
239
+ // 3. 拉取 AES key 并加密
240
+ try {
241
+ const aesKey = await (0, secretKey_1.getAesKey)({ envId });
242
+ const encrypted = this.encryptPasswordFields(envVariables, passwordFieldNames, aesKey);
243
+ return JSON.stringify(encrypted);
244
+ }
245
+ catch (_a) {
246
+ // 获取密钥失败时以明文写入(与 CLI 行为一致)
247
+ return JSON.stringify(envVariables);
248
+ }
249
+ }
250
+ /**
251
+ * 解密 Ext 中的 password 字段并返回新的 IUserKeyInfo。
252
+ * 解密失败时保留原值,不中断流程。
253
+ */
254
+ async decryptExtIfNeeded(userKeyInfo, envId) {
255
+ if (!userKeyInfo.Ext || !userKeyInfo.AuthTypeCode)
256
+ return userKeyInfo;
257
+ try {
258
+ const passwordFieldNames = await this.getPasswordFieldNames(userKeyInfo.AuthTypeCode);
259
+ if (passwordFieldNames.length === 0)
260
+ return userKeyInfo;
261
+ const extObj = this.parseExt(userKeyInfo.Ext);
262
+ if (!Object.keys(extObj).length)
263
+ return userKeyInfo;
264
+ const hasPasswordValue = passwordFieldNames.some(name => extObj[name]);
265
+ if (!hasPasswordValue)
266
+ return userKeyInfo;
267
+ const aesKey = await (0, secretKey_1.getAesKey)({ envId });
268
+ const decrypted = this.decryptPasswordFields(extObj, passwordFieldNames, aesKey);
269
+ return Object.assign(Object.assign({}, userKeyInfo), { Ext: JSON.stringify(decrypted) });
270
+ }
271
+ catch (_a) {
272
+ return userKeyInfo;
273
+ }
274
+ }
275
+ /** 从模板中获取 password 类型字段名列表 */
276
+ async getPasswordFieldNames(authTypeCode) {
277
+ var _a, _b;
278
+ try {
279
+ const res = await this.listTemplates({ authTypeCode, limit: 1 });
280
+ const templateExt = (_b = (_a = res.KeyTemplateList) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Ext;
281
+ if (!templateExt)
282
+ return [];
283
+ const parsed = JSON.parse(templateExt);
284
+ const fields = Array.isArray(parsed === null || parsed === void 0 ? void 0 : parsed.fields) ? parsed.fields : [];
285
+ return fields
286
+ .filter((f) => f && f.type === 'password' && typeof f.name === 'string')
287
+ .map((f) => f.name);
288
+ }
289
+ catch (_c) {
290
+ return [];
291
+ }
292
+ }
293
+ /** 解析 Ext JSON 字符串为对象,失败返回空对象 */
294
+ parseExt(ext) {
295
+ if (!ext)
296
+ return {};
297
+ try {
298
+ const parsed = JSON.parse(ext);
299
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
300
+ }
301
+ catch (_a) {
302
+ return {};
303
+ }
304
+ }
305
+ /** 加密 ext 对象中的 password 字段(仅加密有值的字段),返回新对象 */
306
+ encryptPasswordFields(extObj, passwordFieldNames, aesKey) {
307
+ const result = Object.assign({}, extObj);
308
+ passwordFieldNames.forEach(name => {
309
+ const value = result[name];
310
+ if (name && value) {
311
+ result[name] = crypto_1.AESCrypto.encrypt(value, aesKey);
312
+ }
313
+ });
314
+ return result;
315
+ }
316
+ /** 解密 ext 对象中的 password 字段,解密失败保留原值,返回新对象 */
317
+ decryptPasswordFields(extObj, passwordFieldNames, aesKey) {
318
+ const result = Object.assign({}, extObj);
319
+ const MIN_CIPHERTEXT_LEN = 24; // base64(16 字节 IV) = 24 字符
320
+ passwordFieldNames.forEach(name => {
321
+ const encrypted = result[name];
322
+ if (typeof encrypted !== 'string' || encrypted.length <= MIN_CIPHERTEXT_LEN) {
323
+ return;
324
+ }
325
+ try {
326
+ result[name] = crypto_1.AESCrypto.decrypt(encrypted, aesKey);
327
+ }
328
+ catch (_a) {
329
+ // 解密失败保留原值,不中断流程
330
+ }
331
+ });
332
+ return result;
333
+ }
152
334
  }
153
335
  exports.IntegrationService = IntegrationService;
154
336
  __decorate([
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAesKey = getAesKey;
4
+ exports.__clearAesKeyCache = __clearAesKeyCache;
5
+ /**
6
+ * 集成中心 AES key 获取(七彩石)
7
+ *
8
+ * 通过 QueryRainbowConfig 拉取 group=tcb-config(国内)/
9
+ * frontend.tcb-config(国际)下字段 dev-platform-apis 的值作为 AES-256-CBC 密钥。
10
+ *
11
+ * 安全约束:
12
+ * - key 仅在进程内存使用,按 group 维度缓存;
13
+ * - 绝不写入任何本地文件、绝不打印日志。
14
+ */
15
+ const http_request_1 = require("../utils/http-request");
16
+ const error_1 = require("../error");
17
+ /** 七彩石配置 */
18
+ const RAINBOW_CONFIG = {
19
+ ENV_TYPE: 'Default',
20
+ GROUP_CN: 'tcb-config',
21
+ GROUP_INTL: 'frontend.tcb-config',
22
+ /** AES key 在 kvs 中的字段名 */
23
+ CRYPTO_KEY_FIELD: 'dev-platform-apis'
24
+ };
25
+ const RAINBOW_HOST_CN = 'wedaapi.tcbautomation.cn';
26
+ const RAINBOW_HOST_INTL = 'gray.decisiontcb.tencentcloud.com';
27
+ /** 无 EnvId 时的占位 Uin */
28
+ const FALLBACK_UIN = '3309033';
29
+ /** 进程内缓存(按 group 维度),key 不落盘、不打印 */
30
+ const keyCache = {};
31
+ /**
32
+ * 获取集成中心 AES key(进程内缓存)
33
+ * @throws CloudBaseError 拉取或解析失败
34
+ */
35
+ async function getAesKey(params = {}) {
36
+ var _a, _b;
37
+ const { envId, isIntl = false } = params;
38
+ const group = isIntl ? RAINBOW_CONFIG.GROUP_INTL : RAINBOW_CONFIG.GROUP_CN;
39
+ if (keyCache[group]) {
40
+ return keyCache[group];
41
+ }
42
+ const host = isIntl ? RAINBOW_HOST_INTL : RAINBOW_HOST_CN;
43
+ const url = `https://${host}/Graydecision/V1/QueryRainbowConfig?group=${group}`;
44
+ const payload = {
45
+ EnvType: RAINBOW_CONFIG.ENV_TYPE,
46
+ Group: group
47
+ };
48
+ if (envId) {
49
+ payload.EnvId = envId;
50
+ }
51
+ else {
52
+ payload.Uin = FALLBACK_UIN;
53
+ }
54
+ let resJson;
55
+ try {
56
+ resJson = await (0, http_request_1.fetch)(url, {
57
+ method: 'POST',
58
+ headers: { 'content-type': 'application/json' },
59
+ body: JSON.stringify(payload)
60
+ });
61
+ }
62
+ catch (e) {
63
+ throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置拉取请求异常');
64
+ }
65
+ const data = (_a = resJson === null || resJson === void 0 ? void 0 : resJson.Response) === null || _a === void 0 ? void 0 : _a.Data;
66
+ if (!data || data.ErrorCode !== '0') {
67
+ throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
68
+ }
69
+ let config;
70
+ try {
71
+ config = JSON.parse(data.ConfigValue)[0];
72
+ }
73
+ catch (e) {
74
+ throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置内容解析失败');
75
+ }
76
+ const kvs = ((_b = config === null || config === void 0 ? void 0 : config.kvs) === null || _b === void 0 ? void 0 : _b.kvs) || [];
77
+ const target = kvs.find(kv => kv.key === RAINBOW_CONFIG.CRYPTO_KEY_FIELD);
78
+ const key = target === null || target === void 0 ? void 0 : target.value;
79
+ if (!key) {
80
+ throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
81
+ }
82
+ keyCache[group] = key;
83
+ return key;
84
+ }
85
+ /** 清空进程内缓存(仅供测试使用) */
86
+ function __clearAesKeyCache() {
87
+ Object.keys(keyCache).forEach(k => delete keyCache[k]);
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.6.0",
3
+ "version": "5.6.1",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  import { Environment } from '../environment';
2
2
  import { IResponseInfo } from '../interfaces';
3
- import { CloudrunServerType, ICloudrunDetailResponse, ICloudrunListResponse, ICloudrunServerBaseConfig, ICloudrunServerBaseInfo, IBuildLogResponse, IProcessLogResponse, IDiffConfigItem, ITemplate, ReleaseTypeEnum, IDescribeCloudRunDeployRecordResponse } from './type';
3
+ import { CloudrunServerType, ICloudrunDetailResponse, ICloudrunListResponse, ICloudrunServerBaseConfig, ICreateVpcInfo, IBuildLogResponse, IProcessLogResponse, IDiffConfigItem, ITemplate, ReleaseTypeEnum, IDescribeCloudRunDeployRecordResponse } from './type';
4
4
  /**
5
5
  * 云托管服务管理类
6
6
  * 提供云托管服务的初始化、下载、列表查询和删除等功能
@@ -128,7 +128,8 @@ export declare class CloudRunService {
128
128
  deployInfo: {
129
129
  ReleaseType: ReleaseTypeEnum;
130
130
  };
131
- serverConfig?: Partial<Pick<ICloudrunServerBaseConfig, 'OpenAccessTypes' | 'Cpu' | 'Mem' | 'MinNum' | 'MaxNum' | 'PolicyDetails' | 'CustomLogs' | 'EnvParams' | 'Port' | 'Dockerfile' | 'BuildDir' | 'InternalAccess' | 'InternalDomain' | 'EntryPoint' | 'Cmd' | 'InstallDependency'>>;
131
+ vpcInfo?: ICreateVpcInfo;
132
+ serverConfig?: Partial<Pick<ICloudrunServerBaseConfig, 'OpenAccessTypes' | 'Cpu' | 'Mem' | 'MinNum' | 'MaxNum' | 'PolicyDetails' | 'CustomLogs' | 'EnvParams' | 'Port' | 'Dockerfile' | 'BuildDir' | 'InternalAccess' | 'InternalDomain' | 'EntryPoint' | 'Cmd' | 'InstallDependency' | 'OperationMode' | 'SessionAffinity' | 'LogType' | 'LogSetId' | 'LogTopicId' | 'LogParseType' | 'Tag' | 'TimerScale' | 'VpcConf' | 'VolumesConf' | 'PublicNetConf'>>;
132
133
  }): Promise<IResponseInfo>;
133
134
  /**
134
135
  * 获取云托管服务模板列表
@@ -157,4 +158,4 @@ export declare function codeToZip(cwd: string, options?: {
157
158
  /**
158
159
  * 将 object 参数转为 [{key:"Port", IntValue:80}] 的格式,并且剔除空字符串
159
160
  */
160
- export declare function parseObjectToDiffConfigItem(data: Partial<ICloudrunServerBaseInfo>): IDiffConfigItem[];
161
+ export declare function parseObjectToDiffConfigItem(data: Partial<ICloudrunServerBaseConfig>): IDiffConfigItem[];
@@ -261,6 +261,22 @@ export interface ICloudrunServerBaseConfig {
261
261
  * 是否在线安装依赖,true 则本地不打包 node_modules,依赖在线装(如Docker)
262
262
  */
263
263
  InstallDependency?: boolean;
264
+ /**
265
+ * 会话亲和性 open | close
266
+ */
267
+ SessionAffinity?: string;
268
+ /**
269
+ * VPC 网络配置
270
+ */
271
+ VpcConf?: IVpcConf;
272
+ /**
273
+ * 存储挂载配置(COS / CFS)
274
+ */
275
+ VolumesConf?: IVolumeConf[];
276
+ /**
277
+ * 公网访问配置
278
+ */
279
+ PublicNetConf?: IPublicNetConf;
264
280
  }
265
281
  export interface IDiffConfigItem {
266
282
  Key: string;
@@ -271,6 +287,9 @@ export interface IDiffConfigItem {
271
287
  ArrayValue?: string[];
272
288
  PolicyDetails?: ICloudrunHpaPolicy[];
273
289
  TimerScale?: ICloudrunTimerScale[];
290
+ VpcConf?: IVpcConf;
291
+ VolumesConf?: IVolumeConf[];
292
+ PublicNetConf?: IPublicNetConf;
274
293
  }
275
294
  /**
276
295
  * 在线版本信息
@@ -471,3 +490,43 @@ export interface IProcessLogResponse {
471
490
  /** 请求ID */
472
491
  RequestId: string;
473
492
  }
493
+ /**
494
+ * VPC 网络配置
495
+ */
496
+ export interface IVpcConf {
497
+ VpcId?: string;
498
+ VpcCIDR?: string;
499
+ SubnetId?: string;
500
+ SubnetCIDR?: string;
501
+ }
502
+ /**
503
+ * 存储挂载配置(COS / CFS)
504
+ */
505
+ export interface IVolumeConf {
506
+ Type?: string;
507
+ BucketName?: string;
508
+ Endpoint?: string;
509
+ KeyID?: string;
510
+ DstPath?: string;
511
+ SrcPath?: string;
512
+ MountIP?: string;
513
+ ReadOnly?: boolean;
514
+ InstanceId?: string;
515
+ }
516
+ /**
517
+ * 公网访问配置
518
+ */
519
+ export interface IPublicNetConf {
520
+ PublicNetStatus?: string;
521
+ }
522
+ /**
523
+ * CreateCloudRunServer VPC 信息(仅创建时使用)
524
+ */
525
+ export interface ICreateVpcInfo {
526
+ /** vpc id,必填 */
527
+ VpcId: string;
528
+ /** 创建类型:1=新建, 2=指定,必填 */
529
+ CreateType: 1 | 2;
530
+ /** 子网ID列表 */
531
+ SubnetIds?: string[];
532
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 使用指定 IV 加密(供测试与内部使用,保证可重现)
3
+ */
4
+ export declare function aesEncryptWithIv(plaintext: string, key: string, iv: Buffer): string;
5
+ declare class AESCryptoImpl {
6
+ /**
7
+ * AES-256-CBC 加密,返回 base64(IV)+base64(密文)
8
+ */
9
+ encrypt(plaintext: string, key: string): string;
10
+ /**
11
+ * 解密 base64(IV)+base64(密文) 格式的密文
12
+ */
13
+ decrypt(encryptedData: string, key: string): string;
14
+ }
15
+ export declare const AESCrypto: AESCryptoImpl;
16
+ export {};
@@ -6,9 +6,10 @@ export * from './types';
6
6
  *
7
7
  * 复用现有 tcb 云 API(version 2018-06-08),统一携带 Business='integration'。
8
8
  *
9
- * 本服务只做云 API 的纯封装:不识别模板字段、不做敏感字段加解密、不做
10
- * "绑定资源"这类编排逻辑——这些依赖模板结构 / 七彩石密钥 / 上层交互,
11
- * 由调用方(CLI / 控制台)按各自场景自行编排后,通过 `ext` 传入最终 JSON 字符串。
9
+ * 提供两种使用模式:
10
+ * 1. 透传模式:直接传 `ext`(JSON 字符串),SDK 原样传递给云 API(向后兼容)
11
+ * 2. 自动加解密模式:传 `envVariables`(明文键值对),SDK 自动识别模板中的
12
+ * password 字段、拉取七彩石密钥完成加密后拼装 ext
12
13
  */
13
14
  export declare class IntegrationService {
14
15
  private tcbService;
@@ -18,20 +19,31 @@ export declare class IntegrationService {
18
19
  listTemplates(params?: IListTemplatesParams): Promise<IGetUserKeyTemplateListResult>;
19
20
  /** 列出集成实例(密钥对列表) */
20
21
  listUserKeys(params?: IListUserKeysParams): Promise<IGetUserKeyListResult>;
21
- /** 查询单个集成实例详情 */
22
+ /**
23
+ * 查询单个集成实例详情。
24
+ *
25
+ * 当 `decryptPwd=true` 时,SDK 自动拉取模板识别 password 字段并从七彩石
26
+ * 获取 AES 密钥解密,Ext 中 password 字段返回明文。解密失败时保留密文原值,
27
+ * 不中断流程。
28
+ */
22
29
  getUserKey(params: IGetUserKeyParams): Promise<IGetOneUserKeyResult>;
23
30
  /**
24
31
  * 创建集成实例。
25
32
  *
26
- * `Ext` 为最终写入的 JSON 字符串,由调用方按模板字段自行拼装(含敏感字段
27
- * 加密),本方法不做任何解析或加工,原样传递给云 API
33
+ * 支持两种传参方式:
34
+ * - 传 `ext`:透传模式,原样传递给云 API(向后兼容,调用方自行加密)
35
+ * - 传 `envVariables`:自动加密模式,SDK 识别模板 password 字段并加密后拼装 ext
36
+ * - 两者都传时以 `ext` 优先
28
37
  */
29
38
  createUserKey(params: ICreateUserKeyParams): Promise<ICreateUserKeyResult>;
30
39
  /**
31
40
  * 更新集成实例。
32
41
  *
33
- * `Ext` 为最终写入的 JSON 字符串,由调用方自行完成"与现有 Ext 合并"
34
- * "敏感字段加密"等编排后传入;未传 `ext` 时后端保留原有 Ext 不变。
42
+ * 支持两种传参方式:
43
+ * - `ext`:透传模式,原样传递给云 API(向后兼容)
44
+ * - 传 `envVariables`:自动加密模式,与现有 Ext 合并后加密写入
45
+ * - 两者都传时以 `ext` 优先
46
+ * - 都未传时后端保留原有 Ext 不变
35
47
  */
36
48
  updateUserKey(params: IUpdateUserKeyParams): Promise<IUpdateUserKeyResult>;
37
49
  /** 删除集成实例 */
@@ -40,4 +52,32 @@ export declare class IntegrationService {
40
52
  private getEnvInfo;
41
53
  /** 校验必填参数 */
42
54
  private validateRequiredParams;
55
+ /**
56
+ * 根据 ext / envVariables 参数构造最终的 Ext JSON 字符串。
57
+ * ext 优先;envVariables 由 SDK 自动加密 password 字段。
58
+ */
59
+ private buildExtFromParams;
60
+ /**
61
+ * 更新场景:将 envVariables 与现有实例的 Ext 合并后加密。
62
+ * 需要先查询现有实例获取当前的 authTypeCode 和 Ext。
63
+ */
64
+ private buildMergedExtForUpdate;
65
+ /**
66
+ * 加密 envVariables 中的 password 字段并返回 JSON 字符串。
67
+ * 无 password 字段时直接 JSON.stringify,不请求七彩石。
68
+ */
69
+ private encryptAndStringify;
70
+ /**
71
+ * 解密 Ext 中的 password 字段并返回新的 IUserKeyInfo。
72
+ * 解密失败时保留原值,不中断流程。
73
+ */
74
+ private decryptExtIfNeeded;
75
+ /** 从模板中获取 password 类型字段名列表 */
76
+ private getPasswordFieldNames;
77
+ /** 解析 Ext JSON 字符串为对象,失败返回空对象 */
78
+ private parseExt;
79
+ /** 加密 ext 对象中的 password 字段(仅加密有值的字段),返回新对象 */
80
+ private encryptPasswordFields;
81
+ /** 解密 ext 对象中的 password 字段,解密失败保留原值,返回新对象 */
82
+ private decryptPasswordFields;
43
83
  }
@@ -0,0 +1,13 @@
1
+ export interface IGetAesKeyParams {
2
+ /** 环境 Id(用于灰度命中,无则用占位 Uin) */
3
+ envId?: string;
4
+ /** 是否国际站 */
5
+ isIntl?: boolean;
6
+ }
7
+ /**
8
+ * 获取集成中心 AES key(进程内缓存)
9
+ * @throws CloudBaseError 拉取或解析失败
10
+ */
11
+ export declare function getAesKey(params?: IGetAesKeyParams): Promise<string>;
12
+ /** 清空进程内缓存(仅供测试使用) */
13
+ export declare function __clearAesKeyCache(): void;
@@ -111,8 +111,10 @@ export interface IListUserKeysParams {
111
111
  /** 查询集成实例详情入参 */
112
112
  export interface IGetUserKeyParams {
113
113
  keyId: string;
114
- /** 为 true 时返回加密字段的密文值 */
114
+ /** 为 true 时返回加密字段的密文值(原始密文)/ 解密后的明文值(当 decryptPwd=true 时) */
115
115
  needPwd?: boolean;
116
+ /** 为 true 时 SDK 自动解密 Ext 中的 password 字段,返回明文(需同时传 needPwd=true) */
117
+ decryptPwd?: boolean;
116
118
  /** 业务类型,integration:从集成中心来的,默认为空,表示是从授权管理中心来的 */
117
119
  business?: string;
118
120
  }
@@ -123,9 +125,17 @@ export interface ICreateUserKeyParams {
123
125
  description?: string;
124
126
  /**
125
127
  * Ext JSON 字符串(模板动态字段的最终取值,含敏感字段密文)。
126
- * 由调用方按模板字段自行拼装(含加密),本层不做任何解析或加工。
128
+ * 与 envVariables 二选一:传入 ext 时直接透传;传入 envVariables 时
129
+ * SDK 自动识别模板中的 password 字段、拉取七彩石密钥完成加密后拼装 ext。
130
+ * 两者都传时以 ext 优先。
127
131
  */
128
132
  ext?: string;
133
+ /**
134
+ * 环境变量明文键值对。
135
+ * SDK 自动识别模板中的 password 字段并加密,无需调用方自行处理。
136
+ * ext 和 envVariables 都未传时,不携带 Ext 字段。
137
+ */
138
+ envVariables?: Record<string, string>;
129
139
  keyId?: string;
130
140
  ticketId?: string;
131
141
  /** 业务类型,integration:从集成中心来的,默认为空,表示是从授权管理中心来的 */
@@ -143,9 +153,15 @@ export interface IUpdateUserKeyParams {
143
153
  description?: string;
144
154
  /**
145
155
  * Ext JSON 字符串(模板动态字段的最终取值,含敏感字段密文)。
146
- * 由调用方自行完成"与现有 Ext 合并"等编排后传入;未传时后端保留原有 Ext 不变。
156
+ * envVariables 二选一。都未传时后端保留原有 Ext 不变。
147
157
  */
148
158
  ext?: string;
159
+ /**
160
+ * 环境变量明文键值对。
161
+ * SDK 自动识别模板中的 password 字段并加密,无需调用方自行处理。
162
+ * 传入时与现有 Ext 合并后再加密写入。
163
+ */
164
+ envVariables?: Record<string, string>;
149
165
  /** 绑定的示例代码云函数名称(bind-resource 使用) */
150
166
  demoCodeFunctionName?: string;
151
167
  /** 业务类型,integration:从集成中心来的,默认为空,表示是从授权管理中心来的 */
@@ -150,6 +150,7 @@ export interface EnvInfo {
150
150
  }>;
151
151
  PostgreSQL?: PostgreSQLInfo[] | Record<string, any>;
152
152
  EnvStatus?: string;
153
+ UserInfo?: AccountInfoInner;
153
154
  }
154
155
  export interface FunctionInfo {
155
156
  Namespace?: string;