הדרכה בנושא אבטחת שירותי Cloud Run

ב-Cloud Run אפשר להגן על עומסי עבודה פנימיים תוך חשיפת תכונות שפונות ללקוח בארכיטקטורה מרובת שירותים. במדריך הזה תיצרו אפליקציה מאובטחת עם שני שירותים, שכוללת עורך Markdown ציבורי בקצה הקדמי ושירות פרטי בקצה העורפי שמעבד טקסט Markdown ל-HTML.

דיאגרמה שמראה את זרימת הבקשות מהקצה הקדמי של "העורך" לבק-אנד של "המעבד".
הקצה העורפי Renderer הוא שירות פרטי. כך אפשר להבטיח תקן אחיד לשינוי טקסט בארגון בלי לעקוב אחרי שינויים בספריות בכמה שפות.

השירות לקצה העורפי הוא פרטי, ומשתמש בתכונה המובנית של Cloud Run, אימות משירות לשירות שמבוסס על IAM, שמגבילה את מי שיכול לקרוא לשירות. שני השירותים מבוססים על העיקרון של הרשאות מינימליות, בלי גישה לשאר החלקים של Google Cloud , אלא אם יש צורך בכך.

מגבלות או מטרות שלא נכללות במדריך הזה

  • במדריך הזה לא מוצג אימות של משתמשי קצה, שמשתמש ב-Identity Platform או באימות ב-Firebase כדי ליצור טוקנים של מזהי משתמשים ולאמת באופן ידני את הזהויות של המשתמשים. למידע נוסף על אימות משתמשי קצה, עיינו במדריך של Cloud Run בנושא אימות משתמשי קצה.

  • במדריך הזה לא מוצג שילוב של שיטות אימות מבוססות-IAM וטוקן של מזהה, כי זה לא נתמך.

מטרות

  • צרו חשבון שירות ייעודי עם הרשאות מינימליות לאימות בין שירותים ולגישה של השירות לשאר החלקים של Google Cloud.
  • לכתוב, לפתח ולפרוס שני שירותים ב-Cloud Run שמקיימים אינטראקציה ביניהם.
  • לשלוח בקשות בין שירות Cloud Run ציבורי לשירות Cloud Run פרטי.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

בעזרת מחשבון העלויות אתם יכולים להעריך את העלויות בהתאם לשימוש הצפוי.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

לפני שמתחילים

  1. נכנסים לחשבון Google Cloud . אנחנו ממליצים למשתמשים חדשים ב- Google Cloud ליצור חשבון כדי שיוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

  6. מפעילים את Cloud Run API, אם הוא עדיין לא מופעל.

    התפקידים שנדרשים כדי להפעיל ממשקי API

    כדי להפעיל ממשקי API, צריך את ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד "בעלים" (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד "אדמין בממשק 'שימוש בשירות'" (roles/serviceusage.serviceUsageAdmin). איך מקצים תפקידים

    להפעלת ה-API

  7. מתקינים ומפעילים את ה-CLI של gcloud.
  8. מתקינים את curl כדי לנסות את השירות.

התפקידים הנדרשים

כדי לקבל את ההרשאות שדרושות להשלמת המדריך, בקשו מהאדמין לתת לכם את התפקידים הבאים ב-IAM ברמת הפרויקט:

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

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

הגדרת ברירות המחדל של ה-CLI של gcloud

כדי להגדיר את Google Cloud CLI עם ברירות מחדל לשירות Cloud Run:

  1. מגדירים את פרויקט ברירת המחדל:

    gcloud config set project PROJECT_ID

    מחליפים את PROJECT_ID בשם הפרויקט שיצרתם בשביל המדריך הזה.

  2. מגדירים את Google Cloud CLI לאזור שבחרתם:

    gcloud config set run/region REGION

    מחליפים את REGION באזור הנתמך של Cloud Run לבחירתכם.

מיקומים של Cloud Run

‫Cloud Run הוא שירות אזורי, כלומר התשתית שמפעילה את שירותי Cloud Run נמצאת באזור ספציפי ומנוהלת על ידי Google, כך שהיא זמינה באופן יתיר בכל האזורים באותו אזור.

הגורמים העיקריים לבחירת האזור שבו יפעלו שירותי Cloud Run הם עמידה בדרישות של זמן אחזור, זמינות או עמידות. בדרך כלל אפשר לבחור את האזור הכי קרוב למשתמשים, אבל כדאי להביא בחשבון את המיקום של המוצרים Google Cloudהאחרים שמשמשים את שירות Cloud Run. שימוש במוצרי Google Cloud ביחד בכמה מיקומים יכול להשפיע על זמן האחזור של השירות וגם על העלות.

אפשר להשתמש ב-Cloud Run באזורים הבאים:

בכפוף לתמחור ברמה 1

בכפוף לתמחור ברמה 2

  • africa-south1 (יוהנסבורג)
  • asia-east2 (הונג קונג)
  • asia-northeast3 (סיאול, דרום קוריאה)
  • asia-southeast1 (סינגפור)
  • asia-southeast2 (ג'קרטה)
  • asia-south2 (דלהי, הודו)
  • australia-southeast1 (סידני)
  • australia-southeast2 (מלבורן)
  • europe-central2 (ורשה, פולין) סמל של עלה רמה נמוכה של CO2
  • europe-west10 (ברלין)
  • ‫europe-west12 (טורינו) סמל של עלה רמה נמוכה של CO2
  • ‫europe-west2 (לונדון, בריטניה) סמל של עלה רמה נמוכה של CO2
  • europe-west3 (פרנקפורט, גרמניה)
  • europe-west6 (ציריך, שווייץ) סמל של עלה רמה נמוכה של CO2
  • me-central1 (דוחא)
  • me-central2 (דמאם)
  • ‫northamerica-northeast1 (מונטריאול) סמל של עלה רמה נמוכה של CO2
  • ‫northamerica-northeast2 (טורונטו) סמל של עלה רמה נמוכה של CO2
  • ‫southamerica-east1 (סאו פאולו, ברזיל) סמל של עלה רמה נמוכה של CO2
  • southamerica-west1 (סנטיאגו, צ'ילה) סמל של עלה רמה נמוכה של CO2
  • us-west2 (לוס אנג'לס)
  • us-west3 (סולט לייק סיטי)
  • us-west4 (לאס וגאס)

אם כבר יצרתם שירות Cloud Run, אתם יכולים לראות את האזור במרכז הבקרה של Cloud Run במסוףGoogle Cloud .

אחזור של דוגמת הקוד

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

  1. משכפלים את המאגר של האפליקציה לדוגמה ומעבירים אותו ל-Cloud Shell או למכונה המקומית:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

    המשך

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

    C#‎

    git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

  2. עוברים לספרייה שמכילה את הקוד לדוגמה של Cloud Run:

    Node.js

    cd nodejs-docs-samples/run/markdown-preview/

    Python

    cd python-docs-samples/run/markdown-preview/

    Go

    cd golang-samples/run/markdown-preview/

    Java

    cd java-docs-samples/run/markdown-preview/

    C#‎

    cd dotnet-docs-samples/run/markdown-preview/

בדיקת השירות הפרטי לעיבוד Markdown

מנקודת המבט של ממשק המשתמש, לשירות Markdown יש את מפרט ה-API הבא:

  • נקודת קצה אחת ב-/
  • מצפה לבקשות POST
  • גוף בקשת ה-POST הוא טקסט ב-Markdown

כדאי לעיין בכל הקוד כדי לבדוק אם יש בעיות אבטחה, או פשוט כדי לקבל מידע נוסף על הקוד על ידי עיון בספרייה ./renderer/. שימו לב שהמדריך לא מסביר את קוד הטרנספורמציה של Markdown.

השקת שירות רינדור פרטי של Markdown

כדי להעביר את הקוד, יוצרים אותו באמצעות Cloud Build, מעלים אותו ל-Artifact Registry ופורסים אותו ב-Cloud Run:

  1. עוברים לספרייה renderer:

    Node.js

    cd renderer/

    Python

    cd renderer/

    Go

    cd renderer/

    Java

    cd renderer/

    C#‎

    cd Samples.Run.MarkdownPreview.Renderer/

  2. יוצרים מאגר ב-Artifact Registry:

    gcloud artifacts repositories create REPOSITORY \
        --repository-format docker \
        --location REGION

    מחליפים את:

    • ‫REPOSITORY בשם ייחודי למאגר. לכל מיקום של מאגר בפרויקט, שמות המאגרים חייבים להיות ייחודיים.
    • REGION באזור שבו רוצים להשתמש במאגר Artifact Registry. Google Cloud
  3. מריצים את הפקודה הבאה כדי ליצור את הקונטיינר ולפרסם אותו ב-Artifact Registry.

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-renderer הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-renderer הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    המשך

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-renderer הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    Java

    בדוגמה הזו משתמשים ב-Jib כדי ליצור קובצי אימג' של Docker באמצעות כלי Java נפוצים. ‫Jib מבצע אופטימיזציה של גרסאות build של קונטיינרים בלי צורך בקובץ Docker או בהתקנה של Docker. מידע נוסף על יצירת קונטיינרים של Java באמצעות Jib

    1. משתמשים בכלי העזר של פרטי הכניסה של gcloud כדי לתת ל-Docker הרשאה להעביר קבצים בדחיפה ל-Artifact Registry.

      gcloud auth configure-docker

    2. משתמשים בפלאגין Jib Maven כדי ליצור את הקונטיינר ולהעביר אותו בדחיפה ל-Artifact Registry.

      mvn compile jib:build -Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-renderer הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע ההודעה BUILD SUCCESS. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    C#‎

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-renderer הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

  4. פריסה כשירות פרטי עם גישה מוגבלת.

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

    1. יוצרים חשבון שירות שישמש כ "זהות המחשוב" של שירות העיבוד. כברירת מחדל, אין לו הרשאות מלבד חברות בפרויקט.

      שורת הפקודה

      gcloud iam service-accounts create renderer-identity

      Terraform

      כדי ללמוד איך להחיל הגדרות ב-Terraform או להסיר אותן, ראו פקודות בסיסיות ב-Terraform.

      resource "google_service_account" "renderer" {
        account_id   = "renderer-identity"
        display_name = "Service identity of the Renderer (Backend) service."
      }

      שירות העיבוד של Markdown לא משתלב ישירות עם שום דבר אחר ב- Google Cloud. לא נדרשות הרשאות נוספות.

    2. פורסים באמצעות חשבון השירות renderer-identity ודוחים גישה לא מאומתת.

      שורת הפקודה

      gcloud run deploy renderer \
      --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer \
      --service-account renderer-identity \
      --no-allow-unauthenticated

      אם חשבון השירות הוא חלק מאותו פרויקט, ב-Cloud Run אפשר להשתמש בשם הקצר של חשבון השירות במקום בכתובת האימייל המלאה.

      Terraform

      כדי ללמוד איך להחיל הגדרות ב-Terraform או להסיר אותן, ראו פקודות בסיסיות ב-Terraform.

      resource "google_cloud_run_v2_service" "renderer" {
        name     = "renderer"
        location = "us-central1"
      
        deletion_protection = false # set to "true" in production
      
        template {
          containers {
            # Replace with the URL of your Secure Services > Renderer image.
            #   gcr.io/<PROJECT_ID>/renderer
            image = "us-docker.pkg.dev/cloudrun/container/hello"
          }
          service_account = google_service_account.renderer.email
        }
      }

התנסות בשירות הפרטי לעיבוד Markdown

דפדפן אינטרנט לא יכול לטעון ישירות שירותים פרטיים. במקום זאת, צריך להשתמש ב-curl או בכלי CLI דומה לבקשות HTTP שמאפשר להוסיף כותרת Authorization.

כדי לשלוח טקסט מודגש לשירות ולראות איך הוא ממיר את הכוכביות של Markdown לתגי HTML <strong>:

  1. משיגים את כתובת ה-URL מהפלט של הפריסה.

  2. משתמשים ב-gcloud כדי להפיק אסימון זהות מיוחד לפיתוח בלבד לצורך אימות:

    TOKEN=$(gcloud auth print-identity-token)
  3. יוצרים בקשת curl שמעבירה את הטקסט הגולמי ב-Markdown כפרמטר של מחרוזת שאילתה עם תווי בריחה (escape) בכתובת URL:

    curl -H "Authorization: Bearer $TOKEN" \
       -H 'Content-Type: text/plain' \
       -d '**Hello Bold Text**' \
       SERVICE_URL

    מחליפים את SERVICE_URL בכתובת ה-URL שסופקה אחרי פריסת שירות העיבוד של Markdown.

  4. התשובה צריכה להיות קטע קוד ב-HTML:

     <strong>Hello Bold Text</strong>
    

בדיקת השילוב בין שירותי העריכה והעיבוד

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

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

Node.js

המודול render.js יוצר בקשות מאומתות לשירות העיבוד הפרטי. הוא משתמש בשרת המטא-נתונים של Google Cloud בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization.

בסביבות אחרות, render.js משתמש בApplication Default Credentials כדי לבקש אסימון מהשרתים של Google.

import {GoogleAuth} from 'google-auth-library';
import got from 'got';
const auth = new GoogleAuth();

let client, serviceUrl;

// renderRequest creates a new HTTP request with IAM ID Token credential.
// This token is automatically handled by private Cloud Run (fully managed) and Cloud Functions.
const renderRequest = async markdown => {
  if (!process.env.EDITOR_UPSTREAM_RENDER_URL)
    throw Error('EDITOR_UPSTREAM_RENDER_URL needs to be set.');
  serviceUrl = process.env.EDITOR_UPSTREAM_RENDER_URL;

  // Build the request to the Renderer receiving service.
  const serviceRequestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'text/plain',
    },
    body: markdown,
    timeout: {
      request: 3000,
    },
  };

  try {
      // Create a Google Auth client with the Renderer service url as the target audience.
      if (!client) client = await auth.getIdTokenClient(serviceUrl);
      // Fetch the client request headers and add them to the service request headers.
      // The client request headers include an ID token that authenticates the request.
      const clientHeaders = await client.getRequestHeaders();
      serviceRequestOptions.headers['Authorization'] =
        clientHeaders['Authorization'];
  } catch (err) {
    throw Error('could not create an identity token: ' + err.message);
  }

  try {
    // serviceResponse converts the Markdown plaintext to HTML.
    const serviceResponse = await got(serviceUrl, serviceRequestOptions);
    return serviceResponse.body;
  } catch (err) {
    throw Error('request to rendering service failed: ' + err.message);
  }
};

מנתחים את ה-Markdown מ-JSON ושולחים אותו לשירות Renderer כדי להמיר אותו ל-HTML.

app.post('/render', async (req, res) => {
  try {
    const markdown = req.body.data;
    const response = await renderRequest(markdown);
    res.status(200).send(response);
  } catch (err) {
    console.error('Error rendering markdown:', err);
    res.status(500).send(err);
  }
});

Python

ה-method‏ new_request יוצרת בקשות מאומתות לשירותים פרטיים. הוא משתמש בשרת המטא-נתונים של Google Cloud בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization.

בסביבות אחרות, new_request מבקש אסימון זהות מהשרתים של Google באמצעות אימות עם Application Default Credentials.

import os
import urllib

import google.auth.transport.requests
import google.oauth2.id_token


def new_request(data):
    """Creates a new HTTP request with IAM ID Token credential.

    This token is automatically handled by private Cloud Run and Cloud Functions.

    Args:
        data: data for the authenticated request

    Returns:
        The response from the HTTP request
    """
    url = os.environ.get("EDITOR_UPSTREAM_RENDER_URL")
    if not url:
        raise Exception("EDITOR_UPSTREAM_RENDER_URL missing")

    req = urllib.request.Request(url, data=data.encode())
    auth_req = google.auth.transport.requests.Request()
    target_audience = url

    id_token = google.oauth2.id_token.fetch_id_token(auth_req, target_audience)
    req.add_header("Authorization", f"Bearer {id_token}")

    response = urllib.request.urlopen(req)
    return response.read()

מנתחים את ה-Markdown מ-JSON ושולחים אותו לשירות Renderer כדי להמיר אותו ל-HTML.

@app.route("/render", methods=["POST"])
def render_handler():
    """Parse the markdown from JSON and send it to the Renderer service to be
    transformed into HTML.
    """
    body = request.get_json(silent=True)
    if not body:
        return "Error rendering markdown: Invalid JSON", 400

    data = body["data"]
    try:
        parsed_markdown = render.new_request(data)
        return parsed_markdown, 200
    except Exception as err:
        return f"Error rendering markdown: {err}", 500

המשך

‫RenderService יוצר בקשות מאומתות לשירותים פרטיים. הוא משתמש בשרת המטא-נתונים של Google Cloud בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization.

בסביבות אחרות, RenderService מבקש אסימון זהות מהשרתים של Google באמצעות אימות עם Application Default Credentials.

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"time"

	"golang.org/x/oauth2"
	"google.golang.org/api/idtoken"
)

// RenderService represents our upstream render service.
type RenderService struct {
	// URL is the render service address.
	URL string
	// tokenSource provides an identity token for requests to the Render Service.
	tokenSource oauth2.TokenSource
}

// NewRequest creates a new HTTP request to the Render service.
// If authentication is enabled, an Identity Token is created and added.
func (s *RenderService) NewRequest(method string) (*http.Request, error) {
	req, err := http.NewRequest(method, s.URL, nil)
	if err != nil {
		return nil, fmt.Errorf("http.NewRequest: %w", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Create a TokenSource if none exists.
	if s.tokenSource == nil {
		s.tokenSource, err = idtoken.NewTokenSource(ctx, s.URL)
		if err != nil {
			return nil, fmt.Errorf("idtoken.NewTokenSource: %w", err)
		}
	}

	// Retrieve an identity token. Will reuse tokens until refresh needed.
	token, err := s.tokenSource.Token()
	if err != nil {
		return nil, fmt.Errorf("TokenSource.Token: %w", err)
	}
	token.SetAuthHeader(req)

	return req, nil
}

הבקשה נשלחת לשירות Renderer אחרי הוספת טקסט Markdown שצריך להמיר ל-HTML. שגיאות בתגובה מטופלות כדי להבדיל בין בעיות בתקשורת לבין פונקציונליות העיבוד.


var renderClient = &http.Client{Timeout: 30 * time.Second}

// Render converts the Markdown plaintext to HTML.
func (s *RenderService) Render(in []byte) ([]byte, error) {
	req, err := s.NewRequest(http.MethodPost)
	if err != nil {
		return nil, fmt.Errorf("RenderService.NewRequest: %w", err)
	}

	req.Body = io.NopCloser(bytes.NewReader(in))
	defer req.Body.Close()

	resp, err := renderClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http.Client.Do: %w", err)
	}

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("ioutil.ReadAll: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return out, fmt.Errorf("http.Client.Do: %s (%d): request not OK", http.StatusText(resp.StatusCode), resp.StatusCode)
	}

	return out, nil
}

Java

‫makeAuthenticatedRequest יוצר בקשות מאומתות לשירותים פרטיים. הוא משתמש בשרת המטא-נתונים של Google Cloud בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization.

בסביבות אחרות, makeAuthenticatedRequest מבקש אסימון זהות משרתי Google באמצעות אימות עם Application Default Credentials.

// makeAuthenticatedRequest creates a new HTTP request authenticated by a JSON Web Tokens (JWT)
// retrievd from Application Default Credentials.
public String makeAuthenticatedRequest(String url, String markdown) {
  String html = "";
  try {
    // Retrieve Application Default Credentials
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    IdTokenCredentials tokenCredentials =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider((IdTokenProvider) credentials)
            .setTargetAudience(url)
            .build();

    // Create an ID token
    String token = tokenCredentials.refreshAccessToken().getTokenValue();
    // Instantiate HTTP request
    MediaType contentType = MediaType.get("text/plain; charset=utf-8");
    okhttp3.RequestBody body = okhttp3.RequestBody.create(markdown, contentType);
    Request request =
        new Request.Builder()
            .url(url)
            .addHeader("Authorization", "Bearer " + token)
            .post(body)
            .build();

    Response response = ok.newCall(request).execute();
    html = response.body().string();
  } catch (IOException e) {
    logger.error("Unable to get rendered data", e);
  }
  return html;
}

מנתחים את ה-Markdown מ-JSON ושולחים אותו לשירות Renderer כדי להמיר אותו ל-HTML.

// '/render' expects a JSON body payload with a 'data' property holding plain text
// for rendering.
@PostMapping(value = "/render", consumes = "application/json")
public String render(@RequestBody Data data) {
  String markdown = data.getData();

  String url = System.getenv("EDITOR_UPSTREAM_RENDER_URL");
  if (url == null) {
    String msg =
        "No configuration for upstream render service: "
            + "add EDITOR_UPSTREAM_RENDER_URL environment variable";
    logger.error(msg);
    throw new IllegalStateException(msg);
  }

  String html = makeAuthenticatedRequest(url, markdown);
  return html;
}

C#‎

‫GetAuthenticatedPostResponse יוצר בקשות מאומתות לשירותים פרטיים. הוא משתמש בשרת המטא-נתונים של Google Cloud בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization.

בסביבות אחרות, GetAuthenticatedPostResponse מבקש אסימון זהות משרתי Google באמצעות אימות עם Application Default Credentials.

private async Task<string> GetAuthenticatedPostResponse(string url, string postBody)
{
    // Get the OIDC access token from the service account via Application Default Credentials
    GoogleCredential credential = await GoogleCredential.GetApplicationDefaultAsync();  
    OidcToken token = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(url));
    string accessToken = await token.GetAccessTokenAsync();

    // Create request to the upstream service with the generated OAuth access token in the Authorization header
    var upstreamRequest = new HttpRequestMessage(HttpMethod.Post, url);
    upstreamRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    upstreamRequest.Content = new StringContent(postBody);

    var upstreamResponse = await _httpClient.SendAsync(upstreamRequest);
    upstreamResponse.EnsureSuccessStatusCode();

    return await upstreamResponse.Content.ReadAsStringAsync();
}

מנתחים את ה-Markdown מ-JSON ושולחים אותו לשירות Renderer כדי להמיר אותו ל-HTML.

public async Task<IActionResult> Index([FromBody] RenderModel model)
{
    var markdown = model.Data ?? string.Empty;
    var renderedHtml = await GetAuthenticatedPostResponse(_editorUpstreamRenderUrl, markdown);
    return Content(renderedHtml);
}

השקת שירות העריכה הציבורי

כדי לבנות ולפרוס את הקוד:

  1. עוברים לספרייה editor:

    Node.js

    cd ../editor

    Python

    cd ../editor

    Go

    cd ../editor

    Java

    cd ../editor

    C#‎

    cd ../Samples.Run.MarkdownPreview.Editor/

  2. מריצים את הפקודה הבאה כדי ליצור את הקונטיינר ולפרסם אותו ב-Artifact Registry.

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-editor הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Container Registry ואפשר להשתמש בה שוב לפי הצורך.

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-editor הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    המשך

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-editor הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    Java

    בדוגמה הזו משתמשים ב-Jib כדי ליצור קובצי אימג' של Docker באמצעות כלי Java נפוצים. ‫Jib מבצע אופטימיזציה של גרסאות build של קונטיינרים בלי צורך בקובץ Docker או בהתקנה של Docker. מידע נוסף על יצירת קונטיינרים של Java באמצעות Jib

    mvn compile jib:build -Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-editor הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע ההודעה BUILD SUCCESS. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

    C#‎

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    כאשר PROJECT_ID הוא מזהה הפרויקט ב- Google Cloud , ו-editor הוא השם שרוצים לתת לשירות.

    אם הפעולה תצליח, תופיע הודעת SUCCESS עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-Artifact Registry ואפשר להשתמש בה שוב לפי הצורך.

  3. פריסה כשירות פרטי עם גישה מיוחדת לשירות הרינדור.

    1. יוצרים חשבון שירות שישמש כ "זהות המחשוב" של השירות הפרטי. כברירת מחדל, אין לו הרשאות מלבד חברות בפרויקט.

      שורת הפקודה

      gcloud iam service-accounts create editor-identity

      Terraform

      כדי ללמוד איך להחיל הגדרות ב-Terraform או להסיר אותן, ראו פקודות בסיסיות ב-Terraform.

      resource "google_service_account" "editor" {
        account_id   = "editor-identity"
        display_name = "Service identity of the Editor (Frontend) service."
      }

      שירות העריכה לא צריך לקיים אינטראקציה עם שום דבר אחר ב- Google Cloud , מלבד שירות העיבוד של Markdown.

    2. נותנים לזהות המחשוב של editor-identity גישה להפעלת שירות העיבוד של Markdown. כל שירות שמשתמש בזהות הזו לצורך מחשוב יקבל את ההרשאה הזו.

      שורת הפקודה

      gcloud run services add-iam-policy-binding renderer \
      --member serviceAccount:editor-identity@PROJECT_ID.iam.gserviceaccount.com \
      --role roles/run.invoker

      Terraform

      כדי ללמוד איך להחיל הגדרות ב-Terraform או להסיר אותן, ראו פקודות בסיסיות ב-Terraform.

      resource "google_cloud_run_service_iam_member" "editor_invokes_renderer" {
        location = google_cloud_run_v2_service.renderer.location
        service  = google_cloud_run_v2_service.renderer.name
        role     = "roles/run.invoker"
        member   = "serviceAccount:${google_service_account.editor.email}"
      }

      מכיוון שהתפקיד "מפעיל" ניתן בהקשר של שירות העיבוד, שירות העיבוד הוא השירות הפרטי היחיד ב-Cloud Run שהעורך יכול להפעיל.

    3. פורסים באמצעות חשבון השירות editor-identity ומאפשרים גישה ציבורית ללא אימות.

      שורת הפקודה

      gcloud run deploy editor --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor \
      --service-account editor-identity \
      --set-env-vars EDITOR_UPSTREAM_RENDER_URL=SERVICE_URL \
      --allow-unauthenticated

      מחליפים את:

      • ‫PROJECT_ID במזהה הפרויקט
      • ‫SERVICE_URL בכתובת ה-URL שסופקה אחרי פריסת שירות העיבוד של Markdown.

      Terraform

      כדי ללמוד איך להחיל הגדרות ב-Terraform או להסיר אותן, ראו פקודות בסיסיות ב-Terraform.

      פורסים את שירות העריכה:

      resource "google_cloud_run_v2_service" "editor" {
        name     = "editor"
        location = "us-central1"
      
        deletion_protection = false # set to "true" in production
      
        template {
          containers {
            # Replace with the URL of your Secure Services > Editor image.
            #   gcr.io/<PROJECT_ID>/editor
            image = "us-docker.pkg.dev/cloudrun/container/hello"
            env {
              name  = "EDITOR_UPSTREAM_RENDER_URL"
              value = google_cloud_run_v2_service.renderer.uri
            }
          }
          service_account = google_service_account.editor.email
      
        }
      }

      נותנים ל-allUsers הרשאה להפעיל את השירות:

      data "google_iam_policy" "noauth" {
        binding {
          role = "roles/run.invoker"
          members = [
            "allUsers",
          ]
        }
      }
      
      resource "google_cloud_run_service_iam_policy" "noauth" {
        location = google_cloud_run_v2_service.editor.location
        project  = google_cloud_run_v2_service.editor.project
        service  = google_cloud_run_v2_service.editor.name
      
        policy_data = data.google_iam_policy.noauth.policy_data
      }

הסבר על תנועת הגולשים ב-HTTPS

יש שלוש בקשות HTTP שמעורבות בעיבוד של Markdown באמצעות השירותים האלה.

תרשים שמראה את תהליך הבקשה מהמשתמש לעורך, מהעורך לקבלת אסימון משרת המטא-נתונים, מהעורך לשליחת בקשה לשירות העיבוד, ומשירות העיבוד להחזרת HTML לעורך.
השירות בחזית עם editor-identity מפעיל את שירות העיבוד. גם ל-editor-identity וגם ל-renderer-identity יש הרשאות מוגבלות, כך שלכל ניצול פרצת אבטחה או הזרקת קוד יש גישה מוגבלת למשאבים אחרים של Google Cloud .

רוצים לנסות?

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

  1. בדפדפן, עוברים לכתובת ה-URL שסופקה בשלב הפריסה הקודם.

  2. נסו לערוך את הטקסט ב-Markdown בצד ימין ולחצו על הכפתור כדי לראות תצוגה מקדימה שלו בצד שמאל.

    הוא אמור להיראות כך: ממשק המשתמש של Markdown Editor, שבו מוצג טקסט Markdown בצד ימין ו-HTML שעבר עיבוד בצד שמאל.

אם תבחרו להמשיך לפתח את השירותים האלה, זכרו שיש להם גישה מוגבלת לניהול זהויות והרשאות גישה (IAM) לשאר השירותים של Google Cloud , וצריך לתת להם תפקידי IAM נוספים כדי לגשת לשירותים רבים אחרים.

הסרת המשאבים

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

מחיקת הפרויקט

הדרך הקלה ביותר לבטל את החיוב היא למחוק את הפרויקט שיצרתם בשביל המדריך הזה.

כדי למחוק את הפרויקט:

  1. במסוף Google Cloud , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

מחיקת משאבי המדריך

  1. מוחקים את שירותי Cloud Run שפרסתם במדריך הזה:

    gcloud

    gcloud run services delete editor
    gcloud run services delete renderer

    אפשר גם למחוק שירותי Cloud Run ממסוףGoogle Cloud .

  2. מסירים את הגדרות ברירת המחדל של Google Cloud CLI שהוספתם במהלך ההגדרה של המדריך.

     gcloud config unset run/region
    
  3. מסירים את הגדרות הפרויקט:

     gcloud config unset project
    
  4. מוחקים משאבים אחרים של Google Cloud שנוצרו במדריך הזה:

המאמרים הבאים