feat(ai): authenticate to the Claude API with Workload Identity Federation
정적 API 키 대신 Workload Identity Federation을 쓴다. 에이전트는 AI VM의 Google 서명 ID 토큰(메타데이터 서버, audience https://api.anthropic.com, format=full)을 POST /v1/oauth/token(jwt-bearer)으로 교환해 단기 access token을 받고 Authorization: Bearer로 호출한다. 토큰은 캐시하고 만료 120초 전에 재교환하며, 401이면 한 번 재교환한다. 규칙·조직·서비스 계정·워크스페이스 ID 4개는 비밀이 아니므로 ai.env 환경 변수로 받고, 비어 있으면 기존 키 파일 경로로 동작한다. /selfcheck가 auth 모드와 실제 교환 결과를 보고해 캠페인 없이 설정을 검증할 수 있다. 테스트: 교환 1회 재사용·401 재교환·만료 전 갱신·거부 응답·불완전 ID 무시.
This commit is contained in:
1 parent
53ff243183
commit
684e70ba60
4 files changed
+387
-13
No files matched your search
@@ -99,7 +99,15 @@ func main() {
|
||||
// Claude Messages API for claude-* model ids chosen per campaign; the key may be registered later (empty = diagnoses with
|
||||
// a claude-* model fail with "anthropic api key is empty", recorded in the harness diagnosis event).
|
||||
anth := anthropic.New(env("ANTHROPIC_BASE_URL", ""), keyReader(env("ANTHROPIC_KEY_FILE", "/run/secrets/anthropic-api-key")))
|
||||
// Workload Identity Federation (preferred, no static key): the four ids are plain settings in ai.env; the VM's
|
||||
// Google identity token is exchanged for a short-lived access token (internal/anthropic/federation.go).
|
||||
anth.SetFederation(&anthropic.Federation{
|
||||
RuleID: env("ANTHROPIC_FEDERATION_RULE_ID", ""), OrgID: env("ANTHROPIC_ORGANIZATION_ID", ""),
|
||||
ServiceAccountID: env("ANTHROPIC_SERVICE_ACCOUNT_ID", ""), WorkspaceID: env("ANTHROPIC_WORKSPACE_ID", ""),
|
||||
IdentityTokenURL: env("ANTHROPIC_IDENTITY_TOKEN_URL", ""),
|
||||
})
|
||||
ag.SetAnthropic(anth)
|
||||
log.Printf("anthropic auth: %s", anth.AuthMode())
|
||||
jevMode := env("JEV_MODE", "off") // off | shadow | enforce (shadow records Jev answers next to the diagnosis; enforce also applies the two review rules)
|
||||
var jc *jev.Client
|
||||
if jevMode == "shadow" || jevMode == "enforce" {
|
||||
@@ -136,6 +144,10 @@ func main() {
|
||||
key, kerr := keyReader(env("OPENAI_KEY_FILE", "/run/secrets/openai-api-key"))()
|
||||
out["openai_key_present"] = kerr == nil && key != ""
|
||||
out["anthropic_key_present"] = anth.KeyPresent()
|
||||
out["anthropic_auth"] = anth.AuthMode()
|
||||
if f := anth.Federation(); f != nil {
|
||||
out["anthropic_federation"] = f.Probe(c, anth.BaseURL) // one real token exchange; the Console history page shows the deny reason on failure
|
||||
}
|
||||
out["jev_mode"] = jevMode
|
||||
if jc != nil {
|
||||
out["jev_key_present"] = jc.KeyPresent()
|
||||
|
||||
Reference in new issue
Block a user