@vizamodo/aws-sts-core 0.4.10 → 0.4.11
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 +24 -27
- 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,38 +18,33 @@ 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
|
-
//
|
|
51
|
-
// Cache vars are updated inside .then() so concurrent callers awaiting
|
|
52
|
-
// the same promise all get the correct key and hit the fast path next call.
|
|
47
|
+
// FIX 2: cache vars updated inside .then() — no race condition.
|
|
53
48
|
signingKeyPromise = null;
|
|
54
49
|
cachedSigningKey = null;
|
|
55
50
|
cachedCertBase64 = null;
|
|
@@ -63,7 +58,7 @@ async function getSigningMaterial(certBase64, privateKeyPkcs8Base64) {
|
|
|
63
58
|
return key;
|
|
64
59
|
})
|
|
65
60
|
.catch(() => {
|
|
66
|
-
signingKeyPromise = null;
|
|
61
|
+
signingKeyPromise = null;
|
|
67
62
|
throw new InternalError("invalid_signing_material");
|
|
68
63
|
});
|
|
69
64
|
return signingKeyPromise;
|
|
@@ -78,9 +73,17 @@ export async function issueAwsCredentials(input) {
|
|
|
78
73
|
const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, forceRefresh, } = input;
|
|
79
74
|
const sessionTtl = resolveSessionTtl(profile);
|
|
80
75
|
const normalizedCert = normalizeCert(certBase64);
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
76
|
+
// BUG 5 KEPT: isolate-level cert serial cache — can serve stale value
|
|
77
|
+
// from a previous (buggy) code version in the same long-lived isolate.
|
|
78
|
+
let certSerialDec;
|
|
79
|
+
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
80
|
+
certSerialDec = cachedCertSerialDec;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
certSerialDec = parseCertSerialDec(normalizedCert);
|
|
84
|
+
cachedCertSerialDec = certSerialDec;
|
|
85
|
+
cachedCertSerialSource = normalizedCert;
|
|
86
|
+
}
|
|
84
87
|
const cacheKey = `${region}|${roleArn}|${profileArn}|${trustAnchorArn}|${certSerialDec}`;
|
|
85
88
|
return getCachedOrFetch(cacheKey, () => fetchCredentials({
|
|
86
89
|
roleArn, profileArn, trustAnchorArn,
|
|
@@ -90,7 +93,7 @@ export async function issueAwsCredentials(input) {
|
|
|
90
93
|
}
|
|
91
94
|
async function fetchCredentials(input) {
|
|
92
95
|
const { roleArn, profileArn, trustAnchorArn, region, normalizedCert, privateKeyPkcs8Base64, sessionTtl, } = input;
|
|
93
|
-
//
|
|
96
|
+
// FIX 1: signing happens inside fetcher — only runs on cache miss.
|
|
94
97
|
const signingKey = await getSigningMaterial(normalizedCert, privateKeyPkcs8Base64);
|
|
95
98
|
const certSerialDec = parseCertSerialDec(normalizedCert);
|
|
96
99
|
const host = `${SERVICE}.${region}.amazonaws.com`;
|
|
@@ -122,6 +125,7 @@ async function fetchCredentials(input) {
|
|
|
122
125
|
"X-Amz-X509": normalizedCert,
|
|
123
126
|
"Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
|
|
124
127
|
});
|
|
128
|
+
// FIX 4: catch network errors as aws_unreachable.
|
|
125
129
|
let res;
|
|
126
130
|
try {
|
|
127
131
|
res = await fetch(`https://${host}${PATH}`, {
|
|
@@ -150,8 +154,7 @@ async function fetchCredentials(input) {
|
|
|
150
154
|
sessionToken: creds.sessionToken,
|
|
151
155
|
expiration: creds.expiration,
|
|
152
156
|
};
|
|
153
|
-
//
|
|
154
|
-
// getCachedOrFetch will further subtract EDGE_BUFFER_SEC (5 min).
|
|
157
|
+
// FIX 3: no unsafe cast — fetcher typed correctly.
|
|
155
158
|
const receivedAt = Date.now();
|
|
156
159
|
const expiresAtMs = Date.parse(creds.expiration);
|
|
157
160
|
const credLifetimeSec = Math.floor((expiresAtMs - receivedAt) / 1000);
|
|
@@ -168,11 +171,6 @@ function normalizeCert(raw) {
|
|
|
168
171
|
throw new InternalError("pem_not_allowed");
|
|
169
172
|
return raw.replace(/\s+/g, "");
|
|
170
173
|
}
|
|
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
174
|
function parseCertSerialDec(normalizedCertBase64) {
|
|
177
175
|
try {
|
|
178
176
|
const der = base64ToBytes(normalizedCertBase64);
|
|
@@ -197,7 +195,6 @@ function parseCertSerialDec(normalizedCertBase64) {
|
|
|
197
195
|
if (der[offset++] !== 0x30)
|
|
198
196
|
throw new Error("bad tbs");
|
|
199
197
|
readLen();
|
|
200
|
-
// Skip optional [0] EXPLICIT version field.
|
|
201
198
|
if (der[offset] === 0xa0) {
|
|
202
199
|
offset++;
|
|
203
200
|
offset += readLen();
|