Secure Cloud Run services tutorial Stay organized with collections Save and categorize content based on your preferences.
This tutorial walks through how to create a secure two-service applicationrunning on Cloud Run. This application is aMarkdown editor whichincludes a public "frontend" service which anyone can use to compose markdowntext, and a private "backend" service which renders Markdown text to HTML.
The backend service is private using Cloud Run's built-in,IAM-based service-to-service authenticationfeature, that limits who can call the service. Both services are built with theprinciple of least privilege,with no access to the rest of Google Cloud except where necessary.
Limitations or non-goals of this tutorial
This tutorial does not showend user authentication,which usesIdentity Platform orFirebase Authentication to generate user ID tokens andmanually verify user identities. To learn more about end userauthentication, refer to the Cloud Run tutorial forend user authentication.
This tutorial does not show combining IAM-based authentication and ID tokenmethods because this is not supported.
Objectives
- Create a dedicated service account with minimal permissions for service-to-serviceauthentication and service access to the rest of Google Cloud.
- Write, build, and deploy two services to Cloud Run which interact.
- Make requests between a public and private Cloud Run service.
Costs
In this document, you use the following billable components of Google Cloud:
To generate a cost estimate based on your projected usage, use thepricing calculator.
Before you begin
- 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.
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project.Roles required to select or create a project
- Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
- Create a project: To create a project, you need the Project Creator role (
roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.createpermission.Learn how to grant roles.
Verify that billing is enabled for your Google Cloud project.
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project.Roles required to select or create a project
- Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
- Create a project: To create a project, you need the Project Creator role (
roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.createpermission.Learn how to grant roles.
Verify that billing is enabled for your Google Cloud project.
Enable the Cloud Run API.
Roles required to enable APIs
To enable APIs, you need the Service Usage Admin IAM role (
roles/serviceusage.serviceUsageAdmin), which contains theserviceusage.services.enablepermission.Learn how to grant roles.- Install and initialize the gcloud CLI.
- Installcurl to try out the service
Required roles
To get the permissions that you need to complete the tutorial, ask your administrator to grant you the following IAM roles on your project:
- Cloud Build Editor (
roles/cloudbuild.builds.editor) - Cloud Run Admin (
roles/run.admin) - Create Service Accounts (
roles/iam.serviceAccountCreator) - Project IAM Admin (
roles/resourcemanager.projectIamAdmin) - Service Account User (
roles/iam.serviceAccountUser) - Service Usage Consumer (
roles/serviceusage.serviceUsageConsumer) - Storage Admin (
roles/storage.admin) - Artifact Registry Repository Administrator (
roles/artifactregistry.repoAdmin)
For more information about granting roles, seeManage access to projects, folders, and organizations.
You might also be able to get the required permissions throughcustom roles or otherpredefined roles.
Note:IAM basic roles might also contain permissions to complete the tutorial. You shouldn't grant basic roles in a production environment, but you can grant them in a development or test environment.Set up gcloud defaults
To configure gcloud with defaults for your Cloud Run service:
Set your default project:
gcloudconfigsetprojectPROJECT_IDReplacePROJECT_ID with the name of the project you created forthis tutorial.
Configure gcloud for your chosen region:
gcloudconfigsetrun/regionREGIONReplaceREGION with the supported Cloud Runregionof your choice.
Cloud Run locations
Cloud Run is regional, which means the infrastructure thatruns your Cloud Run services is located in a specific region and ismanaged by Google to be redundantly available acrossall the zones within that region.
Meeting your latency, availability, or durability requirements are primaryfactors for selecting the region where your Cloud Run services are run.You can generally select the region nearest to your users but you should considerthe location of theother Google Cloudproducts that are used by your Cloud Run service.Using Google Cloud products together across multiple locations can affectyour service's latency as well as cost.
Cloud Run is available in the following regions:
Subject toTier 1 pricing
asia-east1(Taiwan)asia-northeast1(Tokyo)asia-northeast2(Osaka)asia-south1(Mumbai, India)asia-southeast3(Bangkok)europe-north1(Finland)Low CO2
europe-north2(Stockholm)Low CO2
europe-southwest1(Madrid)Low CO2
europe-west1(Belgium)Low CO2
europe-west4(Netherlands)Low CO2
europe-west8(Milan)europe-west9(Paris)Low CO2
me-west1(Tel Aviv)northamerica-south1(Mexico)us-central1(Iowa)Low CO2
us-east1(South Carolina)us-east4(Northern Virginia)us-east5(Columbus)us-south1(Dallas)Low CO2
us-west1(Oregon)Low CO2
Subject toTier 2 pricing
africa-south1(Johannesburg)asia-east2(Hong Kong)asia-northeast3(Seoul, South Korea)asia-southeast1(Singapore)asia-southeast2(Jakarta)asia-south2(Delhi, India)australia-southeast1(Sydney)australia-southeast2(Melbourne)europe-central2(Warsaw, Poland)europe-west10(Berlin)europe-west12(Turin)europe-west2(London, UK)Low CO2
europe-west3(Frankfurt, Germany)europe-west6(Zurich, Switzerland)Low CO2
me-central1(Doha)me-central2(Dammam)northamerica-northeast1(Montreal)Low CO2
northamerica-northeast2(Toronto)Low CO2
southamerica-east1(Sao Paulo, Brazil)Low CO2
southamerica-west1(Santiago, Chile)Low CO2
us-west2(Los Angeles)us-west3(Salt Lake City)us-west4(Las Vegas)
If you already created a Cloud Run service, you can view theregion in the Cloud Run dashboard in theGoogle Cloud console.
Retrieve the code sample
To retrieve the code sample for use:
Clone the sample app repository to your Cloud Shell or local machine:
Node.js
gitclonehttps://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
Alternatively, you can download the sample as a zip file and extract it.
Python
gitclonehttps://github.com/GoogleCloudPlatform/python-docs-samples.git
Alternatively, you can download the sample as a zip file and extract it.
Go
gitclonehttps://github.com/GoogleCloudPlatform/golang-samples.git
Alternatively, you can download the sample as a zip file and extract it.
Java
gitclonehttps://github.com/GoogleCloudPlatform/java-docs-samples.git
Alternatively, you can download the sample as a zip file and extract it.
C#
gitclonehttps://github.com/GoogleCloudPlatform/dotnet-docs-samples.git
Alternatively, you can download the sample as a zip file and extract it.
Change to the directory that contains the Cloud Run samplecode:
Node.js
cdnodejs-docs-samples/run/markdown-preview/Python
cdpython-docs-samples/run/markdown-preview/Go
cdgolang-samples/run/markdown-preview/Java
cdjava-docs-samples/run/markdown-preview/C#
cddotnet-docs-samples/run/markdown-preview/
Review the private Markdown rendering service
From the perspective of the frontend there is a simple API specification forthe Markdown service:
- One endpoint at
/ - Expects POST requests
- The body of the POST request is Markdown text
You may want to review all of the code for any security concerns or just tolearn more about it by exploring the./renderer/ directory. Note that thetutorial does not explain the Markdown transformation code.
Ship the private Markdown rendering service
To ship your code, build with Cloud Build, upload toArtifact Registry, and deploy to Cloud Run:
Change to the
rendererdirectory:Node.js
cdrenderer/Python
cdrenderer/Go
cdrenderer/Java
cdrenderer/C#
cdSamples.Run.MarkdownPreview.Renderer/Create an Artifact Registry:
gcloudartifactsrepositoriescreateREPOSITORY\--repository-formatdocker\--locationREGION
Replace:
- REPOSITORY with a unique name for the repository. For each repository location in a project, repository names must be unique.
- REGION with the Google Cloud region to be used for theArtifact Registry repository.
Run the following command to build your container and publish onArtifact Registry.
Node.js
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer
WherePROJECT_ID is your Google Cloud project ID, and
rendereris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Python
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer
WherePROJECT_ID is your Google Cloud project ID, and
rendereris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Go
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer
WherePROJECT_ID is your Google Cloud project ID, and
rendereris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Java
This sample usesJib to buildDocker images using common Java tools. Jib optimizes container builds withoutthe need for a Dockerfile or havingDockerinstalled. Learn more aboutbuilding Java containers with Jib.
Use thegcloud credential helperto authorize Docker to push to your Artifact Registry.
gcloudauthconfigure-docker
Use the Jib Maven Plugin to build and push the container to Artifact Registry.
mvncompilejib:build-Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer
WherePROJECT_ID is your Google Cloud project ID, and
rendereris thename you want to give your service.Upon success, you will see a BUILD SUCCESS message. The image is stored inArtifact Registry and can be reused if needed.
C#
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer
WherePROJECT_ID is your Google Cloud project ID, and
rendereris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Deploy as a private service with restricted access.
Cloud Run provides out-of-the-boxaccess controlandservice identity features. Access control provides anauthentication layer that restricts users and other services from invoking theservice. Service identity allows restricting your service from accessing otherGoogle Cloud resources by creating a dedicated service account with limitedpermissions.
Create a service account to serve as the "compute identity" of the renderservice. By default this has no privileges other than project membership.
Command line
gcloudiamservice-accountscreaterenderer-identity
Terraform
To learn how to apply or remove a Terraform configuration, seeBasic Terraform commands.
resource"google_service_account""renderer"{account_id="renderer-identity"display_name="Service identity of the Renderer (Backend) service."}The Markdown rendering service does not integrate directly with anythingelse in Google Cloud. It needs no further permissions.
Deploy with the
renderer-identityservice account and deny unauthenticatedaccess.Command line
gcloudrundeployrenderer\--imageREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer\--service-accountrenderer-identity\--no-allow-unauthenticated
Cloud Run can use the short form service account name instead of the full email address if the service account is part of the same project.
Terraform
To learn how to apply or remove a Terraform configuration, seeBasic Terraform commands.
resource"google_cloud_run_v2_service""renderer"{name="renderer"location="us-central1"deletion_protection=false # set to "true" in productiontemplate{containers{ # Replace with the URL of your Secure Services > Renderer image. # gcr.io/<PROJECT_ID>/rendererimage="us-docker.pkg.dev/cloudrun/container/hello"}service_account=google_service_account.renderer.email}}
Try out the private Markdown rendering service
Private services cannot be directly loaded by a web browser. Instead, usecurlor a similar HTTP request CLI tool that allows injecting anAuthorization header.
To send some bold text to the service and see it convert the markdown asterisks toHTML<strong> tags:
Get the URL from the deployment output.
Use
gcloudto derive a special development-only identity token for authentication:TOKEN=$(gcloudauthprint-identity-token)
Create a curl request that passes the raw Markdown text as a URL-escaped query string parameter:
curl-H"Authorization: Bearer$TOKEN"\-H'Content-Type: text/plain'\-d'**Hello Bold Text**'\SERVICE_URL
ReplaceSERVICE_URL with the URL provided after deploying theMarkdown rendering service.
The response should be an HTML snippet:
<strong>Hello Bold Text</strong>
Review the integration between editor and rendering services
The editor service provides a simple text-entry UI and a space to see the HTMLpreview. Before continuing, review the code retrieved earlier by opening the./editor/ directory.
Next, explore the following few sections of code that securelyintegrates the two services.
Node.js
Therender.js module creates authenticated requests to the private rendererservice. It uses the Google Cloud metadata server in theCloud Run environment to create an identity token and addit to the HTTP request as part of anAuthorization header.
In other environments,render.js usesApplication Default Credentialsto request a token from Google's servers.
import{GoogleAuth}from'google-auth-library';importgotfrom'got';constauth=newGoogleAuth();letclient,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.constrenderRequest=asyncmarkdown=>{if(!process.env.EDITOR_UPSTREAM_RENDER_URL)throwError('EDITOR_UPSTREAM_RENDER_URL needs to be set.');serviceUrl=process.env.EDITOR_UPSTREAM_RENDER_URL;// Build the request to the Renderer receiving service.constserviceRequestOptions={method:'POST',headers:{'Content-Type':'text/plain',},body:markdown,timeout:{request:3000,},};try{// Create a Google Auth client with the Renderer service url as the target audience.if(!client)client=awaitauth.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.constclientHeaders=awaitclient.getRequestHeaders();serviceRequestOptions.headers['Authorization']=clientHeaders['Authorization'];}catch(err){throwError('could not create an identity token: '+err.message);}try{// serviceResponse converts the Markdown plaintext to HTML.constserviceResponse=awaitgot(serviceUrl,serviceRequestOptions);returnserviceResponse.body;}catch(err){throwError('request to rendering service failed: '+err.message);}};Parse the markdown from JSON and send it to the Renderer service to betransformed into HTML.
app.post('/render',async(req,res)=>{try{constmarkdown=req.body.data;constresponse=awaitrenderRequest(markdown);res.status(200).send(response);}catch(err){console.error('Error rendering markdown:',err);res.status(500).send(err);}});Python
Thenew_request method creates authenticated requests to private services.It uses the Google Cloud metadata server in the Cloud Runenvironment to create an identity token and add it to the HTTP requestas part of anAuthorization header.
In other environments,new_request requests an identity token from Google'sservers by authenticating withApplication Default Credentials.
importosimporturllibimportgoogle.auth.transport.requestsimportgoogle.oauth2.id_tokendefnew_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")ifnoturl:raiseException("EDITOR_UPSTREAM_RENDER_URL missing")req=urllib.request.Request(url,data=data.encode())auth_req=google.auth.transport.requests.Request()target_audience=urlid_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)returnresponse.read()Parse the markdown from JSON and send it to the Renderer service to betransformed into HTML.
@app.route("/render",methods=["POST"])defrender_handler():"""Parse the markdown from JSON and send it to the Renderer service to be transformed into HTML. """body=request.get_json(silent=True)ifnotbody:return"Error rendering markdown: Invalid JSON",400data=body["data"]try:parsed_markdown=render.new_request(data)returnparsed_markdown,200exceptExceptionaserr:returnf"Error rendering markdown:{err}",500Go
RenderService creates authenticated requests to private services. It uses the Google Cloud metadata server in the Cloud Run environment tocreate an identity token and add it to the HTTP request as part of anAuthorization header.
In other environments,RenderService requests an identity token from Google'sservers by authenticating withApplication Default Credentials.
import("bytes""context""fmt""io""net/http""time""golang.org/x/oauth2""google.golang.org/api/idtoken")// RenderService represents our upstream render service.typeRenderServicestruct{// URL is the render service address.URLstring// tokenSource provides an identity token for requests to the Render Service.tokenSourceoauth2.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(methodstring)(*http.Request,error){req,err:=http.NewRequest(method,s.URL,nil)iferr!=nil{returnnil,fmt.Errorf("http.NewRequest: %w",err)}ctx,cancel:=context.WithTimeout(context.Background(),30*time.Second)defercancel()// Create a TokenSource if none exists.ifs.tokenSource==nil{s.tokenSource,err=idtoken.NewTokenSource(ctx,s.URL)iferr!=nil{returnnil,fmt.Errorf("idtoken.NewTokenSource: %w",err)}}// Retrieve an identity token. Will reuse tokens until refresh needed.token,err:=s.tokenSource.Token()iferr!=nil{returnnil,fmt.Errorf("TokenSource.Token: %w",err)}token.SetAuthHeader(req)returnreq,nil}The request is sent to the Renderer service after adding the markdowntext to be transformed into HTML. Response errors are handled to differentiatecommunication problems from rendering functionality.
varrenderClient=&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)iferr!=nil{returnnil,fmt.Errorf("RenderService.NewRequest: %w",err)}req.Body=io.NopCloser(bytes.NewReader(in))deferreq.Body.Close()resp,err:=renderClient.Do(req)iferr!=nil{returnnil,fmt.Errorf("http.Client.Do: %w",err)}out,err:=io.ReadAll(resp.Body)iferr!=nil{returnnil,fmt.Errorf("ioutil.ReadAll: %w",err)}ifresp.StatusCode!=http.StatusOK{returnout,fmt.Errorf("http.Client.Do: %s (%d): request not OK",http.StatusText(resp.StatusCode),resp.StatusCode)}returnout,nil}Java
makeAuthenticatedRequest creates authenticated requests to privateservices. It uses the Google Cloud metadata server in theCloud Run environment to create an identity token and add it to theHTTP request as part of anAuthorization header.
In other environments,makeAuthenticatedRequest requests an identity tokenfrom Google's servers by authenticating withApplication Default Credentials.
// makeAuthenticatedRequest creates a new HTTP request authenticated by a JSON Web Tokens (JWT)// retrievd from Application Default Credentials.publicStringmakeAuthenticatedRequest(Stringurl,Stringmarkdown){Stringhtml="";try{// Retrieve Application Default CredentialsGoogleCredentialscredentials=GoogleCredentials.getApplicationDefault();IdTokenCredentialstokenCredentials=IdTokenCredentials.newBuilder().setIdTokenProvider((IdTokenProvider)credentials).setTargetAudience(url).build();// Create an ID tokenStringtoken=tokenCredentials.refreshAccessToken().getTokenValue();// Instantiate HTTP requestMediaTypecontentType=MediaType.get("text/plain; charset=utf-8");okhttp3.RequestBodybody=okhttp3.RequestBody.create(markdown,contentType);Requestrequest=newRequest.Builder().url(url).addHeader("Authorization","Bearer "+token).post(body).build();Responseresponse=ok.newCall(request).execute();html=response.body().string();}catch(IOExceptione){logger.error("Unable to get rendered data",e);}returnhtml;}Parse the markdown from JSON and send it to the Renderer service to betransformed into HTML.
// '/render' expects a JSON body payload with a 'data' property holding plain text// for rendering.@PostMapping(value="/render",consumes="application/json")publicStringrender(@RequestBodyDatadata){Stringmarkdown=data.getData();Stringurl=System.getenv("EDITOR_UPSTREAM_RENDER_URL");if(url==null){Stringmsg="No configuration for upstream render service: "+"add EDITOR_UPSTREAM_RENDER_URL environment variable";logger.error(msg);thrownewIllegalStateException(msg);}Stringhtml=makeAuthenticatedRequest(url,markdown);returnhtml;}C#
GetAuthenticatedPostResponse creates authenticated requests to privateservices. It uses the Google Cloud metadata server in theCloud Run environment to create an identity token and add it to theHTTP request as part of anAuthorization header.
In other environments,GetAuthenticatedPostResponse requests an identity tokenfrom Google's servers by authenticating withApplication Default Credentials.
privateasyncTask<string>GetAuthenticatedPostResponse(stringurl,stringpostBody){// Get the OIDC access token from the service account via Application Default CredentialsGoogleCredentialcredential=awaitGoogleCredential.GetApplicationDefaultAsync();OidcTokentoken=awaitcredential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(url));stringaccessToken=awaittoken.GetAccessTokenAsync();// Create request to the upstream service with the generated OAuth access token in the Authorization headervarupstreamRequest=newHttpRequestMessage(HttpMethod.Post,url);upstreamRequest.Headers.Authorization=newAuthenticationHeaderValue("Bearer",accessToken);upstreamRequest.Content=newStringContent(postBody);varupstreamResponse=await_httpClient.SendAsync(upstreamRequest);upstreamResponse.EnsureSuccessStatusCode();returnawaitupstreamResponse.Content.ReadAsStringAsync();}Parse the markdown from JSON and send it to the Renderer service to betransformed into HTML.
publicasyncTask<IActionResult>Index([FromBody]RenderModelmodel){varmarkdown=model.Data??string.Empty;varrenderedHtml=awaitGetAuthenticatedPostResponse(_editorUpstreamRenderUrl,markdown);returnContent(renderedHtml);}Ship the public editor service
To build and deploy your code:
Change to the
editordirectory:Node.js
cd../editorPython
cd../editorGo
cd../editorJava
cd../editorC#
cd../Samples.Run.MarkdownPreview.Editor/Run the following command to build your container and publish onArtifact Registry.
Node.js
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor
WherePROJECT_ID is your Google Cloud project ID, and
editoris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Container Registry and can bereused if needed.
Python
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor
WherePROJECT_ID is your Google Cloud project ID, and
editoris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Go
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor
WherePROJECT_ID is your Google Cloud project ID, and
editoris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Java
This sample usesJib to buildDocker images using common Java tools. Jib optimizes container builds withoutthe need for a Dockerfile or havingDockerinstalled. Learn more aboutbuilding Java containers with Jib.mvncompilejib:build-Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editorWherePROJECT_ID is your Google Cloud project ID, and
editoris thename you want to give your service.Upon success, you will see a BUILD SUCCESS message. The image is stored inArtifact Registry and can be reused if needed.
C#
gcloudbuildssubmit--tagREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor
WherePROJECT_ID is your Google Cloud project ID, and
editoris thename you want to give your service.Upon success, you will see a SUCCESS message containing the ID, creationtime, and image name. The image is stored in Artifact Registry and can bereused if needed.
Deploy as a private service with special access to the rendering service.
Create a service account to serve as the "compute identity" of the privateservice. By default this has no privileges other than project membership.
Command line
gcloudiamservice-accountscreateeditor-identity
Terraform
To learn how to apply or remove a Terraform configuration, seeBasic Terraform commands.
resource"google_service_account""editor"{account_id="editor-identity"display_name="Service identity of the Editor (Frontend) service."}The Editor service does not need to interact with anything else in Google Cloud other than the Markdown rendering service.
Grant access to the
editor-identitycompute identity to invoke the Markdownrendering service. Any service which uses this as a compute identity willhave this privilege.Command line
gcloudrunservicesadd-iam-policy-bindingrenderer\--memberserviceAccount:editor-identity@PROJECT_ID.iam.gserviceaccount.com\--roleroles/run.invoker
Terraform
To learn how to apply or remove a Terraform configuration, seeBasic Terraform commands.
resource"google_cloud_run_service_iam_member""editor_invokes_renderer"{location=google_cloud_run_v2_service.renderer.locationservice=google_cloud_run_v2_service.renderer.namerole="roles/run.invoker"member="serviceAccount:${google_service_account.editor.email}"}Because this is given the invoker role in the context of the render service,the render service is the only private Cloud Run service the editorcan invoke.
Deploy with the
editor-identityservice account and allow public,unauthenticated access.Command line
gcloudrundeployeditor--imageREGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor\--service-accounteditor-identity\--set-env-varsEDITOR_UPSTREAM_RENDER_URL=SERVICE_URL\--allow-unauthenticated
Replace:
- PROJECT_ID with your project ID
- SERVICE_URL with the URL provided after deploying the Markdown rendering service.
Terraform
To learn how to apply or remove a Terraform configuration, seeBasic Terraform commands.
Deploy the editor service:
resource"google_cloud_run_v2_service""editor"{name="editor"location="us-central1"deletion_protection=false # set to "true" in productiontemplate{containers{ # Replace with the URL of your Secure Services > Editor image. # gcr.io/<PROJECT_ID>/editorimage="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}}Grant
allUserspermission to invoke the service: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.locationproject=google_cloud_run_v2_service.editor.projectservice=google_cloud_run_v2_service.editor.namepolicy_data=data.google_iam_policy.noauth.policy_data}
Understand the HTTPS traffic
There are three HTTP requests involved in rendering markdown using these services.
editor-identity invokes the render service. Botheditor-identity andrenderer-identity have limited permissions so any security exploit or code injection has limited access to other Google Cloud resources.Trying it out
To try out the complete two-service application:
Navigate your browser to the URL provided by the deployment step above.
Try editing the Markdown text on the left and click the button to see itpreview on the right.
It should look like this:
Success: You created a secure two-service application running on Cloud Run.
If you choose to continue developing these services, remember that they haverestricted Identity and Access Management (IAM) access to the rest of Google Cloud andwill need to be given additional IAM roles to access many otherservices.
Clean up
If you created a new project for this tutorial,delete the project.If you used an existing project and wish to keep it without the changes addedin this tutorial,delete resources created for the tutorial.
Delete the project
The easiest way to eliminate billing is to delete the project that you created for the tutorial.
To delete the project:
Delete tutorial resources
Delete the Cloud Run services you deployed in this tutorial:
gcloud
gcloudrunservicesdeleteeditorgcloudrunservicesdeleterenderer
You can also delete Cloud Run services from theGoogle Cloud console.
Remove the gcloud default configurations you added during tutorial setup.
gcloudconfigunsetrun/regionRemove the project configuration:
gcloud config unset projectDelete other Google Cloud resources created in this tutorial:
- Delete the editor container image named
REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editorfrom Artifact Registry - Delete the render container image named
REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/rendererfrom Artifact Registry - Delete the editor service account
editor-identity@PROJECT_ID.iam.gserviceaccount.com - Delete the render service account
renderer-identity@PROJECT_ID.iam.gserviceaccount.com
- Delete the editor container image named
What's next
- Further secure your project by walking through theusing IAM securely checklist
- Extend this sample application to track Markdown usage withCloud Monitoring custom metrics
- Review thePub/Sub tutorial for an approach tosecure, asynchronous microservices
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-19 UTC.