Service account identity from AppEngine to Cloud Function - google-app-engine

I have a private HTTP Google Cloud Function which I'd like to call from an AppEngine app in another project.
Ideally, the AppEngine Service Account would have roles/cloudfunctions.invoker on my Cloud Function, I'd turn off all other invokers, and I wouldn't have to worry about auth at all inside of the CF. I'm struggling to get the AppEngine identity passed along.
Google's docs show how to do this from one Cloud Function to another, but AppEngine instead uses its own identity library to simplify getting access tokens. AppEngine docs outline:
Identity for other AppEngine apps in the same project
Identity for Google APIs
Something seemingly unrelated: verifying a payload's signature
Any way to include the AppEngine identity such that Google's native Cloud Function invoker role will the request through?

For this situation you will need to do the authentication programmatically by yourself.
First you need to add the app engine service account to the Cloud Functions permission.
After that, you need to follow the steps for this situation. Basically you will need to create a JWT, to authorize it and then to include the JWT in your request.
Here you can find a code example for creating and authorising a JWT.
I have reproduced your situation in python. I used the code from the link I have sent to you, and then after I had my JWT alright, I made a request like this :
#app.route('/')
def index():
data = {'headers': request.headers,
'service_name': os.environ.get('GAE_SERVICE', '(running locally)'),
'environment': os.environ}
return render_template('index.html', data=data)
#app.route('/request')
def send_request():
import requests
receiving_function_url = 'YOUR-CLOUD-FUNCT-URL'
r=requests.get("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?audience="+receiving_function_url,
headers={'Metadata-Flavor': 'Google'})
response = make_iap_request('YOUR-CLOUD-FUNCTION-URL', 'YOUR-CLOUD-FUNCTION-URL')
print(response)
return response
if __name__ == '__main__':
app.run('127.0.0.1', port=8080, debug=True)
The dependencies you need, in requirements.txt:
flask
PyJWT==1.7.1
cryptography==2.7
google-auth==1.6.3
gunicorn==19.9.0
requests==2.22.0
requests_toolbelt==0.9.1
In this repository you can find more code examples on how to do IAP(Identity Aware Proxy) requests.

Related

Authentication using Google Service Account in a flask app and deploying on Google App Engine

Below are my requirements.
Develop a flask app.
Use collections in the firebase in the app.
Deploy this app on Google App Engine using a standard service account
What I have done.
Created a service account
Downloaded the corresponding credentials json; I am calling it as key.json
written a main.py
cred = credentials.Certificate('key.json')
default_app = initialize_app(cred)
db = firestore.client()
user_ref = db.collection_group('Users')
#app.route('/', methods=['GET'])
def home():
return "<h1>Welcome to my first app</h1>"
#app.route('/users', methods=['GET'])
def getUsers():
try:
result = [user.to_dict() for user in user_ref .stream()]
return jsonify(result), 200
except Exception as e:
result = { "message:"failed"}
return jsonify(result), 500
I have tested this locally and also on deployed on Google App Engine.
In both the cases, key.json was in the same directory as the code.
I have verified that if this key.json is modified to store wrong data, then /users endpoint won't work and gives me a 500 error.
So far so good. I want to know if this is even the right approach.
I want the key.json authentication to applied even for the root / endpoint.
i.e., if the user supplies a valid key.json, only then the Welcome to my first app should be displayed.
Else, Unauthorized user message needs to be displayed.
As mentioned by #Gaefan and #DishantMakwana, as well as in this documentation:
An API key only identifies the application and doesn't require user authentication. It is sufficient for accessing public data.
So in order to authenticate/authorize your users you should reconsider your strategy. I would recommend you to follow the instructions in the Authenticating as an end user Documentation.
I have found that we can use Google Cloud Endpoints for API management. Works as a charm.

Authorize requests to app engine app with a service account

I am using the app.yaml's login: admin in handlers to restrict access to my app only to selected Google accounts (which I can edit in IAM). I'm using the python27 standard environment on GAE.
I would like to use the JSON API my app exposes from another server app (not hosted on GAE). Using a service account looks like a straightforward solution, but I am unable to get the scopes or the request itself right, so the endpoint would see an authenticated Google user.
The service-user currently has Project/Viewer role in the IAM. I tried a few more like AppEngine/Viewer, AppEngine/Admin. I also tried some more scopes.
My test code:
"""Try do do an API request to a deployed app
with the current service account.
https://google-auth.readthedocs.io/en/latest/user-guide.html
"""
import sys
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
def main():
if len(sys.argv) < 2:
sys.exit("use: %s url" % sys.argv[0])
credentials = service_account.Credentials.from_service_account_file(
'service-user.json')
scoped_credentials = credentials.with_scopes(
['https://www.googleapis.com/auth/cloud-platform.read-only'])
authed_http = AuthorizedSession(scoped_credentials)
response = authed_http.request('GET', sys.argv[1])
print response.status_code, response.reason
print response.text.encode('utf-8')
if __name__ == '__main__':
main()
There is no error, the request behaves like unauthenticated. I checked the headers on the server, and while requesting from the browser there are several session cookies, the AuthorizedSession request contains single Authorization: Bearer .. header.
Normally the roles you would need is App Engine Admin; it's designed for this purpose. It should also work with the viewer/editor/owner primitive roles. That being said, to make sure it's not a "role" issue, simply give it the project owner role and also the explicit App Engine Admin role and try again. This will eliminate any role-based issue.
Let me know if that works for you.

How often should I request authorization to access Google APIs?

My GAE applications works with Google's Fusion Tables thru Google API. Currently I request authorization each time I should add some data to Fusion Tables:
class TestHandler(webapp2.RequestHandler):
def get(self):
credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/fusiontables')
http = credentials.authorize(httplib2.Http(memcache))
service = build('fusiontables', 'v1', http=http)
# addition of the data happens here
But do I really need to do it? Probably I should store authorization data (http?) somewhere? Like memcache? And then re-use it? Then how long the auth data is valid for? Or, is it already done by Google APIs Client Library for Python I use?

Is there a way to check if the user is an admin in AppEngine Cloud Endpoints

I am using AppEngine Cloud Endpoints with the Javascript client and Google+ Sign In, I am using endpoints.get_current_user(). Is there a way to check if the user is an AppEngine admin? Similar to users.is_current_user_admin() in users API.
Thanks
See Google Endpoints API + Chrome Extension returns None for endpoints.get_current_user().user_id() for a long description of the difference between ID tokens and Bearer tokens when performing auth.
If you aren't using an Android application, you can freely use Bearer tokens and not have to worry about some of the limitations of ID tokens.
Right next get_current_user(), the oauth library provides the oauth.is_current_user_admin() method. Exactly as get_current_user(), this method calls _maybe_call_get_oauth_user and then checks a simple environment variable.
As mentioned in the other answer:
The oauth.get_current_user() call is only expensive IF it makes
the RPC. The _maybe_call_get_oauth_user method stores the value from
the last call, so calling oauth.get_current_user() a second time
will incur no network/speed overhead other than the few nanoseconds to
lookup a value from a Python dict.
So if you are only using Bearer tokens, you could do the following
from google.appengine.api import oauth
from google.appengine.ext import endpoints
...
endpoints_user = endpoints.get_current_user()
if endpoints_user is None:
raise endpoints.UnauthorizedException(...)
is_admin = oauth.is_current_user_admin(known_scope)
if not is_admin:
# Throw a 403 FORBIDDEN
raise endpoints.ForbiddenException(...)

Securing Google App Engine Authsub callback url ('next_url')

I have run through the google example of using Authsub to retrieve Google feed data (http://code.google.com/appengine/articles/python/retrieving_gdata_feeds.html)
If I understand correctly, it is possible for a malicious hacker to call the 'next_url' (which google auth service calls with your token) and inject their own token?
Meaning that they could cause the Web apps to write to the Hackers google doc account instead of the authenticated user!
Does anyone know how to secure this url so that only google auth service can call it?
Below is the code I am referring to:
def get(self):
next_url = atom.url.Url('http', settings.HOST_NAME, path='/step1')
# Initialize a client to talk to Google Data API services.
client = gdata.service.GDataService()
gdata.alt.appengine.run_on_appengine(client)
# Generate the AuthSub URL and write a page that includes the link
self.response.out.write("""<html><body>
Request token for the Google Documents Scope
</body></html>""" % client.GenerateAuthSubURL(next_url,
('http://docs.google.com/feeds/',), secure=False, session=True))

Resources