엔터프라이즈 급 환경에서
에이전트들이 수백개를 관리한다고 생각해보자. 곧바로 두 가지 질문이 생긴다.

1. 이 에이전트가 어디로 나가는지 알고 있나?
=> 툴 하나가 늘어날 때마다 아웃바운드 목적지가 하나씩 늘어난다.
=> MCP 서버를 붙이면 그 서버가 다시 어디로 나가는지는 더 안 보인다.
2. 누가 이 에이전트를 부를 수 있나? 그리고 들어오는 프롬프트는 검사되고 있나?
GCP 환경에서 테스트.

Agent Registry
에이전트가 나갈 수 있는 곳들을 미리 등록해두는 화이트리스트다. 세 종류를 등록한다.
| Endpoint | ex: api.open-meteo.com | agent-registry endpoints |
| MCP Server | ex: mcp.deepwiki.com/mcp | agent-registry mcp-servers |
| Agent | ex: 에이전트 서버 | agent-registry agents |
중요한 건 기본값이 deny라는 점이다. 등록 안 된 곳은 못 나간다.
등록만 해도 안 되고, 그 리소스에 대해 IAM 권한까지 받아야 나간다.

Agent Identity
에이전트가 IAM의 주체(principal)가 된다. 서비스 계정을 빌려 쓰는 게 아니라, 에이전트 리소스 경로 자체가 신원이다.
principal://agents.global.org-<ORG_NUMBER>.system.id.goog/resources/aiplatform/
projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<ENGINE_ID>
이게 왜 중요하냐면, 에이전트 단위로 권한을 다르게 줄 수 있기 때문이다.
서비스 계정을 공유하면 같은 SA를 쓰는 모든 에이전트가 같은 목적지에 나갈 수 있다.
Agent Identity는 "이 에이전트만 GitHub API에 나갈 수 있음"을 표현한다.
Agent Gateway
게이트웨이 하나당 통제 경로 하나다. egress (agent -> any)용, ingress(client -> agent)용을 따로 만든다.

간단한 실습.
에이전트 하나에 성격이 다른 툴들을 달아서, 같은 에이전트 안에서 통과와 차단을 동시에 관찰하는 구조로 잡았다.
툴 목적지 Registry
| get_weather | api.open-meteo.com | weather-api | 부여 | 통과 |
| get_github_user | api.github.com | blocked-test-api | 미부여 | 차단 |
| deepwiki (MCP) | mcp.deepwiki.com/mcp | deepwiki | 부여 | 통과 |
두 REST 툴 모두 예외를 잡아서 그대로 돌려주게 만들었다.
게이트웨이가 막았을 때의 에러 자체가 관찰 대상이라, 예외를 삼키면 실습이 성립하지 않는다.
def get_github_user(username: str) -> dict:
"""GitHub 사용자의 공개 프로필을 조회한다."""
import requests
try:
response = requests.get(f"https://api.github.com/users/{username}", timeout=10)
return {"status": response.status_code, "body": response.text[:2000]}
except Exception as exc:
return {"error": f"{type(exc).__name__}: {exc}"}
에이전트 instruction에도 못을 박아뒀다.
중요: 툴이 error 나 4xx/5xx status 를 돌려주거나 연결이 거부되면, 그 내용을
요약하거나 감추지 말고 원문 그대로 사용자에게 보여줘라. 실패를 성공처럼 포장하지 마라.
이걸 안 넣으면 모델이 친절하게 "죄송합니다, 일시적인 문제가 있는 것 같습니다"로 포장해버려서 뭐가 막혔는지 안 보인다.
1부: Egress => 나가는 길 막기
Audit-only(Dry Run - 로그만 확인가능 차단 기능이 작동했는지!)에서는 아무것도 안 막힌다
게이트웨이를 처음 만들면 Audit-only다. 이 상태로 아무리 호출해도 다 성공한다. 로그에는 DENIED 판정이 찍히지만 트래픽은 그대로 나간다.
Enforce(설정 ON)로 올리자 에이전트가 죽었다
여기서 첫 번째 진짜 교훈이 나왔다. Enforce로 바꾸자 GitHub 호출이 막힌 게 아니라 에이전트 자체가 아예 동작하지 않았다. "안녕"이라고 보내도 400이 났다. 게이트웨이 로그를 보니 403의 목적지가 이것들이었다.
us-central1-aiplatform.mtls.googleapis.com
iamcredentials.mtls.googleapis.com
telemetry.googleapis.com
Enforce는 런타임 자신의 컨트롤 플레인 호출도 막는다.
에이전트가 자기 세션을 읽고, 토큰을 발급받고, 텔레메트리를 내보내는 것 전부가 egress다. 화이트리스트에 없으면 차단이다.
결국 아래 7개를 레지스트리에 등록하고 egressor를 줬다.
| agentregistry.googleapis.com | 문서 |
| telemetry.googleapis.com | 문서 |
| telemetry.mtls.googleapis.com | 문서 (mTLS 조건부) |
| logging.googleapis.com | 문서 |
| logging.mtls.googleapis.com | 문서 (mTLS 조건부) |
| iamcredentials.mtls.googleapis.com | 로그에서 발견 |
| us-central1-aiplatform.mtls.googleapis.com | 로그에서 발견 |


2부: Ingress => 들어오는 길 검사하기
Client-to-Agent의 신원 검사는 게이트웨이가 아니라 aiplatform API 계층에서 네이티브로 이뤄진다.
에이전트를 호출하려면 reasoningEngines 리소스에 대한 IAM 권한이 필요하고,
그래서 ingress 게이트웨이의 실질적인 역할은 콘텐츠 검사다. 여기에 Model Armor가 붙는다.
[브라우저] ──Google OAuth──▶ [백엔드] ──ADC + IAM──▶ [Agent Runtime]
↑ ↑ ↑
사용자 토큰 토큰 검증 후 Agent Identity로
(프론트에만) 인가 판정 egress 통제
핵심은 프론트엔드가 Agent Runtime을 직접 부르지 않는 것이다.
직접 부르려면 브라우저에 GCP 자격증명이 있어야 하고, 그 순간 그 사용자는 자기 권한으로 무엇이든 할 수 있게 된다.
백엔드가 중간에 서면:
- 사용자 OAuth 토큰을 검증해서 "이 사람이 이 에이전트를 쓸 자격이 있나"를 판정하고
- Agent Runtime 호출은 백엔드에 붙은 서비스 계정(Cloud Run이면 attached SA + ADC)으로 한다
"Claude Code에서는 되게 하고 Cursor에서는 안 되게" 같은 클라이언트 구분도 같은 자리에서 한다. 클라이언트가 스스로 신고하는 값(User-Agent 등)은 신뢰할 수 없으니, 클라이언트마다 별도 credential을 발급해서 그걸로 구분해야 한다.
3부: Model Armor
Model Armor는 확률 기반 분류기다. 패턴 매칭이 아니다.
그래서 설정에 "confidence threshold"가 있고 (Low and above / Medium and above / ...), Inspect only와 Inspect and block을 고를 수 있다.

기본적으로 모델 자체 안전 필터를 켤수 있는데 굳이?

Model Armor를 켜놓으면 모델 자체 안전 필터가 발동하는 걸 볼 수 없다. Model Armor가 항상 먼저 걸리기 때문이다.

모델 아머의 가장 강점이라고 생각되는 부분이다.
모델 자체 거절에 의존하면 통제권이 없다.
모델 버전을 바꾸는 순간 거절 기준이 바뀌고, 조직이 그걸 조정할 방법이 없다. 로그에도 "정책 위반 차단"이 아니라 평범한 대화로 남는다.
'AI' 카테고리의 다른 글
| 혼자서도 4인분: Claude Code로 구축한 AI 에이전트 팀 개발 회고 (0) | 2026.02.19 |
|---|