Bläddra i källkod

feat(opencode): add AWS Web Identity Token File support for Bedrock (#8461)

Jacopo Binosi 2 månader sedan
förälder
incheckning
3a9e6b558c

+ 2 - 2
packages/opencode/src/cli/cmd/auth.ts

@@ -338,9 +338,9 @@ export const AuthLoginCommand = cmd({
           prompts.log.info(
             "Amazon Bedrock authentication priority:\n" +
               "  1. Bearer token (AWS_BEARER_TOKEN_BEDROCK or /connect)\n" +
-              "  2. AWS credential chain (profile, access keys, IAM roles)\n\n" +
+              "  2. AWS credential chain (profile, access keys, IAM roles, EKS IRSA)\n\n" +
               "Configure via opencode.json options (profile, region, endpoint) or\n" +
-              "AWS environment variables (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID).",
+              "AWS environment variables (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_WEB_IDENTITY_TOKEN_FILE).",
           )
         }
 

+ 13 - 6
packages/opencode/src/provider/provider.ts

@@ -197,16 +197,23 @@ export namespace Provider {
         return undefined
       })
 
-      if (!profile && !awsAccessKeyId && !awsBearerToken) return { autoload: false }
+      const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE")
 
-      const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
-
-      // Build credential provider options (only pass profile if specified)
-      const credentialProviderOptions = profile ? { profile } : {}
+      if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile) return { autoload: false }
 
       const providerOptions: AmazonBedrockProviderSettings = {
         region: defaultRegion,
-        credentialProvider: fromNodeProviderChain(credentialProviderOptions),
+      }
+
+      // Only use credential chain if no bearer token exists
+      // Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens)
+      if (!awsBearerToken) {
+        const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
+
+        // Build credential provider options (only pass profile if specified)
+        const credentialProviderOptions = profile ? { profile } : {}
+
+        providerOptions.credentialProvider = fromNodeProviderChain(credentialProviderOptions)
       }
 
       // Add custom endpoint if specified (endpoint takes precedence over baseURL)

+ 80 - 22
packages/opencode/test/provider/amazon-bedrock.test.ts

@@ -1,5 +1,6 @@
 import { test, expect, mock } from "bun:test"
 import path from "path"
+import { unlink } from "fs/promises"
 
 // === Mocks ===
 // These mocks are required because Provider.list() triggers:
@@ -118,29 +119,52 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
   })
 
   const authPath = path.join(Global.Path.data, "auth.json")
-  await Bun.write(
-    authPath,
-    JSON.stringify({
-      "amazon-bedrock": {
-        type: "api",
-        key: "test-bearer-token",
-      },
-    }),
-  )
 
-  await Instance.provide({
-    directory: tmp.path,
-    init: async () => {
-      Env.set("AWS_PROFILE", "")
-      Env.set("AWS_ACCESS_KEY_ID", "")
-      Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
-    },
-    fn: async () => {
-      const providers = await Provider.list()
-      expect(providers["amazon-bedrock"]).toBeDefined()
-      expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
-    },
-  })
+  // Save original auth.json if it exists
+  let originalAuth: string | undefined
+  try {
+    originalAuth = await Bun.file(authPath).text()
+  } catch {
+    // File doesn't exist, that's fine
+  }
+
+  try {
+    // Write test auth.json
+    await Bun.write(
+      authPath,
+      JSON.stringify({
+        "amazon-bedrock": {
+          type: "api",
+          key: "test-bearer-token",
+        },
+      }),
+    )
+
+    await Instance.provide({
+      directory: tmp.path,
+      init: async () => {
+        Env.set("AWS_PROFILE", "")
+        Env.set("AWS_ACCESS_KEY_ID", "")
+        Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
+      },
+      fn: async () => {
+        const providers = await Provider.list()
+        expect(providers["amazon-bedrock"]).toBeDefined()
+        expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
+      },
+    })
+  } finally {
+    // Restore original or delete
+    if (originalAuth !== undefined) {
+      await Bun.write(authPath, originalAuth)
+    } else {
+      try {
+        await unlink(authPath)
+      } catch {
+        // Ignore errors if file doesn't exist
+      }
+    }
+  }
 })
 
 test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async () => {
@@ -208,3 +232,37 @@ test("Bedrock: includes custom endpoint in options when specified", async () =>
     },
   })
 })
+
+test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+          provider: {
+            "amazon-bedrock": {
+              options: {
+                region: "us-east-1",
+              },
+            },
+          },
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    init: async () => {
+      Env.set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token")
+      Env.set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role")
+      Env.set("AWS_PROFILE", "")
+      Env.set("AWS_ACCESS_KEY_ID", "")
+    },
+    fn: async () => {
+      const providers = await Provider.list()
+      expect(providers["amazon-bedrock"]).toBeDefined()
+      expect(providers["amazon-bedrock"].options?.region).toBe("us-east-1")
+    },
+  })
+})

+ 2 - 1
packages/web/src/content/docs/providers.mdx

@@ -211,12 +211,13 @@ To use Amazon Bedrock with OpenCode:
    - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: Create an IAM user and generate access keys in the AWS Console
    - **`AWS_PROFILE`**: Use named profiles from `~/.aws/credentials`. First configure with `aws configure --profile my-profile` or `aws sso login`
    - **`AWS_BEARER_TOKEN_BEDROCK`**: Generate long-term API keys from the Amazon Bedrock console
+   - **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: For EKS IRSA (IAM Roles for Service Accounts) or other Kubernetes environments with OIDC federation. These environment variables are automatically injected by Kubernetes when using service account annotations.
 
    #### Authentication Precedence
 
    Amazon Bedrock uses the following authentication priority:
    1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` environment variable or token from `/connect` command
-   2. **AWS Credential Chain** - Profile, access keys, shared credentials, IAM roles, instance metadata
+   2. **AWS Credential Chain** - Profile, access keys, shared credentials, IAM roles, Web Identity Tokens (EKS IRSA), instance metadata
 
    :::note
    When a bearer token is set (via `/connect` or `AWS_BEARER_TOKEN_BEDROCK`), it takes precedence over all AWS credential methods including configured profiles.