Secure Web App access with GCP Identity-Aware Proxy
Tags: googlecloud, security, architecture, node
Published: 2021-05-21 22:35:11

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](https://cloud.google.com/iap)

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nybyz40mi9m5cixgkd0r.png)

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ujgl55umntspnhwzkxo.png)

**4.-** [Service-to-service](https://cloud.google.com/run/docs/authenticating/service-to-service#python) 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)](https://cloud.google.com/iap/docs/concepts-overview#when_to_use_iap) : Use it when you want to enforce access control policies for applications and resources. We will focus in this part.


# The Code

* For the internal service let's use the API we made in [GCP Cloud Run: containers without Dockerfile](https://dev.to/alvardev/gcp-cloud-run-containers-without-dockerfile-2jh3). 

```shell
# 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 
```    

* For the [service-to-service](https://cloud.google.com/run/docs/authenticating/service-to-service#python) authentication add the `roles/run.invoker` permission to the App Engine Service Account.

```shell
gcloud run services add-iam-policy-binding my-go-api-service \
  --member='serviceAccount:[Your-app-engine-service-account]' \
  --role='roles/run.invoker'
```

> 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](https://nuxtjs.org) for the SSR because I'm familiar to [VueJS](https://vuejs.org) 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.

* Use the [Get Started from NuxtJS](https://nuxtjs.org/docs/2.x/get-started/installation) to create a SSR project. 

```html
<!-- 
*******************************************
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>
``` 
* Add the `app.yaml` file to deploy on App Engine Standard 
```yaml
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'
```

* And deploy to App Engine Standard 
```shell
# Build our project
yarn build

# Deploy to App Engine Standard 
gcloud app deploy
```

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](https://cloud.google.com/iap/docs/concepts-overview)

Sounds great but why to use it instead of Firebase Authentication, for example: [_Firebase Firestore Rules with Custom Claims - an easy way_](https://dev.to/alvardev/firebase-firestore-rules-with-custom-claims-an-easy-way-523d).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qnffp4it2frpo55wuvq.png)

> And if you use [Workspaces](https://workspace.google.com) you can limit the access to only accounts from your organization. 

* First we have to enable IAP on our project

```shell
gcloud services enable iap.googleapis.com 
```
* Then [configure the OAuth consent screen](https://cloud.google.com/iap/docs/app-engine-quickstart#oauth-configure), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jezbl7jc8am0whlkmge9.png)

* 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njk6tud42pp5dldy696n.png)

* Now let's add an account to our IAP-secured Web App Users list

```shell
gcloud iap web add-iam-policy-binding  \  
  --member='user:alvardev@example.com' \
  --resource-type='app-engine' \ 
  --role='roles/iap.httpsResourceAccessor'
```

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

![Web app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztcax1fkk1gi4l4t9y7m.png)

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](https://github.com/lucasturci) for the review!