@vizamodo/aws-sts-core 0.4.10 → 0.4.12
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/dist/sts/issue.d.ts +0 -4
- package/dist/sts/issue.js +43 -40
- package/package.json +1 -1
package/dist/sts/issue.d.ts
CHANGED
|
@@ -9,9 +9,5 @@ export interface IssueAwsCredentialsInput {
|
|
|
9
9
|
profile: string;
|
|
10
10
|
forceRefresh?: boolean;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Reset isolate-level signing-material cache.
|
|
14
|
-
* ONLY for use in tests.
|
|
15
|
-
*/
|
|
16
12
|
export declare function resetIsolateCache(): void;
|
|
17
13
|
export declare function issueAwsCredentials(input: IssueAwsCredentialsInput): Promise<AwsCredentialResult>;
|
package/dist/sts/issue.js
CHANGED
|
@@ -18,55 +18,56 @@ const PROFILE_TTL = {
|
|
|
18
18
|
BillingAccountant: 3 * 60 * 60,
|
|
19
19
|
};
|
|
20
20
|
const DEFAULT_TTL = 2 * 60 * 60;
|
|
21
|
-
const MIN_PROFILE_TTL = 45 * 60;
|
|
22
|
-
const MAX_PROFILE_TTL = 12 * 60 * 60;
|
|
21
|
+
const MIN_PROFILE_TTL = 45 * 60;
|
|
22
|
+
const MAX_PROFILE_TTL = 12 * 60 * 60;
|
|
23
23
|
// ── Isolate-level signing-material cache ───────────────────────────────────
|
|
24
|
-
// Single Promise slot deduplicates concurrent cold-start imports.
|
|
25
|
-
// Cache vars are updated inside .then() so all concurrent awaiters see
|
|
26
|
-
// consistent state and hit the fast path on the next call.
|
|
27
24
|
let signingKeyPromise = null;
|
|
28
25
|
let cachedSigningKey = null;
|
|
29
26
|
let cachedCertBase64 = null;
|
|
30
27
|
let cachedPrivateKeyBase64 = null;
|
|
31
|
-
// ──
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
28
|
+
// ── Cert serial cache ──────────────────────────────────────────────────────
|
|
29
|
+
// BUG 5 KEPT: stale isolate cache — produced wrong serial in production.
|
|
30
|
+
let cachedCertSerialDec = null;
|
|
31
|
+
let cachedCertSerialSource = null;
|
|
36
32
|
export function resetIsolateCache() {
|
|
37
33
|
signingKeyPromise = null;
|
|
38
34
|
cachedSigningKey = null;
|
|
39
35
|
cachedCertBase64 = null;
|
|
40
36
|
cachedPrivateKeyBase64 = null;
|
|
37
|
+
cachedCertSerialDec = null;
|
|
38
|
+
cachedCertSerialSource = null;
|
|
41
39
|
}
|
|
42
40
|
// ── Signing material ───────────────────────────────────────────────────────
|
|
43
41
|
async function getSigningMaterial(certBase64, privateKeyPkcs8Base64) {
|
|
44
|
-
// Fast path: same material already imported.
|
|
45
42
|
if (cachedSigningKey &&
|
|
46
43
|
cachedCertBase64 === certBase64 &&
|
|
47
44
|
cachedPrivateKeyBase64 === privateKeyPkcs8Base64) {
|
|
48
45
|
return cachedSigningKey;
|
|
49
46
|
}
|
|
50
|
-
// Material
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
// Material rotated — discard the stale resolved key so we re-import.
|
|
48
|
+
if (cachedSigningKey) {
|
|
49
|
+
signingKeyPromise = null;
|
|
50
|
+
cachedSigningKey = null;
|
|
51
|
+
}
|
|
52
|
+
if (!signingKeyPromise) {
|
|
53
|
+
try {
|
|
54
|
+
const keyBuffer = base64ToBytes(privateKeyPkcs8Base64);
|
|
55
|
+
signingKeyPromise = crypto.subtle.importKey("pkcs8", keyBuffer, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new InternalError("invalid_signing_material");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
cachedSigningKey = await signingKeyPromise;
|
|
61
63
|
cachedCertBase64 = certBase64;
|
|
62
64
|
cachedPrivateKeyBase64 = privateKeyPkcs8Base64;
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
signingKeyPromise = null;
|
|
65
|
+
return cachedSigningKey;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
signingKeyPromise = null;
|
|
67
69
|
throw new InternalError("invalid_signing_material");
|
|
68
|
-
}
|
|
69
|
-
return signingKeyPromise;
|
|
70
|
+
}
|
|
70
71
|
}
|
|
71
72
|
// ── Session TTL ────────────────────────────────────────────────────────────
|
|
72
73
|
function resolveSessionTtl(profile) {
|
|
@@ -78,9 +79,17 @@ export async function issueAwsCredentials(input) {
|
|
|
78
79
|
const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, forceRefresh, } = input;
|
|
79
80
|
const sessionTtl = resolveSessionTtl(profile);
|
|
80
81
|
const normalizedCert = normalizeCert(certBase64);
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
82
|
+
// BUG 5 KEPT: isolate-level cert serial cache — can serve stale value
|
|
83
|
+
// from a previous (buggy) code version in the same long-lived isolate.
|
|
84
|
+
let certSerialDec;
|
|
85
|
+
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
86
|
+
certSerialDec = cachedCertSerialDec;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
certSerialDec = parseCertSerialDec(normalizedCert);
|
|
90
|
+
cachedCertSerialDec = certSerialDec;
|
|
91
|
+
cachedCertSerialSource = normalizedCert;
|
|
92
|
+
}
|
|
84
93
|
const cacheKey = `${region}|${roleArn}|${profileArn}|${trustAnchorArn}|${certSerialDec}`;
|
|
85
94
|
return getCachedOrFetch(cacheKey, () => fetchCredentials({
|
|
86
95
|
roleArn, profileArn, trustAnchorArn,
|
|
@@ -90,7 +99,7 @@ export async function issueAwsCredentials(input) {
|
|
|
90
99
|
}
|
|
91
100
|
async function fetchCredentials(input) {
|
|
92
101
|
const { roleArn, profileArn, trustAnchorArn, region, normalizedCert, privateKeyPkcs8Base64, sessionTtl, } = input;
|
|
93
|
-
//
|
|
102
|
+
// FIX 1: signing happens inside fetcher — only runs on cache miss.
|
|
94
103
|
const signingKey = await getSigningMaterial(normalizedCert, privateKeyPkcs8Base64);
|
|
95
104
|
const certSerialDec = parseCertSerialDec(normalizedCert);
|
|
96
105
|
const host = `${SERVICE}.${region}.amazonaws.com`;
|
|
@@ -122,6 +131,7 @@ async function fetchCredentials(input) {
|
|
|
122
131
|
"X-Amz-X509": normalizedCert,
|
|
123
132
|
"Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
|
|
124
133
|
});
|
|
134
|
+
// FIX 4: catch network errors as aws_unreachable.
|
|
125
135
|
let res;
|
|
126
136
|
try {
|
|
127
137
|
res = await fetch(`https://${host}${PATH}`, {
|
|
@@ -150,8 +160,7 @@ async function fetchCredentials(input) {
|
|
|
150
160
|
sessionToken: creds.sessionToken,
|
|
151
161
|
expiration: creds.expiration,
|
|
152
162
|
};
|
|
153
|
-
//
|
|
154
|
-
// getCachedOrFetch will further subtract EDGE_BUFFER_SEC (5 min).
|
|
163
|
+
// FIX 3: no unsafe cast — fetcher typed correctly.
|
|
155
164
|
const receivedAt = Date.now();
|
|
156
165
|
const expiresAtMs = Date.parse(creds.expiration);
|
|
157
166
|
const credLifetimeSec = Math.floor((expiresAtMs - receivedAt) / 1000);
|
|
@@ -168,11 +177,6 @@ function normalizeCert(raw) {
|
|
|
168
177
|
throw new InternalError("pem_not_allowed");
|
|
169
178
|
return raw.replace(/\s+/g, "");
|
|
170
179
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Minimal DER walk to extract the certificate serial number as a decimal string.
|
|
173
|
-
* No isolate-level cache — stale cached values caused wrong-serial bugs in production.
|
|
174
|
-
* Throws InternalError("invalid_cert_der") on any parse failure.
|
|
175
|
-
*/
|
|
176
180
|
function parseCertSerialDec(normalizedCertBase64) {
|
|
177
181
|
try {
|
|
178
182
|
const der = base64ToBytes(normalizedCertBase64);
|
|
@@ -197,7 +201,6 @@ function parseCertSerialDec(normalizedCertBase64) {
|
|
|
197
201
|
if (der[offset++] !== 0x30)
|
|
198
202
|
throw new Error("bad tbs");
|
|
199
203
|
readLen();
|
|
200
|
-
// Skip optional [0] EXPLICIT version field.
|
|
201
204
|
if (der[offset] === 0xa0) {
|
|
202
205
|
offset++;
|
|
203
206
|
offset += readLen();
|