Secure Web App access with GCP Identity-Aware Proxy
Alvaro David
Posted on May 21, 2021
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.
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.
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
- For the internal service let's use the API we made in GCP Cloud Run: containers without Dockerfile.
# 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 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'
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.
- Use the Get Started from NuxtJS to create a SSR project.
<!--
*******************************************
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
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
# 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
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:
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
- 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.
- 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.
- 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'
So when this account enters to the web app the HTML will be displayed
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!
Posted on May 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.