Anleitung zur Sicherung von Cloud Run-Diensten


In dieser Anleitung wird beschrieben, wie Sie eine sichere Zwei-Dienst-Anwendung erstellen, die in Cloud Run ausgeführt wird. Diese Anwendung ist ein Markdown-Editor, der einen öffentlichen "Front-End"-Dienst enthält, den jeder zum Erstellen von Markdown-Text verwenden kann, sowie einen privaten "Back-End"-Dienst, der Markdown-Text in HTML rendert.

Diagramm, das den Anfragefluss vom Frontend-"Editor" zum Backend-"Renderer" zeigt.
The "Renderer" backend is a private service. Dadurch wird ein Standard für Texttransformationen innerhalb einer Organisation garantiert, ohne Änderungen in Bibliotheken in mehreren Sprachen verfolgen zu müssen.

Der Back-End-Dienst ist privat und nutzt das Feature für IAM-basierte Dienst-zu-Dienst-Authentifizierung von Cloud Run, das den Aufruf des Dienstes einschränkt. Beide Dienste werden mit dem Prinzip der geringsten Berechtigung erstellt, ohne Zugriff auf den Rest von Google Cloud, sofern nicht erforderlich.

Beschränkungen und nicht behandelte Themen in dieser Anleitung

  • In dieser Anleitung wird nicht die Endnutzerauthentifizierung erläutert, die Identity Platform oder Firebase Authentication dazu verwendet, Nutzer-ID-Tokens zu generieren und Nutzeridentitäten manuell überprüfen. Weitere Informationen zur Endnutzerauthentifizierung finden Sie in der Cloud Run-Anleitung zur Endnutzerauthentifizierung.

  • In dieser Anleitung wird nicht die Kombination von IAM-basierter Authentifizierung und ID-Token-Methoden erläutert, da diese nicht unterstützt wird.

Ziele

  • Ein dediziertes Dienstkonto mit minimalen Berechtigungen für die Dienst-zu-Dienst-Authentifizierung und den Dienstzugriff auf den Rest von Google Cloud erstellen
  • Zwei Dienste in Cloud Run, die miteinander interagieren, schreiben, erstellen und bereitstellen
  • Anfragen zwischen einem öffentlichen und einem privaten Cloud Run-Dienst stellen

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten von Google Cloud:

Mit dem Preisrechner können Sie eine Kostenschätzung für Ihre voraussichtliche Nutzung vornehmen. Neuen Google Cloud-Nutzern steht möglicherweise eine kostenlose Testversion zur Verfügung.

Hinweise

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure 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.

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

  6. Enable the Cloud Run API.

    Enable the API

  7. Installieren und initialisieren Sie die gcloud CLI.
  8. Installieren Sie curl, um den Dienst zu testen.

Erforderliche Rollen

Bitten Sie Ihren Administrator, Ihnen die folgenden IAM-Rollen für Ihr Projekt zuzuweisen, um die Berechtigungen zu erhalten, die Sie zum Ausführen der Anleitung benötigen:

Weitere Informationen zum Zuweisen von Rollen finden Sie unter Zugriff auf Projekte, Ordner und Organisationen verwalten.

Sie können die erforderlichen Berechtigungen auch über benutzerdefinierte Rollen oder andere vordefinierte Rollen erhalten.

gcloud-Standardeinstellungen einrichten

So konfigurieren Sie gcloud mit Standardeinstellungen für den Cloud Run-Dienst:

  1. Legen Sie ein Standardprojekt fest:

    gcloud config set project PROJECT_ID

    Ersetzen Sie PROJECT_ID durch den Namen des Projekts, das Sie für diese Anleitung erstellt haben.

  2. Konfigurieren Sie gcloud für die von Ihnen ausgewählte Region:

    gcloud config set run/region REGION

    Ersetzen Sie REGION durch die unterstützte Cloud Run-Region Ihrer Wahl.

Cloud Run-Standorte

Cloud Run ist regional. Die Infrastruktur, in der die Cloud Run-Dienste ausgeführt werden, befindet sich demnach in einer bestimmten Region. Aufgrund der Verwaltung durch Google sind die Anwendungen in allen Zonen innerhalb dieser Region redundant verfügbar.

Bei der Auswahl der Region, in der Ihre Cloud Run-Dienste ausgeführt werden, ist vorrangig, dass die Anforderungen hinsichtlich Latenz, Verfügbarkeit oder Langlebigkeit erfüllt werden. Sie können im Allgemeinen die Region auswählen, die Ihren Nutzern am nächsten liegt, aber Sie sollten den Standort der anderen Google Cloud-Produkte berücksichtigen, die von Ihrem Cloud Run-Dienst verwendet werden. Die gemeinsame Nutzung von Google Cloud-Produkten an mehreren Standorten kann sich auf die Latenz und die Kosten des Dienstes auswirken.

Cloud Run ist in diesen Regionen verfügbar:

Unterliegt Preisstufe 1

Unterliegt Preisstufe 2

Wenn Sie bereits einen Cloud Run-Dienst erstellt haben, können Sie dessen Region im Cloud Run-Dashboard der Google Cloud Console aufrufen.

Codebeispiel abrufen

So rufen Sie das gewünschte Codebeispiel ab:

  1. Klonen Sie das Repository der Beispiel-App in Ihre Cloud Shell oder auf Ihren lokalen Computer:

    Node.js

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Python

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Go

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Java

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    C#

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

  2. Wechseln Sie in das Verzeichnis, das den Cloud Run-Beispielcode enthält:

    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/

Privaten Markdown-Renderingdienst prüfen

Aus Sicht des Front-Ends gibt es eine einfache API-Spezifikation für den Markdown-Dienst:

  • Ein Endpunkt bei /
  • Erwartet POST-Anfragen
  • Der Text der POST-Anfrage ist Markdown-Text

Möglicherweise möchten Sie den gesamten Code auf Sicherheitsprobleme prüfen oder einfach weitere Informationen dazu erhalten, indem Sie das Verzeichnis ./renderer/ untersuchen. Beachten Sie, dass der Markdown-Transformationscode in dieser Anleitung nicht erläutert wird.

Privaten Markdown-Renderingdienst senden

Um Ihren Code zu senden, erstellen Sie ihn mit Cloud Build, laden Sie ihn in Artifact Registry hoch und stellen Sie ihn in Cloud Run bereit:

  1. Wechseln Sie in das Verzeichnis renderer:

    Node.js

    cd renderer/

    Python

    cd renderer/

    Go

    cd renderer/

    Java

    cd renderer/

    C#

    cd Samples.Run.MarkdownPreview.Renderer/

  2. So erstellen Sie eine Artifact Registry:

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

    Ersetzen Sie:

    • REPOSITORY durch einen eindeutigen Namen für das Repository. Repository-Namen können für jeden Repository-Speicherort in einem Projekt nur einmal vorkommen.
    • REGION mit der Google Cloud-Region, die für das Artifact Registry-Repository verwendet werden soll.
  3. Führen Sie den folgenden Befehl aus, um den Container zu erstellen und ihn in Artifact Registry zu veröffentlichen:

    Node.js

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und renderer der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Python

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und renderer der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Go

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und renderer der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Java

    In diesem Beispiel wird Jib verwendet, um Docker-Images mit gängigen Java-Tools zu erstellen. Jib optimiert Container-Builds, ohne dass ein Dockerfile erforderlich ist oder Docker installiert sein muss. Weitere Informationen zum Erstellen von Java-Containern mit Jib

    1. Verwenden Sie den gcloud Credential Helper, um Docker für das Übertragen per Push in Ihre Artifact Registry zu autorisieren.

      gcloud auth configure-docker

    2. Verwenden Sie das Jib-Maven-Plug-in, um den Container zu erstellen und per Push in Artifact Registry zu übertragen.

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und renderer der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg sollte die Nachricht "BUILD SUCCESS" angezeigt werden. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    C#

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und renderer der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

  4. Als privaten Dienst mit eingeschränktem Zugriff bereitstellen

    Cloud Run bietet sofort einsatzbereit Zugriffskontrolle und Dienstidentität-Features. Die Zugriffssteuerung stellt eine Authentifizierungsschicht bereit, die verhindert, dass Nutzer und andere Dienste den Dienst aufrufen. Mit der Dienstidentität können Sie verhindern, dass Ihr Dienst auf andere Google Cloud-Ressourcen zugreift. Dazu erstellen Sie ein dediziertes Dienstkonto mit eingeschränkten Berechtigungen.

    1. Erstellen Sie ein Dienstkonto, das als "Compute-Identität" des Renderingdienstes dient. Standardmäßig hat dieses keine anderen Berechtigungen als die Projektmitgliedschaft.

      Befehlszeile

      gcloud iam service-accounts create renderer-identity

      Terraform

      Informationen zum Anwenden oder Entfernen einer Terraform-Konfiguration finden Sie unter Grundlegende Terraform-Befehle.

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

      Der Markdown-Renderingdienst lässt sich nicht direkt in andere Komponenten in Google Cloud einbinden. Er benötigt keine weiteren Berechtigungen.

    2. Mit dem renderer-identity-Dienstkonto bereitstellen und nicht authentifizierten Zugriff verweigern

      Befehlszeile

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

      Cloud Run kann den Kurznamen des Dienstkontos anstelle der vollständigen E-Mail-Adresse verwenden, wenn das Dienstkonto Teil desselben Projekts ist.

      Terraform

      Informationen zum Anwenden oder Entfernen einer Terraform-Konfiguration finden Sie unter Grundlegende Terraform-Befehle.

      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
        }
      }

Privaten Markdown-Renderingdienst testen

Private Dienste können nicht direkt von einem Webbrowser geladen werden. Verwenden Sie stattdessen curl oder ein ähnliches HTTP-Anfrage-Befehlszeilentool, mit dem ein Authorization-Header eingefügt werden kann.

So senden Sie fett formatierten Text an den Dienst und sehen, wie die Markdown-Sternchen in HTML-<strong>-Tags konvertiert werden:

  1. Rufen Sie die URL aus der Bereitstellungsausgabe ab.

  2. Verwenden Sie gcloud, um ein spezielles Identitätstoken nur für die Entwicklung abzuleiten, das zur Authentifizierung dient:

    TOKEN=$(gcloud auth print-identity-token)
  3. Erstellen Sie eine curl-Anfrage, die den Markdown-Rohtext als URL-codierten Abfragestringparameter übergibt:

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

    Ersetzen Sie SERVICE_URL durch die nach der Bereitstellung des Markdown-Renderingdiensts bereitgestellte URL.

  4. Die Antwort sollte ein HTML-Snippet sein:

     <strong>Hello Bold Text</strong>
    

Integration von Editor- und Renderingdiensten prüfen

Der Editordienst bietet eine einfache Benutzeroberfläche für Texteingaben und einen Bereich, in dem die HTML-Vorschau angezeigt wird. Bevor Sie fortfahren, prüfen Sie den zuvor abgerufenen Code, indem Sie das Verzeichnis ./editor/ öffnen.

Sehen Sie sich als Nächstes die folgenden Codeabschnitte an, in denen die beiden Dienste sicher eingebunden werden.

Node.js

Das Modul render.js erstellt authentifizierte Anfragen an den privaten Renderer-Dienst. Dabei wird der Google Cloud-Metadatenserver in der Cloud Run-Umgebung verwendet, um ein Identitätstoken zu erstellen und es der HTTP-Anfrage als Teil eines Authorization-Headers hinzuzufügen.

In anderen Umgebungen verwendet render.js die Standardanmeldedaten für Anwendungen, um ein Token von Google-Servern anzufordern.

const {GoogleAuth} = require('google-auth-library');
const got = require('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: 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);
  }
};

Parst den Markdown-Text aus JSON und sendet ihn an den Renderer-Dienst, um ihn in HTML umzuwandeln.

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

Mit der Methode new_request werden authentifizierte Anfragen an private Dienste erstellt. Dabei wird der Google Cloud-Metadatenserver in der Cloud Run-Umgebung verwendet, um ein Identitätstoken zu erstellen und es der HTTP-Anfrage als Teil eines Authorization-Headers hinzuzufügen.

In anderen Umgebungen fordert new_request ein Identitätstoken von den Google-Servern an. Dazu erfolgt eine Authentifizierung mit Standardanmeldedaten für Anwendungen.

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()

Parst den Markdown-Text aus JSON und sendet ihn an den Renderer-Dienst, um ihn in HTML umzuwandeln.

@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

Go

Mit RenderService werden authentifizierte Anfragen an private Dienste erstellt. Dabei wird der Google Cloud-Metadatenserver in der Cloud Run-Umgebung verwendet, um ein Identitätstoken zu erstellen und es der HTTP-Anfrage als Teil eines Authorization-Headers hinzuzufügen.

In anderen Umgebungen fordert RenderService ein Identitätstoken von den Google-Servern an. Dazu erfolgt eine Authentifizierung mit Standardanmeldedaten für Anwendungen.

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
}

Die Anfrage wird an den Renderer-Dienst gesendet, nachdem der Markdown-Text in HTML umgewandelt wurde. Antwortfehler werden verarbeitet, um zwischen Kommunikationsproblemen und der Renderingfunktion zu unterscheiden.


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

Mit makeAuthenticatedRequest werden authentifizierte Anfragen an private Dienste erstellt. Dabei wird der Google Cloud-Metadatenserver in der Cloud Run-Umgebung verwendet, um ein Identitätstoken zu erstellen und es der HTTP-Anfrage als Teil eines Authorization-Headers hinzuzufügen.

In anderen Umgebungen fordert makeAuthenticatedRequest ein Identitätstoken von den Google-Servern an. Dazu erfolgt eine Authentifizierung mit Standardanmeldedaten für Anwendungen.

// 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;
}

Parst den Markdown-Text aus JSON und sendet ihn an den Renderer-Dienst, um ihn in HTML umzuwandeln.

// '/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#

Mit GetAuthenticatedPostResponse werden authentifizierte Anfragen an private Dienste erstellt. Dabei wird der Google Cloud-Metadatenserver in der Cloud Run-Umgebung verwendet, um ein Identitätstoken zu erstellen und es der HTTP-Anfrage als Teil eines Authorization-Headers hinzuzufügen.

In anderen Umgebungen fordert GetAuthenticatedPostResponse ein Identitätstoken von den Google-Servern an. Dazu erfolgt eine Authentifizierung mit Standardanmeldedaten für Anwendungen.

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();
}

Parst den Markdown-Text aus JSON und sendet ihn an den Renderer-Dienst, um ihn in HTML umzuwandeln.

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

Öffentlichen Editordienst senden

So erstellen Sie Ihren Code und stellen ihn bereit:

  1. Wechseln Sie in das Verzeichnis editor:

    Node.js

    cd ../editor

    Python

    cd ../editor

    Go

    cd ../editor

    Java

    cd ../editor

    C#

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

  2. Führen Sie den folgenden Befehl aus, um den Container zu erstellen und ihn in Artifact Registry zu veröffentlichen:

    Node.js

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und editor der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Python

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und editor der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Go

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und editor der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Java

    In diesem Beispiel wird Jib verwendet, um Docker-Images mit gängigen Java-Tools zu erstellen. Jib optimiert Container-Builds, ohne dass ein Dockerfile erforderlich ist oder Docker installiert sein muss. Weitere Informationen zum Erstellen von Java-Containern mit Jib

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und editor der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg sollte die Nachricht "BUILD SUCCESS" angezeigt werden. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    C#

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

    Dabei ist PROJECT_ID Ihre Google-Cloud-Projekt-ID und editor der Name, den Sie dem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Artifact Registry gespeichert und kann bei Bedarf wiederverwendet werden.

  3. Als privaten Dienst mit speziellem Zugriff auf den Renderingdienst bereitstellen

    1. Erstellen Sie ein Dienstkonto, das als „Compute-Identität“ des privaten Dienstes dient. Standardmäßig hat dieses keine anderen Berechtigungen als die Projektmitgliedschaft.

      Befehlszeile

      gcloud iam service-accounts create editor-identity

      Terraform

      Informationen zum Anwenden oder Entfernen einer Terraform-Konfiguration finden Sie unter Grundlegende Terraform-Befehle.

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

      Der Editordienst muss außer mit dem Markdown-Renderingdienst mit keinen anderen Komponenten in Google Cloud interagieren.

    2. Gewähren Sie der Compute-Identität editor-identity Zugriff, um den Markdown-Renderingdienst aufzurufen. Jeder Dienst, der dies als Compute-Identität verwendet, hat diese Berechtigung.

      Befehlszeile

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

      Terraform

      Informationen zum Anwenden oder Entfernen einer Terraform-Konfiguration finden Sie unter Grundlegende Terraform-Befehle.

      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}"
      }

      Da hiermit im Kontext des Renderingdienstes die Invoker-Rolle zugewiesen wird, ist der Renderingdienst der einzige private Cloud Run-Dienst, den der Editor aufrufen kann.

    3. Mit dem Dienstkonto editor-identity bereitstellen und öffentlichen, nicht authentifizierten Zugriff zulassen

      Befehlszeile

      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

      Ersetzen Sie:

      • PROJECT_ID durch Ihre Projekt-ID
      • SERVICE_URL durch die nach der Bereitstellung des Markdown-Renderingdiensts bereitgestellte URL.

      Terraform

      Informationen zum Anwenden oder Entfernen einer Terraform-Konfiguration finden Sie unter Grundlegende Terraform-Befehle.

      Stellen Sie den Editordienst bereit:

      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
      
        }
      }

      Gewähren Sie die Berechtigung allUsers zum Aufrufen des Dienstes:

      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
      }

Informationen zum HTTPS-Traffic

Für das Rendering von Markdown mithilfe dieser Dienste gibt es drei HTTP-Anfragen.

Diagramm, das den Anfragefluss vom Nutzer zum Editor zeigt, Editor zum Abrufen eines Tokens vom Metadatenserver, Editor zum Senden einer Anfrage an den Renderingdienst, Renderingdienst zur Rückgabe von HTML an den Editor.
The frontend service with the editor-identity invokes the render service. Sowohl editor-identity als auch renderer-identity haben eingeschränkte Berechtigungen, sodass jeder Sicherheits-Exploit oder jede Code-Injektion eingeschränkten Zugriff auf andere Google Cloud-Ressourcen hat.

Testen

So testen Sie die fertige Zwei-Dienst-Anwendung:

  1. Rufen Sie im Browser die URL auf, die Sie im oben beschriebenen Bereitstellungsschritt erhalten haben.

  2. Versuchen Sie, den Markdown-Text auf der linken Seite zu bearbeiten, und klicken Sie auf die Schaltfläche, um rechts eine Vorschau davon zu sehen.

    Sie sollte so aussehen:

    Screenshot der Benutzeroberfläche des Markdown-Editors

Wenn Sie sich dafür entscheiden, mit der Entwicklung dieser Dienste fortzufahren, denken Sie daran, dass diese nur eingeschränkten IAM-Zugriff (Identity and Access Management) auf den Rest von Google Cloud haben und ihnen zusätzliche IAM-Rollen zugewiesen werden müssen, um auf viele andere Dienste zugreifen zu können.

Bereinigen

Wenn Sie ein neues Projekt für diese Anleitung erstellt haben, löschen Sie das Projekt. Wenn Sie ein vorhandenes Projekt verwendet haben und es beibehalten möchten, ohne die Änderungen in dieser Anleitung hinzuzufügen, löschen Sie die für die Anleitung erstellten Ressourcen.

Projekt löschen

Am einfachsten vermeiden Sie weitere Kosten, wenn Sie das zum Ausführen der Anleitung erstellte Projekt löschen.

So löschen Sie das Projekt:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Anleitungsressourcen löschen

  1. Löschen Sie den Cloud Run-Dienste, den Sie in dieser Anleitung bereitgestellt haben:

    gcloud

    gcloud run services delete editor
    gcloud run services delete renderer

    Sie können Cloud Run-Dienste auch über die Google Cloud Console löschen.

  2. Entfernen Sie die gcloud-Standardkonfigurationen, die Sie während der Einrichtung der Anleitung hinzugefügt haben.

     gcloud config unset run/region
    
  3. Entfernen Sie die Projektkonfiguration:

     gcloud config unset project
    
  4. Löschen Sie sonstige Google Cloud-Ressourcen, die in dieser Anleitung erstellt wurden:

Nächste Schritte