Como proteger agentes de IA com Amazon Bedrock AgentCore Identity no Amazon ECS

Agentes de IA em produção precisam de identidade segura

Colocar agentes de IA em produção vai muito além de fazer o modelo responder corretamente. Um dos desafios mais críticos é garantir que o agente acesse serviços externos de forma segura e com o escopo certo. A AWS endereça exatamente esse problema com o Amazon Bedrock AgentCore Identity, um serviço disponível de forma independente que gerencia como agentes de IA acessam recursos externos — seja rodando no Amazon ECS, no Amazon EKS, no AWS Lambda ou até em ambientes on-premises.

Um post anterior cobriu o gerenciamento de credenciais com o AgentCore Identity. Agora, a AWS foi além e publicou uma implementação completa de como usar o serviço em ambientes como o ECS, respondendo duas perguntas práticas: como construir um endpoint de Session Binding gerenciado pela própria aplicação, e como controlar o ciclo de vida dos tokens de acesso do workload.

OAuth 2.0 e OIDC: a base da solução

A solução utiliza dois protocolos complementares: OAuth 2.0 (RFC 6749) e Protocolo de Conexão OpenID (OIDC). O OIDC cuida da autenticação — ou seja, confirma quem é o usuário. O OAuth 2.0 cuida da autorização — ou seja, o que esse usuário pode fazer. O foco aqui é no fluxo Authorization Code Grant, indicado para workloads que precisam agir em nome de usuários reais.

Nesse fluxo, o usuário se autentica com um provedor de identidade e concede permissão para o agente acessar recursos específicos em seu nome. A aplicação troca o código de autorização resultante por um token de acesso com escopo definido, que o AgentCore Identity armazena no seu cofre de tokens. Como cada token está vinculado a uma identidade de usuário específica com consentimento explícito, a solução mantém uma cadeia auditável desde a autenticação até a ação do agente.

O Authorization Code Grant é especialmente adequado para workloads agênticos porque oferece três garantias importantes:

  • Consentimento do usuário antes que o agente possa agir
  • Session binding que verifica se o usuário que iniciou a requisição é o mesmo que concedeu o consentimento
  • Delegação com escopo que limita o agente apenas às permissões aprovadas pelo usuário

Callback URL vs. Session Binding URL

Um ponto que costuma gerar confusão são as duas URLs envolvidas no fluxo:

  • Callback URL: gerada automaticamente ao criar um cliente OAuth no AgentCore Identity. Aponta para o próprio AgentCore Identity e precisa ser registrada no servidor de autorização como destino do redirecionamento após a autenticação do usuário.
  • Session Binding URL: aponta para um serviço gerenciado pelo cliente que completa o session binding entre o usuário autenticado e o fluxo OAuth. Esse endpoint é implementado e hospedado pelo próprio cliente.

Arquitetura da solução no Amazon ECS

A solução implanta dois serviços no Amazon ECS atrás de um Application Load Balancer (ALB). O primeiro é o Agentic Workload, que executa o agente de IA e processa as requisições dos usuários. O segundo é o Session Binding Service, responsável por processar os callbacks OAuth e vincular as sessões de usuário aos tokens de acesso de terceiros. O código-fonte completo e os pré-requisitos estão disponíveis no repositório GitHub que acompanha o post.

O walkthrough usa o Microsoft Entra ID como provedor de identidade, mas qualquer provedor compatível com OIDC funciona. A arquitetura inclui os seguintes componentes de suporte:

  • Tráfego criptografado com HTTPS via AWS Certificate Manager
  • Roteamento DNS com Amazon Route 53
  • Logs capturados pelo Amazon CloudWatch e armazenados em bucket S3
  • Imagens de container no Amazon ECR
  • Regras básicas do AWS WAF no load balancer para proteção contra exploits comuns
  • Chave gerenciada pelo cliente no AWS KMS para criptografia de dados

O fluxo completo do Authorization Code Grant

O diagrama de sequência da solução mostra como a identidade de workload do AgentCore Identity, os tokens de acesso de workload e o provedor de credenciais OAuth 2.0 trabalham juntos para entregar tokens OAuth ao agente em nome do usuário. O fluxo parte do pressuposto de que o usuário ainda não autorizou o agente, ou seja, não há token válido no Token Vault do AgentCore Identity.

O fluxo funciona assim:

  • O usuário autenticado envia uma requisição ao workload agêntico.
  • O workload extrai o ID do usuário do campo sub no JWT assinado pelo ALB (header x-amzn-oidc-data).
  • O workload chama a API GetWorkloadAccessTokenForUserId, passando o userId e o workloadName, para obter um token de acesso de workload com escopo para aquele usuário.
  • Em seguida, chama a API GetResourceOauth2Token, passando o token de workload, o nome do provedor OAuth 2.0, a Session Binding URL (parâmetro callbackUrl) e os escopos necessários.
  • Como não há token válido, o AgentCore Identity cria um sessionURI que rastreia o estado do fluxo de autorização e retorna uma URL de autorização para o workload.
  • O workload apresenta essa URL ao usuário, que clica e concede permissão na tela de consentimento do provedor.
  • O servidor de autorização envia o código de autorização ao AgentCore Identity, que redireciona o usuário para o Session Binding Service via Session Binding URL com o sessionURI anexado.
  • O Session Binding Service chama a API CompleteResourceTokenAuth com o sessionURI e o ID do usuário extraído do JWT, vinculando a autorização à sessão correta.
  • O AgentCore Identity troca o código de autorização com o servidor de autorização, recebe o token OAuth2, armazena no Token Vault e retorna sucesso ao Session Binding Service.

Em requisições subsequentes, o comportamento depende das credenciais emitidas pelo servidor de autorização. Quando há um refresh token disponível — como no GitHub com expiração de token habilitada — o AgentCore Identity o usa automaticamente para renovar o access token sem precisar envolver o usuário novamente. Se não houver refresh token e o access token expirar, o usuário será solicitado a autorizar novamente. Tokens revogados pelo provedor podem ser tratados com o parâmetro forceAuthentication: true, que força um novo fluxo de autenticação.

Por que o session binding é crítico para segurança

O session binding protege contra dois tipos de ataque:

  • Falsificação de Requisição entre Sites (CSRF): um atacante tenta vincular seu próprio token OAuth à identidade da vítima, fazendo o agente da vítima acessar recursos do atacante sem que ela perceba — o que possibilita exfiltração de dados e injeção de conteúdo.
  • Browser Swapping Attack (ataque de troca de navegador): um atacante engana a vítima para que ela dê consentimento em nome do atacante, vinculando o token OAuth da vítima à identidade do atacante e concedendo acesso direto aos recursos da vítima.

O session binding previne ambos ao garantir que o ID do usuário no workload agêntico seja o mesmo no Session Binding Service, com ambas as identidades verificadas criptograficamente pela cadeia de autenticação. O AgentCore Identity também suporta o parâmetro opcional customState na API GetResourceOauth2Token para passar um nonce aleatório criptograficamente seguro, protegendo o endpoint de callback contra ataques CSRF conforme recomendado pela especificação OAuth 2.0.

Por que usar GetWorkloadAccessTokenForUserId com ALB e Microsoft Entra ID

A API recomendada para obter um token de acesso de workload é a GetWorkloadAccessTokenForJWT, mas essa solução usa a GetWorkloadAccessTokenForUserId. O motivo é uma incompatibilidade técnica entre o fluxo OIDC do ALB e o Microsoft Entra ID.

A GetWorkloadAccessTokenForJWT exige um JWT dinamicamente validável, com assinatura verificável em runtime e claim aud correspondente à aplicação. Para obter esse token do Entra ID, é necessário incluir o Application ID no escopo da requisição OIDC — veja a documentação do AgentCore para Microsoft Inbound. O problema é que isso é incompatível com o fluxo OIDC do ALB.

Como parte do handshake OIDC (descrito na documentação de OIDC do ALB), o ALB envia o access token para o endpoint UserInfo do Entra para recuperar os claims do usuário autenticado. Esse endpoint UserInfo fica no Microsoft Graph (https://graph.microsoft.com/oidc/userinfo) e só aceita tokens com escopo para o Microsoft Graph. Quando o Application ID é incluído no escopo, o access token resultante tem a aplicação como audiência — o endpoint UserInfo rejeita com 401 e o ALB retorna 561. Se o Application ID for removido do escopo, o Entra define a audiência do access token como Microsoft Graph, o handshake do ALB funciona, mas o JWT resultante não pode ser validado dinamicamente pelo AgentCore e é inutilizável com a GetWorkloadAccessTokenForJWT.

A solução adotada: o ALB completa o handshake usando o token com escopo para o Graph e injeta um JWT assinado pelo próprio ALB no header x-amzn-oidc-data com os claims do usuário, incluindo o sub. Esse JWT é validado usando as chaves de assinatura publicadas pela AWS, o sub é extraído e passado para a GetWorkloadAccessTokenForUserId.

Implementação: os principais trechos de código

O código completo está disponível no repositório GitHub. Abaixo estão os trechos mais relevantes.

Obtendo o token de acesso de workload

O servidor extrai o ID do usuário do claim sub do JWT e solicita um token de acesso de workload ao AgentCore Identity. Em seguida, usa esse token, o ID da sessão e a mensagem para invocar o agente em nome do usuário:

@router.post("/invocations")
async def invoke_agent(
    request: InvocationRequest,
    user_id: str = Depends(get_current_user),
    settings: Settings = Depends(get_settings),
    agent_service: AgentService = Depends(get_agent_service),
) -> StreamingResponse:
    """Invoke agent with streaming response."""
    try:
        agentcore = boto3.client("bedrock-agentcore", region_name=settings.identity_aws_region)
        response = agentcore.get_workload_access_token_for_user_id(
            workloadName=settings.workload_identity_name,
            userId=user_id
        )
        workload_access_token = response["workloadAccessToken"]
        return StreamingResponse(
            content=agent_service.stream_response(
                user_message=request.user_message,
                session_id=request.session_id,
                user_id=user_id,
                workload_access_token=workload_access_token,
            ),
            media_type="text/event-stream",
        )

Solicitando o token de acesso OAuth

O servidor usa o decorator require_access_token do AgentCore SDK para recuperar o token OAuth 2.0 (veja como obter um token de acesso OAuth 2.0). A implementação modifica o decorator para aceitar o token de workload como parâmetro explícito, dando controle direto sobre o ciclo de vida do token enquanto preserva a lógica de recuperação e tratamento de erros do SDK:

def requires_access_token(
    *,
    provider_name: str,
    scopes: list[str],
    auth_flow: Literal["M2M", "USER_FEDERATION"],
    workload_access_token: str | None = None,
    session_binding_url: str | None = None,
    on_auth_url: Callable[[str], Any] | None = None,
    force_authentication: bool = False,
    token_poller: TokenPoller | None = None,
    custom_state: str | None = None,
    custom_parameters: dict[str, str] | None = None,
    into: str = "access_token",
    region: str | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        client = IdentityClient(region)
        @wraps(func)
        async def wrapper(*args: Any, **kwargs: Any) -> Any:
            try:
                if not workload_access_token:
                    raise ValueError("workload_access_token is required")
                token = await client.get_token(
                    provider_name=provider_name,
                    agent_identity_token=workload_access_token,
                    scopes=scopes,
                    auth_flow=auth_flow,
                    callback_url=session_binding_url,
                    on_auth_url=on_auth_url,
                    force_authentication=force_authentication,
                    token_poller=token_poller,
                    custom_state=custom_state,
                    custom_parameters=custom_parameters,
                )
                kwargs[into] = token
                return await func(*args, **kwargs)
            except Exception:
                logger.exception("Error in requires_access_token decorator")
                raise
        return wrapper
    return decorator

Três decisões de design importantes

A implementação das ferramentas do agente destaca três escolhas de design relevantes:

  • Pydantic BaseModel como tipos de retorno: as classes GitHubUser e GitHubProject são subclasses de BaseModel. O Strands deriva automaticamente descrições de ferramentas a partir do schema e das docstrings, dando ao modelo de linguagem contexto estruturado sobre cada ferramenta.
  • Tratamento de erros consistente com o tipo: quando não há token e o AgentCore Identity retorna uma URL de autorização, o callback on_auth_url lança um AuthorizationRequiredError em vez de retornar uma string — uma ferramenta que declara GitHubUser como tipo de retorno não pode retornar uma URL. A camada de streaming do agente captura a exceção e apresenta a URL ao usuário.
  • Escopos por ferramenta: cada ferramenta declara apenas os escopos OAuth que precisa, mantendo o consentimento alinhado ao princípio do menor privilégio.

Completando o session binding

No Session Binding Service, quando o usuário autoriza o acesso no GitHub, o GitHub redireciona para {session_binding_url}?session_id={session_id}, onde session_id corresponde ao sessionURI que o AgentCore Identity incluiu na URL de autorização original:

@router.get("/session-binding", response_class=HTMLResponse)
async def oauth_session_binding(
    session_id: str = Query(..., description="Session URI from AgentCore Identity"),
    user_id: str = Depends(get_current_user),
    settings: Settings = Depends(get_settings),
) -> HTMLResponse:
    """Handle OAuth2 session binding from external providers."""
    client = boto3.client("bedrock-agentcore", region_name=settings.identity_region)
    try:
        client.complete_resource_token_auth(
            sessionUri=session_id,
            userIdentifier={"userId": user_id},
        )

O serviço extrai o ID do usuário do claim sub no header x-amzn-oidc-data, garantindo identidade consistente ao longo de todo o fluxo. Em seguida, chama complete_resource_token_auth com o sessionURI e o ID do usuário, vinculando o token de acesso resultante à sessão correta.

Limpeza dos recursos

Para evitar cobranças desnecessárias, é recomendável excluir os recursos criados pela solução quando não forem mais necessários. As instruções estão disponíveis no guia de limpeza dos recursos.

Próximos passos

O padrão apresentado funciona em diferentes plataformas de computação — ECS, EKS, Lambda ou fora da AWS — e se estende além do GitHub para outros serviços compatíveis com OAuth 2.0, como Jira, Salesforce ou Google Calendar. Para aprofundar:

Fonte

Secure AI agents with Amazon Bedrock AgentCore Identity on Amazon ECS (https://aws.amazon.com/blogs/machine-learning/secure-ai-agents-with-amazon-bedrock-agentcore-identity-on-amazon-ecs/)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *