Secure Google Cloud Functions http trigger with auth
Asked Answered
K

7

50

I am trying out Google Cloud Functions today following this guide: https://cloud.google.com/functions/docs/quickstart

I created a function with an HTTP trigger, and was able to perform a POST request to trigger a function to write to Datastore.

I was wondering if there's a way I can secure this HTTP endpoint? Currently it seems that it will accept a request from anywhere/anyone.

When googling around, I see most results talk about securing things with Firebase. However, I am not using the Firebase service here.

Would my options be either let it open, and hope no one knows the URL endpoint (security by obscurity), or implement my own auth check in the function itself?

Kermanshah answered 22/9, 2017 at 6:14 Comment(11)
I have the same question!Corcoran
I am having the same issue!Thumping
I also had the same doubt. AWS API gateway has a concept of api-key. Is there anything similar in GCPConsignor
Possible duplicate of How to protect firebase Cloud Function HTTP endpoint to allow only Firebase authenticated users?Jed
No, it is not a duplicateKermanshah
Hey folks, we've got a feature in Alpha that allows you to set IAM policies that restrict invocation on a per-function basis. Sign up at bit.ly/gcf-iam-alpha and I'll get you in :)Epigeous
Thanks @MikeMcDonald. I've signed up.Kermanshah
@MikeMcDonald any idea when this might come into Beta? - the "GCF IAM Trusted Tester" contract for Alpha has some not so great terms. Can't wait for it!Preemption
Soon, I promise :)Epigeous
Securing HTTP functions in CLoud Function is incredibly poorly documented. Google basically does not have anything comprehensive that does not require you to chase through 5-8 different document pages.Taeniasis
Well oops, closed previous comment too fast. I have posted below a current method as of Sept 2020.Taeniasis
K
15

After looking into this further, and taking a hint from @ricka's answer, I have decided to implement an authentication check for my cloud function with a JWT token passed into the form of an Authorization header access token.

Here's the implementation in JavaScript:

const client = jwksClient({
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 5,
  jwksUri: "https://<auth0-account>.auth0.com/.well-known/jwks.json",
});

function verifyToken(token, cb) {
  let decodedToken;
  try {
    decodedToken = jwt.decode(token, { complete: true });
  } catch (e) {
    console.error(e);
    cb(e);
    return;
  }
  client.getSigningKey(decodedToken.header.kid, function (err, key) {
    if (err) {
      console.error(err);
      cb(err);
      return;
    }
    const signingKey = key.publicKey || key.rsaPublicKey;
    jwt.verify(token, signingKey, function (err, decoded) {
      if (err) {
        console.error(err);
        cb(err);
        return;
      }
      console.log(decoded);
      cb(null, decoded);
    });
  });
}

function checkAuth(fn) {
  return function (req, res) {
    if (!req.headers || !req.headers.authorization) {
      res.status(401).send("No authorization token found.");
      return;
    }
    const parts = req.headers.authorization.split(" ");
    if (parts.length != 2) {
      res.status(401).send("Bad credential format.");
      return;
    }
    const scheme = parts[0];
    const credentials = parts[1];

    if (!/^Bearer$/i.test(scheme)) {
      res.status(401).send("Bad credential format.");
      return;
    }
    verifyToken(credentials, function (err) {
      if (err) {
        res.status(401).send("Invalid token");
        return;
      }
      fn(req, res);
    });
  };
}

I use jsonwebtoken to verify the JWT token, and jwks-rsa to retrieve the public key. I use Auth0, so jwks-rsa reaches out to the list of public keys to retrieve them.

The checkAuth function can then be used to safeguard the cloud function as:

exports.get = checkAuth(function (req, res) {
  // do things safely here
});

You can see this change at my GitHub repository.

The JWT/access token can be retrieved in a number of ways. For Auth0, the API documentation can be found here.

Once this is in place, you can trigger the cloud function (if you have yours enabled with http trigger) like this:

curl -X POST -H "Content-Type: application/json" \
  -H "Authorization: Bearer access-token" \
  -d '{"foo": "bar"}' \
  "https://<cloud-function-endpoint>.cloudfunctions.net/get"
Kermanshah answered 25/10, 2017 at 5:19 Comment(3)
Is this the best solution so far?Gensler
Does this mean we have to put this piece of code in all of the cloud functions that we write?Jetport
Yeah, I put it in a module and refers to it from all the functions.Kermanshah
T
15

I spent a day vexed over this same question three years later and the Google documentation was er, not very illustrative. For those that do not want to implement this in code(me), I outline below how to authenticate Cloud Functions using only the GCP Console. Following is an example that authenticates an HTTP Trigger to a new service account that is then scheduled to run in Cloud Scheduler. You can extend and generalize this further to suit other needs.

Assumptions: 1.You have already created a Cloud Function that uses HTTP and made it require authentication. 2.Your function works when you do Test Runs. This is important, you don't want to be solving two or more problems at once later. 3.You know how to get around the GCP Web browser console.

Steps

  1. I suggest creating a new service account that will be used for the task of invoking the HTTP Cloud Function. Do this via GCP's "IAM & Admin" page. Go to "Services Accounts" then "Create New" enter image description here

  2. Name your new service account. A service account ID will be auto-generated based on the name you made. It will look like a GCP service account email. "@yourproject-name.iam.gserviceaccount.com. Copy this for later. Click the "Create" button to finish the new account creation.

  3. On the next page, you need to select a role for the service account. Best practice to just run a function is "Cloud Functions Invoker". Click the "Continue" button. You can skip the 3rd part. (Grant users access to this service account) enter image description here

  4. Ok now lets add this new service account to the cloud function that needs to be secured. Go to the Cloud Function panel and check the box to the left of the name of the function. Then on the upper right of the same panel, click "Show Info Panel" - notice in the screen that authentication is required. (You must add from here, not the functions "Permissions" page - you can't add new members from there.) enter image description here

  5. Now add the service account as a new member. Paste the service account e-mail you copied earlier into the blank field in the red box. You must put in the email account, the name alone will not work. For "Role" - in the drop down, once again, select "Cloud Functions Invoker". Click Save. enter image description here

  6. Within the Cloud Function's properties there are the provided HTTP Triggers, copy yours and keep it handy for later. enter image description here

  7. Now go to the Google Cloud Scheduler and select a Schedule. (Or create one if you do not have one already. The screen below shows one already made.) enter image description here

  8. With the Schedule's box checked, click "Edit" and you'll be presented with the screen below. Select "Show More" at the bottom of the initial screen to see all fields. The important fields regarding permissions:

For "URL" - Paste in the trigger url you copied in step 6.
For "Auth Header" select OIDC token. These are managed by the GCP for your project and sufficient for authentication.
For "Service Account" paste in the same one from the steps above.
"Audience" will auto-fill, no need to put anything there. When done, click "Update" or "Save" depending on your entry point. enter image description here

  1. Back in the Cloud Scheduler dashboard, run your function by clicking the "Run Now" button. If all went well, it should run and the status "Success" should appear. If not, check the logs to see what happened. enter image description here

  2. So now you know your authenticated Cloud Function works with the service account that was created for it. From here, you can do all kinds of things in the context of this service account as your projects demand.

  3. As a check, be sure to paste the HTTP trigger URL into your browser to ensure it cannot run. You should get the following Forbidden: enter image description here

Taeniasis answered 25/9, 2020 at 20:13 Comment(5)
Brilliant, thank you! I wish I could say the same about official google documentation though...Strappado
Thanks for the great answer. Now I want to call that HTTP function from a third party application like LMS or any custom server, then how do I secure the HTTP function will be executed by the service account you created here. Here you have added service accound in scheduler but what if I call it from the thordpary server ??Bureaucratic
thanks for this detailed answer. I am curious, arenot Step 3 and Step 5 the same. I believed the Step 5 was simply a quicker way to add service account from cloud function interface instead of going to IAM tab etc.Emission
also I am curious if you used the same service account (the one you created) as the "Runtime service account"?Emission
I am also looking for the solution like Manish commented above, How can I let any authenticated user trigger this function from CURL, Postman etc?Superficial
D
4

You can set project-wide or per-function permissions outside the function(s), so that only authenticated users can cause the function to fire, even if they try to hit the endpoint.

Here's Google Cloud Platform documentation on setting permissions and authenticating users. Note that, as of writing, I believe using this method requires users to use a Google account to authenticate.

Dividend answered 3/2, 2020 at 17:50 Comment(2)
Yeah, this is something only recently available. Wasn't available when I made the post.Kermanshah
indeed -- just nice to keep the options up to date for folks visiting in 2020Dividend
U
1

You should not "leave it open and hope no one knows". You can implement your own security check or you may want to try the Google Function Authorizer module (https://www.npmjs.com/package/google-function-authorizer).

Uhlan answered 31/10, 2017 at 16:40 Comment(0)
K
0

It seems like there are currently 2 ways to secure a Google Cloud Function HTTP endpoint.

1) Use a hard to guess function name (ex: my-function-vrf55m6f5Dvkrerytf35)

2) Check for password/credentials/signed-request within the function itself (using a header or parameter)

Probably best to do both.

Kemppe answered 9/10, 2017 at 18:23 Comment(2)
Yeah, it seems that authentication is something that needs to be checked within the function itself.Kermanshah
"Use a hard to guess function name", no, not a solution. What if someone listens the connection?Martella
K
0

You can create custom authentication algorithm to verify the Client.

Check out the algorithm from; https://security.stackexchange.com/q/210085/22239

Koster answered 13/5, 2019 at 12:30 Comment(0)
A
0

For what it's worth, it looks like some upgrades have been made, and Google Cloud Functions now support two types of authentication and authorization: Identity and Access Management (IAM) and OAuth 2.0. Documentation can be found here

Anthropopathy answered 29/10, 2021 at 19:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.