DEV Community

Alvaro David
Alvaro David

Posted on

Secure Web App access with GCP Identity-Aware Proxy

Security is an important part of any architecture, especially for remote workers.

During the pandemic, many challenges have appeared for security teams, I saw a lot... a lot... a lot of cloud admins creating VPNs for remote workers in order to access to internal apps.

Imagine this: workers for financial teams, managements teams, people that never heard about VPNs... and now they have to create them to be able to work! Poor people from DevOps, trying to explain and create a peering for each worker on the company =(

So, what if I tell you that you can secure your apps with no VPN client required? Yep, it's possible with GCP Identity-Aware Proxy

The objective for today is create a web app which only authorized accounts can access and it should also be able to consume internal services.

As Is

1.- Only authorized accounts can access the app hosted in App Engine.

2.- Only App Engine can consume an internal service. (It could be a VM or cluster, I just chose Cloud Run to make it simpler).

3.- No one can access directly the internal service.

If we look deeper to this architecture we can found more built-in resources on GCP that can help us achieve our objective.

Final architecture

4.- Service-to-service authentication is the ability for one service, which can be an App Engine service, to invoke a Cloud Run (fully managed) service.

5.- Identity-Aware Proxy (IAP) : Use it when you want to enforce access control policies for applications and resources. We will focus in this part.

The Code

# Just remember to add `--no-allow-unauthenticated` flag 
# to secure the API. 
# This means only authorized services can request this API, 
# in this case App Engine is using its Service Account.

gcloud run deploy my-go-api-service \ 
  --image gcr.io/$PROJECT_ID/my-go-api:v0.1 \
  --region southamerica-east1 \
  --no-allow-unauthenticated \
  --platform managed 
Enter fullscreen mode Exit fullscreen mode
  • For the service-to-service authentication add the roles/run.invoker permission to the App Engine Service Account.
gcloud run services add-iam-policy-binding my-go-api-service \
  --member='serviceAccount:[Your-app-engine-service-account]' \
  --role='roles/run.invoker'
Enter fullscreen mode Exit fullscreen mode

How service-to-service authentication works is an extensive topic, it could be a new post, but just keep this in mind:

  • The client service has to get a token to be able to request the protected service.

  • This token can only be generated inside the GCP internal networking.

  • This means that only instances running on GCP can get this token, it's not possible to get this token outside GCP (like from you computer for example).

With this in mind, the internal request has to be done from App Engine (inside GCP). If we do the request from the client side (Chrome) we won't be able to get the token because Chrome is running on your computer (outside GCP).

So.. what can we do now?

Server Side Rendered

Server-side rendering (SSR) is the process of rendering web pages on a server and passing them to the browser (client-side), instead of rendering them in the browser.

I decided to use NuxtJS for the SSR because I'm familiar to VueJS and it's so simple to use.

I'm absolutely not a frontend guy, so probably I won't use the best practices for the client side (web app), please take it easy :D.

<!-- 
*******************************************
I only made three modifications: 
- Get the "token" and 
- Request the internal service via "axios"
- Show the message from the internal service
*******************************************
-->

<!--index.vue-->
<template>
  <div class="container">
    <div>
      <h1 class="title">Cloud Run says:</h1>
      <h2>{{rsp.message}}</h2>
    </div>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, $config }) {

    // Getting token
    const token = await $axios.$get(`http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://my-go-api-service-[your-hash]-rj.a.run.app`, {headers: { 'Metadata-Flavor': 'Google'}})

    // Request internal service
    const rsp = await $axios.$get(`https://my-go-api-service-[your-hash]-rj.a.run.app`, {headers: { 'Authorization': 'Bearer ' + token}})

    return { rsp }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode
  • Add the app.yaml file to deploy on App Engine Standard
runtime: nodejs10

instance_class: F2

handlers:
  - url: /_nuxt
    static_dir: .nuxt/dist/client
    secure: always

  - url: /(.*\.(gif|png|jpg|ico|txt))$
    static_files: static/\1
    upload: static/.*\.(gif|png|jpg|ico|txt)$
    secure: always

  - url: /.*
    script: auto
    secure: always

env_variables:
  HOST: '0.0.0.0'
Enter fullscreen mode Exit fullscreen mode
  • And deploy to App Engine Standard
# Build our project
yarn build

# Deploy to App Engine Standard 
gcloud app deploy
Enter fullscreen mode Exit fullscreen mode

Great! Our internal service is protected and we can consume it from App Engine, but the Web app is still open to everyone, let's secure it.

Identity-Aware Proxy

Simpler for cloud admins: Secure access to apps in less time than it takes to implement a VPN. Let your developers focus on application logic, while IAP takes care of authentication and authorization.

IAP lets you establish a central authorization layer for applications accessed by HTTPS, so you can use an application-level access control model instead of relying on network-level firewalls. Docs

Sounds great but why to use it instead of Firebase Authentication, for example: Firebase Firestore Rules with Custom Claims - an easy way.

Simple, with Firebase Authentication anyone on the internet can register to your app, if they can access the app content is another story.

With IAP you implement a zero-trust access model, this means accounts that are not listed on you policy won't be able even to see the HTML, they will receive this message:

Not allowed

And if you use Workspaces you can limit the access to only accounts from your organization.

  • First we have to enable IAP on our project
gcloud services enable iap.googleapis.com 
Enter fullscreen mode Exit fullscreen mode
  • Then configure the OAuth consent screen, basically Google displays a consent screen to the user including a summary of your project and its policies and the requested scopes of access.

OAuth consent screen

  • Go to the Identity-Aware Proxy page and select the resource you wish to modify by checking the box to its left, in this case App Engine.

IAP - App Engine

  • Now let's add an account to our IAP-secured Web App Users list
gcloud iap web add-iam-policy-binding  \  
  --member='user:alvardev@example.com' \
  --resource-type='app-engine' \ 
  --role='roles/iap.httpsResourceAccessor'
Enter fullscreen mode Exit fullscreen mode

So when this account enters to the web app the HTML will be displayed

Web app

That's it!

Our Web app is secured, no VPN client required and we consume an internal service (message: "Hello world! v0.2").

Thanks @lucasturci for the review!

Latest comments (0)