mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-22 06:42:19 +03:00
fix(kiro): probe the IdC region too during profileArn discovery (any IdC region)
Make profile discovery general for an IdC in ANY of the ~30 IdC-supported AWS regions (us-west-2, ap-southeast-2, me-central-1, af-south-1, ...), not just eu-north-1. buildKiroProfileDiscoveryRegions now probes the two documented Q Developer profile regions FIRST (us-east-1 / eu-central-1, EU-first for EMEA IdC regions to cut latency), then appends the IdC/stored region itself as a forward-compatible fallback: if AWS ever co-locates the profile with the IdC or expands the profile-region list, a same-region probe still finds it. Probing a region with no profile simply returns nothing and we fall through. The profileArn's own region remains authoritative for every runtime call (resolveKiroRuntimeRegion), so a newly-issued ARN in any region is honored automatically. Adds ap-southeast-2 (APAC) cross-region coverage and updates the discovery-order tests.
This commit is contained in:
committed by
Diego Rodrigues de Sa e Souza
parent
f2370991ec
commit
f8c2f23ae6
@@ -1,28 +1,31 @@
|
||||
/**
|
||||
* Shared Amazon Q Developer (Kiro / AWS CodeWhisperer) region resolution.
|
||||
*
|
||||
* CRITICAL AWS constraint — verified against the AWS docs
|
||||
* "Amazon Q Developer Pro Region support" → "Supported Regions for the Q Developer console and
|
||||
* Q Developer profile":
|
||||
* TWO DISTINCT REGIONS — verified against the AWS docs "Amazon Q Developer Pro Region support"
|
||||
* ("Supported Regions for the Q Developer console and Q Developer profile"):
|
||||
*
|
||||
* • An IAM Identity Center instance (and therefore the SSO OIDC token endpoint
|
||||
* `oidc.{region}.amazonaws.com`) may live in MANY regions, e.g. eu-north-1 (Stockholm),
|
||||
* us-west-1, ap-southeast-2, ...
|
||||
* • The Amazon Q Developer PROFILE — which produces the `profileArn` and hosts EVERY
|
||||
* CodeWhisperer runtime call (generateAssistantResponse, GetUsageLimits, ListAvailableModels,
|
||||
* ListAvailableProfiles) — is only hosted in **us-east-1** and **eu-central-1**.
|
||||
* • "Regardless of the IAM Identity Center Region, data is stored in the Region where you create
|
||||
* the Amazon Q Developer profile." → the IdC region and the runtime region are frequently
|
||||
* DIFFERENT (e.g. IdC in eu-north-1 → profile in eu-central-1).
|
||||
* • IdC / OIDC / token region — `providerSpecificData.region`. May be ANY of the ~30 IdC-
|
||||
* supported AWS regions (us-east-1, us-west-2, ca-central-1, sa-east-1, eu-west-1/2/3,
|
||||
* eu-central-1/2, eu-north-1, eu-south-1/2, ap-south-1/2, ap-east-1/2, ap-southeast-1..7,
|
||||
* ap-northeast-1/2/3, me-central-1, me-south-1, af-south-1, il-central-1, …). Used ONLY for
|
||||
* `oidc.{region}.amazonaws.com` token mint/refresh (see tokenRefresh.ts / oauth providers).
|
||||
* • Q Developer PROFILE / RUNTIME region — where the `profileArn` lives and every CodeWhisperer
|
||||
* runtime call is served (generateAssistantResponse, GetUsageLimits, ListAvailableModels,
|
||||
* ListAvailableProfiles). AWS currently hosts the profile ONLY in us-east-1 and eu-central-1,
|
||||
* REGARDLESS of the IdC region ("Regardless of the IAM Identity Center Region, data is stored
|
||||
* in the Region where you create the Amazon Q Developer profile"). The AWS docs' own example:
|
||||
* an IdC in us-west-1 → profile in us-east-1.
|
||||
*
|
||||
* Consequences enforced here:
|
||||
* • `providerSpecificData.region` is the IdC/OIDC/token region. It must ONLY be used for
|
||||
* `oidc.{region}.amazonaws.com` token mint/refresh (see tokenRefresh.ts / oauth providers).
|
||||
* • The RUNTIME region is the region embedded in the `profileArn` (us-east-1 / eu-central-1),
|
||||
* NOT the IdC region. Routing a runtime call to `q.eu-north-1.amazonaws.com` — a host that
|
||||
* does not exist as a Q Developer runtime endpoint — is the root cause of the
|
||||
* "Kiro IAM shows no limits + every request returns 502" failure for enterprise IdC accounts
|
||||
* whose IdC lives outside us-east-1 / eu-central-1.
|
||||
* • The RUNTIME region is the region embedded in the `profileArn` (authoritative — whatever
|
||||
* region AWS actually hosts the profile in), NOT the IdC region. Routing a runtime call to
|
||||
* `q.{idcRegion}.amazonaws.com` for a non-profile IdC region (e.g. q.eu-north-1, which does
|
||||
* not exist as a Q Developer runtime endpoint) is the root cause of the "Kiro IAM shows no
|
||||
* limits + every request returns 502" failure.
|
||||
* • profileArn discovery works for an IdC in ANY region: it probes the known profile regions
|
||||
* (us-east-1 / eu-central-1) with the cross-region SSO token, AND the IdC's own region as a
|
||||
* forward-compatible fallback (in case AWS ever co-locates or expands profile regions). The
|
||||
* discovered ARN's region then drives every runtime call.
|
||||
*/
|
||||
|
||||
// Canonical AWS region shape — kept local (identical to AWS_REGION_PATTERN in
|
||||
@@ -32,9 +35,11 @@
|
||||
export const AWS_REGION_PATTERN = /^[a-z]{2}-[a-z]+-\d{1,2}$/;
|
||||
|
||||
/**
|
||||
* Regions where the Amazon Q Developer *profile* (identity-aware / IdC Pro tier) can exist, and
|
||||
* therefore the only regions that host a Q Developer runtime endpoint for profile-bound calls.
|
||||
* Order is the default probe order (us-east-1 is CodeWhisperer's home region).
|
||||
* Regions where the Amazon Q Developer *profile* is currently hosted (AWS docs: "Supported
|
||||
* Regions for the Q Developer console and Q Developer profile"). These are the guaranteed
|
||||
* discovery targets and the only regions trusted as a runtime fallback when no profileArn is
|
||||
* known. The profileArn's own region is always honored above this list, so a future AWS
|
||||
* profile-region expansion works automatically once an ARN is discovered.
|
||||
*/
|
||||
export const KIRO_PROFILE_REGIONS = ["us-east-1", "eu-central-1"] as const;
|
||||
|
||||
@@ -88,25 +93,27 @@ export function resolveKiroRuntimeRegion(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ordered list of Q Developer profile regions to probe for `ListAvailableProfiles`.
|
||||
* The IdC/token region is only useful here as a hint for geographic proximity — the actual
|
||||
* profile always lives in one of KIRO_PROFILE_REGIONS, so those are the only regions probed.
|
||||
* Build the ordered list of regions to probe for `ListAvailableProfiles`.
|
||||
*
|
||||
* The Amazon Q Developer profile (and thus every runtime endpoint) is currently hosted only in
|
||||
* KIRO_PROFILE_REGIONS (us-east-1 / eu-central-1) regardless of the IdC region, so those are
|
||||
* probed FIRST — EU-first when the IdC region is in EMEA (eu-, af-, me-, il- prefixes) to
|
||||
* minimize latency. The IdC/stored region is then appended as a forward-compatible fallback: if
|
||||
* AWS ever co-locates the profile with the IdC, or expands the profile-region list, a same-region
|
||||
* probe still finds it. It is only appended when it is a valid AWS region distinct from the known
|
||||
* profile regions; probing a region with no profile simply returns nothing and we fall through.
|
||||
* This makes discovery work for an IdC in ANY region (us-west-2, ap-southeast-2, me-central-1,
|
||||
* af-south-1, …), not just eu-north-1.
|
||||
*/
|
||||
export function buildKiroProfileDiscoveryRegions(storedRegion?: string | null): string[] {
|
||||
const regions: string[] = [];
|
||||
const stored = normalizeRegion(storedRegion);
|
||||
|
||||
// If the IdC/token region happens to be a Q profile region itself, probe it first.
|
||||
if (stored && (KIRO_PROFILE_REGIONS as readonly string[]).includes(stored)) {
|
||||
regions.push(stored);
|
||||
}
|
||||
|
||||
// Otherwise order the two known profile regions by rough geographic proximity to the IdC
|
||||
// region so an EU IdC (e.g. eu-north-1) hits eu-central-1 first.
|
||||
const preferEu = /^(eu|af|me|il)-/.test(stored);
|
||||
const ordered = preferEu ? ["eu-central-1", "us-east-1"] : ["us-east-1", "eu-central-1"];
|
||||
for (const r of ordered) {
|
||||
if (!regions.includes(r)) regions.push(r);
|
||||
const regions: string[] = preferEu
|
||||
? ["eu-central-1", "us-east-1"]
|
||||
: ["us-east-1", "eu-central-1"];
|
||||
|
||||
if (stored && AWS_REGION_PATTERN.test(stored) && !regions.includes(stored)) {
|
||||
regions.push(stored);
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
@@ -151,11 +158,12 @@ async function listKiroProfileArnForRegion(
|
||||
|
||||
/**
|
||||
* Discover a Kiro/CodeWhisperer profile ARN by probing the Q Developer profile regions
|
||||
* (us-east-1 / eu-central-1) with the account's access token. The SSO bearer token minted from
|
||||
* the IdC region works cross-region against the Q Developer profile's region (AWS's documented
|
||||
* multi-region IdC ⇄ profile setup). Returns the first ARN found (its embedded region is the
|
||||
* authoritative runtime region), or undefined when no profile is available (e.g. AWS Builder ID
|
||||
* accounts, or an org/token with no Kiro entitlement). Best-effort: never throws.
|
||||
* (us-east-1 / eu-central-1) AND the IdC/stored region with the account's access token. The SSO
|
||||
* bearer token minted from the IdC region works cross-region against the Q Developer profile's
|
||||
* region (AWS's documented multi-region IdC ⇄ profile setup), so an IdC in ANY region resolves.
|
||||
* Returns the first ARN found (its embedded region is the authoritative runtime region), or
|
||||
* undefined when no profile is available (e.g. AWS Builder ID accounts, or an org/token with no
|
||||
* Kiro entitlement). Best-effort: never throws.
|
||||
*/
|
||||
export async function discoverKiroProfileArnAcrossRegions(
|
||||
accessToken: string | null | undefined,
|
||||
|
||||
@@ -79,14 +79,32 @@ test("the executor's resolveKiroRegion routes an eu-north-1 IdC account to the p
|
||||
assert.equal(kiroRuntimeHost("eu-central-1"), "https://q.eu-central-1.amazonaws.com");
|
||||
});
|
||||
|
||||
test("buildKiroProfileDiscoveryRegions: EU IdC probes eu-central-1 first, never eu-north-1", () => {
|
||||
test("buildKiroProfileDiscoveryRegions: EU IdC probes the profile regions first, then the IdC region", () => {
|
||||
const regions = buildKiroProfileDiscoveryRegions("eu-north-1");
|
||||
assert.deepEqual(regions, ["eu-central-1", "us-east-1"]);
|
||||
assert.ok(!regions.includes("eu-north-1"), "must not probe the nonexistent q.eu-north-1 host");
|
||||
assert.deepEqual(regions, ["eu-central-1", "us-east-1", "eu-north-1"]);
|
||||
// The profile regions (fast path) are tried BEFORE the IdC-region fallback.
|
||||
assert.ok(regions.indexOf("eu-central-1") < regions.indexOf("eu-north-1"));
|
||||
assert.ok(regions.indexOf("us-east-1") < regions.indexOf("eu-north-1"));
|
||||
// Another EMEA IdC region → still EU-first, IdC region appended as fallback.
|
||||
assert.deepEqual(buildKiroProfileDiscoveryRegions("me-central-1"), [
|
||||
"eu-central-1",
|
||||
"us-east-1",
|
||||
"me-central-1",
|
||||
]);
|
||||
});
|
||||
|
||||
test("buildKiroProfileDiscoveryRegions: non-EU IdC probes us-east-1 first", () => {
|
||||
assert.deepEqual(buildKiroProfileDiscoveryRegions("us-west-2"), ["us-east-1", "eu-central-1"]);
|
||||
test("buildKiroProfileDiscoveryRegions: non-EU IdC probes us-east-1 first, then the IdC region", () => {
|
||||
assert.deepEqual(buildKiroProfileDiscoveryRegions("us-west-2"), [
|
||||
"us-east-1",
|
||||
"eu-central-1",
|
||||
"us-west-2",
|
||||
]);
|
||||
assert.deepEqual(buildKiroProfileDiscoveryRegions("ap-southeast-2"), [
|
||||
"us-east-1",
|
||||
"eu-central-1",
|
||||
"ap-southeast-2",
|
||||
]);
|
||||
// No stored region → just the two profile regions.
|
||||
assert.deepEqual(buildKiroProfileDiscoveryRegions(undefined), ["us-east-1", "eu-central-1"]);
|
||||
});
|
||||
|
||||
@@ -121,6 +139,30 @@ test("discoverKiroProfileArnAcrossRegions: eu-north-1 IdC finds the eu-central-1
|
||||
);
|
||||
});
|
||||
|
||||
test("discoverKiroProfileArnAcrossRegions: a non-EU (ap-southeast-2) IdC resolves a us-east-1 profile", async () => {
|
||||
// Proves the fix is general, not eu-north-1-specific: an APAC IdC's profile lives in a Q
|
||||
// profile region (us-east-1 here) and is found via the cross-region SSO token.
|
||||
const US_EAST_ARN = "arn:aws:codewhisperer:us-east-1:111111111111:profile/APAC";
|
||||
const requested: string[] = [];
|
||||
const fetchImpl = (async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
requested.push(url);
|
||||
if (url.includes("us-east-1")) {
|
||||
return new Response(JSON.stringify({ profiles: [{ arn: US_EAST_ARN }] }), { status: 200 });
|
||||
}
|
||||
return new Response(JSON.stringify({ profiles: [] }), { status: 200 });
|
||||
}) as unknown as typeof fetch;
|
||||
|
||||
const arn = await discoverKiroProfileArnAcrossRegions("sso-token", "ap-southeast-2", fetchImpl);
|
||||
assert.equal(arn, US_EAST_ARN);
|
||||
assert.equal(
|
||||
resolveKiroRuntimeRegion({ region: "ap-southeast-2", profileArn: arn }),
|
||||
"us-east-1"
|
||||
);
|
||||
// The us-east-1 profile region is probed before the ap-southeast-2 IdC-region fallback.
|
||||
assert.ok(requested.some((u) => u.startsWith("https://codewhisperer.us-east-1.amazonaws.com/")));
|
||||
});
|
||||
|
||||
test("discoverKiroProfileArnAcrossRegions: no token / no profile yields undefined without throwing", async () => {
|
||||
assert.equal(await discoverKiroProfileArnAcrossRegions("", "eu-north-1"), undefined);
|
||||
const emptyFetch = (async () =>
|
||||
|
||||
Reference in New Issue
Block a user