feat(opencode): add AWS Web Identity Token File support for Bedrock (#8461)
This commit is contained in:
@@ -338,9 +338,9 @@ export const AuthLoginCommand = cmd({
|
|||||||
prompts.log.info(
|
prompts.log.info(
|
||||||
"Amazon Bedrock authentication priority:\n" +
|
"Amazon Bedrock authentication priority:\n" +
|
||||||
" 1. Bearer token (AWS_BEARER_TOKEN_BEDROCK or /connect)\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" +
|
"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).",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,16 +197,23 @@ export namespace Provider {
|
|||||||
return undefined
|
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"))
|
if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile) return { autoload: false }
|
||||||
|
|
||||||
// Build credential provider options (only pass profile if specified)
|
|
||||||
const credentialProviderOptions = profile ? { profile } : {}
|
|
||||||
|
|
||||||
const providerOptions: AmazonBedrockProviderSettings = {
|
const providerOptions: AmazonBedrockProviderSettings = {
|
||||||
region: defaultRegion,
|
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)
|
// Add custom endpoint if specified (endpoint takes precedence over baseURL)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { test, expect, mock } from "bun:test"
|
import { test, expect, mock } from "bun:test"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { unlink } from "fs/promises"
|
||||||
|
|
||||||
// === Mocks ===
|
// === Mocks ===
|
||||||
// These mocks are required because Provider.list() triggers:
|
// 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")
|
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({
|
// Save original auth.json if it exists
|
||||||
directory: tmp.path,
|
let originalAuth: string | undefined
|
||||||
init: async () => {
|
try {
|
||||||
Env.set("AWS_PROFILE", "")
|
originalAuth = await Bun.file(authPath).text()
|
||||||
Env.set("AWS_ACCESS_KEY_ID", "")
|
} catch {
|
||||||
Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
|
// File doesn't exist, that's fine
|
||||||
},
|
}
|
||||||
fn: async () => {
|
|
||||||
const providers = await Provider.list()
|
try {
|
||||||
expect(providers["amazon-bedrock"]).toBeDefined()
|
// Write test auth.json
|
||||||
expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
|
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 () => {
|
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")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -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_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_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_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
|
#### Authentication Precedence
|
||||||
|
|
||||||
Amazon Bedrock uses the following authentication priority:
|
Amazon Bedrock uses the following authentication priority:
|
||||||
1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` environment variable or token from `/connect` command
|
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
|
:::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.
|
When a bearer token is set (via `/connect` or `AWS_BEARER_TOKEN_BEDROCK`), it takes precedence over all AWS credential methods including configured profiles.
|
||||||
|
|||||||
Reference in New Issue
Block a user