Warm tip: This article is reproduced from serverfault.com, please click

Invoking GCloud Function without Authentication

发布于 2020-12-04 23:04:00

I am pretty new to using gcloud functions.

I have a google cloud function which is supposed to start up a VM instance, but I want to invoke this function from a website without authentication (for now) when sending the HTTP request. How can I do this?

I also don't need to send any params, just need to invoke function

Here is my code for a simple website to invoke the cloud function, though this seems to be using auth which I don't want to do, I have disabled authentication when making the function in the console:

<!DOCTYPE html>
<html lang="en">

<body>
    <div class="bordered-text-block">
        <h1>VM Controls</h1>
        <button id="id_start_button" onclick="startVM()">Start
        </button>
        <button id="id_stop_button" onclick="stopVM()">Stop
        </button>
    </div>

    <script>
        function startVM() {
            const fetch = require('node-fetch');

            // TODO(developer): set these values
            const REGION = '<my region>';
            const PROJECT_ID = '<project name>';
            const RECEIVING_FUNCTION = '<function name>';

            // Constants for setting up metadata server request
            // See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
            const functionURL = `https://${REGION}-${PROJECT_ID}.cloudfunctions.net/${RECEIVING_FUNCTION}`;
            const metadataServerURL =
                'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
            const tokenUrl = metadataServerURL + functionURL;

            exports.callingFunction = async (req, res) => {
                // Fetch the token
                const tokenResponse = await fetch(tokenUrl, {
                    headers: {
                        'Metadata-Flavor': 'Google',
                    },
                });
                const token = await tokenResponse.text();

                // Provide the token in the request to the receiving function
                try {
                    const functionResponse = await fetch(functionURL, {
                        headers: { Authorization: `bearer ${token}` },
                    });
                    res.status(200).send(await functionResponse.text());
                } catch (err) {
                    console.error(err);
                    res.status(500).send('An error occurred! See logs for more details.');
                }
            };
        }
    </script>
</body>

</html>
Questioner
Mr. Ace
Viewed
0
DazWilkin 2020-12-07 22:32:15

Following up on my comment, here's the simplest answer that should work.

NOTE I think you must ensure your Cloud Functions returns appropriate CORS headers

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET

I wrote a Cloud Function in Golang:

package p

import (
    "encoding/json"
    "net/http"
)

type Message struct {
    Name string `json:"name"`
}

func WebPage(w http.ResponseWriter, r *http.Request) {
    m := Message{
        Name: "Freddie",
    }
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "GET")
    w.Header().Set("Content-Type", "application/json")
    err := json.NewEncoder(w).Encode(m)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
}

I deployed this Function:

PROJECT="<your-project>"
REGION="<your-region>"
FUNCTION="<your-function>"

gcloud projects create ${PROJECT}

BILLING=$(gcloud beta billing accounts list --format="value(name)")
gcloud beta billing projects link ${PROJECT} --billing-account=${BILLING}

for SERVICE in "cloudbuild" "cloudfunctions"
do
  gcloud services enable ${SERVICE}.googleapis.com \
  --project=${PROJECT}
done

gcloud functions deploy ${FUNCTION} \
--entry-point=WebPage \
--source=. \
--project=${PROJECT} \
--allow-unauthenticated \
--region=${REGION} \
--runtime=go113 \
--trigger-http

You can grab your function's endpoint in curl:

URL=$(\
  gcloud functions describe ${FUNCTION} \
  --project=${PROJECT} \
  --format="value(httpsTrigger.url)")

curl \
--silent \
--include \
--header "Accept: application/json" \
--request GET \
${URL}

Yields:

HTTP/2 200 
access-control-allow-methods: GET
access-control-allow-origin: *
content-type: application/json
...

{"name":"Freddie"}

Then you can GET the value and put it into an HTML id:

<!DOCTYPE html>
<html lang="en">

<body>
<div id="result">/div>

<script>
    const PROJECT = "<your-project>";
    const REGION = "<your-region>";
    const FUNCTION = "<your-function>";

    const ID = "result";

    const URL = `https://${REGION}-${PROJECT}.cloudfunctions.net/${FUNCTION}`;

    fetch(URL,{
        method: "GET",
        headers: {
            "Accept": "application/json"
        }
    })
    .then(resp => resp.json())
    .then(json => JSON.stringify(json))
    .then(html => document.getElementById(ID).innerHTML = html);
</script>
</body>
</html>

I tested this (locally) using Caddy:

docker run \
--rm \
--name=caddy \
--interactive --tty \
--publish=80:80 \
--volume=$PWD/index.html:/usr/share/caddy/index.html \
--volume=caddy_data:/data \
caddy

And then browsing http://localhost:80, I get:

{"name":"Freddie"}