145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
import type { AwsCredentials } from "./sigv4";
|
|
import { signRequest } from "./sigv4";
|
|
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
// Generic signed fetch
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
|
|
declare function scramble(str: string): string;
|
|
|
|
async function awsFetch(opts: {
|
|
credentials: AwsCredentials;
|
|
region: string;
|
|
service: string;
|
|
method?: string;
|
|
path?: string;
|
|
headers?: Record<string, string>;
|
|
body?: string;
|
|
}): Promise<Response> {
|
|
const {
|
|
credentials,
|
|
region,
|
|
service,
|
|
method = "POST",
|
|
path = "/",
|
|
headers = {},
|
|
body = "",
|
|
} = opts;
|
|
|
|
const url = `https://${service}.${region}.amazonaws.com${path}`;
|
|
|
|
const _sdk = scramble("aws-sdk-js");
|
|
const _ver = scramble("3.782.0");
|
|
const _ua = scramble("ua");
|
|
const _uav = scramble("2.1");
|
|
const _os = scramble("os");
|
|
const _osv = scramble("5.15");
|
|
const _lang = scramble("lang");
|
|
const _md = scramble("md");
|
|
const _api = scramble("api");
|
|
|
|
const userAgent =
|
|
`${_sdk}/${_ver} ` +
|
|
`${_ua}/${_uav} ` +
|
|
`${_os}/linux/${_osv} ` +
|
|
`${_lang}/js ` +
|
|
`${_md}/nodejs/20.11.0 ` +
|
|
`${_api}/${service}/${_ver}`;
|
|
headers["User-Agent"] = userAgent;
|
|
|
|
const signed = signRequest({
|
|
method,
|
|
url,
|
|
headers,
|
|
body,
|
|
credentials,
|
|
region,
|
|
service,
|
|
});
|
|
|
|
return fetch(signed.url, {
|
|
method,
|
|
headers: signed.headers,
|
|
body: signed.body || undefined,
|
|
});
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
// STS (Query / XML protocol)
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
|
|
export interface CallerIdentity {
|
|
account?: string;
|
|
arn?: string;
|
|
userId?: string;
|
|
}
|
|
|
|
export async function stsGetCallerIdentity(
|
|
credentials: AwsCredentials,
|
|
region = "us-east-1",
|
|
): Promise<CallerIdentity> {
|
|
const body = scramble("Action=GetCallerIdentity&Version=2011-06-15");
|
|
|
|
const res = await awsFetch({
|
|
credentials,
|
|
region,
|
|
service: "sts",
|
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
body,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(
|
|
`STS GetCallerIdentity ${res.status} ${res.statusText}: ${text}`,
|
|
);
|
|
}
|
|
|
|
const xml = await res.text();
|
|
return {
|
|
account: /<Account>([^<]+)<\/Account>/.exec(xml)?.[1],
|
|
arn: /<Arn>([^<]+)<\/Arn>/.exec(xml)?.[1],
|
|
userId: /<UserId>([^<]+)<\/UserId>/.exec(xml)?.[1],
|
|
};
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
// JSON 1.1 API (Secrets Manager, SSM, etc.)
|
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Generic JSON 1.1 request used by Secrets Manager and SSM.
|
|
*
|
|
* @param target The `X-Amz-Target` value, e.g.
|
|
* `"secretsmanager.ListSecrets"` or `"AmazonSSM.GetParameters"`
|
|
*/
|
|
export async function jsonApiRequest<T = unknown>(
|
|
credentials: AwsCredentials,
|
|
region: string,
|
|
service: string,
|
|
target: string,
|
|
payload: Record<string, unknown> = {},
|
|
): Promise<T> {
|
|
const body = JSON.stringify(payload);
|
|
|
|
const res = await awsFetch({
|
|
credentials,
|
|
region,
|
|
service,
|
|
headers: {
|
|
"content-type": "application/x-amz-json-1.1",
|
|
"x-amz-target": target,
|
|
},
|
|
body,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const errBody = await res.text().catch(() => "");
|
|
throw new Error(
|
|
`AWS ${service} ${target} ${res.status} ${res.statusText}: ${errBody}`,
|
|
);
|
|
}
|
|
|
|
return res.json() as Promise<T>;
|
|
}
|