‫Gemini Enterprise עם Agent Gateway, יציאה של כלל התנועה מ-VPC וכתובת IP סטטית של Cloud NAT לשרת MCP חיצוני

1. מבוא

ב-Codelab הזה נסביר איך להשתמש ב-Agent Gateway ב-agent-to-anywhere (יציאה) כדי ליצור קישוריות יציאה עם כתובת IP סטטית ל-Gemini Enterprise. תגדירו אפליקציית Gemini Enterprise שתפעיל שרת Model Context Protocol‏ (MCP) בהתאמה אישית, שמוגן על ידי רשימת היתרים של כתובות IP קפדנית ואימות OAuth 2.0, על ידי ניתוב כל התנועה היוצאת דרך Agent Gateway באמצעות הגדרת תבנית קישוריות. מחבר רשת של Private Service Connect ‏(PSC) מחבר את Agent Gateway לרשת ה-VPC, ו-Cloud NAT מספק כתובת IP חיצונית יציבה לתעבורת נתונים יוצאת (egress) באינטרנט.

כברירת מחדל, חיבורים יוצאים מ-Gemini Enterprise לנקודות קצה באינטרנט הציבורי מגיעים ממאגרים דינמיים משותפים של כתובות IP של Google, שלא ניתן להצמיד אותם לכלל של רשימת כתובות IP להיתר בבעלות לקוח יחיד. שילוב של Agent Gateway עם AgentConnectivityTemplate במצב ALL_TRAFFIC מנתב את כל התנועה היוצאת של הסוכן לרשת ה-VPC, שבה Cloud NAT מתרגם חיבורים שמיועדים לאינטרנט לכתובת IPv4 חיצונית סטטית שמורה שנמצאת בשליטתכם, תוך שמירה על ניהול מרכזי של כלי MCP באמצעות Agent Registry ו-Identity-Aware Proxy (IAP).

מה יוצרים

  • תבנית קישוריות של סוכן (vpcEgress: ALL_TRAFFIC) ו-Agent Gateway שפועל במצב יציאה (agent-to-anywhere) עם אימות של נקודת הקצה של Agent Registry
  • רשת VPC, מחבר רשת PSC,‏ Cloud Router ושער Cloud NAT שקשורים לכתובת IPv4 חיצונית סטטית שמורה
  • שרת משאבים חיצוני מסוג HTTP MCP שאפשר להזרים ממנו (mcp-sprockets) עם Caddy TLS בחזית, שמוגבל על ידי כללי חומת אש רק לכתובת ה-IP הסטטית של Cloud NAT, ומאמת אסימוני Bearer של Google OAuth 2.0 שהוקצו למשתמשים מול https://oauth2.googleapis.com/tokeninfo
  • מסך ההסכמה של OAuth 2.0 בפלטפורמת האימות של Google ופרטי הכניסה של לקוח OAuth 2.0 באפליקציית אינטרנט (accounts.google.com ו-oauth2.googleapis.com/token)
  • ערך בקטלוג שרתי ה-MCP של Agent Registry ומפרט הכלי (toolspec.json)
  • תוסף שמפנה לבדיקת הרשאות של שרת proxy לאימות זהויות (IAP) ומדיניות ההרשאות של שער עבור Agent Gateway
  • אפליקציית Gemini Enterprise שמשויכת ל-Agent Gateway ומחוברת למאגר נתונים של שרת MCP מותאם אישית ב-OAuth 2.0 שיובא מ-Agent Registry

figure1

איור 1. ארכיטקטורת Codelab

מה תלמדו

  • איך מגדירים AgentConnectivityTemplate עם vpcEgress: ALL_TRAFFIC כדי לנתב את כל תעבורת הנתונים היוצאת של Agent Gateway לרשת VPC
  • איך שומרים כתובת IPv4 חיצונית סטטית ומקשרים אותה ל-Cloud NAT ברשת משנה של קובץ מצורף לרשת PSC
  • איך פורסים שרת משאבים חיצוני מסוג HTTP MCP שניתן להזרמה, שמאמת אסימוני Bearer מסוג Google OAuth 2.0 ‏ (oauth2.googleapis.com/tokeninfo) ומגביל את חומת האש שלו בכניסה לכתובת ה-IP הסטטית של Cloud NAT ‏ (${NAT_EXT_IP}/32)
  • איך מגדירים פרטי לקוח של Google Identity OAuth 2.0 ורושמים שרת MCP וסכימת הכלים שלו ב-Agent Registry
  • איך מחברים שרת MCP רשום של OAuth 2.0 ל-Gemini Enterprise ומנתבים קריאות לכלים שהמשתמשים הקצו דרך Agent Gateway
  • איך מאמתים תרגום של כתובות IP סטטיות, אימות זהות של משתמשי קצה ב-OAuth 2.0 ‏ (authenticated_user) ואכיפה של מדיניות שער באמצעות פקודות CLI ו-Cloud Logging

מה צריך

  • פרויקט ב-Google Cloud שהחיוב בו מופעל
  • רישיון פעיל ל-Gemini Enterprise או תקופת ניסיון של 30 יום
  • הרשאות IAM להקצאת שירותי רשת, מדיניות ארגונית, Gemini Enterprise ומשאבי Agent Platform
  • מעטפת תואמת POSIX‏ (bash או zsh) עם Google Cloud CLI‏ (gcloud),‏ curl ו-jq מותקנים

סיימנו את חלק המבוא. עכשיו נעבור לקטע מושגים.

2. מושגים

סדר הפריסה

בשיעור Codelab הזה נסביר איך לפרוס קודם את תשתית הרשת והניהול, כדי שיהיו נתיבי יציאה סטטיים של כתובות IP ופקדי שער פעילים לפני שרושמים ומקשרים כלי MCP ל-Gemini Enterprise:

  1. תשתית הרשת: שמירת כתובות IPv4 חיצוניות סטטיות והקצאת VPC ליציאה, רשת משנה לצירוף PSC, צירוף רשת PSC, Cloud Router, שער Cloud NAT וכללי חומת אש חיצונית של רשת MCP.
  2. תבנית קישוריות לסוכן ו-Agent Gateway: פריסה של AgentConnectivityTemplate (vpcEgress: ALL_TRAFFIC) אזורי AgentGateway במצב יציאה (AGENT_TO_ANYWHERE) עם שילוב של Agent Registry‏ (registries).
  3. מדיניות הרשאות: הגדרת תוסף ההרשאות של IAP‏ (AuthzExtension), מדיניות ההרשאות של שער (AuthzPolicy) ומדיניות גישה מאוחדת ב-IAM v3‏ (AccessPolicy ו-PolicyBinding).
  4. פריסה ורישום של שרת MCP: פורסים את שרת המשאבים החיצוני של mcp-sprockets OAuth 2.0 מאחורי Caddy TLS, מאמתים את האכיפה של Google OAuth 2.0 tokeninfo, נועלים את חומת האש של הכניסה באופן קפדני לכתובת ה-IP הסטטית של Cloud NAT ‏ (${NAT_EXT_IP}/32) ורושמים את המפרטים של השירות והכלי (get_sprockets ו-get_sprocket_by_id) ב-Agent Registry.
  5. אפליקציית Gemini Enterprise ולקוח OAuth 2.0: מגדירים את ספק הזהויות של Google, את רישיון הניסיון, את מסך ההסכמה של OAuth 2.0 ואת פרטי הכניסה של לקוח אפליקציית האינטרנט (OAUTH_CLIENT_ID ו-OAUTH_CLIENT_SECRET), יוצרים את אפליקציית Gemini Enterprise (Engine) ומקשרים את התעבורה היוצאת אל Agent Gateway (agentGatewaySetting).
  6. ייבוא מחבר נתונים מותאם אישית של MCP: יוצרים ומפעילים את REGISTRY_MCP מחבר הנתונים (:setUpDataConnector) שהוגדר עם auth_type: "OAUTH" כדי לקשר את שרת ה-MCP הרשום שמשמש כמאגר נתונים לאפליקציית Gemini Enterprise.
  7. אימות: משלימים את תהליך ההסכמה החד-פעמי של משתמש OAuth 2.0, מפעילים כלי MCP דרך העוזר הדיגיטלי של Gemini Enterprise (מאשרים גם את egress_source_ip וגם את authenticated_user), ומאמתים את אכיפת המדיניות ואת תרגום ה-NAT של כתובת ה-IP הסטטית ב-Cloud Logging.

ארכיטקטורה של שרת משאבים של MCP שמוגן באמצעות OAuth + ספק זהויות מרכזי

בפריסות ארגוניות, ניהול הזהויות והפעלת הכלי מתבצעים בהתאם להפרדת התפקידים הסטנדרטית של OAuth 2.0 בין ספק זהויות מרכזי (שרת הרשאות) לבין שרת משאבים מוגן של MCP:

תפקיד OAuth 2.0

רכיב ב-Codelab הזה

אחריות

בעלי המשאב

משתמש קצה (${USER_IDENTITY})

המשתמש נכנס לאפליקציית האינטרנט של Gemini Enterprise ומעניק הסכמה במהלך תהליך ההרשאה של OAuth 2.0 עם 3 רגליים, שמתבצע פעם אחת.

לקוח OAuth 2.0

‫Gemini Enterprise custom_mcp Data Connector

מחזיק ב-OAUTH_CLIENT_ID וב-OAUTH_CLIENT_SECRET, מחליף את קוד ההרשאה ב-oauth2.googleapis.com/token, שומר את הטוקנים של OAuth 2.0 של המשתמש ומציג Authorization: Bearer [access_token] בכל קריאה יוצאת של כלי MCP.

שרת ההרשאות

‫Google Identity OAuth 2.0 (accounts.google.com ו-oauth2.googleapis.com/token)

מאמת את המשתמש במהלך ההפניה האוטומטית להסכמה בדפדפן ומנפיק ל-Gemini Enterprise את אסימוני הגישה והרענון של OAuth 2.0.

שרת משאבים

חיצוני mcp-sprockets שרת (https://mcp.[MCP_EXT_IP].sslip.io/mcp)

השירות אוכף זהות רשת בשכבה 3 באמצעות חומת האש של ה-VPC שלו (${NAT_EXT_IP}/32) וזהות משתמש בשכבה 7 על ידי אימות הכותרת Authorization: Bearer [access_token] מול https://oauth2.googleapis.com/tokeninfo.

מכיוון שההפניה האוטומטית של הסכמה בדפדפן (accounts.google.com) והחלפת האסימונים בערוץ האחורי (oauth2.googleapis.com/token) מתרחשות מול נקודות קצה של OAuth 2.0 שמנוהלות על ידי Google, חומת האש של שרת ה-MCP החיצוני מקבלת רק בקשות JSON-RPC של MCP בזמן ריצה (POST /mcp) מ-Agent Gateway דרך Cloud NAT, ולכן אפשר לנעול את חומת האש של הכניסה באופן מוחלט ל-${NAT_EXT_IP}/32.

מודל המשאבים של Discovery Engine

אפליקציית Gemini Enterprise (שמיוצגת כמשאב Engine ב-Discovery Engine API) היא שכבת התיאום המרכזית וממשק השיחה למשתמשי הקצה. הוא מנהל סשנים של צ'אטים עם משתמשים, מבסס מודלים גנרטיביים על נתונים ארגוניים ומתאם בין הפעלות דינמיות של כלים.

אפליקציות Gemini Enterprise מקיימות אינטראקציה עם נתונים ומערכות באמצעות מאגרי נתונים:

  • מאגרי נתונים של ידע: הטמעה ואינדוקס של תוכן סטטי (למשל, Cloud Storage, ‏ Google Drive, ‏ BigQuery) לצורך יצירה משופרת באחזור (RAG).
  • מחברי נתונים (ספקי פעולות): מתחברים לממשקי API דינמיים של צד שלישי או לממשקי API בהתאמה אישית. מאגר נתונים של שרת MCP מותאם אישית חושף כלים שמוגדרים על ידי Model Context Protocol‏ (MCP), ומאפשר למודל להפעיל באופן דינמי פונקציות חיצוניות במהלך שיחה.

יציאת נתונים מ-Gemini Enterprise

‫Gemini Enterprise מעביר בקשות לכלים של שרת MCP בהתאמה אישית אל Agent Gateway כשגם agentGatewaySetting ב-Engine וגם use_agent_gateway_egress: true ב-DataConnector מוגדרים.

figure2

איור 2. ארכיטקטורת יציאה של Gemini Enterprise

אפליקציית Gemini Enterprise מארגנת את ניתוב הכלים בארבעה תחומים מרכזיים:

  1. ווידג'ט (default_search_widget_config):
    • מגיש את ממשק לקוח האינטרנט. הווידג'ט מקבל הנחיות מהמשתמש, מפעיל את ההפניה האוטומטית להסכמת משתמש חד-פעמית של OAuth 2.0 כשכלי מסוים דורש הרשאה, ומתחיל סשנים של צ'אט עם המנוע הבסיסי.
  2. העוזר הראשי (assistants/default_assistant/agents/default/core_assistant):
    • סוכן ה-AI בממשק שיחה ברמת הבסיס במנוע. כשמעריכים שאילתת משתמש, עוזר הליבה בודק את הכלים הזמינים ומקצה את ההרצה לסוכן המשנה של Agent Gateway הסינתטי.
  3. מאגר נתונים ומחבר נתונים:
    • ‫DataStore: מוקצה בתוך Collection ייעודי כשמריצים את :setUpDataConnector, הוא מקשר (dataStoreIds) את הסכימות של כלי Agent Registry המיובא (get_sprockets, get_sprocket_by_id), את סוגי הארגומנטים ואת ההוראות לסוכן אל Gemini Enterprise Engine.
    • ‫DataConnector: מנהל את חיבור הפעולה REGISTRY_MCP (createBapConnection: true) לשרת ה-MCP המרוחק (instance_uri), פותר את משאב שרת ה-MCP של Agent Registry‏ (registry_mcp_server_name), מצרף את אסימון ה-Bearer של OAuth 2.0 שנשמר בכספת של המשתמש (Authorization: Bearer [access_token]) ומאפשר יציאה של Agent Gateway‏ (use_agent_gateway_egress: true).
  4. Agent Identity, Agent Registry ו-Agent Gateway:
    • כשמחבר הנתונים שולח את הקריאה היוצאת לכלי, הוא מנתב את התנועה לשער שצוין ב-agentGatewaySetting. הכלי Core Assistant יוצר אסימון זהות SPIFFE שמאשר את הזהות של עומס העבודה: principal://agents.global.org-.../agents/default/core_assistant.
    • ‫Agent Gateway משתלב עם Agent Registry באמצעות השדה registries כדי לפתור באופן דינמי נקודות קצה של יעד וסכימות של כלים רשומים, מעריך מדיניות הרשאות באמצעות IAP ומעביר בקשות מורשות (תוך שמירה על כותרת המשתמש Authorization: Bearer) לרשת ה-VPC באמצעות AgentConnectivityTemplate.

קישוריות של שער VPC ו-NAT של כתובות IP סטטיות

‫Agent Gateway תומך בקישוריות לרשת VPC באמצעות משאב AgentConnectivityTemplate שאליו מתבצעת הפניה על ידי agentConnectivityTemplate בשער. התבנית egressNetworkConfig מגדירה איך תעבורת נתונים נכנסת ל-VPC דרך מחבר רשת של PSC:

  • ‫PRIVATE_RANGES_ONLY (ברירת מחדל): ניתוב רק של טווחי כתובות IP פרטיות מסוג RFC 1918,‏ RFC 6598 ו-Class E דרך חיבור הרשת של PSC. תעבורת נתונים שמיועדת לאינטרנט עוקפת את ה-VPC ויוצאת ישירות דרך יציאת ברירת המחדל של Google לאינטרנט.
  • ‫ALL_TRAFFIC (משמש ב-Codelab הזה): מעביר את כל התעבורה היוצאת מ-Agent Gateway – כולל האינטרנט הציבורי ונקודות הקצה של שותפי MCP חיצוניים – לרשת המשנה של ה-VPC דרך מחבר הרשת של ה-PSC ‏ (10.10.10.2).

כשפועלים במצב ALL_TRAFFIC, נדרשות שתי הגדרות עבור רשת המשנה של קובץ ה-PSC:

  • גישה פרטית ל-Google‏ (--enable-private-ip-google-access): מאפשרת להמשיך להפנות קריאות פנימיות לממשקי Google Cloud API‏ (*.googleapis.com) באופן פרטי בתוך רשת Google.
  • ‫Cloud Router ו-Cloud NAT (MANUAL_ONLY): תנועה שיוצאת לאינטרנט ונכנסת לתת-הרשת של קובץ ה-PSC מצורף, עוברת במסלול ברירת המחדל (0.0.0.0/0) דרך Cloud NAT, שמבצע תרגום כתובת רשת של המקור (SNAT) לכתובת ה-IPv4 החיצונית הסטטית השמורה (${NAT_EXT_IP}). לכן, חומות אש חיצוניות יכולות להגביל את הגישה הנכנסת ל-${NAT_EXT_IP}/32 בלבד.

הגבלות ודרישות

המיקום של אפליקציית Gemini Enterprise

מיקום של Agent Gateway ו-NAT

מיקום Agent Registry

global

us-central1

us-central1, us או global

us

us-central1

us-central1 או us

eu

europe-west1

europe-west1 או eu

סיימנו את החלק של ההסברים… עכשיו נעבור לקטע הגדרה.

3. הגדרה

התפקידים שצריך ב-IAM

כדי להשלים את ה-Codelab הזה, צריך את התפקידים הבאים:

דומיין

התפקידים שצריך ב-IAM

פרויקט ו-IAM

roles/orgpolicy.policyAdmin
roles/resourcemanager.projectIamAdmin
roles/iam.accessPolicyAdmin
roles/serviceusage.serviceUsageAdmin
roles/iam.serviceAccountUser

Networking & Gateway

roles/networkservices.admin
roles/networksecurity.admin
roles/serviceextensions.admin
roles/iap.admin
roles/compute.networkAdmin
roles/compute.securityAdmin

‫Gemini Enterprise והמרשם

roles/discoveryengine.admin
roles/agentregistry.admin

עומסי עבודה (workloads)

roles/compute.instanceAdmin.v1 (או roles/compute.admin)

ניראות (observability)

roles/logging.viewer

אפשר גם להשתמש בתפקיד בסיסי רחב כמו roles/owner בשילוב עם roles/orgpolicy.policyAdmin (כי roles/owner לבד לא יכול לשנות את מדיניות הארגון).

גישה לפרויקט

ב-Codelab הזה משתמשים בפרויקט אחד ב-Google Cloud. בשלבי ההגדרה נעשה שימוש בפקודות של gcloud CLI ושל מעטפת Linux.

כדי להתחיל, ניגשים לשורת הפקודה של הפרויקט בענן ב-Google Cloud:

הגדרת מזהה הפרויקט

# set target project id
gcloud config set project SET_YOUR_PROJECT_ID_HERE

אימות סשן

# login to gcloud cli
gcloud auth login
# login for gcloud api
gcloud auth application-default login

הגדרת משתני סביבה של מעטפת

# set custom var for slug (eg, "foo") and region preference
export SLUG="foo"
export REGION="us-central1"
export ZONE="us-central1-a"

echo ${SLUG}
echo ${REGION}
echo ${ZONE}
# create project vars (automatic)
export PROJ_ID=$(gcloud config list --format="value(core.project)")
export PROJ_NO=$(gcloud projects describe ${PROJ_ID} --format="value(projectNumber)")
export ORG_ID=$(gcloud projects get-ancestors ${PROJ_ID} --format="value(id)" | tail -n 1)
export USER_IDENTITY=$(gcloud config get-value account)

echo ${PROJ_ID}
echo ${PROJ_NO}
echo ${ORG_ID}
echo ${USER_IDENTITY}
# create resource vars for agent platform (automatic)
export AGW_NAME="agw-${SLUG}-${REGION}-ata"
export AGW_URI="projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
export MCP_NAME="mcp-sprockets"

echo ${AGW_NAME}
echo ${AGW_URI}
echo ${MCP_NAME}
# create resource vars for gemini enterprise (automatic)
export GE_LOCATION="global"
export GE_APP_ORG_NAME="${SLUG}, Inc."
export GE_APP_DISPLAY_NAME="${SLUG} ${GE_LOCATION} app"
export GE_APP_NAME="app-${SLUG}-${GE_LOCATION}"
export GE_APP_INIT="${GE_APP_NAME}_$(date +%s)"

echo ${GE_LOCATION}
echo ${GE_APP_ORG_NAME}
echo ${GE_APP_DISPLAY_NAME}
echo ${GE_APP_NAME}
echo ${GE_APP_INIT}

הגדרת דומיינים מהימנים של זהות הסוכן

ההצהרה if-then-else בודקת אם הפרויקט שייך לארגון כדי להגדיר את מתחם האמון (trust domain) הנכון עבור זהויות הישות המורשית של הסוכן הראשי.

# set var for trust domain
if [[ -n "${ORG_ID}" ]]; then
  export TRUST_DOMAIN="agents.global.org-${ORG_ID}.system.id.goog"
else
  export TRUST_DOMAIN="agents.global.proj-${PROJ_NO}.system.id.goog"
fi

echo "trust domain: ${TRUST_DOMAIN}"

הגדרת פרויקט לחיוב ולמכסה

# set cli quota project
gcloud config set billing/quota_project ${PROJ_ID}
# set api quota project
gcloud auth application-default set-quota-project ${PROJ_ID}

יצירת ספרייה מקומית לקובצי תצורה

# create config folder
mkdir -p cfg

אם אתם מריצים התקנה בניהול עצמי של Google Cloud SDK (כלומר, מחוץ ל-Cloud Shell), אתם צריכים לעדכן את הרכיבים לגרסה האחרונה.

# update gcloud cli
gcloud components update

הפעלת שירותי API

# enable google cloud apis
gcloud services enable \
  agentregistry.googleapis.com \
  aiplatform.googleapis.com \
  cloudapiregistry.googleapis.com \
  cloudresourcemanager.googleapis.com \
  compute.googleapis.com \
  discoveryengine.googleapis.com \
  dns.googleapis.com \
  iam.googleapis.com \
  iap.googleapis.com \
  logging.googleapis.com \
  monitoring.googleapis.com \
  networkconnectivity.googleapis.com \
  networksecurity.googleapis.com \
  networkservices.googleapis.com \
  orgpolicy.googleapis.com \
  serviceextensions.googleapis.com \
  serviceusage.googleapis.com

כללי מדיניות של הארגון

אילוצים מנוהלים על מדיניות הארגון ומדיניות ארגז החול ב-Google Cloud יכולים להגביל את התכונות שבהן נעשה שימוש ב-Codelab הזה:

  • ‫compute.vmExternalIpAccess: מגביל כתובות IPv4 חיצוניות במכונות וירטואליות ב-Compute Engine בארגז חול נעול.
  • ‫discoveryengine.managed.disableCustomMcpServerConnector: מגביל את היצירה של מחברי נתונים שמשתמשים בשרת MCP מותאם אישית (custom_mcp) כמקור נתונים (ההגדרה הזו נאכפת כברירת מחדל).
  • ‫discoveryengine.managed.allowedEgressFqdns: מגביל דומיינים יוצאים (instance_uri FQDN) של מחברי נתונים כש-VPC Service Controls‏ (VPC-SC) פעיל או כשהפרויקט מופיע ב-enforcedProjects.
  • ‫discoveryengine.managed.allowedDataSources: מגביל את סוגי מחברי הנתונים המותרים (dataSource) כש-VPC-SC פעיל או כשהפרויקט מופיע ב-enforcedProjects.

ביטול של מגבלות שקשורות למדיניות הארגון שעברו בירושה ברמת הפרויקט.

השבתת אילוץ MCP בהתאמה אישית

# disable data connector constraint (allow custom mcp servers)
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.disableCustomMcpServerConnector
spec:
  rules:
  - enforce: false
EOF

השבתת אילוצים מותנים של מחבר נתונים

# disable egress fqdn constraint on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.allowedEgressFqdns
spec:
  rules:
  - enforce: false
EOF
# disable allowed data sources constraint on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.allowedDataSources
spec:
  rules:
  - enforce: false
EOF

השבתת אילוץ של מדיניות גישה

# disable iam v3 constraint (allow v3 access policies)
gcloud org-policies set-policy /dev/stdin << EOF
name: projects/${PROJ_NO}/policies/iam.managed.disableAccessPolicyBinding
spec:
  rules:
  - enforce: false
EOF

איך מאפשרים כתובות IP חיצוניות

# allow external ip address for vms on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/compute.vmExternalIpAccess
spec:
  rules:
  - allowAll: true
EOF

אימות מדיניות הארגון

בודקים שהערך של compute.vmExternalIpAccess הוא allowAll: True וששאר המדיניות היא enforce: False:

# verify effective org policy constraints on project
for policy in \
  discoveryengine.managed.disableCustomMcpServerConnector \
  discoveryengine.managed.allowedEgressFqdns \
  discoveryengine.managed.allowedDataSources \
  iam.managed.disableAccessPolicyBinding \
  compute.vmExternalIpAccess; do
  gcloud org-policies describe ${policy} \
    --project=${PROJ_ID} \
    --effective \
    --format="flattened[no-pad](name.basename(),spec.rules[0])"
done

אימות של הקישורים של סוכני השירות (אמצעי זהירות)

בפרויקט חדש, מערכת Google Cloud מקצה באופן אוטומטי את סוכן השירות של Agent Gateway ומעניקה לו את ההרשאה roles/agentgateway.serviceAgent כשמפעילים לראשונה את networkservices.googleapis.com. אם אתם משתמשים מחדש בפרויקט קיים, יכול להיות שפעולות ניקוי קודמות הסירו את הקישורים של סוכן השירות שמוגדר כברירת מחדל. כדי לוודא שהזהות והקישור לתפקיד תקינים, מריצים את הפקודות הבאות:

# ensure network services service account has been created
gcloud beta services identity create \
  --service=networkservices.googleapis.com \
  --project="${PROJ_ID}"

# ensure network services service account has service agent roles applied
gcloud projects add-iam-policy-binding "${PROJ_ID}" \
  --member="serviceAccount:service-${PROJ_NO}@gcp-sa-agentgateway.iam.gserviceaccount.com" \
  --role="roles/agentgateway.serviceAgent"

כאן מסתיים חלק ההגדרה... ממשיכים לקטע רשת.

4. רשת

בקטע הזה, שומרים את כתובות ה-IPv4 החיצוניות הסטטיות, יוצרים את רשת ה-VPC הפנימית ליציאת נתונים ואת רשת ה-VPC החיצונית המבודדת, מקצים את הצירוף לרשת PSC, מגדירים את Cloud NAT ויוצרים כללי חומת אש.

שמירת כתובות IP חיצוניות סטטיות

שומרים שתי כתובות IPv4 חיצוניות סטטיות אזוריות ל-Cloud NAT ולמכונה הווירטואלית של שרת ה-MCP החיצוני:

# reserve static external ipv4 address for cloud nat
gcloud compute addresses create ip-${SLUG}-nat \
  --region=${REGION}

# reserve static external ipv4 address for the mcp server
gcloud compute addresses create ip-${SLUG}-mcp \
  --region=${REGION}

אימות כתובות IP חיצוניות סטטיות

כדי להשתמש ב-Gemini Enterprise, נקודות הקצה של MCP מרוחקות צריכות להשתמש ב-HTTPS עם אישור TLS מהימן באופן ציבורי. במקום לרשום דומיין בהתאמה אישית למעבדה הזו, משתמשים ב-sslip.io, שירות DNS חינמי עם תו כללי לחיפוש, שמבצע התאמת נתונים (resolve) של כל שם מארח שמכיל כתובת IP (כמו mcp.[IP].sslip.io) ישירות לכתובת ה-IP הזו. מכיוון שרשויות אישורים כמו Let's Encrypt לא מנפיקות אישורים לכתובות IP חשופות, שם המארח sslip.io מספק ל-Caddy proxy הפוך שם דומיין תקין כדי להקצות באופן אוטומטי אישור TLS מהימן לשרת MCP.

מייצאים את כתובות ה-IPv4 ויוצרים את sslip.io שם הדומיין וכתובת ה-URL של שרת ה-MCP החיצוני:

# export static ip addresses and set vars for domain
export NAT_EXT_IP=$(gcloud compute addresses describe ip-${SLUG}-nat \
  --region=${REGION} \
  --format="value(address)")

export MCP_EXT_IP=$(gcloud compute addresses describe ip-${SLUG}-mcp \
  --region=${REGION} \
  --format="value(address)")

export MCP_DOMAIN="mcp.${MCP_EXT_IP}.sslip.io"
export MCP_URL="https://${MCP_DOMAIN}/mcp"

echo "cloud nat external ip    : ${NAT_EXT_IP}"
echo "mcp server external ip   : ${MCP_EXT_IP}"
echo "mcp url                  : ${MCP_URL}"

פלט לדוגמה:

cloud nat external ip    : 203.0.113.10
mcp server external ip   : 203.0.113.20
mcp url                  : https://mcp.203.0.113.20.sslip.io/mcp

יצירת רשת פנימית

כשמגדירים את תבנית הקישוריות של Agent Gateway לשימוש ב-egressNetworkConfig עם networkAttachment, רשת המשנה שמארחת את מחבר הרשת של PSC צריכה להיות לפחות בלוק CIDR של /28, וצריך להפעיל בה גישה פרטית ל-Google ‏ (--enable-private-ip-google-access).

יוצרים רשת VPC גלובלית לתעבורת נתונים יוצאת (egress) של Agent Gateway פנימי:

# create vpc network
gcloud compute networks create vnet-${SLUG}-internal --subnet-mode=custom

# create subnet for agent gateway psc na
gcloud compute networks subnets create subnet-${REGION}-agw \
  --network=vnet-${SLUG}-internal \
  --range=10.10.10.0/28 \
  --region=${REGION} \
  --enable-private-ip-google-access

יצירת כללים פנימיים לחומת האש

יוצרים מדיניות חומת אש שמאפשרת את כל תעבורת הנתונים היוצאת (egress) עם הפעלת הרישום ביומן. המידע הזה ישמש למעקב אחרי תנועת הנתונים שיוצאת מ-Agent Gateway לרשת ה-VPC הפנימית. ‫Cloud NGFW תומך ברמות Essentials ו-Standard לאבטחת רשת ולניטור תעבורה.

# create fw policy
gcloud compute network-firewall-policies create fw-policy-${SLUG}-internal --global
# create fw policy rule
gcloud compute network-firewall-policies rules create 1001 \
  --description="allow all out and log" \
  --firewall-policy=fw-policy-${SLUG}-internal \
  --global-firewall-policy \
  --action=allow \
  --direction=EGRESS \
  --layer4-configs=all \
  --dest-ip-ranges=0.0.0.0/0 \
  --enable-logging
# bind fw policy to network
gcloud compute network-firewall-policies associations create \
  --name=fw-policy-bind-${SLUG}-internal \
  --firewall-policy=fw-policy-${SLUG}-internal \
  --network=vnet-${SLUG}-internal \
  --global-firewall-policy

יצירת קובץ מצורף לרשת PSC

יוצרים קובץ מצורף לרשת Private Service Connect ‏ (PSC) שמוגדר לקבל באופן אוטומטי חיבורים מ-Agent Gateway. מחבר הרשת יוצר את הצד של רשת ה-VPC של הצרכן בחיבור, כדי לקשר בצורה מאובטחת לצד של הספק של Agent Gateway לתעבורת נתונים יוצאת. מידע נוסף על דרישות של רשתות משנה ומפרטים של טווחי כתובות IP זמין במאמר הגדרת קישוריות VPC.

# create psc network attachment
gcloud compute network-attachments create psc-na-${REGION}-agw \
  --region=${REGION} \
  --subnets=subnet-${REGION}-agw \
  --connection-preference=ACCEPT_AUTOMATIC

אימות של צירוף לרשת של נציבות שירות המדינה

# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw --region=${REGION}

מאחזרים את ה-URI של משאב ה-PSC של מחבר הרשת ומאחסנים אותו במשתנה הסביבה PSC_NA_URI. כתובת ה-URI הזו תהיה מוזכרת בהגדרת Agent Gateway ‏(networkConfig.egress.networkAttachment) כדי להקצות את ממשק ה-PSC לתעבורת נתונים יוצאת (egress) לרשת ה-VPC:

# fetch psc network attachment uri
export PSC_NA_URI=$(gcloud compute network-attachments describe psc-na-${REGION}-agw \
  --region=${REGION} \
  --format="value(selfLink.scope(v1))")
echo ${PSC_NA_URI}

יצירת Cloud NAT

‫Cloud NAT משמש לתרגום כתובת ה-IP של המקור של תעבורת הנתונים היוצאת של Agent Gateway לכתובת ה-IP הציבורית השמורה לצורך ניתוב באינטרנט הציבורי.

רק מקורות מרשת המשנה של מחבר רשת תעבורת נתונים יוצאת (egress) של Agent Gateway מוגדרים ככאלה שעומדים בדרישות לשימוש ב-Cloud NAT.

# create router for nat
gcloud compute routers create cr-nat-${SLUG}-${REGION} \
  --network=vnet-${SLUG}-internal \
  --asn=16550 \
  --region=${REGION}

# create nat
gcloud compute routers nats create nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION} \
  --nat-custom-subnet-ip-ranges="subnet-${REGION}-agw" \
  --nat-external-ip-pool="ip-${SLUG}-nat" \
  --enable-logging \
  --log-filter=ALL

אימות של Cloud NAT

בודקים את הסטטוס של Cloud NAT ומוודאים שהמקור natIP הצפוי מוגדר:

# verify cloud nat configuration
gcloud compute routers nats describe nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION} \
  --format="yaml(name,natIps,subnetworks.name)"

פלט לדוגמה:

name: nat-foo-us-central1
natIps:
- https://www.googleapis.com/compute/v1/projects/[PROJ_ID]/regions/us-central1/addresses/ip-foo-nat

יצירת רשת חיצונית

יוצרים רשת VPC נפרדת ומבודדת לאירוח המכונה הווירטואלית של שרת ה-MCP החיצוני.

# create vpc network
gcloud compute networks create vnet-${SLUG}-external --subnet-mode=custom

# create subnet for mcp server
gcloud compute networks subnets create subnet-${REGION}-mcp \
  --network=vnet-${SLUG}-external \
  --range=240.240.240.0/24 \
  --region=${REGION} \
  --enable-private-ip-google-access

יצירת כללים חיצוניים לחומת האש

יוצרים שני כללי חומת אש לתעבורת נתונים נכנסת ברשת ה-VPC החיצונית:

  • ‫2001: מאפשר יציאת TCP נכנסת 80 כדי ש-Let's Encrypt יוכל להשלים את אתגר אימות הדומיין ACME HTTP-01 עבור ${MCP_DOMAIN}.
  • ‫2002: מאפשרת בהתחלה יציאת TCP נכנסת 443 מ-0.0.0.0/0 כדי לאמת את המוכנות ל-TLS ואת האכיפה של OAuth 2.0 tokeninfo ממעטפת מקומית לפני הנעילה שלה באופן מוחלט ל-${NAT_EXT_IP}/32.
# create fw policy
gcloud compute network-firewall-policies create fw-policy-${SLUG}-external --global
# create fw policy rules
gcloud compute network-firewall-policies rules create 2001 \
  --description="allow in any http" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:80 \
  --src-ip-ranges=0.0.0.0/0 \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging

gcloud compute network-firewall-policies rules create 2002 \
  --description="allow in any https" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:443 \
  --src-ip-ranges=0.0.0.0/0 \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging
# bind fw policy to network
gcloud compute network-firewall-policies associations create \
  --name=fw-policy-bind-${SLUG}-external \
  --firewall-policy=fw-policy-${SLUG}-external \
  --network=vnet-${SLUG}-external \
  --global-firewall-policy

עד כאן בנושא הרשת... עכשיו נעבור לקטע Agent Gateway.

5. Agent Gateway

בקטע הזה נפרס את תבנית הקישוריות של הסוכן באמצעות vpcEgress: ALL_TRAFFIC, נפרס את Agent Gateway האזורי, נאציל הרשאה ל-Identity-Aware Proxy ‏(IAP) ונגדיר כללים של מדיניות מאוחדת לניהול זהויות והרשאות גישה (IAM) כדי לשלוט בבקשות יוצאות מהרשת.

יצירת תבנית קישוריות

מפרט תבנית הקישוריות מגדיר:

  • ‫egressNetworkConfig.networkAttachment: מצביע על רשת ה-PSC המצורפת, ומחבר את Agent Gateway לרשת ה-VPC.
  • ‫egressNetworkConfig.vpcEgress: מוגדר ל-ALL_TRAFFIC כדי שכל החיבורים היוצאים של הסוכן (כולל נקודות קצה של MCP באינטרנט הציבורי) ינותבו לרשת ה-VPC ויצאו באמצעות תרגום של כתובת ה-IP של המקור ב-Cloud NAT.

יוצרים ומייבאים את קובץ התצורה של תבנית הקישוריות:

# create connectivity template config file (vpcEgress: ALL_TRAFFIC)
cat > cfg/act-${AGW_NAME}.yaml << EOF
accessPath: AGENT_TO_ANYWHERE
name: projects/${PROJ_NO}/locations/${REGION}/agentConnectivityTemplates/act-${AGW_NAME}
deploymentModel: CENTRALIZED
egressNetworkConfig:
  networkAttachment: ${PSC_NA_URI}
  vpcEgress: ALL_TRAFFIC
EOF
# import connectivity template file (create template)
gcloud network-services agent-connectivity-templates import act-${AGW_NAME} \
  --source="cfg/act-${AGW_NAME}.yaml" \
  --location=${REGION}

יצירת Agent Gateway

במפרט של Agent Gateway מוגדרים:

  • ‫agentConnectivityTemplate: מצביע על תבנית הקישוריות, כדי שהשער יוכל להשתמש בצירוף רשת PSC לקישוריות VPC.
  • ‫registries: משייך את שער הגישה ל-Agent Registry האזורי כדי לפתור נקודות קצה של MCP שמופיעות בקטלוג וסכימות של כלים.

יוצרים ומייבאים את קובץ ההגדרות של Agent Gateway:

# create agent gateway config file
cat > cfg/${AGW_NAME}.yaml << EOF
name: ${AGW_NAME}
protocols:
  - MCP
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
agentConnectivityTemplate: projects/${PROJ_NO}/locations/${REGION}/agentConnectivityTemplates/act-${AGW_NAME}
registries:
  - "//agentregistry.googleapis.com/projects/${PROJ_ID}/locations/${REGION}"
EOF
# import agent gateway config file (create gateway)
gcloud network-services agent-gateways import ${AGW_NAME} \
  --source="cfg/${AGW_NAME}.yaml" \
  --location=${REGION}

אימות הפריסה של Agent Gateway

מאשרים את ההגדרה של תבנית הקישוריות ושל Agent Registry:

# verify agent gateway deployment details
gcloud network-services agent-gateways describe ${AGW_NAME} \
  --location=${REGION} \
  --format="yaml(name,agentConnectivityTemplate,registries)"

פלט לדוגמה:

agentConnectivityTemplate: projects/[PROJ_NO]/locations/us-central1/agentConnectivityTemplates/act-foo-us-central1
name: projects/[PROJ_ID]/locations/us-central1/agentGateways/agw-foo-us-central1-ata
registries:
- //agentregistry.googleapis.com/projects/[PROJ_ID]/locations/us-central1

בודקים את מחבר הרשת של PSC כדי לוודא שהשער מחובר:

# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw \
  --region=${REGION} \
  --format="yaml(connectionEndpoints)"

פלט לדוגמה:

connectionEndpoints:
- ipAddress: 10.10.10.2
  projectIdOrNum: '[TENANT_PROJ_NO]'
  status: ACCEPTED
  subnetwork: https://www.googleapis.com/compute/v1/projects/[PROJ_ID]/regions/us-central1/subnetworks/subnet-us-central1-agw

הענקת הרשאה

‫Agent Gateway מאבטח את תעבורת הנתונים היוצאת של כלי ומנהל אותה באמצעות מדיניות הרשאות (networksecurity.authzPolicies) שמשולבת עם מדיניות מאוחדת למתן הרשאות גישה (UAP) של שרת proxy לאימות זהויות (IAP).

למרות ש-Agent Gateway תומך בכללים בסיסיים של ALLOW ושל DENY, בסביבות ארגוניות נדרש ניהול מרכזי שמתמקד בזהויות. באמצעות מדיניות גישה מאוחדת ב-IAM (או מדיניות גישה), אתם יכולים לנהל כללי גישה ליציאה באמצעות מדיניות גישה רגילה ב-IAM v3.

figure3

איור 3. ארכיטקטורת הרשאות

תהליך ההרשאה מחבר בין שלושה רכיבים:

  1. מדיניות הרשאות ל-Gateway (authzPolicy):
    • משאב אזורי שמטרגט את Agent Gateway.
    • ההגדרה בוצעה באמצעות policyProfile: REQUEST_AUTHZ ו-action: CUSTOM כדי להפנות את כל בדיקות ההרשאה היוצאות לתוסף ההרשאה של IAP.
  2. IAP service extension (authzExtension):
    • משאב אזורי שמקצה הרשאת בקשה לשרת proxy לאימות זהויות (iap.googleapis.com).
    • המערכת מעריכה את כללי המדיניות במצב ENFORCE באמצעות גרסת המדיניות V2.
  3. מדיניות מאוחדת של IAM וקישור (accessPolicy ו-policyBinding):
    • משאבי IAM v3 גלובליים שמכילים כללי גישה פרטניים.
    • מאמת את זהות המנהל (principal) של SPIFFE של הסוכן שקורא, מאמת את הרשאת iap.googleapis.com/resources.egressViaIAP האוניברסלית ומעריך תנאים של Common Expression Language‏ (CEL) ביחס למאפייני היעד.

פריסת תוסף שמפנה לבדיקת הרשאות

# create authz extension config file
cat > "cfg/${AGW_NAME}-svc-ext-authz-iap.yaml" << EOF
name: ${AGW_NAME}-svc-ext-authz-iap
service: iap.googleapis.com
failOpen: false
timeout: 1s
metadata:
  iapPolicyVersion: "V2"
EOF
# import authz extension config file (create authz extension)
gcloud service-extensions authz-extensions import ${AGW_NAME}-svc-ext-authz-iap \
  --source="cfg/${AGW_NAME}-svc-ext-authz-iap.yaml" \
  --location=${REGION}

אימות תוסף ההרשאות

בודקים שתוסף ההרשאה פעיל:

# list authz extensions
gcloud service-extensions authz-extensions list \
  --location=${REGION} \
  --format="table(
    name.basename():label=NAME,
    createTime.date(tz=LOCAL):label=CREATED,
    service:label=SERVICE,
    metadata:label=METADATA,
    timeout:label=TIMEOUT
  )"

פריסת מדיניות הרשאות

יוצרים הגדרת מדיניות הרשאות ב-network-security שמטרגטת את Agent Gateway ומאצילה את אימות הבקשות לתוסף שמפנה לבדיקת הרשאות של IAP:

# create authz policy config file
cat > cfg/${AGW_NAME}-authz-policy-iap.yaml << EOF
name: ${AGW_NAME}-authz-policy-iap
target:
  resources:
    - "projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
policyProfile: REQUEST_AUTHZ
action: CUSTOM
customProvider:
  authzExtension:
    resources:
      - "projects/${PROJ_ID}/locations/${REGION}/authzExtensions/${AGW_NAME}-svc-ext-authz-iap"
EOF
# import authz policy config file (create authz policy)
gcloud network-security authz-policies import ${AGW_NAME}-authz-policy-iap \
  --source=cfg/${AGW_NAME}-authz-policy-iap.yaml \
  --location=${REGION}

אימות מדיניות ההרשאות

בודקים שמדיניות ההרשאות פעילה:

# list authz policies
gcloud network-security authz-policies list \
  --location=${REGION} \
  --format="table(
    name.basename():label=NAME,
    action:label=ACTION,
    policyProfile:label=POLICY_PROFILE,
    customProvider.authzExtension.resources[0].basename():label=CUSTOM_PROVIDER_RESOURCE
  )"

יצירת מדיניות גישה ב-IAM

הסמכת סוכנים (Agent Gateway) מעבירה עכשיו את בדיקות ההרשאות ל-IAP ומקבלת את המטא-נתונים של היעד מ-Agent Registry. בשלב הבא, מגדירים כלל מדיניות גישה מאוחדת ב-IAM כדי לשלוט בהפעלת כלי יוצאים.

‫IAP מעריך ביטויי מאפיינים ב-CEL ביחס למאפייני היעד הבאים של Agent Registry:

  • סטטוס רשום (destination.is_registered):
    • ערך בוליאני (true/false) שמציין אם היעד רשום ב-Agent Registry.
  • שם שרת MCP (destination.agent_registry.mcp_server.name):
    • שם משאב קנוני של שרת MCP שרשום ב-Agent Registry.
  • שיטת MCP‏ (destination.agent_registry.mcp_server.method):
    • שיטת ה-MCP שמופעלת (לדוגמה, tools/call, ‏ tools/list, ‏ initialize).
  • שם הכלי (destination.agent_registry.mcp_server.tool.name):
    • שם הכלי הספציפי שהופעל (למשל, get_sprocket_by_id או get_sprockets), שמאפשר הרשאה ברמת הכלי בשרתי MCP רשומים.

הגדרת כלל של מדיניות הגישה ב-IAM

מניפסט הכללים של מדיניות IAM מציין:

  • חשבונות משתמש: זהות חשבון המשתמש ב-SPIFFE שמייצגת את הסוכן של העוזר הדיגיטלי המרכזי של Gemini Enterprise.
  • הרשאות: נדרשת ההרשאה האוניברסלית iap.googleapis.com/resources.egressViaIAP לכל תעבורת הנתונים היוצאת שמנוהלת על ידי IAP.
  • תנאים: ביטוי CEL ‏ (destination.is_registered == true) שמבטיח שהסוכן יוכל להפעיל רק נקודות קצה שמופיעות בקטלוג של Agent Registry.

יוצרים את קובץ המניפסט של כלל המדיניות:

# create access policy rule file
cat > cfg/uap-policy-${SLUG}-rules.json << EOF
[
  {
    "description": "allow ge assistant to any registered service",
    "effect": "ALLOW",
    "principals": [
      "principal://${TRUST_DOMAIN}/resources/discoveryengine/projects/${PROJ_NO}/locations/global/engines/${GE_APP_INIT}/assistants/default_assistant/agents/default/core_assistant"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true"
      }
    }
  }
]
EOF

פריסת מדיניות גישה ב-IAM

יוצרים את מדיניות הגישה הגלובלית ב-IAM באמצעות הכללים שמוגדרים בקובץ המניפסט:

# create iam access policy
gcloud iam access-policies create uap-policy-${SLUG} \
  --details-rules=cfg/uap-policy-${SLUG}-rules.json \
  --project=${PROJ_ID} \
  --location=global

אימות מדיניות הגישה ב-IAM

בודקים שמדיניות הגישה ב-IAM נוצרה בהצלחה, ומעיינים בפרטי הכלל:

# show iam access policy details
gcloud iam access-policies describe uap-policy-${SLUG} \
  --project=${PROJ_ID} \
  --location=global

פלט לדוגמה:

details:
  rules:
  - conditions:
      iap.googleapis.com:
        expression: destination.is_registered == true
    description: allow ge assistant to any registered service
    effect: ALLOW
    operation:
      permissions:
      - iap.googleapis.com/resources.egressViaIAP
    principals:
    - principal://agents.global.org-[ORG_ID].system.id.goog/resources/discoveryengine/projects/[PROJ_NO]/locations/global/engines/app-foo-global_[EPOCH]/assistants/default_assistant/agents/default/core_assistant
name: projects/[PROJ_ID]/locations/global/accessPolicies/uap-policy-foo

קישור מדיניות גישה ב-IAM לפרויקט

כדי להפעיל את האכיפה בכל Agent Gateway בפרויקט, יוצרים מדיניות מחייבת שמקשרת את מדיניות הגישה של IAM למשאב הפרויקט:

# bind iam access policy to project resource
gcloud iam policy-bindings create uap-bind-${SLUG} \
  --policy="projects/${PROJ_ID}/locations/global/accessPolicies/uap-policy-${SLUG}" \
  --target-resource="//cloudresourcemanager.googleapis.com/projects/${PROJ_ID}" \
  --project=${PROJ_ID} \
  --location=global

אימות הקישור של מדיניות הגישה ב-IAM

בודקים את נקודות הקישור הפעילות למדיניות הנכונה ולטירגוט הנכון:

# show policy binding details
gcloud iam policy-bindings describe uap-bind-${SLUG} \
  --project=${PROJ_ID} \
  --location=global

בזה מסתיים החלק שקשור ל-Agent Gateway… עכשיו נעבור לקטע שרת MCP.

6. שרת MCP

בקטע הזה נפרס שרת משאבים חיצוני מסוג OAuth 2.0‏ (mcp-sprockets) של FastMCP שחושף את הכלים get_sprockets ו-get_sprocket_by_id, ונוודא שהוא מבצע אימות קריפטוגרפי של אסימוני Bearer מסוג Google OAuth 2.0 מול https://oauth2.googleapis.com/tokeninfo. בשלב הבא תגבילו את כלל חומת האש החיצונית לכתובת ה-IP הסטטית של Cloud NAT.

פריסת שרת MCP חיצוני

סקריפט הפעלה מתקין את Caddy ואת FastMCP, מפעיל את שרת mcp-sprockets ב-127.0.0.1:8080 ומגדיר את Caddy להצגת HTTPS ב-${MCP_DOMAIN}.

בכל פעם ש-Gemini Enterprise קורא לכלי MCP (לדוגמה, get_sprockets ו-get_sprocket_by_id), השרת:

  1. קריאת כתובת ה-IP של המקור של המתקשר מהכותרת X-Forwarded-For (egress_source_ip).
  2. מחפשים את הכותרת Authorization: Bearer [access_token] ומאמתים את הטוקן מול נקודת האימות של Google OAuth 2.0 ‏ (https://oauth2.googleapis.com/tokeninfo?access_token=[token]).
  3. החזרת "authenticated_user" ([user_email]) לצד "egress_source_ip", שמוכיחה ששרת ה-MCP החיצוני אימת גם את כתובת ה-IP הסטטית של Cloud NAT וגם את הזהות של משתמש הקצה ב-Google OAuth 2.0.
# create startup script for the sprockets mcp server vm
cat > cfg/server-startup.sh << 'EOF'
#!/bin/bash
set -euo pipefail

MCP_DOMAIN=$(curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/attributes/mcp-domain)

export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y caddy python3-venv curl

mkdir -p /opt/mcp-sprockets
python3 -m venv /opt/mcp-sprockets/venv
/opt/mcp-sprockets/venv/bin/pip install --upgrade pip
/opt/mcp-sprockets/venv/bin/pip install "fastmcp>=2.14.0" "uvicorn>=0.34.0"

cat <<'PYEOF' > /opt/mcp-sprockets/main.py
import json
import urllib.parse
import urllib.request
from typing import Any, Dict
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_headers
from mcp.types import ToolAnnotations

mcp = FastMCP("mcp-sprockets")

SPROCKETS = {
    "SPK-101": {"id": "SPK-101", "model": "ANSI-40-12T", "teeth": 12, "pitch_in": 0.5, "material": "1045 Carbon Steel", "stock": 140, "unit_price": 18.50},
    "SPK-102": {"id": "SPK-102", "model": "ANSI-40-24T", "teeth": 24, "pitch_in": 0.5, "material": "1045 Carbon Steel", "stock": 85, "unit_price": 29.00},
    "SPK-103": {"id": "SPK-103", "model": "ANSI-60-16T", "teeth": 16, "pitch_in": 0.75, "material": "304 Stainless Steel", "stock": 60, "unit_price": 42.00},
    "SPK-104": {"id": "SPK-104", "model": "ANSI-60-32T", "teeth": 32, "pitch_in": 0.75, "material": "304 Stainless Steel", "stock": 35, "unit_price": 68.00},
    "SPK-105": {"id": "SPK-105", "model": "ANSI-80-20T", "teeth": 20, "pitch_in": 1.0, "material": "4140 Alloy Steel", "stock": 50, "unit_price": 95.00}
}

def verify_google_oauth_token(headers: Dict[str, str]) -> str:
    token = headers.get("authorization", "")[7:].strip() if headers.get("authorization", "").lower().startswith("bearer ") else ""
    if not token:
        return "unauthenticated (missing Bearer token)"
    url = "https://oauth2.googleapis.com/tokeninfo?" + urllib.parse.urlencode({"access_token": token})
    with urllib.request.urlopen(url, timeout=5) as resp:
        info = json.loads(resp.read().decode("utf-8"))
        return info.get("email", info.get("sub", "verified-oauth-user"))

@mcp.tool(
    description="list all sprockets in the inventory catalog",
    annotations=ToolAnnotations(
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False
    )
)
def get_sprockets() -> Dict[str, Any]:
    headers = get_http_headers(include_all=True)
    caller_ip = headers.get("x-forwarded-for", "unknown").split(",")[0].strip()
    try:
        user_email = verify_google_oauth_token(headers)
    except Exception as exc:
        return {"egress_source_ip": caller_ip, "authenticated_user": None, "error": f"OAuth token verification failed: {exc}"}
    return {
        "egress_source_ip": caller_ip,
        "authenticated_user": user_email,
        "sprockets": list(SPROCKETS.values())
    }

@mcp.tool(
    description="fetch details of a specific sprocket by its ID (eg, SPK-101)",
    annotations=ToolAnnotations(
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False
    )
)
def get_sprocket_by_id(sprocket_id: str) -> Dict[str, Any]:
    headers = get_http_headers(include_all=True)
    caller_ip = headers.get("x-forwarded-for", "unknown").split(",")[0].strip()
    try:
        user_email = verify_google_oauth_token(headers)
    except Exception as exc:
        return {"egress_source_ip": caller_ip, "authenticated_user": None, "error": f"OAuth token verification failed: {exc}"}
    sprocket = SPROCKETS.get(sprocket_id.upper())
    if sprocket:
        return {
            "egress_source_ip": caller_ip,
            "authenticated_user": user_email,
            "sprocket": sprocket
        }
    return {
        "egress_source_ip": caller_ip,
        "authenticated_user": user_email,
        "error": f"Sprocket '{sprocket_id}' not found."
    }

app = mcp.http_app(stateless_http=True)
PYEOF

cat <<'SVCEOF' > /etc/systemd/system/mcp-sprockets.service
[Unit]
Description=Sprockets FastMCP Server
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/mcp-sprockets
ExecStart=/opt/mcp-sprockets/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8080 --forwarded-allow-ips="*"
Restart=always

[Install]
WantedBy=multi-user.target
SVCEOF

systemctl daemon-reload
systemctl enable --now mcp-sprockets.service
systemctl restart mcp-sprockets.service

cat <<CADDYEOF > "/etc/caddy/Caddyfile"
${MCP_DOMAIN} {
    reverse_proxy 127.0.0.1:8080
}
CADDYEOF

systemctl restart caddy
EOF
# create vm instance
gcloud compute instances create vm-${MCP_NAME} \
  --zone=${ZONE} \
  --machine-type=e2-micro \
  --image-family=debian-12 \
  --image-project=debian-cloud \
  --shielded-secure-boot \
  --network-interface="subnet=subnet-${REGION}-mcp,address=${MCP_EXT_IP}" \
  --metadata="mcp-domain=${MCP_DOMAIN}" \
  --metadata-from-file="startup-script=cfg/server-startup.sh"

אימות המוּכנוּת (readiness) של שרת ה-MCP ואימות OAuth 2.0 tokeninfo

מחכים כמה דקות עד שסקריפט לטעינה בזמן ההפעלה יתקין את FastMCP ואת Caddy ויקצה את אישור ה-TLS של Let's Encrypt.

# poll until caddy serves valid tls and fastmcp responds to tools/list
for i in $(seq 1 30); do
  RESP=$(curl -s -X POST "${MCP_URL}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' || true)
  if echo "${RESP}" | grep -q "get_sprockets"; then
    echo "mcp server and let's encrypt tls ready"
    break
  fi
  echo "...waiting for mcp server and let's encrypt tls (attempt ${i}/30)..."
  sleep 5
done

בודקים את פענוח ה-DNS של sslip.io, את אישור ה-TLS של Let's Encrypt שמוגש על ידי Caddy ואת התגובה של FastMCP tools/list:

# query mcp server for tools list
curl -v -X POST "${MCP_URL}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
  2>&1 | grep -E "Connected to|subject:|issuer:|SSL certificate|HTTP/|event:|data:"

פלט לדוגמה:

* Connected to mcp.203.0.113.20.sslip.io (203.0.113.20) port 443
*  subject: CN=mcp.203.0.113.20.sslip.io
*  issuer: C=US; O=Let's Encrypt; CN=E5
*  SSL certificate verify ok.
> POST /mcp HTTP/2
< HTTP/2 200
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_sprockets","description":"list all sprockets in the inventory catalog","inputSchema":{"properties":{},"type":"object"},"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true}},{"name":"get_sprocket_by_id","description":"fetch details of a specific sprocket by its ID (eg, SPK-101)","inputSchema":{"properties":{"sprocket_id":{"type":"string"}},"required":["sprocket_id"],"type":"object"},"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true}}]}}

לפני שנועלים את חומת האש החיצונית, מריצים בדיקה tools/call של העברת טוקן Bearer מסוג Google OAuth 2.0 ‏ (gcloud auth print-access-token) כדי לוודא ש-mcp-sprockets מאמת את הטוקן מול https://oauth2.googleapis.com/tokeninfo ומחזיר את כתובת האימייל שלכם "authenticated_user" לצד כתובת ה-IP של המקור שממנו מתבצעת הקריאה:

# query mcp server (test tools/call with google oauth 2.0 bearer token)
curl -s -X POST "${MCP_URL}" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "get_sprocket_by_id",
      "arguments": {"sprocket_id": "SPK-105"}
    }
  }' | sed -n 's/^data: //p' | jq '.result.structuredContent'

פלט לדוגמה (שמציג את כתובת ה-IP הישירה של Cloud Shell לפני נעילת חומת האש ואת האימייל המאומת של Google OAuth):

{
  "egress_source_ip": "198.51.100.154",
  "authenticated_user": "user@example.com",
  "sprocket": {
    "id": "SPK-105",
    "model": "ANSI-80-20T",
    "teeth": 20,
    "pitch_in": 1.0,
    "material": "4140 Alloy Steel",
    "stock": 50,
    "unit_price": 95.0
  }
}

נעילת חומת האש לכתובת IP סטטית של Cloud NAT

מעדכנים את מדיניות חומת האש של רשת ה-VPC החיצונית כך שהיציאה 443 ב-TCP תקבל תעבורת נתונים רק מכתובת ה-IP החיצונית הסטטית של Cloud NAT ‏ (${NAT_EXT_IP}/32):

# create fw policy rule allowing https only from cloud nat static ip
gcloud compute network-firewall-policies rules create 2003 \
  --description="allow in any https from nat ip" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:443 \
  --src-ip-ranges="${NAT_EXT_IP}/32" \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging
# delete temporary open fw policy rules
gcloud compute network-firewall-policies rules delete 2001 \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

gcloud compute network-firewall-policies rules delete 2002 \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

בודקים אם הוגדרו כללים במדיניות חומת האש החיצונית:

# list all rules in the global network firewall policy
gcloud compute network-firewall-policies describe fw-policy-${SLUG}-external \
  --global \
  --flatten="rules[]" \
  --format="table(
    rules.priority:label=RULE,
    rules.action:label=ACTION,
    rules.direction:label=DIRECTION,
    firstof(
      rules.match.srcIpRanges,
      rules.match.srcAddressGroups,
      rules.match.srcSecureTags.name
    ).map().sub('projects/[^/]+/(locations/global/|global/)', '').list():label=SRC,
    firstof(
      rules.targetForwardingRules,
      rules.match.destIpRanges,
      rules.targetType
    ).map().sub('projects/[^/]+/(locations/global/|global/)', '').list():label=DST
  )"

בודקים שחיבורים ישירים מחוץ לכתובת ה-IP הסטטית של Cloud NAT חסומים עכשיו:

# query mcp server
curl -i --connect-timeout 5 "${MCP_URL}" \
  || echo "Expected: Direct access blocked (not originating from ${NAT_EXT_IP})"

פלט לדוגמה:

curl: (28) Connection timed out after 5001 milliseconds
Expected: Direct access blocked (not originating from 203.0.113.10)

בזה מסתיים החלק על שרת ה-MCP... עכשיו נעבור לקטע Agent Registry.

7. Agent Registry

‫Agent Registry הוא מלאי מרכזי של כל הסוכנים, שרתי ה-MCP ונקודות הקצה (ממשקי API) בסביבת ה-AI. הוא גם קטלוג שאפשר להשתמש בו כדי לרשום, לחפש ולגלות כלים ושירותים רשומים אחרים לשימוש באפליקציות AI. ‫Agent Gateway משתמש במודל הנתונים של המרשם כבסיס למדיניות כדי לאכוף בקרת גישה ליציאה. ההרשאות של תפקידי IAM שניתנות לסוכני חשבונות משתמשים נבדקות מול המדיניות שקשורה למשאב המרשם.

בקטע הזה נרשום את שרת ה-MCP ואת סכימת הכלים שלו (toolspec.json) ב-Agent Registry. כדי לתמוך באינטראקציה של Gemini Enterprise עם ממשקי API ושירותים של Google דרך Agent Gateway, תיצרו גם שירות יחיד של נקודת קצה של מאגר (oauth2-googleapis) שמציין את ממשקי Google API שמשמשים את Gemini Enterprise להחלפת קוד האימות באסימונים.

רישום שרת MCP

כדי לאפשר ל-Gemini Enterprise לגלות את הכלים שזמינים בשרת ה-MCP, צריך לספק קובץ מפרט כלים (cfg/toolspec.json) כשרושמים את השירות ב-Agent Registry.

יצירת מפרט של כלי MCP

# create tool spec file for the sprockets mcp server
cat <<'EOF' > cfg/toolspec.json
{
  "tools": [
    {
      "name": "get_sprockets",
      "description": "List all sprockets in the inventory catalog.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "isReadOnly": true,
      "isDestructive": false,
      "isIdempotent": true,
      "isOpenWorld": false
    },
    {
      "name": "get_sprocket_by_id",
      "description": "Fetch details of a specific sprocket by its ID (e.g., SPK-101).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "sprocket_id": {
            "type": "string",
            "description": "The sprocket identifier (e.g., SPK-101, SPK-105)."
          }
        },
        "required": [
          "sprocket_id"
        ]
      },
      "isReadOnly": true,
      "isDestructive": false,
      "isIdempotent": true,
      "isOpenWorld": false
    }
  ]
}
EOF

יצירת שירות שרת MCP

# create registry service (mcp server)
gcloud agent-registry services create ${MCP_NAME} \
  --project=${PROJ_ID} \
  --location=${REGION} \
  --display-name="${MCP_DOMAIN}" \
  --description="MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: You MUST \
ALWAYS invoke this tool (get_sprockets or get_sprocket_by_id) for ANY question \
about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through \
SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, \
material, stock levels, unit prices, authenticated_user, or egress_source_ip \
verification. NEVER guess or fabricate inventory data yourself and NEVER \
transfer sprocket queries to file_and_coding_agent or web search. Always \
delegate every sprocket or inventory lookup to this tool." \
  --mcp-server-spec-type=tool-spec \
  --mcp-server-spec-content=cfg/toolspec.json \
  --interfaces=protocolBinding=JSONRPC,url="${MCP_URL}"

אימות שרת MCP ב-Agent Registry

מוודאים שהשירות מופיע כשרת MCP רשום באזור, יחד עם כתובת ה-URL של נקודת הקצה והכלים הזמינים:

# list registry regional mcp servers
gcloud agent-registry mcp-servers list \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --format="table(
    name.basename():label=REGISTRY_ID,
    displayName:label=DISPLAY_NAME,
    interfaces[0].url:label=ENDPOINT_URL,
    tools[].name.list():label=TOOLS
  )"

פלט לדוגמה:

REGISTRY_ID                                          DISPLAY_NAME               ENDPOINT_URL                          TOOLS
agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8   mcp.203.0.113.20.sslip.io  https://mcp.203.0.113.20.sslip.io/mcp  get_sprockets,get_sprocket_by_id

כדי לראות שהשירות רושם את ההגדרות המדויקות של הכלים, את סכימות הקלט ואת הערות ההתנהגות של כל כלי, אפשר לעיין במפרט ההגדרות של השירות:

# describe mcp server tool specs
gcloud agent-registry services describe ${MCP_NAME} \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --format="yaml(mcpServerSpec.content.tools)"

רישום של נקודת קצה של OAuth

כדי לאפשר ל-backend של Gemini Enterprise DataConnector (שפועל כלקוח OAuth בתהליך ההרשאה) לשלוח בקשות POST לשרת ההרשאה של Google OAuth 2.0‏ (https://oauth2.googleapis.com/token) דרך Agent Gateway, צריך לרשום את נקודת הקצה של הטוקן ב-Agent Registry כדי ש-IAP יוכל להעריך את destination.is_registered == true.

יצירת שירות נקודת קצה

# create registry service (endpoint)
gcloud agent-registry services create oauth2-googleapis \
  --project=${PROJ_ID} \
  --location=${REGION} \
  --display-name="oauth2.googleapis.com" \
  --description="google oauth2 endpoint" \
  --endpoint-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://oauth2.googleapis.com

אימות הרישום של נקודת קצה

# list registry regional endpoints
gcloud agent-registry endpoints list \
  --project=${PROJ_ID} \
  --location=${REGION} \
  --flatten="interfaces[]" \
  --format="table(
    displayName,
    name.basename():label=ENDPOINT_ID,
    interfaces.url:label=URL
  )"

הפלט הצפוי:

DISPLAY_NAME           ENDPOINT_ID                                         URL
oauth2.googleapis.com  agentregistry-00000000-0000-0000-0012-3456789abcde  https://oauth2.googleapis.com

מדיניות גישה ב-IAM

מכיוון שמדיניות הגישה של IAM מגדירה מדיניות הרשאות לכל שירות רשום שמבוקש על ידי זהות הסוכן של אפליקציית Gemini Enterprise‏ core_assistant, לא נדרשים קישורי מדיניות נוספים של IAM להחלפת טוקן OAuth.

  - conditions:
      iap.googleapis.com:
        expression: destination.is_registered == true
    description: allow ge assistant to any registered service
    effect: ALLOW

כאן מסתיים החלק שקשור ל-Agent Registry... עכשיו נעבור לקטע Gemini Enterprise.

8. Gemini Enterprise

בקטע הזה מגדירים את ספק הזהויות, את רישיון הניסיון ואת פרטי הכניסה של לקוח Google Identity OAuth 2.0, יוצרים אפליקציית Gemini Enterprise (${GE_APP_ID}), מקשרים את התעבורה היוצאת ל-Agent Gateway (${AGW_URI}) ומחברים את שרת ה-MCP של sprockets הרשום כמאגר נתונים מותאם אישית של MCP מסוג OAuth 2.0.

יצירת אפליקציית Gemini Enterprise

השיטה הבאה משתמשת ב-API‏ discoveryengine.googleapis.com כדי ליצור את המשאבים וההגדרות של אפליקציית Gemini Enterprise. הוראות להגדרה באמצעות ממשק המשתמש של מסוף Google Cloud מופיעות במאמר יצירת אפליקציה.

# create engine (ge app)
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines?engineId=${GE_APP_INIT}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "displayName": "${GE_APP_DISPLAY_NAME}",
  "dataStoreIds": [],
  "solutionType": "SOLUTION_TYPE_SEARCH",
  "industryVertical": "GENERIC",
  "appType": "APP_TYPE_INTRANET",
  "searchEngineConfig": {
    "searchTier": "SEARCH_TIER_ENTERPRISE",
    "searchAddOns": [
      "SEARCH_ADD_ON_LLM"
    ]
  },
  "commonConfig": {
    "companyName": "${GE_APP_ORG_NAME}"
  },
  "observabilityConfig": {
    "observabilityEnabled": true,
    "sensitiveLoggingEnabled": true
  }
}
EOF

אימות יצירת האפליקציה

# fetch engine (ge app) id
export GE_APP_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq -r --arg name "${GE_APP_DISPLAY_NAME}" '.engines[] | select(.displayName==$name) | .name | split("/") | last')

echo "engine (ge app) id: ${GE_APP_ID}"

כדי לראות את ההגדרה שנוצרה, צופים בפרטי המנוע:

# get engine (ge app) details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

הפעלת ספק זהויות

מפעילים את Google Identity כספק הזהויות לאימות משתמשי קצה באפליקציית Gemini Enterprise.

בשיטה הבאה משתמשים ב-discoveryengine.googleapis.com API כדי להגדיר את ספק הזהויות של אפליקציית Gemini Enterprise. הוראות להגדרה באמצעות ממשק המשתמש של מסוף Google Cloud מופיעות במאמר הגדרת ספק זהויות.

# set identity provider
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "idpConfig": {
    "idpType": "GSUITE"
  }
}
EOF

אימות ספק הזהויות

# show identity provider
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

הפלט "idpType": "GSUITE" תואם לספק הזהויות של Google.

(אופציונלי) הפעלת רישיון ניסיון ל-Gemini Enterprise

אם אתם משתמשים בפרויקט שהוקצו לו רישיונות ל-Gemini Enterprise, אתם יכולים לדלג על השלב הזה. אם אתם משתמשים בפרויקט חדש בלי רישיון, ממשיכים לפעול לפי השלבים הבאים.

יוצרים משאב של הגדרת רישיון כדי להקצות מושבי משתמשים ב-Gemini Enterprise למשך 30 ימים. הפעולה הזו תגדיר את רישיון ברירת המחדל לניסיון החדש, כך שלכל משתמש שמתחבר יוקצה מושב באופן אוטומטי:

# configure free trial subscription
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/licenseConfigs?licenseConfigId=free_trial_gemini" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "subscriptionTier": "SUBSCRIPTION_TIER_SEARCH_AND_ASSISTANT",
  "freeTrial": true
}
EOF

אימות החלת הרישיון

# show license config
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/licenseConfigs/free_trial_gemini" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

בודקים אם יש "subscriptionTerm": "SUBSCRIPTION_TERM_ONE_MONTH" ו"freeTrial": true.

# verify auto-registration enabled on default user store
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/userStores/default_user_store" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

בודקים אם יש ../free_trial_gemini" ו"enableLicenseAutoRegister": true.

כדי לאפשר ל-Gemini Enterprise (לקוח OAuth 2.0) לקבל אסימון Bearer של Google OAuth 2.0 שהוקצה למשתמש ולהציג אותו לmcp-sprocketsשרת המשאבים בשמכם, צריך להגדיר את מסך ההסכמה של Google Auth Platform OAuth וליצור מזהה לקוח של אפליקציית אינטרנט ב-OAuth 2.0 בפרויקט.

מדפיסים את כתובת ה-URL הישירה של מסוף Google Cloud עבור Google Auth Platform Overview בפרויקט:

# print google auth platform consent screen url
echo "https://console.cloud.google.com/auth/overview?project=${PROJ_ID}"
  1. פותחים את כתובת ה-URL בדפדפן ולוחצים על Get started (אם עדיין לא הגדרתם את הפרויקט).
  2. בקטע App Information, מגדירים את App name ל-mcp-sprockets-oauth, בוחרים את כתובת האימייל (${USER_IDENTITY}) בתור User support email ולוחצים על Next.
  3. בקטע קהל, בוחרים באפשרות פנימי (מומלץ אם החשבון שלכם שייך לארגון Google Workspace) או באפשרות חיצוני (אם אתם משתמשים בחשבון אישי). אם בוחרים באפשרות חיצוני, מוסיפים את ${USER_IDENTITY} בקטע **משתמשי בדיקה של קהל**, ואז לוחצים על הבא.
  4. מזינים את כתובת האימייל בקטע פרטים ליצירת קשר, מאשרים את הסכם המדיניות ולוחצים על יצירה.

2. יצירת מזהה לקוח ב-OAuth 2.0 לאפליקציית אינטרנט

הדפסה של כתובת ה-URL הישירה של מסוף Google Cloud לדף Clients:

# print google auth platform clients url
echo "https://console.cloud.google.com/auth/clients?project=${PROJ_ID}"
  1. פותחים את כתובת ה-URL ולוחצים על + Create client (יצירת לקוח).
  2. בוחרים באפשרות אפליקציית אינטרנט בתור Application type ומגדירים את Name ל-ge-mcp-sprockets-client.
  3. מסמנים את התיבה This client will be used by an AI-powered agent (הלקוח הזה ישמש סוכן מבוסס-AI) כדי להגדיר את לקוח ה-OAuth עבור תהליכי עבודה של סוכני AI שפועלים בשם המשתמשים.
  4. בקטע Authorized redirect URIs (כתובות URI מורשות להפניה אוטומטית), לוחצים על + Add URI (הוספת כתובת URI) ומוסיפים את כתובת ה-URI של קריאת החזרה (callback) של Gemini Enterprise OAuth:
    • https://vertexaisearch.cloud.google.com/oauth-redirect
  5. לוחצים על Create, מעתיקים את מזהה הלקוח ואת הסוד של הלקוח שנוצרו, ומייצאים אותם בסשן של מעטפת הפקודות:

מגדירים את מזהה הלקוח ואת סוד הלקוח ב-OAuth 2.0 שנוצרו בשלב הקודם:

# set oauth 2.0 client id (paste in id)
export OAUTH_CLIENT_ID="SET_YOUR_OAUTH_CLIENT_ID_HERE"
# set oauth 2.0 client secret (paste in secret)
export OAUTH_CLIENT_SECRET="SET_YOUR_OAUTH_CLIENT_SECRET_HERE"

בודקים שמזהה הלקוח מוגדר:

# print oauth 2.0 client id
echo "oauth client id: ${OAUTH_CLIENT_ID}"

איגוד אל Agent Gateway

ניתוב תנועה יוצאת מ-Gemini Enterprise דרך Agent Gateway יוצר גבול מרכזי לאכיפה ולניתוב של כתובות IP סטטיות לכל הפעלות כלי סוכן ה-AI:

  • אכיפת מדיניות מרכזית: Agent Gateway פועל כ-proxy מוטבע שבודק בקשות לכלים יוצאים מול מדיניות הרשאות לפני שהתנועה יוצאת מסביבת הסוכן.
  • יציאה דטרמיניסטית מ-VPC ומ-Cloud NAT: קישור של Gemini Enterprise ל-Agent Gateway שהוגדר עם vpcEgress: ALL_TRAFFIC מבטיח שההפניות לכלים ינותבו דרך צירוף הרשת של PSC ויצאו באמצעות כתובת ה-IP הסטטית השמורה של Cloud NAT.
  • יכולת ביקורת מאוחדת: מאפשרת רישום מרכזי של בקשות ושל נתיבי ביקורת בכל שרתי ה-MCP המחוברים.

מטמיעים תיקון במנוע agentGatewaySetting כדי להפעיל:

# bind engine (ge app) to agent gateway
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}?updateMask=agentGatewaySetting.defaultEgressAgentGateway.name" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "agentGatewaySetting": {
    "defaultEgressAgentGateway": {
      "name": "${AGW_URI}"
    }
  }
}
EOF

אימות הקישור של Agent Gateway

מאחזרים את הגדרת האפליקציה כדי לאשר את agentGatewaySetting הקישור:

# verify engine (ge app) agent gateway configuration
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name: .name, displayName: .displayName, agentGatewaySetting: .agentGatewaySetting}'

פלט לדוגמה:

{
  "name": "projects/[PROJ_NO]/locations/global/collections/default_collection/engines/app-foo-global_[EPOCH]",
  "displayName": "foo global app",
  "agentGatewaySetting": {
    "defaultEgressAgentGateway": {
      "name": "projects/[PROJ_ID]/locations/us-central1/agentGateways/agw-foo-us-central1-ata"
    }
  }
}

יצירת מאגר נתונים של שרת MCP בהתאמה אישית

בקטע הזה נסביר איך לקשר את mcp-sprockets ל-Gemini Enterprise באמצעות יצירה של מאגר נתונים מותאם אישית של MCP עם OAuth 2.0, שמוגדר עם Google Identity (https://accounts.google.com/o/oauth2/v2/auth ו-https://oauth2.googleapis.com/token).

התהליך הזה כולל שני שלבים באמצעות Discovery Engine API:

  1. יצירה (:setUpDataConnector): יוצרת משאב ייעודי של Collection (${MCP_NAME}-collection-%timestamp), מצרפת את DataConnector (custom_mcp) עם auth_type: "OAUTH" ומקצה את DataStore (..._mcp_data) שמשמש כגיבוי.
  2. הפעלה (PATCH .../dataConnector?updateMask=actionConfig): מפעילה את זמן הריצה של פעולת המחבר (actionState: "ACTIVE") באמצעות מפרט כלי Agent Registry, פרמטרים של Google Identity OAuth 2.0 ומקשרת את DataStore (dataStoreIds) ל-Gemini Enterprise Engine.
# fetch mcp server agent registry resource name
export MCP_REGISTRY_URI=$(gcloud agent-registry mcp-servers list \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --filter="displayName:${MCP_DOMAIN}" \
  --format="value(name)")

echo "mcp registry name: ${MCP_REGISTRY_URI}"
echo "mcp url: ${MCP_URL}"

יצירת מחבר נתונים

# create custom mcp data connector with google identity oauth 2.0 and link to engine
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}:setUpDataConnector" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "collectionId": "${MCP_NAME}-collection-$(date +%s)",
  "collectionDisplayName": "${MCP_NAME}-collection",
  "dataConnector": {
    "dataSource": "custom_mcp",
    "dataSourceVersion": 1,
    "params": {
      "oauth_access_token": "unused"
    },
    "refreshInterval": "86400s",
    "entities": [
      {
        "entityName": "mcp_data"
      }
    ],
    "connectorModes": [
      "FEDERATED"
    ],
    "actionConfig": {
      "isActionConfigured": true,
      "createBapConnection": true,
      "actionParams": {
        "auth_type": "OAUTH",
        "instance_uri": "${MCP_URL}",
        "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
        "auth_uri_params": "&access_type=offline&prompt=consent",
        "token_uri": "https://oauth2.googleapis.com/token",
        "client_id": "${OAUTH_CLIENT_ID}",
        "client_secret": "${OAUTH_CLIENT_SECRET}",
        "scopes": "openid email profile",
        "client_secret_basic_override": true,
        "mcp_server_source": "REGISTRY_MCP",
        "registry_mcp_server_name": "${MCP_REGISTRY_URI}",
        "mcp_agent_instructions": "MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: Always invoke get_sprockets or get_sprocket_by_id for any question about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, material, stock levels, unit prices, authenticated_user, or egress_source_ip verification.",
        "use_agent_gateway_egress": true,
        "agent_gateway_engine": "projects/${PROJ_ID}/locations/global/collections/default_collection/engines/${GE_APP_ID}"
      }
    }
  }
}
EOF

אימות של יצירת מחבר נתונים

# fetch collection id
export GE_COLLECTION_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq -r --arg dname "${MCP_NAME}-collection" '.collections[] | select(.displayName == $dname) | .name | split("/") | last' | head -n 1)

echo "ge collection id: ${GE_COLLECTION_ID}"

בודקים שהשדה "registry_mcp_server_name" מאוכלס ב-UUID של Agent Registry עבור שרת ה-MCP, ושהשדה "auth_type" הוא OAUTH:

# show data connector details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}/dataConnector" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name, state, actionState, connectorModes, bapConfig, auth_type: .actionConfig.actionParams.auth_type, registry_mcp_server_name: .actionConfig.actionParams.registry_mcp_server_name}'

צופים ברשומה של שרת MCP בממשק המשתמש של מסוף Google Cloud:

echo "mcp server registry page url: https://console.cloud.google.com/agent-platform/agent-registry/mcp-servers/${REGION}/${MCP_REGISTRY_URI##*/}/overview?project=${PROJ_ID}"

הפעלת מחבר נתונים

# activate and bind data connector with google identity oauth 2.0
curl -s -X PATCH "https://discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/global/collections/${GE_COLLECTION_ID}/dataConnector?updateMask=actionConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "name": "projects/${PROJ_ID}/locations/global/collections/${GE_COLLECTION_ID}/dataConnector",
  "actionConfig": {
    "isActionConfigured": true,
    "createBapConnection": true,
    "actionParams": {
      "auth_type": "OAUTH",
      "instance_uri": "${MCP_URL}",
      "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
      "auth_uri_params": "&access_type=offline&prompt=consent",
      "token_uri": "https://oauth2.googleapis.com/token",
      "client_id": "${OAUTH_CLIENT_ID}",
      "client_secret": "${OAUTH_CLIENT_SECRET}",
      "scopes": "openid email profile",
      "client_secret_basic_override": true,
      "mcp_server_source": "REGISTRY_MCP",
      "registry_mcp_server_name": "${MCP_REGISTRY_URI}",
      "mcp_agent_instructions": "MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: Always invoke get_sprockets or get_sprocket_by_id for any question about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, material, stock levels, unit prices, authenticated_user, or egress_source_ip verification.",
      "use_agent_gateway_egress": true,
      "agent_gateway_engine": "projects/${PROJ_ID}/locations/global/collections/default_collection/engines/${GE_APP_ID}"
    }
  }
}
EOF

אימות של קישורים לשרתי MCP בהתאמה אישית

# show engine (ge app) details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name: .name, dataStoreIds: .dataStoreIds, agentGatewaySetting: .agentGatewaySetting}'

בודקים את מאגר הנתונים המקושר "dataStoreIds": ["mcp-sprockets-collection-[EPOCH]_mcp_data"].

# show collection details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq --arg app "${GE_APP_ID}" '.collections[] | select(.dataConnector.actionConfig.actionParams.agent_gateway_engine // "" | endswith($app)) | .dataConnector | {name: .name, state: .state, actionState: .actionState, connectorModes: .connectorModes, auth_type: .actionConfig.actionParams.auth_type}'

בודקים אם יש "state": "ACTIVE" ו"auth_type": "OAUTH".

פעולות שקשורות לכלים

כשבודקים את mcp-sprockets-collection מאגר הנתונים בלוח הבקרה של Gemini Enterprise, אפשר לראות שלא נעשה שימוש בכרטיסייה פעולות והלחצן ↻ טעינה מחדש של פעולות מותאמות אישית מושבת. זו התנהגות צפויה.

צופים בדף הפרטים של מאגר הנתונים בממשק המשתמש של מסוף Google Cloud:

echo "data store details page url: https://console.cloud.google.com/gemini-enterprise/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}/connector/details?project=${PROJ_ID}"

בהתאם לאופן שבו מקשרים שרת MCP בהתאמה אישית ל-Gemini Enterprise, גילוי הכלים והניהול שלהם מתבצעים באחת משתי דרכים:

  • MCP מותאם אישית ישירות (BYO_MCP תהליך עבודה): כשמגדירים שרת MCP מותאם אישית ישירות ב-Gemini Enterprise בלי Agent Registry, מאגר הנתונים עצמו מנהל את קטלוג הכלים (connectorModes: ["FEDERATED", "ACTIONS"]). צריך לפתוח את הכרטיסייה פעולות, ללחוץ על ↻ טעינה מחדש של פעולות מותאמות אישית כדי לאחזר את סכימת tools/list, ולהפעיל או להשבית כל כלי בנפרד (add ו-subtract) בממשק המשתמש.
  • ייבוא מ-Agent Registry (REGISTRY_MCP תהליך העבודה שמשמש במעבדת התכנות הזו): כשמייבאים שרת MCP מ-Agent Registry, ‏ Agent Registry משמש כמקור האמת המוסמך לנקודת הקצה של ה-MCP, למטא-נתונים של הממשק ולספריית הכלים שלו (connectorModes: ["FEDERATED"]). Gemini Enterprise מפעיל באופן אוטומטי את כלי ה-MCP הרשומים בזמן הריצה דרך ההגדרה של מנוע Agent Gateway, בלי שתצטרכו לטעון מחדש או להפעיל ולכבות פעולות באופן ידני בממשק המשתמש של מאגר הנתונים.

כאן מסתיים החלק של Gemini Enterprise… עכשיו נעבור לקטע אימות.

9. אימות

בקטע הזה, משלימים את תהליך ההסכמה מהמשתמש ב-Google OAuth 2.0, מפעילים כלי MCP בשרת החיצוני mcp-sprockets מ-Gemini Enterprise, ומוודאים ששרת ה-MCP החיצוני אימת גם את כתובת ה-IP הסטטית של Cloud NAT שהוזמנה (egress_source_ip) וגם את הזהות שלכם ב-Google OAuth 2.0 (authenticated_user).

גישת משתמש

יוצרים את כתובת ה-URL של אפליקציית האינטרנט של Gemini Enterprise:

# fetch app user url
export GE_WIDGET_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/widgetConfigs/default_search_widget_config" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  | jq -r '.configId')

export GE_APP_USER_URL="https://vertexaisearch.cloud.google.com/home/cid/${GE_WIDGET_ID}"

echo "app user url: ${GE_APP_USER_URL}"

לפני שמריצים שאילתה לסוכן, צריך להשלים תהליך חד-פעמי של מתן הסכמה ל-OAuth 2.0, שמעניק ל-Gemini Enterprise הרשאה לקבל אסימון גישה לזהות שלכם ב-Google ‏ (${USER_IDENTITY}). כך Gemini Enterprise יכול לאמת קריאות כלים יוצאות דרך Agent Gateway, כדי ששרת mcp-sprockets החיצוני יוכל לאמת את הזהות שלכם בכל בקשה.

  1. פותחים את ${GE_APP_USER_URL} בדפדפן ולוחצים על Get started (תחילת העבודה).
  2. בתיבת הצ'אט, לוחצים על סמל המחברים (סמל חלקי הפאזל) ואז על אישור לצד mcp-sprockets-collection.
  3. ייפתח חלון חדש עם ההנחיה בחירת חשבון כדי להמשיך אל mcp-sprockets-oauth (שם האפליקציה של מסך ההסכמה ל-OAuth שהגדרתם). בוחרים את חשבון המשתמש (${USER_IDENTITY}).
  4. במסך ההסכמה ל-OAuth של Google, בודקים את ההרשאות הנדרשות שמאפשרות ל-Google (ספק הזהויות) לשתף את זהות המשתמש (openid, ‏ email, ‏ profile) עם mcp-sprockets-oauth, ואז לוחצים על המשך.
  5. לוחצים שוב על סמל המחברים ומוודאים שהמתג של mcp-sprockets-collection מופעל, כדי לוודא שההרשאה הצליחה.

בדיקת שאילתות של סוכנים

בדיקות של ממשק המשתמש של הצ'אט עם Assistant

שולחים את שאילתת הבדיקה הבאה בצ'אט:

look up sprocket SPK-105 and confirm the model, material, unit_price, authenticated_user, and egress_source_ip

מנסים שאילתה שנייה בצ'אט:

list all sprockets in the inventory catalog and report the authenticated_user and egress_source_ip

מוודאים שהעוזר הדיגיטלי מחזיר את התשובות הנכונות ומציג תג אינטראקטיבי של ציטוט פעולה.

לדוגמה, הסימן Sprocket Lookup SPK-105 (5s)סמל כלי ה-MCPAgent Gatewayסמל של סימן וי מוקף בעיגול מתחת לכל תשובה מאשר שהכלי הופעל.

לראות שהעוזר הדיגיטלי מחזיר את המפרט של גלגל השיניים יחד עם "authenticated_user" התאמה ל-${USER_IDENTITY} ו"egress_source_ip" התאמה ל-${NAT_EXT_IP} שהזמנת.

בדיקות של Assistant streamAssist API

אחרי שחשבון המשתמש שלכם מאשר את מחבר OAuth 2.0, אתם יכולים גם להפעיל את default_assistant:streamAssist API ישירות מה-CLI ולוודא ששרת mcp-sprockets החיצוני מחזיר גם את כתובת ה-IP הסטטית של Cloud NAT (${NAT_EXT_IP}) וגם את הזהות המאומתת שלכם ב-Google OAuth 2.0 (${USER_IDENTITY}):

שולחים את שאילתת הבדיקה הבאה:

# query engine (ge app) streamAssist
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/assistants/default_assistant:streamAssist" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.[].answer.replies[]?.groundedContent?.content?.text // empty'
{
  "query": {
    "text": "look up sprocket SPK-103 and confirm the model, material, unit_price, authenticated_user, and egress_source_ip"
  },
  "toolsSpec": {
    "vertexAiSearchSpec": {
      "dataStoreSpecs": [
        {
          "dataStore": "projects/${PROJ_NO}/locations/${GE_LOCATION}/collections/default_collection/dataStores/${GE_COLLECTION_ID}_mcp_data"
        }
      ]
    }
  }
}
EOF

פלט לדוגמה:

Here are the details for sprocket SPK-103 from the inventory catalog:
- Model: ANSI-60-16T
- Material: 304 Stainless Steel
- Unit Price: $42.00
- Authenticated User (Google OAuth 2.0): user@example.com
- Egress Source IP (Cloud NAT): 203.0.113.10

אפשר לנסות שאילתה אחרת:

# query engine (ge app) streamAssist
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/assistants/default_assistant:streamAssist" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.[].answer.replies[]?.groundedContent?.content?.text // empty'
{
  "query": {
    "text": "find a sprocket model i can get for under \$20 and report the details, authenticated_user, and egress_source_ip"
  },
  "toolsSpec": {
    "vertexAiSearchSpec": {
      "dataStoreSpecs": [
        {
          "dataStore": "projects/${PROJ_NO}/locations/${GE_LOCATION}/collections/default_collection/dataStores/${GE_COLLECTION_ID}_mcp_data"
        }
      ]
    }
  }
}
EOF

עיון ביומנים ב-Cloud Logging

כדי לוודא ש-Gemini Enterprise העביר את הקריאות לכלים דרך Agent Gateway, ‏ מחבר רשת של PSC ו-Cloud NAT, בודקים את היומנים ב-Cloud Logging.

אימות יומני Agent Gateway

מוודאים ש-Agent Gateway перехватил запрос, разрешил цель в реестре агентов, оценил политику авторизации и переслал вызов инструмента:

# show agent gateway logs
gcloud logging read \
  "resource.type=networkservices.googleapis.com/Gateway
   AND resource.labels.location=${REGION}" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    httpRequest.status:label=STATUS,
    httpRequest.serverIp:label=SERVER_IP,
    jsonPayload.agentGatewayInfo.mcpInfo.method:label=MCP_METHOD,
    jsonPayload.agentGatewayInfo.mcpInfo.parameter:label=TOOL,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ,
    jsonPayload.agentGatewayInfo.agentRegistryResource.basename():label=REGISTRY_MCP
  )"

פלט לדוגמה:

TIMESTAMP                STATUS  SERVER_IP         MCP_METHOD                 TOOL                AUTHZ    REGISTRY_MCP
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  tools/call                 get_sprocket_by_id  ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  tools/list                                     ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  202     203.0.113.20:443  notifications/initialized                      ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  initialize                                     ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8

מוודאים שהפלט מכיל:

  • ‫STATUS: 200 (ההרצה הסתיימה בהצלחה) ו-202 (לחיצת היד של notifications/initialized).
  • ‫SERVER_IP: כתובת ה-IP והיציאה של שרת ה-MCP החיצוני (${MCP_EXT_IP}:443).
  • ‫MCP_METHOD ו-TOOL: הרצף של פרוטוקול ה-MCP ‏ (initialize,‏ notifications/initialized,‏ tools/list ו-tools/call עם get_sprocket_by_id).
  • ‫AUTHZ: ALLOWED (יציאה מותרת עם הרשאה של IAP).
  • REGISTRY_MCP: מזהה המשאב של Agent Registry שנפתר (agentregistry-...).

אימות יומנים של Cloud NAT

מוודאים ש-Cloud NAT תרגם את החיבור היוצא מכתובת ה-IP של מחבר הרשת של Agent Gateway PSC (10.10.10.2) לכתובת ה-IPv4 החיצונית הסטטית השמורה (${NAT_EXT_IP}) כשמתחברים אל ${MCP_EXT_IP}:443:

# show cloud nat logs
gcloud logging read \
  "resource.type=nat_gateway
   AND resource.labels.gateway_name=nat-${SLUG}-${REGION}
   AND jsonPayload.connection.dest_ip=${MCP_EXT_IP}" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.allocation_status:label=ALLOCATION_STATUS,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.nat_ip:label=NAT_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT
  )"

פלט לדוגמה:

TIMESTAMP                ALLOCATION_STATUS  SRC_IP      NAT_IP        DEST_IP       PORT
YYYY-MM-DDTHH:MM:SS.ZZZ  OK                 10.10.10.2  203.0.113.10  203.0.113.20  443

אימות יומני חומת האש

מוודאים שמדיניות חומת האש הפנימית (fw-policy-${SLUG}-internal) מאפשרת תעבורת יציאה משער הסוכן ליעד באינטרנט:

# show firewall policy logs (internal)
gcloud logging read \
  "logName:compute.googleapis.com%2Ffirewall
   AND jsonPayload.rule_details.reference:fw-policy-${SLUG}-internal" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT,
    jsonPayload.vpc.vpc_name:label=VPC_NETWORK,
    jsonPayload.rule_details.reference.basename():label=POLICY,
    jsonPayload.rule_details.priority:label=RULE,
    jsonPayload.rule_details.direction:label=DIRECTION,
    jsonPayload.disposition:label=DISPOSITION
  )"

מוודאים שמדיניות חומת האש החיצונית (fw-policy-${SLUG}-external) אפשרה תעבורת ingress לשרת ה-MCP מכתובת ה-IP של ה-NAT הציבורי ברשת הפנימית:

# show firewall policy logs (external)
gcloud logging read \
  "logName:compute.googleapis.com%2Ffirewall
   AND jsonPayload.rule_details.reference:fw-policy-${SLUG}-external" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT,
    jsonPayload.vpc.vpc_name:label=VPC_NETWORK,
    jsonPayload.rule_details.reference.basename():label=POLICY,
    jsonPayload.rule_details.priority:label=RULE,
    jsonPayload.rule_details.direction:label=DIRECTION,
    jsonPayload.disposition:label=DISPOSITION
  )"

בזה מסתיים החלק של האימות. עכשיו עוברים לקטע ניקוי.

10. הסרת המשאבים

כדי למחוק את המשאבים וההגדרות שנוצרו בשיעור ה-Lab הזה, פועלים לפי השלבים הבאים.

הסרת רכיבים של Gemini Enterprise

# delete gemini enterprise engine (app)
curl -s -X DELETE "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

# delete custom mcp collection, data connector, and backing data store
curl -s -X DELETE "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

# reset identity provider configuration
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d '{"idpConfig":{"idpType":"IDP_TYPE_UNSPECIFIED"}}'

כדי להסיר את פרטי הכניסה של לקוח OAuth 2.0‏ (ge-mcp-sprockets-client), פותחים את הדף 'לקוחות של פלטפורמת האימות של Google' ולוחצים על סמל ההסרה (פח האשפה) לצד ge-mcp-sprockets-client:

# print google auth platform clients url
echo "https://console.cloud.google.com/auth/clients?project=${PROJ_ID}"

הסרת שרת MCP ורכיבי Agent Registry

# delete external mcp server vm
gcloud -q compute instances delete vm-${MCP_NAME} --zone=${ZONE}

# delete agent registry mcp service
gcloud -q agent-registry services delete ${MCP_NAME} --location=${REGION}

# delete agent registry oauth2 endpoint
gcloud -q agent-registry services delete oauth2-googleapis --location=${REGION}

הסרת רכיבים של Agent Gateway

# delete gateway authz policy and iap service extension
gcloud -q network-security authz-policies delete ${AGW_NAME}-authz-policy-iap \
  --location=${REGION}

gcloud -q service-extensions authz-extensions delete ${AGW_NAME}-svc-ext-authz-iap \
  --location=${REGION}

# delete agent gateway and connectivity template
gcloud -q network-services agent-gateways delete ${AGW_NAME} \
  --location=${REGION}

gcloud -q network-services agent-connectivity-templates delete act-${AGW_NAME} \
  --location=${REGION}
# delete iam unified access policy binding and access policy
gcloud -q iam policy-bindings delete uap-bind-${SLUG} \
  --location=global \
  --project=${PROJ_ID}

gcloud -q iam access-policies delete uap-policy-${SLUG} \
  --location=global \
  --project=${PROJ_ID}

הסרת רכיבים של חומת האש

# delete network firewall policy associations
gcloud -q compute network-firewall-policies associations delete \
  --name=fw-policy-bind-${SLUG}-internal \
  --firewall-policy=fw-policy-${SLUG}-internal \
  --global-firewall-policy

gcloud -q compute network-firewall-policies associations delete \
  --name=fw-policy-bind-${SLUG}-external \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

# delete custom firewall policy rules
gcloud -q compute network-firewall-policies rules delete 1001 \
  --firewall-policy=fw-policy-${SLUG}-internal \
  --global-firewall-policy

gcloud -q compute network-firewall-policies rules delete 2003 \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

# delete network firewall policies
gcloud -q compute network-firewall-policies delete fw-policy-${SLUG}-internal \
  --global

gcloud -q compute network-firewall-policies delete fw-policy-${SLUG}-external \
  --global

הסרת רכיבי רשת

# delete cloud nat gateway and cloud router
gcloud -q compute routers nats delete nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION}

gcloud -q compute routers delete cr-nat-${SLUG}-${REGION} \
  --region=${REGION}

# delete psc network attachment
gcloud -q compute network-attachments delete psc-na-${REGION}-agw \
  --region=${REGION}

# delete subnets
gcloud -q compute networks subnets delete subnet-${REGION}-agw \
  --region=${REGION}

gcloud -q compute networks subnets delete subnet-${REGION}-mcp \
  --region=${REGION}

# delete reserved static external ip addresses
gcloud -q compute addresses delete ip-${SLUG}-nat ip-${SLUG}-mcp \
  --region=${REGION}

# delete internal and external vpc networks
gcloud -q compute networks delete \
  vnet-${SLUG}-internal \
  vnet-${SLUG}-external

הסרת ביטולים של מדיניות הארגון וקבצים מקומיים

# delete project-level organization policy overrides
for policy in \
  discoveryengine.managed.disableCustomMcpServerConnector \
  discoveryengine.managed.allowedEgressFqdns \
  discoveryengine.managed.allowedDataSources \
  iam.managed.disableAccessPolicyBinding \
  compute.vmExternalIpAccess; do
  gcloud -q org-policies delete ${policy} --project=${PROJ_ID} 2>/dev/null || true
done

# remove local project files
rm -rf cfg

סיימנו את עבודת ניקוי הנתונים... עכשיו נעבור אל המסקנה!

11. סיכום

מעולה! יצרתם ארכיטקטורה מקצה לקצה שמאפשרת לאפליקציית Gemini Enterprise לגלות ולהפעיל כלים באופן מאובטח בשרת MCP מותאם אישית חיצוני שמוגן באמצעות OAuth 2.0 ומוגדר בו אישור של כתובות IP:

  • יציאה מ-VPC עם כתובת IP סטטית (ALL_TRAFFIC ו-Cloud NAT): כל תעבורת הנתונים היוצאת של Agent Gateway מנותבת דרך מחבר רשת PSC ‏ (10.10.10.2) והבקשות שמופנות לאינטרנט מתורגמות לכתובת IPv4 חיצונית סטטית שמורה (${NAT_EXT_IP}) באמצעות Cloud NAT.
  • שרת משאבים של OAuth 2.0 MCP ו-Agent Registry: הטמענו שרת HTTP FastMCP חיצוני שניתן להזרמה (mcp-sprockets) שמוגן על ידי רשימת כתובות IP מותרות של חומת אש (${NAT_EXT_IP}/32) ואימות Google OAuth 2.0 tokeninfo, ורשמנו את נקודת הקצה ואת סכימת הכלי (get_sprockets ו-get_sprocket_by_id) ב-Agent Registry.
  • שילוב של Gemini Enterprise עם OAuth 2.0 וניהול: הקצאת אפליקציית Gemini Enterprise, קישור של תנועת נתונים יוצאת של כלי ל-Agent Gateway, צירוף של שרת MCP רשום כמחבר נתונים של OAuth 2.0 REGISTRY_MCP ואימות של egress_source_ip וגם של authenticated_user לצד טלמטריה של Cloud Logging.

cosmopup

Cosmopup חושב ש-Codelabs הם מדהימים!

מה השלב הבא?

אתם מוזמנים לשלוח הערות, שאלות או תיקונים באמצעות טופס המשוב הזה.

תודה!