Google Cloud Run: Calling from outside GCP - python

I'm trying to access a secure cloud run service. If I try from my machine with the SDK it works fine:
curl --header "Authorization: Bearer $(gcloud auth print-identity-token)"
However, I can not get it to work using the Python API using the same service account, I've tried using google-auth to get an access token but that gives me a 401 authentication error. This is the code I am using to try to get a token:
import google.auth
import google.auth.transport.requests
scopes = ['https://www.googleapis.com/auth/cloud-platform']
creds, projects = google.auth.default(scopes=scopes)
auth_req = google.auth.transport.requests.Request()
creds.refresh(auth_req)
# then using creds.token
Looking at the docs: https://cloud.google.com/run/docs/authenticating/service-to-service#calling_from_outside_gcp it says to follow the sample code here: https://cloud.google.com/iap/docs/authentication-howto#iap_make_request-python I can't seem to follow the guide as it says to enable IAP but it seems that IAP is only for app engine and not cloud run?
Has anyone go any advice on this one?
Thanks

OK it seems that there is an IDTokenCredentials class that works for this as it uses Open ID Connect ID Tokens instead of OAuth 2.0 Access Tokens:
https://google-auth.readthedocs.io/en/latest/reference/google.oauth2.service_account.html
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
service_url = 'example.com'
key_file = 'key.json'
credentials = service_account.IDTokenCredentials.from_service_account_file(
key_file, target_audience=service_url)
authed_session = AuthorizedSession(credentials)
response = authed_session.get(service_url)
It's confusing as I don't see it in the docs and it's leading me to something else about IAP which I don't think is working with Cloud Run.

If you still want to obtain your ID token and not use method from Dan's response there is an updated code:
import requests
import google.auth
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
service_url = 'example.com'
key_file = 'key.json'
credentials = service_account.IDTokenCredentials.from_service_account_file(
key_file, target_audience=service_url)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
token = credentials.token
print('ID Token:', token)

Related

Cloud function get oauth2 token python api

I am having trouble sending an authenticated firestore REST API request from a cloud function. The request needs to have an oauth2 token in the header. I've followed the docs here https://cloud.google.com/functions/docs/securing/authenticating and tried the python function they provided, but that still does not give the right authentication. What has worked so far is copying the token from gcloud auth application-default print-access-token but that expires after an hour. Any ideas?
import urllib
import google.auth.transport.requests
import google.oauth2.id_token
def make_authorized_get_request(endpoint, audience):
"""
make_authorized_get_request makes a GET request to the specified HTTP endpoint
by authenticating with the ID token obtained from the google-auth client library
using the specified audience value.
"""
# Cloud Functions uses your function's URL as the `audience` value
# audience = https://project-region-projectid.cloudfunctions.net/myFunction
# For Cloud Functions, `endpoint` and `audience` should be equal
req = urllib.request.Request(endpoint)
auth_req = google.auth.transport.requests.Request()
id_token = google.oauth2.id_token.fetch_id_token(auth_req, audience)
req.add_header("Authorization", f"Bearer {id_token}")
response = urllib.request.urlopen(req)
return response.read()
The above function results in aurllib.error.HTTPError: HTTP Error 401: Unauthorized"
The solution in my case was to add datastore.importExportAdmin "Cloud Datastore Import Export Admin" role to the service account that would generate the token. And to use the code below to generate a token from credentials:
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
https://cloud.google.com/firestore/docs/security/iam#roles

Invoking Google Cloud Function from python using service account for authentication

I have a cloud function with trigger type set to HTTP and also have a service account which is having permissions to Invoke the cloud function. I want to invoke the cloud function from a python script. I am using the following script to invoke the function:
from google.oauth2 import service_account
from google.auth.transport.urllib3 import AuthorizedHttp
credentials = service_account.Credentials.from_service_account_file('/path/to/service-account-credentials.json')
scoped_credentials = credentials.with_scopes(['https://www.googleapis.com/auth/cloud-platform'])
authed_http = AuthorizedHttp(scoped_credentials)
response = authed_http.request('GET', 'https://test-123456.cloudfunctions.net/my-cloud-function')
print(response.status)
I am getting Unauthorized ( 401 ) error in response. Is this the correct way of invocation?
To be able to call your cloud function you need an ID Token against a Cloud Functions end point
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
url = 'https://test-123456.cloudfunctions.net/my-cloud-function'
creds = service_account.IDTokenCredentials.from_service_account_file(
'/path/to/service-account-credentials.json', target_audience=url)
authed_session = AuthorizedSession(creds)
# make authenticated request and print the response, status_code
resp = authed_session.get(url)
print(resp.status_code)
print(resp.text)

How do you get gcloud access token in Python?

I'm looking to do the equivalent of the following in Python with out having to call these command using something like os.system and look at the output.
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json"
export PROJECT_ID="my-project-name"
gcloud auth application-default print-access-token
Can this be done with Google SDK?
I think you can do this using the Google Auth Library. You can install it with pip:
$ pip install google-auth
Here's an example code using buckets:
from google.oauth2 import service_account
from google.cloud import storage
KEY='/path/to/key.json'
PROJECT='your_project_id'
# gcloud auth application-default print-access-token is no necessary
credentials = service_account.Credentials.from_service_account_file(KEY)
# Initialize the Cloud Storage client using the credentials
storage_client = storage.Client(PROJECT,credentials)
# List objects in a bucket
blobs = storage_client.list_blobs("a_bucket")
for blob in blobs:
print(blob.name)
Good luck coding!
Here the answer:
import google.auth
import google.auth.transport.requests
from google.oauth2 import service_account
from os.path import expanduser
from os import getenv
# The the service account key ABSOLUTE path from env or current folder
service_account_key = getenv(
'GOOGLE_APPLICATION_CREDENTIALS',
f'{expanduser(".")}/service-account-key.json'
)
# Creates a credentials object from the service account file
credentials = service_account.Credentials.from_service_account_file(
service_account_key, # key path
scopes=['https://www.googleapis.com/auth/cloud-platform'] # scopes
)
# Prepare an authentication request
auth_req = google.auth.transport.requests.Request()
# Request refresh tokens
credentials.refresh(auth_req)
# now we can print the access token
print(credentials.token)
Using python:
import request
requests.get('http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token').json().get('access_token')

Python: How to Use Requests Library to Access Flask RestPlus API hosted on Google App Engine?

I built a rest api using flask restplus and I tested it using the swagger documentation, and it seems to be fully functional and working just fine. I now want to test the API using the python requests library, but I'm getting the following error:
Invalid IAP credentials: empty token
I imagine this is because I did not authorize using the credentials. I do have access to the service account credentials json file, but just wondering how I pass this into requests?
Code Example:
url = 'https://crosssellapi-project-id.appspot.com/auth/login'
r = requests.get(url, headers={'accept': 'application/json'})
Thanks in advance.
I think the best way to accomplish this is using the google-auth library for python.
$ pip install google-auth
I suggest to create a service account, give it the role IAP-secured Web App User and then download the JSON key for that service account.
Go to API's & Services > Credentials and copy the Client ID of the OAuth 2.0 client you want to use for your API.
Finally use this code snippet to make a request:
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
SERVICE_FILENAME = '/path/to/your/service_account_key.json'
API_URL = 'https://YOUR_API_URL'
AUDIENCE = 'YOUR_OAUTH_CLIENT_ID'
credentials = service_account.IDTokenCredentials.from_service_account_file(SERVICE_FILENAME, target_audience=AUDIENCE)
session = AuthorizedSession(credentials)
r = session.get(API_URL, headers={'accept': 'application/json'})
print(r.text)
If you want to know more about google-auth check this.

Google Sites API + OAuth2 (on Appengine)

I've been trying to make use of the Python Library to access the Google Sites API.
The first step requires a user to authorize our application, they recommend to use OAuth2 and they provide a library that can be found here.
At the end of the authorization process you end up with an OAuth2Credentials object.
The problem is, when I try to make requests to the Google Sites API, let's say I do:
import gdata.sites.client
client = gdata.sites.client.SitesClient(site=None, domain='mydomain.com')
I don't know how to make use of the OAuth2Credentials object.
I spent quite a few hours trying to do exactly this and finally found the answer in this blog post:
https://groups.google.com/forum/m/#!msg/google-apps-developer-blog/1pGRCivuSUI/3EAIioKp0-wJ
Here is a soup to nuts example of using the oauth2client together with the gdata API to access Google Sites including the 'Monkey Patching':
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run
import gdata.sites.client
import gdata.sites.data
SCOPE = 'https://sites.google.com/feeds/'
# client_secrets.json is downloaded from the API console:
# https://code.google.com/apis/console/#project:<PROJECT_ID>:access
# where <PROJECT_ID> is the ID of your project
flow = flow_from_clientsecrets('client_secrets.json',
scope=SCOPE,
redirect_uri='http://localhost')
storage = Storage('plus.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run(flow, storage)
# 'Monkey Patch' the data in the credentials into a gdata OAuth2Token
# This is based on information in this blog post:
# https://groups.google.com/forum/m/#!msg/google-apps-developer-blog/1pGRCivuSUI/3EAIioKp0-wJ
auth2token = gdata.gauth.OAuth2Token(client_id=credentials.client_id,
client_secret=credentials.client_secret,
scope=SCOPE,
access_token=credentials.access_token,
refresh_token=credentials.refresh_token,
user_agent='sites-test/1.0')
# Create a gdata client
client = gdata.sites.client.SitesClient(source='sites-test',
site='YOUR.SITE',
domain='YOUR.DOMAIN',
auth_token=auth2token)
# Authorize it
auth2token.authorize(client)
# Call an API e.g. to get the site content feed
feed = client.GetContentFeed()
for entry in feed.entry:
print '%s [%s]' % (entry.title.text, entry.Kind())
My app is using .p12 key instead of flow, here is how I made it work after some tinkering:
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.sites.client
import gdata.sites.data
scope = 'https://sites.google.com/feeds/'
key_file = 'xxxxxxxxxxxxxxxxxxx.p12'
with open(key_file) as f:
key = f.read()
credentials = SignedJwtAssertionCredentials(
'xxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com',
key,
sub='aleh#vaolix.com',
scope=[scope])
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
client = gdata.sites.client.SitesClient(source='sites-test',
domain='mydomain.com')
auth2token.authorize(client)
feed = client.GetSiteFeed()
for entry in feed.entry:
print entry.title.text

Categories

Resources