Claude via Google Cloud Vertex AI na conta GCP do cliente — onboarding IAM
BYOK 4 · ADR 0002, opção D · plano Enterprise (
FEATURE_LLM_BYOK_CLOUD)
Com esta rota, relatórios, sugestões, sinais, assistente, base de conhecimento e varredura de
engenharia do BeryX correm no Claude do projeto Google Cloud da organização, num
endpoint regional da UE (europe-*). Os dados e a fatura dos tokens ficam na conta do
cliente. Não há chaves JSON em lado nenhum: a BeryxTech impersona uma service account (SA) do
cliente a partir da identidade do seu próprio runtime (Cloud Run) e obtém um access token de
curta duração (15 min, só em memória). A BeryxTech guarda só o email da SA, o projeto, a região, o
modelo e um token de verificação gerado por workspace — nenhum deles é uma credencial.
É o mesmo ciclo da rota Bedrock (docs/BYOK_BEDROCK.md): as duas são casos da abstração
«Claude na conta cloud do cliente» (app/infra/llm/providers/cloud.py +
app/domain/llm/cloud_route.py). Um workspace tem uma rota cloud: Bedrock ou Vertex.
Como funciona
BeryX (Cloud Run da BeryxTech) Projeto GCP do cliente (europe-*)
─────────────────────────────── ──────────────────────────────────────────────
SAs de runtime (Workload Identity) ─ generateAccessToken ─▶ SA datavault-claude@<projeto>
datavault-api@… (health-check) iamcredentials API Token Creator: os 2 principais
datavault-worker@… (golden set, IA) da BeryxTech (LLM_VERTEX_BROKER_PRINCIPAL)
description: «… dv-<token> …»
token impersonado (15 min) ─ GET serviceAccounts/<sa> ──▶ prova de posse (lê a description)
(só em memória) ─ AsyncAnthropicVertex ──────▶ <região>-aiplatform.googleapis.com
publishers/anthropic/models/claude-…
- Sem chave JSON: o token de origem vem do servidor de metadados do Cloud Run (Workload
Identity). Em desenvolvimento local, com
google-authinstalado, usam-se as ADC (gcloud auth application-default login). Nenhuma chave de SA é criada, enviada ou guardada. - Residência: só regiões
europe-*(nunca o endpointglobalnem regiões fora da UE) e ids de modeloclaude-…da allowlist da BeryxTech. A política é verificada no domínio e na infra (providers/vertex.py), por isso uma configuração fora dela nunca chega a pedir um token. - Dois principais, uma identidade por passo: o health-check do passo 2 corre na api
(
datavault-api@…) e o golden set e a IA correm no worker (datavault-worker@…) — cada serviço Cloud Run da BeryxTech tem a sua service account dedicada, com acesso só ao que monta (docs/DEPLOY_GCP.md§14). O cliente concede Token Creator às duas; com uma só, metade do ciclo falha comimpersonation_denied. - Confused deputy: o GCP não tem ExternalId. Qualquer cliente da BeryxTech concede Token
Creator aos mesmos principais da BeryxTech — sem mais nada, um tenant podia apontar para a SA
de outro. Por isso a SA tem de trazer na
descriptiono token de verificação do workspace (dv-…), e o health-check lê-a com o token impersonado: só passa quem controla aquela SA. - Falha: se a SA deixar de funcionar (Token Creator removido, modelo desativado, quota), a IA
degrada para os modos offline (heurísticas e templates) e o evento fica como
llm.byok_error. Nunca caímos para a conta da BeryxTech contra a escolha do cliente. - Precedência: uma rota Vertex ativa substitui a chave Anthropic própria (BYOK 1), se existir.
- Model policy (BYOK 2): «IA desligada» e as restrições de modelos por tarefa continuam a aplicar-se. A policy pode recusar o modelo Vertex numa tarefa, mas nunca o troca por outro.
- Sem Message Batches: os pedidos que na rota da BeryxTech iriam em batch correm síncronos.
Passo a passo
Em Equipa & Acessos → Claude via Google Cloud Vertex AI (só administradores):
- Gerar o token de verificação. A UI mostra o token do workspace (
dv-…), os principais da BeryxTech (as duas service accounts de runtime do Cloud Run:datavault-api@…, que valida a ligação no passo 2, edatavault-worker@…, que corre o golden set e a IA) e os comandosgcloudabaixo já preenchidos, com um binding Token Creator por principal. - Preparar a SA no projeto (gcloud ou Terraform abaixo), ativar o modelo Claude no
Model Garden do projeto e colar na UI o email da SA, o ID do projeto, a região e o modelo.
Ao guardar, fazemos um health-check: impersonação + prova de posse + um pedido de 1 token
ao modelo (custo ~10 tokens, na conta do cliente). Se falhar, nada é guardado e a mensagem diz
o que corrigir (Token Creator, token na descrição,
aiplatform.user, modelo, região). - Correr o golden set. O golden set das sugestões (13 casos, GOLDEN v1) corre no modelo
Vertex escolhido, no worker (fila
llm.golden.requested). A rota só fica ativa se passar (limiarDATAVAULT_LLM_CLOUD_GOLDEN_THRESHOLD, por omissão 100%). Reprovado, fica emgolden_failed, com os casos que falharam, e a IA continua como estava. Os tokens do golden set são faturados na conta GCP e aparecem no FinOps (kind=golden,provider=vertex). ComDATAVAULT_LLM_CLOUD_GOLDEN_JUDGE_MIN_SCORE> 0, o gate junta o juiz LLM do GOLDEN v2, como no Bedrock (docs/BYOK_BEDROCK.md§3).
Mudar a SA, o projeto, a região ou o modelo invalida a aprovação: o golden set tem de passar outra vez nesse modelo. Remover a rota é sempre possível, mesmo depois de um downgrade.
gcloud (projeto do cliente)
PROJETO=banco-ia-prod # o vosso projeto
SA=datavault-claude@${PROJETO}.iam.gserviceaccount.com
TOKEN=dv-… # passo 1 da UI
PRAXIS_API=datavault-api@praxis-….iam.gserviceaccount.com # principais da BeryxTech (passo 1 da UI):
PRAXIS_WORKER=datavault-worker@praxis-….iam.gserviceaccount.com # api = health-check, worker = golden set e IA
gcloud services enable aiplatform.googleapis.com --project="$PROJETO"
# 1. A SA que o BeryX vai usar, com o token de verificação na descrição.
gcloud iam service-accounts create datavault-claude --project="$PROJETO" \
--display-name="BeryX (BeryxTech) — Claude" \
--description="BeryX · BeryxTech — ${TOKEN}"
# (SA já existente: gcloud iam service-accounts update "$SA" --description="… ${TOKEN}")
# 2. Pode invocar modelos no Vertex AI deste projeto.
gcloud projects add-iam-policy-binding "$PROJETO" \
--member="serviceAccount:${SA}" --role=roles/aiplatform.user
# 3. Pode ler-se a si própria (prova de posse: o BeryX lê a descrição com o token dela).
gcloud iam service-accounts add-iam-policy-binding "$SA" --project="$PROJETO" \
--member="serviceAccount:${SA}" --role=roles/iam.serviceAccountViewer
# 4. A BeryxTech pode impersoná-la — e só a ela (binding na SA, não no projeto) — a
# partir das duas SAs de runtime: um binding por principal.
for PRAXIS in "$PRAXIS_API" "$PRAXIS_WORKER"; do
gcloud iam service-accounts add-iam-policy-binding "$SA" --project="$PROJETO" \
--member="serviceAccount:${PRAXIS}" --role=roles/iam.serviceAccountTokenCreator
done
Depois, no Model Garden do projeto, ativar o(s) modelo(s) Claude escolhido(s) (aceitar os
termos da Anthropic no Marketplace). Para restringir a SA a Claude, pode trocar-se
roles/aiplatform.user por um role personalizado com aiplatform.endpoints.predict.
Terraform (projeto do cliente)
variable "project_id" { type = string }
variable "datavault_principals" {
description = "Principais da BeryxTech (SAs de runtime do Cloud Run: api e worker, mostrados na UI, passo 1)"
type = list(string)
}
variable "datavault_verification_token" {
description = "Token de verificação do workspace (UI, passo 1)"
type = string
}
resource "google_project_service" "aiplatform" {
project = var.project_id
service = "aiplatform.googleapis.com"
}
resource "google_service_account" "datavault" {
project = var.project_id
account_id = "datavault-claude"
display_name = "BeryX (BeryxTech) — Claude"
description = "BeryX · BeryxTech — ${var.datavault_verification_token}"
}
resource "google_project_iam_member" "datavault_vertex" {
project = var.project_id
role = "roles/aiplatform.user"
member = "serviceAccount:${google_service_account.datavault.email}"
}
resource "google_service_account_iam_member" "datavault_self_viewer" {
service_account_id = google_service_account.datavault.name
role = "roles/iam.serviceAccountViewer"
member = "serviceAccount:${google_service_account.datavault.email}"
}
resource "google_service_account_iam_member" "praxis_token_creator" {
for_each = toset(var.datavault_principals)
service_account_id = google_service_account.datavault.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${each.value}"
}
output "datavault_service_account" {
value = google_service_account.datavault.email
}
Do lado da BeryxTech (operação)
LLM_VERTEX_BROKER_PRINCIPAL= os emails das duas SAs de runtime do Cloud Run do BeryX, separados por vírgula:datavault-api@<projeto>.iam.gserviceaccount.com(o health-check do passo 2 corre na api) edatavault-worker@<projeto>.iam.gserviceaccount.com(o golden set e a IA correm no worker). Odeploy/gcp_deploy.shdefine-a nos dois serviços. É só informativo (mostrado no onboarding, que gera um binding por principal); a identidade real é a da revisão que chama. Cada serviço corre com a sua SA dedicada, semroles/editore só com os segredos que monta (docs/DEPLOY_GCP.md§14, cartão #279).- A API IAM Service Account Credentials (
iamcredentials.googleapis.com) tem de estar ativa no projeto da BeryxTech (deploy/gcp_deploy.shativa-a). LLM_VERTEX_MODELS(JSON{família: id}) substitui a allowlist por omissão sem deploy — os ids do Vertex mudam com as versões (claude-…@AAAAMMDD). Entradas fora do formato saem.- Não é preciso nenhum extra Python: o token de origem vem do servidor de metadados por
httpxe oAsyncAnthropicVertexrecebe umaccess_tokenjá pronto (semgoogle-auth). - Auditoria:
llm_byok_vertex_prepared|configured|golden_requested|activated|golden_failed|removed(categoria sensívelbyok), com o projeto GCP e nunca o token de verificação.
Migração — tenants configurados antes das SAs dedicadas (setembro de 2026)
Até ao cartão #279 o principal da BeryxTech era a compute SA por omissão do projeto
(<nº>-compute@developer.gserviceaccount.com). Um workspace Vertex configurado antes disso tem
Token Creator concedido a essa SA — e a essa SA só. Depois do cutover (docs/DEPLOY_GCP.md
§14.2), a api e o worker chamam com datavault-api@… e datavault-worker@…: sem os bindings
novos, o health-check e o golden set falham com impersonation_denied e a IA degrada para os
modos offline (nunca para a conta da BeryxTech).
O que o cliente faz, antes de a BeryxTech retirar o Editor à compute SA:
for PRAXIS in "$PRAXIS_API" "$PRAXIS_WORKER"; do # os dois emails do passo 1 da UI
gcloud iam service-accounts add-iam-policy-binding "$SA" --project="$PROJETO" \
--member="serviceAccount:${PRAXIS}" --role=roles/iam.serviceAccountTokenCreator
done
# depois de a BeryxTech confirmar o cutover: retirar o binding antigo
gcloud iam service-accounts remove-iam-policy-binding "$SA" --project="$PROJETO" \
--member="serviceAccount:<nº>-compute@developer.gserviceaccount.com" --role=roles/iam.serviceAccountTokenCreator
Em Terraform: trocar datavault_principal (string) por datavault_principals (lista com os dois
emails) — o for_each acima cria um binding por principal. Confirmar na UI com «Validar ligação
e guardar» (passo 2) e, se a rota estava ativa, «Correr golden set» de novo não é preciso: a
aprovação é por modelo, não por principal.