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

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

דיאגרמה שמוצג בה זרימת הבקשות מהחלק הקדמי (frontend) 'עורך' לחלק האחורי (backend) 'מעבד'.
הקצה העורפי 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, צריך את תפקיד ה-IAM 'אדמין של Service Usage' (roles/serviceusage.serviceUsageAdmin), שכולל את ההרשאה serviceusage.services.enable. איך מקצים תפקידים

    להפעלת ה-API

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

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

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

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

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

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

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

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

    gcloud config set project PROJECT_ID

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

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

    gcloud config set run/region REGION

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

מיקומים של Cloud Run

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

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

‫Cloud Run זמין באזורים הבאים:

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

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

  • africa-south1 (יוהנסבורג)
  • asia-east2 (הונג קונג)
  • asia-northeast3 (סיאול, קוריאה הדרומית)
  • asia-southeast1 (סינגפור)
  • asia-southeast2 (ג'אקארטה)
  • asia-south2 (דלהי, הודו)
  • australia-southeast1 (סידני)
  • australia-southeast2 (מלבורן)
  • europe-central2 (ורשה, פולין)
  • europe-west10 (Berlin)
  • europe-west12 (טורינו)
  • 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

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

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

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

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

כדי לשלוח את הקוד, צריך לבצע build באמצעות 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 הוא מזהה הפרויקט שלכם, ו-renderer הוא השם שאתם רוצים לתת לשירות. Google Cloud

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

    Python

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

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

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

    המשך

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

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

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

    Java

    בדוגמה הזו נעשה שימוש ב-Jib כדי ליצור תמונות Docker באמצעות כלים נפוצים של Java. ‫Jib מבצע אופטימיזציה של בניית קונטיינרים בלי צורך בקובץ Dockerfile או בהתקנה של 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 הוא מזהה הפרויקט שלכם, ו-renderer הוא השם שאתם רוצים לתת לשירות. Google Cloud

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

    C#‎

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

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

    אם הפעולה תצליח, תופיע הודעה עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-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 יוצר בקשות מאומתות לשירות העיבוד הפרטי. הוא משתמש בשרת המטא-נתונים בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization. Google Cloud

בסביבות אחרות, 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

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

בסביבות אחרות, 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 יוצר בקשות מאומתות לשירותים פרטיים. הוא משתמש בשרת המטא-נתונים בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization. Google Cloud

בסביבות אחרות, 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 יוצר בקשות מאומתות לשירותים פרטיים. היא משתמשת בשרת המטא-נתונים בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization. Google Cloud

בסביבות אחרות, 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 יוצר בקשות מאומתות לשירותים פרטיים. היא משתמשת בשרת המטא-נתונים בסביבת Cloud Run כדי ליצור אסימון זהות ולהוסיף אותו לבקשת ה-HTTP כחלק מכותרת Authorization. Google Cloud

בסביבות אחרות, 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 הוא מזהה הפרויקט שלכם, ו-editor הוא השם שאתם רוצים לתת לשירות. Google Cloud

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

    Python

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

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

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

    המשך

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

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

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

    Java

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

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

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

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

    C#‎

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

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

    אם הפעולה תצליח, תוצג הודעה על הצלחה עם המזהה, זמן היצירה ושם התמונה. התמונה מאוחסנת ב-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

אם תבחרו להמשיך לפתח את השירותים האלה, חשוב לזכור שהגישה שלהם לניהול זהויות והרשאות גישה (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. מסירים את הגדרות ברירת המחדל של gcloud שהוספתם במהלך הגדרת המדריך.

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

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

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