Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper function for JSON Web Tokens #91

Closed
MarkEdmondson1234 opened this issue Sep 12, 2020 · 3 comments
Closed

Helper function for JSON Web Tokens #91

MarkEdmondson1234 opened this issue Sep 12, 2020 · 3 comments

Comments

@MarkEdmondson1234
Copy link
Owner

MarkEdmondson1234 commented Sep 12, 2020

To call Cloud Run outside of GCP
https://cloud.google.com/run/docs/authenticating/service-to-service

Perhaps using https://github.com/jeroen/jose - https://cran.r-project.org/web/packages/jose/vignettes/jwt.html

This format: https://tools.ietf.org/html/rfc7519

https://medium.com/google-cloud/authenticating-using-google-openid-connect-tokens-e7675051213b
These tokens are not Oauth2 access_tokens you would use to call a Google Service or API directly such as a Google Compute Engine API or Cloud Storage Bucket but id_tokens that assert identity and are signed by Google.

If the ID Token is signed and issued by Google, that token can be used as a token against GCP service perimeters because the service can decode the token, verify its signature and finally identify the caller using values within the JWT claim. For example, the JWT header and payload below describes a token that was issued by google ("iss": "https://accounts.google.com"), identifies the caller ("email": "svc_account@.project.gserviceaccount.com"), has not expired (the service will check the exp: field), and finally will verify the JWT is intended for the service or not to "aud": "https://example.com".

{
    "alg": "RS256",
    "kid": "5d887f26ce32577c4b5a8a1e1a52e19d301f8181",
    "typ": "JWT"
    }.
    {
    "aud": "https://example.com",
    "azp": "107145139691231222712",
    "email": "svc_account@.project.gserviceaccount.com",
    "email_verified": true,
    "exp": 1556665461,
    "iat": 1556661861,
    "iss": "https://accounts.google.com",
    "sub": "107145139691231222712"
    }

aud (audience) would be the cloud run service that is not public

MarkEdmondson1234 added a commit that referenced this issue Sep 12, 2020
@MarkEdmondson1234
Copy link
Owner Author

MarkEdmondson1234 commented Sep 12, 2020

This is cool https://www.jhanley.com/google-cloud-creating-oauth-access-tokens-for-rest-api-calls/

This really helped too: https://medium.com/@stephen.darling/oauth2-authentication-with-google-cloud-run-700015a092c2

############################################################
# Version 1.00
# Date Created: 2018-12-23
# Last Update:  2018-12-23
# https://www.jhanley.com
# Copyright (c) 2018, John J. Hanley
# Author: John Hanley
############################################################
 
'''
This program lists lists the Google Compute Engine Instances in one zone
'''
 
import time
import json
import jwt
import requests
import httplib2
 
# Project ID for this request.
project = 'development-123456'
 
# The name of the zone for this request.
zone = 'us-west1-a'
 
# Service Account Credentials, Json format
json_filename = 'service-account.json'
 
# Permissions to request for Access Token
scopes = "https://www.googleapis.com/auth/cloud-platform"
 
# Set how long this token will be valid in seconds
expires_in = 3600	# Expires in 1 hour
 
def load_json_credentials(filename):
	''' Load the Google Service Account Credentials from Json file '''
 
	with open(filename, 'r') as f:
		data = f.read()
 
	return json.loads(data)
 
def load_private_key(json_cred):
	''' Return the private key from the json credentials '''
 
	return json_cred['private_key']
 
def create_signed_jwt(pkey, pkey_id, email, scope):
	'''
	Create a Signed JWT from a service account Json credentials file
	This Signed JWT will later be exchanged for an Access Token
	'''
 
	# Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
	auth_url = "https://www.googleapis.com/oauth2/v4/token"
 
	issued = int(time.time())
	expires = issued + expires_in	# expires_in is in seconds
 
	# Note: this token expires and cannot be refreshed. The token must be recreated
 
	# JWT Headers
	additional_headers = {
			'kid': pkey_id,
			"alg": "RS256",
			"typ": "JWT"	# Google uses SHA256withRSA
	}
 
	# JWT Payload
	payload = {
		"iss": email,		# Issuer claim
		"sub": email,		# Issuer claim
		"aud": auth_url,	# Audience claim
		"iat": issued,		# Issued At claim
		"exp": expires,		# Expire time
		"scope": scope		# Permissions
	}
 
	# Encode the headers and payload and sign creating a Signed JWT (JWS)
	sig = jwt.encode(payload, pkey, algorithm="RS256", headers=additional_headers)
 
	return sig
 
def exchangeJwtForAccessToken(signed_jwt):
	'''
	This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
	'''
 
	auth_url = "https://www.googleapis.com/oauth2/v4/token"
 
	params = {
		"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
		"assertion": signed_jwt
	}
 
	r = requests.post(auth_url, data=params)
 
	if r.ok:
		return(r.json()['access_token'], '')
 
	return None, r.text
 
def gce_list_instances(accessToken):
	'''
	This functions lists the Google Compute Engine Instances in one zone
	'''
 
	# Endpoint that we will call
	url = "https://www.googleapis.com/compute/v1/projects/" + project + "/zones/" + zone + "/instances"
 
	# One of the headers is "Authorization: Bearer $TOKEN"
	headers = {
		"Host": "www.googleapis.com",
		"Authorization": "Bearer " + accessToken,
		"Content-Type": "application/json"
	}
 
	h = httplib2.Http()
 
	resp, content = h.request(uri=url, method="GET", headers=headers)
 
	status = int(resp.status)
 
	if status < 200 or status >= 300:
		print('Error: HTTP Request failed')
		return
 
	j = json.loads(content.decode('utf-8').replace('\n', ''))
 
	print('Compute instances in zone', zone)
	print('------------------------------------------------------------')
	for item in j['items']:
		print(item['name'])
 
if __name__ == '__main__':
	cred = load_json_credentials(json_filename)
 
	private_key = load_private_key(cred)
 
	s_jwt = create_signed_jwt(
			private_key,
			cred['private_key_id'],
			cred['client_email'],
			scopes)
 
	token, err = exchangeJwtForAccessToken(s_jwt)
 
	if token is None:
		print('Error:', err)
		exit(1)
 
	gce_list_instances(token)

@MarkEdmondson1234
Copy link
Owner Author

MarkEdmondson1234 commented Sep 12, 2020

This looks easier https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken but it wasn't kept saying the user didn't have authorisation to do it

@MarkEdmondson1234
Copy link
Owner Author

Its working nicely now, using creating our own JWTs. This will only work with my service key in GCE_AUTH_FILE

# The private authenticated access only Cloud Run service
the_url <- "https://parallel-cloudrun-ewjogewawq-ew.a.run.app/"

# creating the JWT and token
jwt <- cr_jwt_create(the_url)
token <- cr_jwt_token(jwt, the_url)

# call Cloud Run app using token with any httr verb
library(httr)
res <- cr_jwt_with(GET("https://parallel-cloudrun-ewjogewawq-ew.a.run.app/hello"),
                   token)
content(res)
# {html_document}
#<html>
#[1] <body><h1>hello world</h1></body>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant