Tutorial sobre cómo proteger los servicios de Cloud Run

En este tutorial 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 que incluye un servicio público de frontend que cualquier persona puede usar para escribir texto en Markdown y un servicio privado de backend que convierte texto en Markdown a HTML.

Diagrama que muestra el flujo de solicitudes del frontend "editor" al backend "renderizador".
El backend "Renderer" es un servicio privado. De esta forma, se garantiza un estándar de transformación de texto en toda la organización sin tener que hacer un seguimiento de los cambios en las bibliotecas de varios idiomas.

El servicio de backend es privado y utiliza la función integrada de Cloud Run autenticación de servicio a servicio basada en IAM, que limita quién puede llamar al servicio. Ambos servicios se han creado siguiendo el principio de mínimos accesos, de modo que no tienen acceso al resto de Google Cloud , salvo cuando sea necesario.

Limitaciones u objetivos no incluidos en este tutorial

Configurar los valores predeterminados de gcloud

Para configurar gcloud con los valores predeterminados de tu servicio de Cloud Run, sigue estos pasos:

  1. Configura tu proyecto predeterminado:

    gcloud config set project PROJECT_ID

    Sustituye PROJECT_ID por el nombre del proyecto que has creado para este tutorial.

  2. Configura gcloud para la región que hayas elegido:

    gcloud config set run/region REGION

    Sustituye REGION por la región de Cloud Run compatible que quieras.

Ubicaciones de Cloud Run

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

Cumplir tus requisitos de latencia, disponibilidad o durabilidad son factores primordiales para seleccionar la región en la que se ejecutan tus servicios de Cloud Run. Por lo general, puedes seleccionar la región más cercana a tus usuarios, pero debes tener en cuenta la ubicación de los otros Google Cloudproductos que utiliza tu servicio de Cloud Run. Usar Google Cloud productos juntos en varias ubicaciones puede afectar a la latencia y al coste de tu servicio.

Cloud Run está disponible en las siguientes regiones:

Con sujeción a los precios del nivel 1

  • asia-east1 (Taiwán)
  • asia-northeast1 (Tokio)
  • asia-northeast2 (Osaka)
  • asia-south1 (Bombay, la India)
  • europe-north1 (Finlandia) icono de una hoja CO2 bajo
  • europe-north2 (Estocolmo) icono de una hoja CO2 bajo
  • europe-southwest1 (Madrid) icono de una hoja CO2 bajo
  • europe-west1 (Bélgica) icono de una hoja CO2 bajo
  • europe-west4 (Países Bajos) icono de una hoja CO2 bajo
  • europe-west8 (Milán)
  • europe-west9 (París) icono de una hoja CO2 bajo
  • me-west1 (Tel Aviv)
  • northamerica-south1 (México)
  • us-central1 (Iowa) icono de una hoja CO2 bajo
  • us-east1 (Carolina del Sur)
  • us-east4 (Norte de Virginia)
  • us-east5 (Columbus)
  • us-south1 (Dallas) icono de una hoja CO2 bajo
  • us-west1 (Oregón) icono de una hoja CO2 bajo

Con sujeción 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-south2 (Delhi, la 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) icono de una hoja CO2 bajo
  • europe-west3 (Fráncfort, Alemania)
  • europe-west6 (Zúrich, Suiza) icono de una hoja Bajas emisiones de CO2
  • me-central1 (Doha)
  • me-central2 (Dammam)
  • northamerica-northeast1 (Montreal) icono de una hoja CO2 bajo
  • northamerica-northeast2 (Toronto) icono de una hoja CO2 bajo
  • southamerica-east1 (São Paulo, Brasil) icono de una hoja CO2 bajo
  • southamerica-west1 (Santiago, Chile) icono de una hoja CO2 bajo
  • us-west2 (Los Ángeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

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

Obtener el código de ejemplo

Para obtener el código de muestra que vas a usar, sigue estos pasos:

  1. Clona el repositorio de la aplicación de ejemplo en tu Cloud Shell o en tu máquina local:

    Node.js

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

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    Python

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

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    Go

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

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    Java

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

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    C#

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

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

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

Revisar el servicio de renderización de Markdown privado

Desde el punto de vista del frontend, hay una especificación de API sencilla para el servicio de Markdown:

  • Un endpoint en /
  • Espera solicitudes POST
  • El cuerpo de la solicitud POST es texto en formato Markdown.

Puede revisar todo el código para comprobar si hay algún problema de seguridad o simplemente para obtener más información sobre él explorando el directorio ./renderer/. Ten en cuenta que en el tutorial no se explica el código de transformación de Markdown.

Envío del servicio de renderizado de Markdown privado

Para enviar tu código, compílalo con Cloud Build, súbelo a Artifact Registry y despliégalo en Cloud Run:

  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. Crea un Artifact Registry:

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

    Sustituye:

    • REPOSITORY con un nombre único para el repositorio. Los nombres de los repositorios deben ser únicos en cada ubicación de repositorio de un proyecto.
    • REGION con la Google Cloud región que se va a usar en el repositorio de Artifact Registry.
  3. Ejecuta el siguiente comando para compilar el contenedor y publicarlo en Artifact Registry.

    Node.js

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y renderer es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede reutilizar si se quiere.

    Python

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y renderer es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede reutilizar si se quiere.

    Go

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y renderer es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede volver a usar si quieres.

    Java

    En este ejemplo se usa Jib para crear imágenes de Docker con herramientas comunes de Java. Jib optimiza las compilaciones de contenedores sin necesidad de usar un Dockerfile ni de tener Docker instalado. Más información sobre cómo crear contenedores Java con Jib

    1. Usa el asistente de credenciales de gcloud para autorizar a Docker a enviar contenido a tu Artifact Registry.

      gcloud auth configure-docker

    2. Usa el complemento Jib Maven para compilar y enviar el contenedor a Artifact Registry.

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y renderer es el nombre que quieres dar a tu servicio.

    Si todo va bien, verás el mensaje BUILD SUCCESS. La imagen se almacena en Artifact Registry y se puede volver a usar si se quiere.

    C#

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y renderer es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede volver a usar si quieres.

  4. Implementar como servicio privado con acceso restringido.

    Cloud Run ofrece funciones de control de acceso y de identidad de servicio listas para usar. 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 recursosGoogle Cloud creando una cuenta de servicio específica con permisos limitados.

    1. Crea una cuenta de servicio que actúe como "identidad de proceso" del servicio de renderización. De forma predeterminada, no tiene más privilegios que la pertenencia al proyecto.

      Línea de comandos

      gcloud iam service-accounts create renderer-identity

      Terraform

      Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de Terraform.

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

      El servicio de renderización de Markdown no se integra directamente con ningún otro elemento de Google Cloud. No necesita ningún otro permiso.

    2. Despliega con la cuenta de servicio renderer-identity y deniega el acceso no autenticado.

      Línea de comandos

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

      Cloud Run puede usar el nombre abreviado de la cuenta de servicio en lugar de la dirección de correo completa si la cuenta de servicio forma parte del mismo proyecto.

      Terraform

      Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de 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
        }
      }

Probar el servicio privado de renderización de Markdown

Los servicios privados no se pueden cargar directamente en un navegador web. En su lugar, usa curl o una herramienta de CLI de solicitudes HTTP similar que permita insertar un encabezado Authorization.

Para enviar texto en negrita al servicio y ver cómo convierte los asteriscos de Markdown en etiquetas <strong> de HTML, sigue estos pasos:

  1. Obtén la URL del resultado de la implementación.

  2. Usa gcloud para obtener un token de identidad especial solo para desarrollo con fines de autenticación:

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

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

    Sustituye SERVICE_URL por la URL que se te proporcione después de implementar el servicio de renderización de Markdown.

  4. La respuesta debe ser un fragmento HTML:

     <strong>Hello Bold Text</strong>
    

Revisar la integración entre los servicios de edición y renderización

El servicio de editor proporciona una interfaz de usuario sencilla para introducir texto y un espacio para ver la vista previa del HTML. Antes de continuar, revisa el código que has obtenido anteriormente abriendo el directorio ./editor/.

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

Node.js

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

En otros entornos, render.js usa las credenciales predeterminadas de la aplicación para solicitar un token a 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 Renderer 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 en el entorno de Cloud Run para crear un token de identidad y añadirlo a la solicitud HTTP como parte de un encabezado Authorization. Google Cloud

En otros entornos, new_request solicita un token de identidad a los servidores de Google autenticándose 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 Renderer 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. Utiliza el servidor de metadatos Google Cloud en el entorno de Cloud Run para crear un token de identidad y añadirlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, RenderService solicita un token de identidad a los servidores de Google autenticándose con credenciales predeterminadas de la aplicación.

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
}

La solicitud se envía al servicio Renderer después de añadir el texto de Markdown que se va a transformar en HTML. Los errores de respuesta se gestionan para diferenciar los problemas de comunicación de la funcionalidad de renderización.


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

En otros entornos, makeAuthenticatedRequest solicita un token de identidad a los servidores de Google autenticándose con las credenciales de aplicación predeterminadas.

// 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 Renderer 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 Google Cloud servidor de metadatos del entorno de Cloud Run para crear un token de identidad y añadirlo a la solicitud HTTP como parte de un encabezado Authorization.

En otros entornos, GetAuthenticatedPostResponse solicita un token de identidad a los servidores de Google autenticándose con las credenciales de aplicación predeterminadas.

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

Lanzamiento del servicio de editor público

Para compilar e 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 publicarlo en Artifact Registry.

    Node.js

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y editor es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y se puede volver a usar si quieres.

    Python

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y editor es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede volver a usar si quieres.

    Go

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y editor es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede volver a usar si quieres.

    Java

    En este ejemplo se usa Jib para crear imágenes de Docker con herramientas comunes de Java. Jib optimiza las compilaciones de contenedores sin necesidad de usar un Dockerfile ni de tener Docker instalado. Más información sobre cómo crear contenedores Java con Jib

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y editor es el nombre que quieres dar a tu servicio.

    Si todo va bien, verás el mensaje BUILD SUCCESS. La imagen se almacena en Artifact Registry y se puede reutilizar si se quiere.

    C#

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

    Donde PROJECT_ID es el ID de tu proyecto Google Cloud y editor es el nombre que quieres dar a tu servicio.

    Si la operación se realiza correctamente, verás un mensaje de ÉXITO que contiene el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Artifact Registry y se puede volver a usar si quieres.

  3. Implementar como servicio privado con acceso especial al servicio de renderización.

    1. Crea una cuenta de servicio que actúe como "identidad de cálculo" del servicio privado. De forma predeterminada, no tiene más privilegios que la pertenencia al proyecto.

      Línea de comandos

      gcloud iam service-accounts create editor-identity

      Terraform

      Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de Terraform.

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

      El servicio Editor no necesita interactuar con nada más en Google Cloud que con el servicio de renderización de Markdown.

    2. Concede acceso a la identidad de cálculo editor-identity para invocar el servicio de renderización de Markdown. Cualquier servicio que utilice esto como identidad de cálculo 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

      Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de 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}"
      }

      Como se le ha asignado el rol de invocador en el contexto del servicio de renderización, este es el único servicio privado de Cloud Run que puede invocar el editor.

    3. Implementa con la cuenta de servicio editor-identity y permite el acceso público sin autenticación.

      Línea de comandos

      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

      Sustituye:

      • PROJECT_ID con el ID de tu proyecto
      • SERVICE_URL con la URL proporcionada después de implementar el servicio de renderización de Markdown.

      Terraform

      Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de Terraform.

      Implementa el servicio del editor:

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

      Concede permiso a allUsers para invocar el servicio:

      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
      }

Información sobre el tráfico HTTPS

Para renderizar el markdown con estos servicios, se realizan tres solicitudes HTTP.

Diagrama que muestra el flujo de solicitudes del usuario al editor, del editor para obtener un token del servidor de metadatos, del editor para hacer una solicitud al servicio de renderización y del servicio de renderización para devolver HTML al editor.
El servicio frontend con editor-identity invoca el servicio de renderización. Tanto editor-identity como renderer-identity tienen permisos limitados, por lo que cualquier vulnerabilidad de seguridad o inyección de código tiene un acceso limitado a otros recursos de Google Cloud .

Probar la función

Para probar la aplicación completa de dos servicios, haz lo siguiente:

  1. En tu navegador, ve a la URL que se ha proporcionado en el paso de implementación anterior.

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

    Debería tener este aspecto:

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

Si decides seguir desarrollando estos servicios, recuerda que tienen acceso restringido a la gestión de identidades y accesos (IAM) al resto de Google Cloud y que tendrás que asignarles roles de IAM adicionales para acceder a muchos otros servicios.