GCP IAP clients

In one of my previous blogs I talk about how to secure the “internal only” applications using Google IAP (Identity Aware Proxy). The normal flow is as follows: the user access the url, if is not authenticated is redirected to the Google Sign-in page after authentication the user is redirected to the initial url. But sometimes you need to access the application programmatically.

This blog post is about how to access access Identity-Aware Proxy (IAP)-secured resources programmatically.

TL;DR

You can find the source code for this solution in this repository github.com/gabihodoroaga/gcp-iap-clients.

Using Google Auth libraries

Google provides auth client libraries for the following languages: C#, Go, Java, Node.js, PHP, Python and Ruby.

I won’t go into details about how to access IAP resources programmatically as Google already provides an excellent documentation. You can find it here Programmatic authentication.

Custom solution

If for some particular reason you cannot or you don’t want to use the google provided client libraries there is a solution to obtain the id_token yourself and to use it when you make the call to the application. For example there is no library for Qt and C. There is a library for C++ Google Cloud Platform C++ Client Libraries but it only supports the following services: BigQuery , Bigtable, IAM, Spanner, Pub/Sub and Storage.

The examples here are only valid for Service Accounts.

The general solution is explained in the following diagram

OAuth flow

A service account’s credentials, which you obtain from the Google API Console, include a generated email address that is unique, a client ID, and at least one public/private key pair. You use the client ID and one private key to create a signed JWT and construct an access-token request in the appropriate format. Your application then sends the token request to the Google OAuth 2.0 Authorization Server, which returns an access token. The application uses the token to access a Google API. When the token expires, the application repeats the process.

I created 4 implementations:

The nodejs implementation:

const request = require("request");
const jsonwebtoken = require("jsonwebtoken");

function make_iap_request(keyFile, audience, url) {
    const keys = require(keyFile);
    const jwtProperties = {
        iss: keys["client_email"],
        aud: keys["token_uri"],
        iat: Math.floor(new Date().getTime() / 1000),
        exp: Math.floor(new Date().getTime() / 1000) + 3600,
        target_audience: audience,
    }

    const jwtToken = jsonwebtoken.sign(jwtProperties, keys["private_key"], { algorithm: "RS256" });
    request.post(keys["token_uri"],
        {
            headers: { "Cache-Control": "no-store", 'Content-Type': "application/json" },
            json: {
                grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
                assertion: jwtToken,
            }
        },
        (error, response) => {
            const openIDToken = response.body.id_token;
            request.get(url,
                {
                    headers: { "Authorization": `Bearer ${openIDToken}` },
                    followAllRedirects: false,
                },
                (err, res, body) => {
                    console.info("Status:", res.statusCode);
                }
            );
        });
}

var argv = process.argv.slice(2);
make_iap_request(argv[0],argv[1],argv[2]);

and to test execute the following command:

nodejs main.js "key.json" "audience" "https://example.com"

The python implementation

import sys, time
import jwt, json, requests

def make_iap_request(keyFile, audience, url):
    # read the key file
    with open(keyFile) as f:
        data = json.load(f)
        f.close()

    jwt_token = jwt.encode(
            {
            "iss": data["client_email"],
            "aud": data["token_uri"],
            "exp": int(time.time())+3600,
            "iat": int(time.time()),
            "target_audience": audience
        },
        data["private_key"],
        algorithm="RS256",
    )

    r = requests.post(
        data["token_uri"], 
        headers = {"Cache-Control": "no-store", 'Content-Type':"application/json"},
        json={"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": jwt_token }
    )

    token_data = r.json()

    r = requests.get(url, 
        headers = {
            'authorization': "Bearer " + token_data["id_token"]
        }, allow_redirects=False)

    print("status:", r.status_code)

if __name__ == '__main__':
    make_iap_request(sys.argv[1],sys.argv[2],sys.argv[3])

and you can test it by executing the following command

python3 main.py "key.json" "audience" "https://example.com"

The C implementation

For C with OpenSSL the implementation is quite long so I will just put the link to the github repository.

C using OpenSSL

The Qt(C++) implementation

For Qt I did not wanted to use OpenSSL to sign the JWT token and instead I calculated the signature using the RSA magic formula

RSA formula

This part was inspired by this great article RSA sign and verify: Behind the scene

Check out the code in this repository github.com/gabihodoroaga/gcp-iap-clients and use it as an example.

Conclusion

I was very interesting to calculate the RSA signature without using a library and to test that is actually works. I always wanted to do this experiment but I did not found the time until now.