diff --git a/libraries/deno-commons-mod.ts b/libraries/deno-commons-mod.ts index 2bed60c..51d99be 100644 --- a/libraries/deno-commons-mod.ts +++ b/libraries/deno-commons-mod.ts @@ -1249,3 +1249,133 @@ export async function fetchFileWithCache( throw e; } } + +interface AlibabaCloudInstanceIdentityAudienceMeta { + version: 1 | number; + issuedAtMs: number; + expiresAtMs: number; + purpose: string; +} + +export type AlibabaCloudInstanceIdentityMode = "normal" | "secured"; + +// https://help.aliyun.com/zh/ecs/user-guide/use-instance-identities +export async function fetchAlibabaCloudInstanceIdentityV1( + purpose: string, + mode?: AlibabaCloudInstanceIdentityMode, +): Promise { + let metaDataToken = null; + if (!mode) { + mode = getEnv("ALIBABA_CLOUD_INSTANCE_IDENTITY_MODE"); + } + if (mode === "secured") { + const tokenResponse = await fetchDataWithTimeout( + "http://100.100.100.200/latest/api/token", + { + method: "PUT", + headers: { + "X-aliyun-ecs-metadata-token-ttl-seconds": "60", + }, + }, + ); + if (tokenResponse.status != 200) { + throw new Error( + `Get meta api token failed: ${tokenResponse.status}`, + ); + } + metaDataToken = await tokenResponse.text(); + } + const audienceMeta = { + version: 1, + issuedAtMs: Date.now(), + expiresAtMs: Date.now() + 60 * 1000, + purpose: purpose, + } as AlibabaCloudInstanceIdentityAudienceMeta; + const pkcs7Options = {}; + if (metaDataToken) { + pkcs7Options["X-aliyun-ecs-metadata-token"] = metaDataToken; + } + + const pkcs7Response = await fetchDataWithTimeout( + `http://100.100.100.200/latest/dynamic/instance-identity/pkcs7?audience=${ + encodeURIComponent(JSON.stringify(audienceMeta)) + }`, + pkcs7Options, + ); + if (pkcs7Response.status != 200) { + throw new Error("Get PKCS#7 failed: ${pkcs7Response.status}`)"); + } + return await pkcs7Response.text(); +} + +interface GetSecretResponse { + status: number; + message: string; + data: { + created: number; + modified: number; + name: string; + creatorKeyId: string; + grantedKeyIds: string[]; + comment: string; + value: string; + version: number; + }; +} + +export async function getSecretValueViaAlibabaCloudInstanceIdentity( + key: string, + mode?: AlibabaCloudInstanceIdentityMode, +): Promise { + const pkcs7 = await fetchAlibabaCloudInstanceIdentityV1( + "access_hatter_ink", + mode, + ); + const httpSecretResponse = await fetchDataWithTimeout( + `https://global.hatter.ink//secret/get.json?name=${ + encodeURIComponent(key) + }`, + { + headers: { + "Authorization": `PKCS7 ${pkcs7}`, + }, + }, + ); + if (httpSecretResponse.status != 200) { + throw new Error(`Get secret failed: ${httpSecretResponse.status}`); + } + const secretResponse = await httpSecretResponse + .json() as GetSecretResponse; + log.debug("secretResponse", secretResponse); + if (secretResponse.status != 200) { + throw new Error(`Get secret failed: ${secretResponse.status}`); + } + return secretResponse.data.value; +} + +async function getSecretValueViaHatterCli(key: string): Promise { + const output = await execCommand("hatter", [ + "secret", + "get", + "--name", + key, + ]); + const secretResponse = output.getStdoutAsJson() as GetSecretResponse; + log.debug("secretResponse", secretResponse); + if (secretResponse.status != 200) { + throw new Error(`Get secret failed: ${secretResponse.status}`); + } + return secretResponse.data.value; +} + +export type SecretValueRunEnv = "ALIBABA_CLOUD" | "HATTER_CLI"; + +export async function getSecretValue( + key: string, + runEnv?: SecretValueRunEnv, +): Promise { + if (runEnv == "ALIBABA_CLOUD") { + return await getSecretValueViaAlibabaCloudInstanceIdentity(key); + } + return await getSecretValueViaHatterCli(key); +} diff --git a/single-scripts/openclaw-secret.ts b/single-scripts/openclaw-secret.ts index f3a56f2..f103c0a 100755 --- a/single-scripts/openclaw-secret.ts +++ b/single-scripts/openclaw-secret.ts @@ -2,14 +2,13 @@ import { args, - execCommand, exit, - fetchDataWithTimeout, getEnv, + getSecretValue, log, stdinToArrayBuffer, -} from "https://script.hatter.ink/@50/deno-commons-mod.ts"; -import { parseArgs } from "jsr:@std/cli/parse-args"; +} from "https://script.hatter.ink/@54/deno-commons-mod.ts"; +import {parseArgs} from "jsr:@std/cli/parse-args"; // specification: https://docs.openclaw.ai/gateway/secrets @@ -52,63 +51,6 @@ interface GetSecretResponse { }; } -async function getSecretValueViaAlibabaCloudHttps( - key: string, -): Promise { - const pkcs7Response = await fetchDataWithTimeout( - "http://100.100.100.200/latest/dynamic/instance-identity/pkcs7", - ); - if (pkcs7Response.status != 200) { - throw new Error("Get PKCS#7 failed: ${pkcs7Response.status}`)"); - } - const pkcs7 = await pkcs7Response.text(); - const httpSecretResponse = await fetchDataWithTimeout( - "https://hatter.ink/secret/get.json?name=" + - encodeURIComponent(key), - { - headers: { - "Authorization": `PKCS7 ${pkcs7}`, - }, - }, - ); - if (httpSecretResponse.status != 200) { - throw new Error(`Get secret failed: ${httpSecretResponse.status}`); - } - const secretResponse = await httpSecretResponse - .json() as GetSecretResponse; - log.debug("secretResponse", secretResponse); - if (secretResponse.status != 200) { - throw new Error(`Get secret failed: ${secretResponse.status}`); - } - return secretResponse.data.value; -} - -async function getSecretValueViaHatterCli(key: string): Promise { - const output = await execCommand("hatter", [ - "secret", - "get", - "--name", - key, - ]); - const secretResponse = output.getStdoutAsJson() as GetSecretResponse; - log.debug("secretResponse", secretResponse); - if (secretResponse.status != 200) { - throw new Error(`Get secret failed: ${secretResponse.status}`); - } - return secretResponse.data.value; -} - -async function getSecretValue( - isOnAlibabaCloud: boolean, - key: string, -): Promise { - if (isOnAlibabaCloud) { - return await getSecretValueViaAlibabaCloudHttps(key); - } else { - return await getSecretValueViaHatterCli(key); - } -} - const flags = parseArgs(args(), { boolean: ["help", "direct-output"], collect: ["id"], @@ -132,9 +74,8 @@ if (flags.help) { // RUN_ENV values ALIBABA_CLOUD, LOCAL const runEnv = getEnv("RUN_ENV"); -const isOnAlibabaCloud = runEnv == "ALIBABA_CLOUD"; -log.debug("isOnAlibabaCloud", isOnAlibabaCloud); +log.debug("runEnv", runEnv); let openClawInput; if (flags.id) { @@ -166,13 +107,13 @@ if (flags["direct-output"]) { ); exit(1); } - console.log(await getSecretValue(isOnAlibabaCloud, openClawInput.ids[0])); + console.log(await getSecretValue(openClawInput.ids[0], runEnv)); exit(0); } for (const id of openClawInput.ids) { try { - values[id] = await getSecretValue(isOnAlibabaCloud, id); + values[id] = await getSecretValue(id, runEnv); } catch (e) { errors[id] = { message: e.message }; }