pythonflaskgoogle-cloud-platformgoogle-cloud-functionsflask-httpauth

Using Flask BasicHTTPAuth with Google Cloud Functions


I'm having difficulty with my Cloud Function in GCP that is simply supposed to return the raw XML stored in a GCS Bucket when invoked with a basic GET request. It works fine without any type of authentication, however since I added the Flask-HTTPAuth package to the mix in order to add some measure of security before exposing the endpoint, the application deploys fine, but crashes without any sort of hint as to why as soon as it is invoked. The error in SD Logging is as follows:

severity: "DEBUG"  
textPayload: "Function execution took 1847 ms, finished with status: 'crash'"  
timestamp: "2020-07-15T17:22:15.158036700Z"

The function in question (anonymized):

from flask import Flask, request, jsonify, make_response, abort
from flask_httpauth import HTTPBasicAuth
from google.cloud import storage, secretmanager
import google.cloud.logging
import logging
import sys

app = Flask(__name__)
auth = HTTPBasicAuth()

PROJECT_ID = 'example_project'
GCS_BUCKET = 'example_bucket'
users = ['example_user']

# Instantiate logger
client = google.cloud.logging.Client()
client.get_default_handler()
client.setup_logging()

@auth.verify_password
def verify_password(username, password):
    # Instantiate the Secret Manager client.
    sm_client = secretmanager.SecretManagerServiceClient()

    # Load secrets
    name = sm_client.secret_version_path(PROJECT_ID, 'example_secrets_ref', 1)
    secrets_pass = sm_client.access_secret_version(name)
    passwords = [secrets_pass]
    if username in users and password in passwords:
        logging.info('auth success')
        return username
    logging.info('auth fail')
    return abort(403)

@app.route('/')
@auth.login_required
def latest_xml():
    try:
        request_json = request.get_json()#silent=True)
        storage_client = storage.Client(project=PROJECT_ID)
        bucket = storage_client.get_bucket(GCS_BUCKET)
        blob = bucket.get_blob('latest_pull.xml')
        latest_xml = blob.download_as_string()
        logging.info('Loaded blob from GCS')
        return(latest_xml)
    except exception as e:
        logging.error(str(e))
        logging.error("Failed to load blob from GCS")
        sys.exit(1)

if __name__ == '__main__':
    app.run()

I've tried setting the entrypoint as both the main function as well as the auth function to no avail. My question is: is it possible to even use basic auth in a GCP Cloud Function or am I barking up the wrong tree here?


Solution

  • Your function doesn't enforce the standard signature for http function

    def latest_xml(request):
      ...
    

    Here you use a flask web server, which is not need, and not used by Cloud Functions. However, I recommend you to have a look to Cloud Run, and to add a simple and generic Dockerfile to deploy . You can deploy your "function" as-is in a container and to have the same behavior as Cloud Functions.

    EDIT

    When you use flask, the request object is global for each request. You use it like this:

           request_json = request.get_json()#silent=True)
    

    With Cloud Functions, this object is caught by the Cloud Functions platform and passed in parameter to your function.

    In the request object, you have the body of the request, useless in GET for example. But also, all the request context: headers, user agent, source ip,...