Tutorial: como proteger serviços do Cloud Run


Este tutorial explica como criar um aplicativo seguro de dois serviços em execução no Cloud Run. Esse aplicativo é um editor Markdown que inclui um serviço "front-end" público que qualquer um pode usar para escrever texto de markdown e um serviço de "back-end" particular que renderiza o texto de Markdown para HTML.

Diagrama mostrando o fluxo de solicitação do "editor" de front-end para o "renderizador" de back-end.
O back-end do "Renderizador" é um serviço privado. Isso permite garantir um padrão de transformação de texto em uma organização sem rastrear alterações em bibliotecas em vários idiomas.

O serviço de back-end é privado, usando o recurso de Autenticação de serviço a serviço com base em IAM integrado ao Cloud Run, que limita quem pode chamar o serviço. Os dois serviços são criados com o princípio de menor privilégio, sem acesso ao restante do Google Cloud, exceto quando necessário.

Limitações ou objetivos não abarcados por este tutorial

Objetivos

  • Criar uma conta de serviço dedicada com permissões mínimas de autenticação de serviço a serviço e acesso de serviço ao restante do Google Cloud.
  • Gravar, criar e implantar dois serviços no Cloud Run que interagem.
  • Fazer solicitações entre um serviço público e privado do Cloud Run.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

  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. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

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

    Go to project selector

  5. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  6. Enable the Cloud Run API.

    Enable the API

  7. Instale e inicialize a CLI gcloud.
  8. Instale o curl para testar o serviço

Funções exigidas

Para conseguir as permissões necessárias para concluir o tutorial, peça ao administrador para conceder a você os seguintes papéis do IAM no seu projeto:

Para mais informações sobre a concessão de papéis, consulte Gerenciar o acesso a projetos, pastas e organizações.

Também é possível conseguir as permissões necessárias por meio de papéis personalizados ou de outros papéis predefinidos.

Como configurar padrões do gcloud

Para configurar a gcloud com os padrões do serviço do Cloud Run, realize as etapas a seguir:

  1. Defina seu projeto padrão:

    gcloud config set project PROJECT_ID

    Substitua PROJECT_ID pelo nome do projeto que você criou para este tutorial.

  2. Configure a gcloud para a região escolhida:

    gcloud config set run/region REGION

    Substitua REGION pela região compatível do Cloud Run.

Locais do Cloud Run

O Cloud Run é regional, o que significa que a infraestrutura que executa seus serviços do Cloud Run está localizada em uma região específica e é gerenciada pelo Google para estar disponível de maneira redundante em todas as zonas da região.

Atender aos seus requisitos de latência, disponibilidade ou durabilidade são os principais fatores para selecionar a região em que seus serviços do Cloud Run são executados. Geralmente, é possível selecionar a região mais próxima de seus usuários, mas considere a localização dos outros produtos do Google Cloud usados pelo serviço do Cloud Run. O uso de produtos do Google Cloud em vários locais pode afetar a latência e o custo do serviço.

O Cloud Run está disponível nas regiões a seguir:

Sujeitas aos preços do nível 1

  • asia-east1 (Taiwan)
  • asia-northeast1 (Tóquio)
  • asia-northeast2 (Osaka)
  • europe-north1 (Finlândia) Ícone de folha Baixo CO2
  • europe-southwest1 (Madri) Ícone de folha Baixo CO2
  • europe-west1 (Bélgica) Ícone de folha Baixo CO2
  • europe-west4 (Países Baixos) Ícone de folha Baixo CO2
  • europe-west8 (Milão)
  • europe-west9 (Paris) Ícone de folha Baixo CO2
  • me-west1 (Tel Aviv)
  • us-central1 (Iowa) Ícone de folha Baixo CO2
  • us-east1 (Carolina do Sul)
  • us-east4 (Norte da Virgínia)
  • us-east5 (Columbus)
  • us-south1 (Dallas) Ícone de folha Baixo CO2
  • us-west1 (Oregon) Ícone de folha Baixo CO2

Sujeitas aos preços do nível 2

  • africa-south1 (Johannesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seul, Coreia do Sul)
  • asia-southeast1 (Singapura)
  • asia-southeast2 (Jacarta)
  • asia-south1 (Mumbai, Índia)
  • asia-south2 (Déli, Índia)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsóvia, Polônia)
  • europe-west10 (Berlim) Ícone de folha Baixo CO2
  • europe-west12 (Turim)
  • europe-west2 (Londres, Reino Unido) Ícone de folha Baixo CO2
  • europe-west3 (Frankfurt, Alemanha) Ícone de folha Baixo CO2
  • europe-west6 (Zurique, Suíça) Ícone de folha Baixo CO2
  • me-central1 (Doha)
  • me-central2 (Damã)
  • northamerica-northeast1 (Montreal) Ícone de folha Baixo CO2
  • northamerica-northeast2 (Toronto) Ícone de folha Baixo CO2
  • southamerica-east1 (São Paulo, Brasil) Ícone de folha Baixo CO2
  • southamerica-west1 (Santiago, Chile) Ícone de folha Baixo CO2
  • us-west2 (Los Angeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Se você já criou um serviço do Cloud Run, é possível visualizar a região no painel do Cloud Run no console do Google Cloud.

Como recuperar o exemplo de código

Para recuperar o exemplo de código para uso, siga estas etapas:

  1. Clone o repositório do app de amostra no Cloud Shell ou na máquina local:

    Node.js

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Python

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Go

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Java

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    C#

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  2. Mude para o diretório que contém o código de amostra do 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/

Como revisar o serviço de renderização Markdown privado

Do ponto de vista do front-end, há uma especificação simples da API para o serviço do Markdown:

  • Um endpoint em /
  • Espera solicitações POST
  • O corpo da solicitação POST é o texto Markdown

Analise todo o código em busca de problemas de segurança ou saiba mais sobre ele explorando o diretório ./renderer/. O tutorial não explica o código de transformação do Markdown.

Como enviar o serviço de renderização Markdown privado

Para enviar o código, crie-o com o Cloud Build, faça o upload dele para o Artifact Registry e implante-o no Cloud Run:

  1. Altere para o diretório renderer:

    Node.js

    cd renderer/

    Python

    cd renderer/

    Go

    cd renderer/

    Java

    cd renderer/

    C#

    cd Samples.Run.MarkdownPreview.Renderer/

  2. Crie um Artifact Registry:

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

    Substitua:

    • REPOSITORY por um nome exclusivo para o repositório. Para cada local de repositório em um projeto, os nomes dos repositórios precisam ser exclusivos.
    • REGION pela região do Google Cloud a ser usada para o repositório do Artifact Registry.
  3. Execute o comando a seguir para criar o contêiner e o publique no Artifact Registry.

    Node.js

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e renderer é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    Python

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e renderer é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    Go

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e renderer é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    Java

    Esta amostra usa o Jib (em inglês) para criar imagens do Docker usando ferramentas comuns do Java. O Jib otimiza builds de contêiner sem a necessidade de um Dockerfile ou de ter o Docker (em inglês) instalado. Saiba mais sobre como criar contêineres Java com o Jib.

    1. Use o auxiliar de credencial do gcloud para autorizar o Docker a enviar por push ao Artifact Registry.

      gcloud auth configure-docker

    2. Use o plug-in do Maven do Jib para criar e enviar por push o contêiner ao Artifact Registry.

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e renderer é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem "BUILD SUCCESS". A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    C#

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e renderer é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

  4. Implante como um serviço particular com acesso restrito.

    O Cloud Run oferece recursos de controle de acesso e identidade de serviço prontos para uso. O controle de acesso fornece uma camada de autenticação que impede que usuários e outros serviços invoquem o serviço. A identidade de serviço permite impedir que o serviço acesse outros recursos do Google Cloud criando uma conta de serviço dedicada com permissões limitadas.

    1. Crie uma conta de serviço para servir como a “identidade de computação” do serviço de renderização. Por padrão, ela não tem privilégios além da associação ao projeto.

      Linha de comando

      gcloud iam service-accounts create renderer-identity

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do Terraform.

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

      O serviço de renderização Markdown não se integra diretamente a nada no Google Cloud. Ele não precisa de mais permissões.

    2. Implante com a conta de serviço renderer-identity e negue o acesso não autenticado.

      Linha de comando

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

      O Cloud Run pode usar o nome da conta do serviço de formulário curto em vez do endereço de e-mail completo se a conta de serviço fizer parte do mesmo projeto.

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do 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
        }
      }

Como testar o serviço de renderização Markdown privado

Os serviços privados não podem ser carregados diretamente por um navegador da Web. Em vez disso, use curl ou uma ferramenta de CLI de solicitação HTTP semelhante que permita injetar um cabeçalho Authorization.

Para enviar um texto em negrito ao serviço e vê-lo converter os asteriscos de marcação em tags HTML <strong>:

  1. Receba o URL da saída da implantação.

  2. Use gcloud para derivar um token de identidade especial somente para desenvolvimento para autenticação:

    TOKEN=$(gcloud auth print-identity-token)
  3. Crie uma solicitação de curl que transmita o texto bruto de Markdown como um parâmetro de string de consulta com escape de URL:

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

    Substitua SERVICE_URL pelo URL fornecido após a implantação do serviço de renderização Markdown.

  4. A resposta será um snippet HTML:

     <strong>Hello Bold Text</strong>
    

Como analisar a integração entre o editor e os serviços de renderização

O serviço de edição fornece uma IU de entrada de texto simples e um espaço para visualização de HTML. Antes de continuar, revise o código recuperado anteriormente abrindo o diretório ./editor/.

Em seguida, explore as seguintes seções de código que integram com segurança os dois serviços.

Node.js

O módulo render.js cria solicitações autenticadas para o serviço de renderizador particular. Ele usa o servidor de metadados do Google Cloud no ambiente do Cloud Run para criar um token de identidade e adicioná-lo à solicitação HTTP como parte de um cabeçalho Authorization.

Em outros ambientes, render.js usa o Application Default Credentials para solicitar um token dos servidores do 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);
  }
};

Analise a marcação a partir do JSON e envie-a ao serviço do Renderizador para ser transformada em 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

O método new_request cria solicitações autenticadas para serviços particulares. Ele usa o servidor de metadados do Google Cloud no ambiente do Cloud Run para criar um token de identidade e adicioná-lo à solicitação HTTP como parte de um cabeçalho Authorization.

Em outros ambientes, new_request solicita um token de identidade dos servidores do Google, fazendo a autenticação com Application Default Credentials.

import os
import urllib

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


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

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

    Args:
        data: data for the authenticated request

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

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

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

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

Analise a marcação a partir do JSON e envie-a ao serviço do Renderizador para ser transformada em 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 cria solicitações autenticadas para serviços particulares. Ele usa o servidor de metadados do Google Cloud no ambiente do Cloud Run para criar um token de identidade e adicioná-lo à solicitação HTTP como parte de um cabeçalho Authorization.

Em outros ambientes, RenderService solicita um token de identidade dos servidores do Google, fazendo a autenticação com Application Default Credentials.

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
}

A solicitação é enviada ao serviço renderizador após adicionar o texto de marcação a ser transformado em HTML. Os erros de resposta são tratados diferenciando problemas de comunicação e funcionalidade de renderização.


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 cria solicitações autenticadas para serviços particulares. Ele usa o servidor de metadados do Google Cloud no ambiente do Cloud Run para criar um token de identidade e adicioná-lo à solicitação HTTP como parte de um cabeçalho Authorization.

Em outros ambientes, makeAuthenticatedRequest solicita um token de identidade dos servidores do Google, fazendo a autenticação com Application Default Credentials.

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

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

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

Analise a marcação a partir do JSON e envie-a ao serviço do Renderizador para ser transformada em 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 cria solicitações autenticadas para serviços particulares. Ele usa o servidor de metadados do Google Cloud no ambiente do Cloud Run para criar um token de identidade e adicioná-lo à solicitação HTTP como parte de um cabeçalho Authorization.

Em outros ambientes, GetAuthenticatedPostResponse solicita um token de identidade dos servidores do Google, fazendo a autenticação com Application Default Credentials.

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

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

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

    return await upstreamResponse.Content.ReadAsStringAsync();
}

Analise a marcação a partir do JSON e envie-a ao serviço do Renderizador para ser transformada em HTML.

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

Como enviar o serviço de editor público

Para criar e implantar seu código:

  1. Altere para o diretório editor:

    Node.js

    cd ../editor

    Python

    cd ../editor

    Go

    cd ../editor

    Java

    cd ../editor

    C#

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

  2. Execute o comando a seguir para criar o contêiner e o publique no Artifact Registry.

    Node.js

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e editor é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Python

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e editor é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    Go

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e editor é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    Java

    Esta amostra usa o Jib (em inglês) para criar imagens do Docker usando ferramentas comuns do Java. O Jib otimiza builds de contêiner sem a necessidade de um Dockerfile ou de ter o Docker (em inglês) instalado. Saiba mais sobre como criar contêineres Java com o Jib.

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e editor é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem "BUILD SUCCESS". A imagem é armazenada no Artifact Registry e pode ser reutilizada.

    C#

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

    Em que PROJECT_ID é o ID do projeto do Google Cloud e editor é o nome que você quer dar ao seu serviço.

    Após a conclusão, você receberá uma mensagem de SUCESSO com o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada.

  3. Implante como um serviço particular com acesso especial ao serviço de renderização.

    1. Crie uma conta de serviço para servir como a “identidade de computação” do serviço particular. Por padrão, ela não tem privilégios além da associação ao projeto.

      Linha de comando

      gcloud iam service-accounts create editor-identity

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do Terraform.

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

      O serviço Editor não precisa interagir com mais nada no Google Cloud além do serviço de renderização Markdown.

    2. Conceda acesso à identidade de computação editor-identity para invocar o serviço de renderização Markdown. Qualquer serviço que use isso como uma identidade de computação terá esse privilégio.

      Linha de comando

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

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do 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 isso recebe o papel de invocador no contexto do serviço de renderização, o serviço de renderização é o único serviço privado do Cloud Run que o editor pode invocar.

    3. Implante com a conta de serviço editor-identity e permita acesso público e não autenticado.

      Linha de comando

      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

      Substitua:

      • PROJECT_ID pelo ID do projeto;
      • SERVICE_URL pelo URL fornecido após a implantação do serviço de renderização Markdown.

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do Terraform.

      Implante o serviço de 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
      
        }
      }

      Conceda à permissão allUsers a invocação do serviço:

      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
      }

Noções básicas sobre o tráfego HTTPS

Há três solicitações HTTP envolvidas na renderização de marcação usando esses serviços.

Diagrama mostrando o fluxo de solicitação do usuário para o editor, editor para receber um token do servidor de metadados, editor para fazer a solicitação de renderização do serviço, renderizar serviço para retornar HTML ao editor.
O serviço de front-end com o editor-identity invoca o serviço de renderização. editor-identity e renderer-identity têm permissões limitadas. Portanto, qualquer exploração de segurança ou injeção de código tem acesso limitado a outros recursos do Google Cloud.

Como testar

Para testar o aplicativo de dois serviços completo:

  1. Navegue até o URL fornecido pela etapa de implantação acima.

  2. Tente editar o texto Markdown à esquerda e clique no botão para visualizá-lo à direita.

    O resultado será parecido com este:

    Captura de tela da interface do usuário do Markdown Editor

Se você optar por continuar desenvolvendo esses serviços, lembre-se de que eles têm acesso restrito do gerenciamento de identidade e acesso (IAM, na sigla em inglês) ao restante do Google Cloud e precisarão receber mais papéis do IAM para acessar muitos outros serviços.

Limpar

Se você criou um novo projeto para este tutorial, exclua o projeto. Se você usou um projeto atual e quer mantê-lo sem as alterações incluídas neste tutorial, exclua os recursos criados para o tutorial.

Como excluir o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  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.

Como excluir recursos do tutorial

  1. Exclua os serviços do Cloud Run que você implantou neste tutorial:

    gcloud

    gcloud run services delete editor
    gcloud run services delete renderer

    Também é possível excluir os serviços do Cloud Run no console do Google Cloud.

  2. Remova as configurações padrão do gcloud que você adicionou durante a configuração do tutorial.

     gcloud config unset run/region
    
  3. Remova a configuração do projeto:

     gcloud config unset project
    
  4. Exclua outros recursos do Google Cloud criados neste tutorial:

A seguir