Instructivo para proteger los servicios de Cloud Run


En este instructivo, se explica cómo crear una aplicación segura de dos servicios que se ejecute en Cloud Run. Esta aplicación es un editor de Markdown. Incluye un servicio de “frontend” público que cualquiera puede usar para redactar texto de Markdown y un servicio de “backend” privado que procesa texto de Markdown en HTML.

Diagrama que muestra el flujo de solicitudes desde el “editor” de frontend al “procesador” de backend.
El “procesador” de backend es un servicio privado. Esto permite garantizar un estándar de transformación de texto en una organización sin realizar un seguimiento de los cambios en las bibliotecas de varios lenguajes.

El servicio de backend es privado mediante la función de autenticación de servicio a servicio basada en IAM integrada de Cloud Run, que limita quién puede llamar al servicio. Ambos servicios se compilan con el principio de menor privilegio, sin acceso al resto de Google Cloud, excepto cuando sea necesario.

Limitaciones o no objetivos de este instructivo

Objetivos

  • Crear una cuenta de servicio dedicada con los permisos mínimos para la autenticación de servicio a servicio y acceso del servicio al resto de Google Cloud
  • Escribir, compilar y, también, implementar dos servicios en Cloud Run que interactúen
  • Realizar solicitudes entre un servicio público y uno privado de Cloud Run

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyectos

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  4. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyectos

  5. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  6. Habilita la API de Cloud Run.

    Habilita la API

  7. Instala e inicializa la CLI de gcloud.
  8. Instala curl para probar el servicio.

Roles obligatorios

Si quieres obtener los permisos que necesitas para completar el instructivo, pídele a tu administrador que te otorgue los siguientes roles de IAM en tu proyecto:

Si quieres obtener más información para otorgar roles, consulta Administra el acceso.

También puedes obtener los permisos necesarios mediante roles personalizados o cualquier otro rol predefinido.

Configura los valores predeterminados de gcloud

A fin de configurar gcloud con los valores predeterminados para el servicio de Cloud Run, sigue estos pasos:

  1. Configura el proyecto predeterminado:

    gcloud config set project PROJECT_ID

    Reemplaza PROJECT_ID por el nombre del proyecto que creaste para este instructivo.

  2. Configura gcloud en la región que elegiste:

    gcloud config set run/region REGION

    Reemplaza REGION por la región de Cloud Run compatible que prefieras.

Ubicaciones de Cloud Run

Cloud Run es regional, lo que significa que la infraestructura que ejecuta los servicios se ubica en una región específica, y Google la administra para que esté disponible de manera redundante en todas las zonas de esa región.

El cumplimiento de los requisitos de latencia, disponibilidad o durabilidad es el factor principal para seleccionar la región en la que se ejecutan los servicios de Cloud Run. Por lo general, puedes seleccionar la región más cercana a los usuarios, pero debes considerar la ubicación de los otros productos de Google Cloud que usa el servicio de Cloud Run. Si usas productos de Google Cloud en varias ubicaciones, la latencia y el costo del servicio pueden verse afectados.

Cloud Run está disponible en las siguientes regiones:

Sujetas a los Precios del nivel 1

  • asia-east1 (Taiwán)
  • asia-northeast1 (Tokio)
  • asia-northeast2 (Osaka)
  • europe-north1 (Finlandia)ícono de hoja Bajo nivel de CO2
  • europe-southwest1 (Madrid)
  • europe-west1 (Bélgica) ícono de hoja Bajo nivel de CO2
  • europe-west4 (Países Bajos)
  • europe-west8 (Milán)
  • europe-west9 (París) ícono de hoja Bajo nivel de CO2
  • me-west1 (Tel Aviv)
  • us-central1 (Iowa) ícono de hoja Bajo nivel de CO2
  • us-east1 (Carolina del Sur)
  • us-east4 (Virginia del Norte)
  • us-east5 (Columbus)
  • us-south1 (Dallas)
  • us-west1 (Oregón) ícono de hoja Bajo nivel de CO2

Sujetas a los Precios del nivel 2

  • africa-south1 (Johannesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seúl, Corea del Sur)
  • asia-southeast1 (Singapur)
  • asia-southeast2 (Yakarta)
  • asia-south1 (Bombay, India)
  • asia-south2 Delhi (India)
  • australia-southeast1 (Sídney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsovia, Polonia)
  • europe-west10 (Berlín)
  • europe-west12 (Turín)
  • europe-west2 (Londres, Reino Unido) ícono de hoja Bajo nivel de CO2
  • europe-west3 (Fráncfort, Alemania) ícono de hoja Bajo nivel de CO2
  • europe-west6 (Zúrich, Suiza) ícono de hoja Bajo nivel de CO2
  • me-central1 (Doha)
  • me-central2 (Dammam)
  • northamerica-northeast1 (Montreal) ícono de hoja Bajo nivel de CO2
  • northamerica-northeast2 (Toronto) ícono de hoja Bajo nivel de CO2
  • southamerica-east1 (São Paulo, Brasil) ícono de hoja Bajo nivel de CO2
  • southamerica-west1 (Santiago, Chile) ícono de hoja Bajo nivel de CO2
  • us-west2 (Los Ángeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Si ya creaste un servicio de Cloud Run, puedes ver la región en el panel de Cloud Run en la consola de Google Cloud.

Recupera la muestra de código

A fin de recuperar la muestra de código para su uso, haz lo siguiente:

  1. Clona el repositorio de la app de muestra en tu Cloud Shell o tu máquina local:

    Node.js

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Python

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Go

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

    Java

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

    C#

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

  2. Ve al directorio que contiene el código de muestra de 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/

Revisa el servicio de procesamiento de Markdown privado

Desde la perspectiva del frontend, hay una especificación de API simple para el servicio de Markdown:

  • Un extremo en /
  • Se prevén solicitudes POST
  • El cuerpo de la solicitud POST es texto de Markdown

Te recomendamos revisar todo el código en busca de problemas de seguridad o simplemente para obtener más información al respecto mediante una exploración del directorio ./renderer/. Ten en cuenta que en el instructivo no se explica el código de transformación de Markdown.

Envía el servicio de procesamiento de Markdown privado

Para enviar tu código, compilar con Cloud Build, subir a Container Registry y, además, implementar en Cloud Run, sigue estos pasos:

  1. Cambia al directorio renderer:

    Node.js

    cd renderer/

    Python

    cd renderer/

    Go

    cd renderer/

    Java

    cd renderer/

    C#

    cd Samples.Run.MarkdownPreview.Renderer/

  2. Ejecuta el siguiente comando para compilar el contenedor y publicar en Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/renderer

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y renderer es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/renderer

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y renderer es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/renderer

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y renderer es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Java

    En esta muestra, se usa Jib para compilar imágenes de Docker mediante herramientas de Java comunes. Jib optimiza las compilaciones de contenedores sin la necesidad de tener un Dockerfile o tener Docker instalado. Obtén más información sobre la compilación de contenedores de Java con Jib.

    1. Usa el auxiliar de credenciales de gcloud para autorizar a Docker a que envíe contenido a tu Container Registry.

      gcloud auth configure-docker

    2. Usa el complemento de Maven para Jib a fin de compilar y enviar el contenedor a Container Registry.

      mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/renderer

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y renderer es el nombre que deseas darle a tu servicio.

    Una vez que lo hayas hecho, verás un mensaje de COMPILACIÓN EXITOSA. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    C#

    gcloud builds submit --tag gcr.io/PROJECT_ID/renderer

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y renderer es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

  3. Implementa como un servicio privado con acceso restringido.

    Cloud Run proporciona el control de acceso listo para usarse y funciones de identidad de servicio. El control de acceso proporciona una capa de autenticación que impide que los usuarios y otros servicios invoquen el servicio. La identidad de servicio permite restringir el acceso de tu servicio a otros recursos de Google Cloud mediante la creación de una cuenta de servicio dedicada con permisos limitados.

    1. Crea una cuenta de servicio para que funcione como la “identidad de procesamiento” del servicio de procesamiento. De forma predeterminada, esta no tiene otros privilegios más que la membresía del proyecto.

      Línea de comandos

      gcloud iam service-accounts create renderer-identity

      Terraform

      Si deseas obtener más información para aplicar o quitar una configuración de Terraform, consulta los comandos básicos de Terraform.

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

      El servicio de procesamiento de Markdown no se integra directamente con ningún otro componente en Google Cloud. No necesita más permisos.

    2. Implementa con la cuenta de servicio renderer-identity y rechaza el acceso no autenticado.

      Línea de comandos

      gcloud run deploy renderer \
      --image gcr.io/PROJECT_ID/renderer \
      --service-account renderer-identity \
      --no-allow-unauthenticated

      Cloud Run puede usar el nombre corto de la cuenta de servicio en lugar de la dirección de correo electrónico completa si la cuenta de servicio es parte del mismo proyecto.

      Terraform

      Si deseas obtener más información para aplicar o quitar una configuración de Terraform, consulta los comandos básicos de Terraform.

      resource "google_cloud_run_v2_service" "renderer" {
        provider = google-beta
        name     = "renderer"
        location = "us-central1"
        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
        }
      }

Prueba el servicio de procesamiento de Markdown privado

Un navegador web no puede cargar directamente los servicios privados. En su lugar, usa curl o una herramienta de CLI de solicitud HTTP similar que permita inyectar un encabezado Authorization.

Para enviar texto en negrita al servicio y ver que convierta los asteriscos de Markdown en etiquetas HTML <strong>, sigue estos pasos:

  1. Obtén la URL de la salida de la implementación.

  2. Usa gcloud a fin de derivar un token especial de identidad solo de desarrollo para la autenticación:

    TOKEN=$(gcloud auth print-identity-token)
  3. Crea una solicitud curl que pase el texto de Markdown sin procesar como un parámetro de string de consulta de URL con caracteres de escape:

    curl -H "Authorization: Bearer $TOKEN" \
       -H 'Content-Type: text/plain' \
       -d '**Hello Bold Text**' \
       SERVICE_URL
  4. La respuesta debe ser un fragmento de HTML:

     <strong>Hello Bold Text</strong>
    

Revisa la integración entre los servicios de editor y de procesamiento

El servicio de editor proporciona una IU de entrada de texto simple y un espacio para ver la vista previa de HTML. Antes de continuar, abre el directorio ./editor/ para revisar el código que recuperaste antes.

A continuación, explora las siguientes secciones de código que integran de forma segura los dos servicios.

Node.js

El módulo render.js crea solicitudes autenticadas para el servicio del procesador privado. Usa el servidor de metadatos de Google Cloud en el entorno de Cloud Run para crear un token de identidad y agregarlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, render.js usa credenciales predeterminadas de la aplicación para solicitar un token de los servidores de Google.

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

Analiza el Markdown de JSON y envíalo al servicio del procesador para que se transforme en 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

El método new_request crea solicitudes autenticadas a servicios privados. Usa el servidor de metadatos de Google Cloud en el entorno de Cloud Run para crear un token de identidad y agregarlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, new_request solicita un token de identidad desde los servidores de Google mediante la autenticación con credenciales predeterminadas de la aplicación.

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

Analiza el Markdown de JSON y envíalo al servicio del procesador para que se transforme en 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

Go

RenderService crea solicitudes autenticadas a servicios privados. Usa el servidor de metadatos de Google Cloud en el entorno de Cloud Run para crear un token de identidad y agregarlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, RenderService solicita un token de identidad desde los servidores de Google mediante la autenticación con credenciales predeterminadas de la aplicación.

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"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
}

La solicitud se envía al servicio del procesador después de agregar el texto de Markdown para transformarlo en HTML. Los errores de respuesta se controlan para diferenciar los problemas de comunicación de la funcionalidad de procesamiento.


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 = ioutil.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 := ioutil.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 crea solicitudes autenticadas a servicios privados. Usa el servidor de metadatos de Google Cloud en el entorno de Cloud Run para crear un token de identidad y agregarlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, makeAuthenticatedRequest solicita un token de identidad desde los servidores de Google mediante la autenticación con credenciales predeterminadas de la aplicación.

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

Analiza el Markdown de JSON y envíalo al servicio del procesador para que se transforme en 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 crea solicitudes autenticadas a servicios privados. Usa el servidor de metadatos de Google Cloud en el entorno de Cloud Run para crear un token de identidad y agregarlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, GetAuthenticatedPostResponse solicita un token de identidad desde los servidores de Google mediante la autenticación con credenciales predeterminadas de la aplicación.

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

Analiza el Markdown de JSON y envíalo al servicio del procesador para que se transforme en HTML.

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

Envía el servicio de editor público

Para compilar y, luego, implementar tu código, sigue estos pasos:

  1. Cambia al directorio editor:

    Node.js

    cd ../editor

    Python

    cd ../editor

    Go

    cd ../editor

    Java

    cd ../editor

    C#

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

  2. Ejecuta el siguiente comando para compilar el contenedor y publicar en Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/editor

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y editor es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/editor

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y editor es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/editor

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y editor es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Java

    En esta muestra, se usa Jib para compilar imágenes de Docker mediante herramientas de Java comunes. Jib optimiza las compilaciones de contenedores sin la necesidad de tener un Dockerfile o tener Docker instalado. Obtén más información sobre la compilación de contenedores de Java con Jib.

    mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/editor

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y editor es el nombre que deseas darle a tu servicio.

    Una vez que lo hayas hecho, verás un mensaje de COMPILACIÓN EXITOSA. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    C#

    gcloud builds submit --tag gcr.io/PROJECT_ID/editor

    En el que PROJECT_ID es el ID del proyecto de Google Cloud y editor es el nombre que deseas darle a tu servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

  3. Implementa como un servicio privado con acceso especial al servicio de procesamiento.

    1. Crea una cuenta de servicio para que funcione como la “identidad de procesamiento” del servicio de procesamiento. De forma predeterminada, esta no tiene otros privilegios más que la membresía del proyecto.

      Línea de comandos

      gcloud iam service-accounts create editor-identity

      Terraform

      Si deseas obtener más información para aplicar o quitar una configuración de Terraform, consulta los comandos básicos de Terraform.

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

      No es necesario que el servicio de editor interactúe con ningún otro elemento de Google Cloud que no sea el servicio de procesamiento de Markdown.

    2. Otorga acceso a la identidad de procesamiento editor-identity para invocar el servicio de procesamiento de Markdown. Cualquier servicio que use esto como una identidad de procesamiento tendrá este privilegio.

      Línea de comandos

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

      Terraform

      Si deseas obtener más información para aplicar o quitar una configuración de Terraform, consulta los comandos básicos de Terraform.

      resource "google_cloud_run_service_iam_member" "editor_invokes_renderer" {
        provider = google-beta
        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}"
      }

      Debido a que se le otorga la función de invocador en el contexto del servicio de procesamiento, este servicio es el único servicio privado de Cloud Run que el editor puede invocar.

    3. Implementa con la cuenta de servicio editor-identity y permite el acceso público no autenticado.

      Línea de comandos

      gcloud run deploy editor --image gcr.io/PROJECT_ID/editor \
      --service-account editor-identity \
      --set-env-vars EDITOR_UPSTREAM_RENDER_URL=RENDERER_SERVICE_URL \
      --allow-unauthenticated

      Reemplaza los siguientes elementos:

      • PROJECT_ID por el ID del proyecto.
      • RENDERER_SERVICE_URL por la URL proporcionada después de implementar el servicio de procesamiento de Markdown.

      Terraform

      Si deseas obtener más información para aplicar o quitar una configuración de Terraform, consulta los comandos básicos de Terraform.

      Implementa el servicio de editor:

      resource "google_cloud_run_v2_service" "editor" {
        provider = google-beta
        name     = "editor"
        location = "us-central1"
        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
      
        }
      }

      Otorga el permiso allUsers a fin de invocar el servicio:

      data "google_iam_policy" "noauth" {
        provider = google-beta
        binding {
          role = "roles/run.invoker"
          members = [
            "allUsers",
          ]
        }
      }
      
      resource "google_cloud_run_service_iam_policy" "noauth" {
        provider = google-beta
        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
      }

Información sobre el tráfico HTTPS

Hay tres solicitudes HTTP involucradas en el procesamiento de Markdown con estos servicios.

Diagrama que muestra el flujo de solicitud del usuario al editor, cómo el editor obtiene un token del servidor de metadatos y realiza una solicitud al servicio de procesamiento, y cómo el servicio de procesamiento muestra HTML al editor.
El servicio de frontend con editor-identity invoca el servicio de procesamiento. editor-identity y renderer-identity tienen permisos limitados, por lo que las vulnerabilidades de seguridad o las inyecciones de código tienen acceso limitado a otros recursos de Google Cloud.

Haz una prueba

Para probar la aplicación de dos servicios completa, sigue estos pasos:

  1. Dirige tu navegador a la URL proporcionada en el paso de implementación anterior.

  2. Intenta editar el texto de Markdown a la izquierda y haz clic en el botón para ver la vista previa a la derecha.

    Se verá de la siguiente manera:

    Captura de pantalla de la interfaz de usuario del editor de Markdown

Si eliges seguir desarrollando estos servicios, recuerda que tienen acceso restringido de la administración de identidades y accesos (IAM) al resto de Google Cloud y necesitarán tener funciones de IAM adicionales para acceder a muchos otros servicios.

Limpia

Si creaste un proyecto nuevo para este instructivo, bórralo. Si usaste un proyecto existente y deseas conservarlo sin los cambios que se agregaron en este instructivo, borra los recursos creados para el instructivo.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, sigue estos pasos:

  1. En la consola de Google Cloud, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra los recursos del instructivo

  1. Borra los servicios de Cloud Run que implementaste en este instructivo:

    Línea de comandos

    gcloud run services delete editor
    gcloud run services delete renderer

    También puedes borrar los servicios de Cloud Run desde la consola de Google Cloud.

  2. Quita las opciones de configuración predeterminadas de gcloud que agregaste durante la configuración del instructivo.

     gcloud config unset run/region
    
  3. Quita la configuración del proyecto:

     gcloud config unset project
    
  4. Borra otros recursos de Google Cloud que creaste en este instructivo:

¿Qué sigue?