Tutorial sobre como proteger serviços do Cloud Run


Este tutorial explica como criar uma aplicação segura de dois serviços executada no Cloud Run. Esta aplicação é um editor de Markdown que inclui um serviço de "frontend" público que qualquer pessoa pode usar para compor texto em Markdown e um serviço de "backend" privado que renderiza texto em Markdown para HTML.

Diagrama que mostra o fluxo de pedidos do "editor" de front-end para o "renderizador" de back-end.
O back-end "Renderer" é um serviço privado. Isto permite garantir um padrão de transformação de texto numa organização sem acompanhar as alterações nas bibliotecas em vários idiomas.

O serviço de back-end é privado através da funcionalidade de autenticação serviço a serviço baseada na IAM integrada do Cloud Run, que limita quem pode chamar o serviço. Ambos os serviços são criados com o princípio do menor privilégio, sem acesso ao resto do Google Cloud , exceto quando necessário.

Limitações ou não objetivos deste tutorial

Objetivos

  • Crie uma conta de serviço dedicada com autorizações mínimas para autenticação de serviço a serviço e acesso ao serviço ao resto do Google Cloud.
  • Escreva, crie e implemente dois serviços no Cloud Run que interagem.
  • Fazer pedidos entre um serviço do Cloud Run público e privado.

Custos

Neste documento, usa os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custos com base na sua utilização projetada, use a calculadora de preços.

Os novos Google Cloud utilizadores podem ser elegíveis 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. Verify that billing is enabled for your Google Cloud project.

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

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

  6. Enable the Cloud Run API.

    Enable the API

  7. Instale e inicialize a CLI gcloud.
  8. Instale o curl para experimentar o serviço
  9. Funções necessárias

    Para receber as autorizações de que precisa para concluir o tutorial, peça ao seu administrador para lhe conceder as seguintes funções da IAM no seu projeto:

    Para mais informações sobre a atribuição de funções, consulte o artigo Faça a gestão do acesso a projetos, pastas e organizações.

    Também pode conseguir as autorizações necessárias através de funções personalizadas ou outras funções predefinidas.

Configurar predefinições do gcloud

Para configurar o gcloud com as predefinições do seu serviço do Cloud Run:

  1. Defina o projeto predefinido:

    gcloud config set project PROJECT_ID

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

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

    gcloud config set run/region REGION

    Substitua REGION pela região do Cloud Run suportada à sua escolha.

Localizações do Cloud Run

O Cloud Run é regional, o que significa que a infraestrutura que executa os seus serviços do Cloud Run está localizada numa região específica e é gerida pela Google para estar disponível de forma redundante em todas as zonas dessa região.

O cumprimento dos requisitos de latência, disponibilidade ou durabilidade são fatores principais para selecionar a região onde os seus serviços do Cloud Run são executados. Geralmente, pode selecionar a região mais próxima dos seus utilizadores, mas deve considerar a localização dos outros Google Cloudprodutos usados pelo seu serviço do Cloud Run. A utilização Google Cloud de produtos em conjunto em várias localizações pode afetar a latência do seu serviço, bem como o custo.

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

Sujeito aos preços de Nível 1

  • asia-east1 (Taiwan)
  • asia-northeast1 (Tóquio)
  • asia-northeast2 (Osaca)
  • asia-south1 (Mumbai, Índia)
  • europe-north1 (Finlândia) ícone de folha Baixo CO2
  • europe-north2 (Estocolmo) ícone de folha Baixo CO2
  • europe-southwest1 (Madrid) í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 (Telavive)
  • northamerica-south1 (México)
  • us-central1 (Iowa) ícone de folha Baixo CO2
  • us-east1 (Carolina do Sul)
  • us-east4 (Virgínia do Norte)
  • us-east5 (Columbus)
  • us-south1 (Dallas) ícone de folha Baixo CO2
  • us-west1 (Oregão) ícone de folha Baixo CO2

Sujeito aos preços de Nível 2

  • africa-south1 (Joanesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seul, Coreia do Sul)
  • asia-southeast1 (Singapura)
  • asia-southeast2 (Jacarta)
  • asia-south2 (Deli, Í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)
  • europe-west6 (Zurique, Suíça) ícone de folha Baixo CO2
  • me-central1 (Doha)
  • me-central2 (Dammam)
  • 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 já criou um serviço do Cloud Run, pode ver a região no painel de controlo do Cloud Run na Google Cloud consola.

Obter o exemplo de código

Para obter o exemplo de código para utilização:

  1. Clone o repositório da app de exemplo para o Cloud Shell ou a máquina local:

    Node.js

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

    Python

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

    Ir

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

    Java

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

    C#

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

  2. Altere para o diretório que contém o código de exemplo do Cloud Run:

    Node.js

    cd nodejs-docs-samples/run/markdown-preview/

    Python

    cd python-docs-samples/run/markdown-preview/

    Ir

    cd golang-samples/run/markdown-preview/

    Java

    cd java-docs-samples/run/markdown-preview/

    C#

    cd dotnet-docs-samples/run/markdown-preview/

Rever o serviço de renderização de Markdown privado

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

  • Um ponto final em /
  • Espera pedidos POST
  • O corpo do pedido POST é texto Markdown

Pode querer rever todo o código para verificar se existem preocupações de segurança ou apenas para saber mais sobre o mesmo explorando o diretório ./renderer/. Tenha em atenção que o tutorial não explica o código de transformação Markdown.

Envio do serviço de renderização de Markdown privado

Para enviar o seu código, crie-o com o Cloud Build, carregue-o para o Artifact Registry e implemente-o no Cloud Run:

  1. Altere para o diretório renderer:

    Node.js

    cd renderer/

    Python

    cd renderer/

    Ir

    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

    Substituir:

    • REPOSITORY com um nome exclusivo para o repositório. Para cada localização do repositório num projeto, os nomes dos repositórios têm de ser exclusivos.
    • REGION com a Google Cloud região a usar para o repositório do Artifact Registry.
  3. Execute o seguinte comando para criar o contentor e publicar no Artifact Registry.

    Node.js

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    Python

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    Ir

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    Java

    Este exemplo usa o Jib para criar imagens do Docker com ferramentas Java comuns. O Jib otimiza as compilações de contentores sem precisar de um Dockerfile nem ter o Docker instalado. Saiba mais sobre como criar contentores Java com o Jib.

    1. Use o auxiliar de credenciais gcloud para autorizar o Docker a enviar para o seu Artifact Registry.

      gcloud auth configure-docker

    2. Use o plugin Jib Maven para criar e enviar o contentor para o Artifact Registry.

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

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

    Se tiver êxito, é apresentada a mensagem BUILD SUCCESS. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    C#

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

  4. Implemente como um serviço privado com acesso restrito.

    O Cloud Run oferece funcionalidades de controlo de acesso e identidade do serviço prontas a usar. O controlo de acesso fornece uma camada de autenticação que restringe a invocação do serviço por parte dos utilizadores e de outros serviços. A identidade do serviço permite restringir o acesso do seu serviço a outros recursos, criando uma conta de serviço dedicada com autorizações limitadas.Google Cloud

    1. Crie uma conta de serviço para funcionar como a "identidade de computação" do serviço de renderização. Por predefinição, não tem privilégios além da associação ao projeto.

      Linha de comandos

      gcloud iam service-accounts create renderer-identity

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte os 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 de Markdown não se integra diretamente com mais nada no Google Cloud. Não precisa de mais autorizações.

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

      Linha de comandos

      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 abreviado da conta de serviço em vez do endereço de email 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 os 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
        }
      }

Experimentar o serviço de conversão de Markdown privado

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

Para enviar algum texto em negrito para o serviço e ver a conversão dos asteriscos de markdown em etiquetas <strong> de HTML:

  1. Obtenha o URL do resultado da implementação.

  2. Use gcloud para derivar um símbolo de identidade especial apenas para desenvolvimento para autenticação:

    TOKEN=$(gcloud auth print-identity-token)
  3. Crie um pedido curl que transmita o texto Markdown não processado como um parâmetro de string de consulta com carateres de 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 implementação do serviço de renderização de Markdown.

  4. A resposta deve ser um fragmento HTML:

     <strong>Hello Bold Text</strong>
    

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

O serviço de edição oferece uma IU de introdução de texto simples e um espaço para ver a pré-visualização de HTML. Antes de continuar, reveja o código obtido anteriormente abrindo o diretório ./editor/.

Em seguida, explore as seguintes secções de código que integram os dois serviços de forma segura.

Node.js

O módulo render.js cria pedidos autenticados para o serviço de renderização privado. Usa o servidor de metadados no ambiente do Cloud Run para criar um token de identidade e adicioná-lo ao pedido HTTP como parte de um cabeçalho Authorization. Google Cloud

Noutros ambientes, a render.js usa as Credenciais padrão da aplicação para pedir um token aos servidores da 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);
  }
};

Analisa o Markdown do JSON e envia-o para o serviço de renderização para ser transformado 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 pedidos autenticados para serviços privados. Usa o Google Cloud servidor de metadados no ambiente do Cloud Run para criar um token de identidade e adicioná-lo ao pedido HTTP como parte de um cabeçalho Authorization.

Noutros ambientes, o new_request pede um token de identidade aos servidores da Google autenticando-se com as credenciais predefinidas da aplicação.

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

Analisa o Markdown do JSON e envia-o para o serviço de renderização para ser transformado 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

Ir

RenderService cria pedidos autenticados para serviços privados. Usa o servidor de metadados no ambiente do Cloud Run para criar um token de identidade e adicioná-lo ao pedido HTTP como parte de um cabeçalho Authorization. Google Cloud

Noutros ambientes, o RenderService pede um token de identidade aos servidores da Google autenticando-se com as credenciais predefinidas da aplicação.

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
}

O pedido é enviado para o serviço de renderização depois de adicionar o texto de marcação para ser transformado em HTML. Os erros de resposta são processados para diferenciar problemas de comunicação da 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 = 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 cria pedidos autenticados para serviços privados. Usa o Google Cloud servidor de metadados no ambiente do Cloud Run para criar um token de identidade e adicioná-lo ao pedido HTTP como parte de um cabeçalho Authorization.

Noutros ambientes, makeAuthenticatedRequest pede um token de identidade aos servidores da Google através da autenticação com Credenciais padrão da aplicação.

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

Analisa o Markdown do JSON e envia-o para o serviço de renderização para ser transformado 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 pedidos autenticados para serviços privados. Usa o Google Cloud servidor de metadados no ambiente do Cloud Run para criar um token de identidade e adicioná-lo ao pedido HTTP como parte de um cabeçalho Authorization.

Noutros ambientes, GetAuthenticatedPostResponse pede um token de identidade aos servidores da Google através da autenticação com Credenciais padrão da aplicação.

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

Analisa o Markdown do JSON e envia-o para o serviço de renderização para ser transformado 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);
}

Envio do serviço de edição pública

Para criar e implementar o seu código:

  1. Altere para o diretório editor:

    Node.js

    cd ../editor

    Python

    cd ../editor

    Ir

    cd ../editor

    Java

    cd ../editor

    C#

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

  2. Execute o seguinte comando para criar o contentor e publicar no Artifact Registry.

    Node.js

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Container Registry e pode ser reutilizada, se quiser.

    Python

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    Ir

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    Java

    Este exemplo usa o Jib para criar imagens do Docker com ferramentas Java comuns. O Jib otimiza as compilações de contentores sem precisar de um Dockerfile nem ter o Docker instalado. Saiba mais sobre como criar contentores Java com o Jib.

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

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

    Se tiver êxito, é apresentada a mensagem BUILD SUCCESS. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

    C#

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

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

    Após a conclusão com êxito, é apresentada uma mensagem SUCCESS com o ID, a hora de criação e o nome da imagem. A imagem é armazenada no Artifact Registry e pode ser reutilizada, se quiser.

  3. Implemente como um serviço privado 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 privado. Por predefinição, não tem privilégios além da associação ao projeto.

      Linha de comandos

      gcloud iam service-accounts create editor-identity

      Terraform

      Para saber como aplicar ou remover uma configuração do Terraform, consulte os 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 de interagir com mais nada, Google Cloud exceto com o serviço de renderização de Markdown.

    2. Conceda acesso à identidade de computação para invocar o serviço de renderização de Markdown.editor-identity Qualquer serviço que use isto como uma identidade de computação tem este privilégio.

      Linha 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 como aplicar ou remover uma configuração do Terraform, consulte os 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}"
      }

      Uma vez que lhe é atribuída a função 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. Implemente com a conta de serviço editor-identity e permita o acesso público não autenticado.

      Linha 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

      Substituir:

      • PROJECT_ID com o ID do seu projeto
      • SERVICE_URL com o URL fornecido após a implementação do serviço de renderização de Markdown.

      Terraform

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

      Implemente o serviço do 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 autorização ao allUsers para invocar o 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
      }

Compreender o tráfego HTTPS

Existem três pedidos HTTP envolvidos na renderização de markdown através destes serviços.

Diagrama que mostra o fluxo de pedidos do utilizador para o editor, do editor para obter um token do servidor de metadados, do editor para fazer um pedido ao serviço de renderização e do serviço de renderização para devolver HTML ao editor.
O serviço de front-end com o editor-identity invoca o serviço de renderização. Tanto o editor-identity como o renderer-identity têm autorizações limitadas, pelo que qualquer exploração de segurança ou injeção de código tem acesso limitado a outros Google Cloud recursos.

Experimentar

Para experimentar a aplicação completa de dois serviços:

  1. Navegue no navegador para o URL fornecido no passo de implementação acima.

  2. Experimente editar o texto Markdown à esquerda e clique no botão para ver a pré-visualização à direita.

    Deve ter esta forma:

    Captura de ecrã da interface do utilizador do editor de Markdown

Se optar por continuar a desenvolver estes serviços, lembre-se de que têm acesso restrito à gestão de identidade e acesso (IAM) ao resto do Google Cloud e terão de receber funções adicionais de IAM para aceder a muitos outros serviços.

Limpar

Se criou um novo projeto para este tutorial, elimine o projeto. Se usou um projeto existente e quer mantê-lo sem as alterações adicionadas neste tutorial, elimine os recursos criados para o tutorial.

Eliminar o projeto

A forma mais fácil de eliminar a faturação é eliminar o projeto que criou para o tutorial.

Para eliminar 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.

Eliminar recursos do tutorial

  1. Elimine os serviços do Cloud Run que implementou neste tutorial:

    gcloud

    gcloud run services delete editor
    gcloud run services delete renderer

    Também pode eliminar serviços do Cloud Run a partir da Google Cloud consola.

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

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

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

O que se segue?