My goal is to prevent users from accessing my cloud function endpoints by using an API key and API gateway. I have successfully deployed the API gateway; however, the original endpoint of each cloud function still exists and is accessible to the public. I want to have the cloud function endpoints private, while having the api gateway endpoints public, but I am not sure how to achieve this. Any suggestions would be great.
You can't hide your Cloud Functions endpoint. In any configuration it will be publicly viewable.
However, you can restrict who has access. In your case, deploy your Cloud Functions in secured mode (set the param --no-allow-unauthenticated or remove allUsers from the permissions section)
Then, deploy your API Gateway with a custom (backend) service account. Grant this service account the permission to invoke Cloud Functions (role: cloudfunctions.invoker).
When you have achieve this, only the API Gateway identity will be allowed to access to your Cloud Functions. The users will be able to see and to request the Cloud Functions URL, but they will get a 403 or a 401 error.
EDIT 1
After tests, and with Cloud Functions (I haven't have this case with Cloud Run), the Cloud Functions generated target audience is wrong with you use addition path in your backend. Here the conf that I have
/function:
get:
summary: Greet a user
operationId: function
x-google-backend:
address: https://us-central1-gdglyon-cloudrun.cloudfunctions.net/gdg-go
responses:
'200':
description: A successful response
schema:
type: string
/function-path:
get:
summary: Greet a user
operationId: function-path
x-google-backend:
address: https://us-central1-gdglyon-cloudrun.cloudfunctions.net/gdg-go/path
jwt_audience: https://us-central1-gdglyon-cloudrun.cloudfunctions.net/gdg-go
responses:
'200':
description: A successful response
schema:
type: string
The /function uses the root path of the Cloud Functions, no problem to invoke it directly.
The /function-path add /path to the root path of the Cloud Functions. I guess that API Gateway use this same full URL (with the /path at the end) which is a wrong audience for the function.
You can override that with the jwt_audience parameter.
Related
Related to Security Cloud Run services for end-users and other services
I'm using:
Firebase Auth to generate id tokens for users with Google, Microsoft, GitHub ... identities
Cloud Endpoints on Cloud Run to invoke (Cloud Run) gRPC services
Firebase Auth users are auth'd by one of my services
Where I'm struggling....
My app provides 1 or more Cloud Run services that the app's users should be able to curl. But authenticating Cloud Run services require per-service id tokens; the id token's audience must use the Cloud Run service URL and the Cloud Run service URL is service-specific.
It seems as though I ought to be able to exchange the Firebase Auth id token for (Google Account) id tokens (with appropriate audiences) that can then be used to invoke the Cloud Run service. The proxy could also run on Cloud Run and it would use my app's auth service to verify whether the id token user should be issued with a Google id token.
Guillaume Blaquire's answer proposes either Coud Endpoints or a proxy similar to what I describe above. However, Cloud Endpoints requires that the backend services be known at deploy time (which these Cloud Run services won't be) and I want to provide the user with the id token so that they can use curl or some other tool to make the auth'd request.
Cloud Run has some compelling documentation for Authenticate (sic.) but I want something between:
Authenticating users -- I have the JWT but I want to receive a Google id token for the Cloud Run service
Authenticating service-to-service which Guillaume's alternative proposal in the answer.
Rather than place your Cloud Run behind Cloud Endpoints, where you have to know the Cloud Run instances ahead of time, you can handle the request and authentication inside the Cloud Run instance itself.
To be able to handle Firebase Authentication tokens inside the Cloud Run instance, they must be setup so that they can be invoked unauthenticated. Then, inside the Cloud Run, it should launch a web server, parse the incoming request (paying attention to the Authorization header - Firebase Auth sample) and then either action or terminate the request.
To achieve this, take a look at this thread for details on how you can handle both HTTP and service-service requests. Alternatively, you could just deploy the Functions Framework image from which that thread's code is based.
If you want cleaner URLs, host multiple endpoints within a single Cloud Run instance and then place that instance behind Cloud Endpoints or you can take a more manual approach via a custom domain using a service like Firebase Hosting.
I have an apollo/graphql server sitting behind a GCP API gateway. Google says it requires an OpenAPI spec to secure endpoints:
https://cloud.google.com/api-gateway/docs/get-started-cloud-run#creating_an_api_config
But how exactly would this look for securing a single graphql endpoint? Also, as a side question, is a new API Gateway needed to be created for each Cloud Run/App Engine service?
Thanks in advance.
Here's a repo that demonstrates what you want to achieve. It's a GCP API Gateway fronting a GraphQL API on Cloud Run, secured with Identity-Aware Proxy. Here's the API config from the link:
api-spec.yaml
swagger: '2.0'
info:
title: gcp-demo-api
description: Sample GraphQL API on API Gateway with a Cloud Run backend
version: 1.0.0
schemes:
- https
produces:
- application/json
paths:
/:
post:
summary: GraphQL endpoint
operationId: gql
x-google-backend:
address: https://PROJECT_AND_RANDOM_STRING.a.run.app/graphql # App URL/endpoint
jwt_audience: LONG_RANDOM_STRING.apps.googleusercontent.com # IAP client ID
responses:
'200':
description: A successful response
schema:
type: object
To answer your side question, it is important to understand what an API Gateway does. An API Gateway is what stands between your user requests and your collection of backend services so all API requests goes through it.
Therefore, you don't need to create a new API gateway for each service. It is possible in API Gateway to serve multiple services such as Cloud Run, App Engine, Cloud Functions, etc. by specifying the backend address on each endpoint. Here's a link that further explains the concept.
I'm trying to set up GCP Api Gateway for my Cloud Run hosted containers but I' getting some errors regarding Firebase Authentication.
In the api spec, I copied what is published in the api gateway guide
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/{{ project_id }}"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "{{ project_id }}"
and added the security entry in the endpoints path
security:
- firebase: []
When I make a request through the Api Gateway, some additional headers are added to the request that is proxied to the Cloud Run service, such as x-apigateway-api-userinfo, x-forwarded-authorization and authorization
Then, in the Cloud Run service, I have to use Firebase Admin sdk to verify the token passed and identify the user who is making the request. That's where the problem begins, when I try verifying any of this header's token, I get an error: "FirebaseAuthError: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token."
Am I doing anything wrong? How can I decode the auth token passed to the Api Gateway?
Checking the validity of the token is already done by the API Gateway.
x-apigateway-api-userinfo is not the JWT, it's a base64 encoded json containing the users information. Decode it and you should be golden.
I have a google service account private key (json format) from google console.
How do I create a new client in golang (what google api do I use) and authenticate the credentials I got without setting environment variable?
I would like to provide the Google service account credentials manually in Golang.
I started by passing the json file as byte array:
creds, err := google.CredentialsFromJSON(ctx, blob). Blob is the byte array of the json file.
I can create a client successfully with cloud.google.com/go/secretmanager/apiv1 even after I changed the private key (ouch). So I wonder at what point and how do I authenticate the creds?
Thanks.
Question is unclear. I allow myself to rephrase it a "How do you create a valid credential from a service account's keyfile?"
Well, Google indeed implement a strategy to give an authenticated indentity to the calling process.
At a coarse grain, it indeed look for an environment variable definition called GOOGLE_APPLICATION_CREDENTIAL which value contains a path to a valid SA keyfile. Otherwise it uses the default service account from the piece of compute it runs from (GCE, GKE pod, Appengine, cloud function,...).
Finally you've got an error otherwise.
Working locally, a good practice to set this default credential is to use the cloud SDK with command gcloud auth application-default login. That way the application would act on the behalf of the logged person (you for instance and consume with your permissions and quotas).
Otherwise you could set up the env var manually to point to the service account's keyfile you downloaded.
Now if you run outside from a google cloud environment you can manually build credential unsing a keyfile like you do. The following example is explicit. What you must understand is that the credential you forge is an argument to any Google API client constructor.
Once the API client is built with your credential, you just consume it by calling the methods it exposes. Authentication happens under the hood. Every call to Google API is authenticated with an OAuth2 token which retrieving flow is described here.
You could dig into the source code client of the client API if you need to be convinced, but the nice thing is you don't have to.
I have set up a very simple Node application with Express on Google Cloud Run.
It works great, but when I set it up with "Allow unauthenticated invocations to [service] (y/N)?" to No, I get a 403 Forbidden even though I created an API key and I'm making the calls adding key=[My API key] in the query string, as told in the documentation. My URL ends up looking like
https://service-wodkdj77sba-ew.a.run.app?key=[My API key].
I've tried with restricted (for Google Cloud Run) and unrestricted API keys.
Is there anything I'm missing?
Cloud Run, like many product in GCP, doesn't support API Key authorization. As detailed in your provided link, only a subset of service use API KEY.
It's also mentioned :
API keys do not identify the user or the application making the API request, so you can't restrict access to specific users or service accounts.
Where Cloud Run authentication section specify this here
All Cloud Run services are deployed privately by default, which means that they can't be accessed without providing authentication credentials in the request.
By the way, the Cloud Run expectation and the API Key capabilities aren't compatible.
However, if you want to access to your Cloud Run private service with API Key a workaround exist. You can deploy an Extensible Service Proxy (ESP) on another Cloud Run service. In it, authenticate the API Key and, if it's valid, call the Cloud Run private service with the ServiceAccount of your ESP (which must have roles/run.invoke role).