Authenticated calls between Google App Engine services
Is there a way to make Google App Engine services only callable within the App engine?
For example, I create service-a and service-b in my GAE.
By, default, service-a and service-b are public, so I can send requests to them without authentication.
I would like to make them unreachable from the public, but service-a and service-b can still communicate with each other.
Appreciate it if someone could help me with this.
Thanks.
Use Identity Aware Proxy to secure your services and allow access only to the App Engine Service Account. The benefit of IAP is that you don't need to make any changes in you code to secure your service.
After your service is secured by IAP, you can call it by adding the Authorization header with the service account's OIDC Token as bearer value. This is pretty easy to do with almost any programming language. Take a look at this link to learn how to do it.
I personally find the learning curve for anything related to IAP to be very steep.
An easy way to create your own authentication is to do the following:
Create a secret token and put it in the code of your services.
When your service calls another one of your services, put the secret token in the header.
The receiving service rejects calls that don't have your secret token.
Another way might be to look for the X-Appengine-Inbound-Appid header that Google adds. See https://cloud.google.com/appengine/docs/standard/python/reference/request-response-headers
I haven't done that so you should verify that it would work.
Lets assume your projects are "myprojectABC" and "myprojectDEF" and you want to allow request only for these projects.
ALLOWED_PROJECTS=["myprojectABC","myprojectDEF"]
ALLOWED_APS=["myprojectABC.appspot.com","myprojectDEF.appspot.com"]
Check for the header "X-Appengine-Inbound-Appid" if present and is in ALLOWED_PROJECTS, is authorized. if present and not in allowed project, then is not authorized.
If the header is not present, check header "X-Appengine-Taskname", if present check for the header "Referer" and make some comparisons with the allowed apps to authorize the request. Log the host header to understand how to convert and replace some parts ("-dot-") to build the project name.
if that header is not present check for the header "X-Appengine-Cron" if that header is present check for the header "Host" and if present check if is in ALLOWED_APPS, in that case authorize the request
if none of above, the request is from external site.
NOTE: The examples works for Python GAE, may be for other languages the notation of the app domain in 2) or 3) could change.
Related
I have an AppEngine application that is behind an IAP (identity-aware proxy), so it receives requests that are authenticated and include a JWT token. From the AppEngine application I want to make a request to the Google Sheets API. That also requires an authenticated connection, but given that I want that connection to be made under the same user that accessed the application via the IAP, does anyone know how to create a request from inside the AppEngine application that will forward the token to Google Sheets? Cannot find any information on the subject... I am using Java, so any Java pointers would be appreciated, but general/other language help is good too...
I will describe the 2 approach proposed in the comment
The first one, to reuse the IAP proxy token to access Google Sheet is impossible, and dangerous.
Impossible because you receive an identity token from IAP (at least the requester/browser send an identity token to IAP) and you need an access token to request Sheet APIs.
Dangerous because, if you are able to reuse the IAP token to request the Google Sheet, that means the user is authorized to access to the Google Sheet. And I'm sure that you build an app to prevent any direct access/modification to the Google Sheet.
The second one, is to use a technical account (typically a service account) and generate an access token to access the Sheet API.
This second approach is the best one (don't forget to correctly log the user request and the subsequent sheet API calls in your AppEngine app to have the end to end traceability). BUT, and it's for that you ask this question, it's impossible with the App Engine default service account.
In fact, to access to the Sheet API, you need to scope your access token with the Sheet API. Sadly, you can't do this with App Engine. You can do this with Cloud Run, Cloud Functions, Compute Engine (without the default service account, else you need an extra config to achieve this with the Compute Engine default service account). But not with App Engine.
So, you have 2 solutions:
Either you use another hosting platform (Cloud Run for example), but you loose the IAP capacity (for now)
You continue to use App Engine but you need to request an access token to another service account (it's not required to have a service account key file). You can use the Service Account Credential API for this. I wrote an article on this API
Note: later in 2021, App Engine should be able to accept custom service account, and thus the issue should be solved
I have a react web application with a flask api (I used to use express). The product of this app is the data that it displays. I don't want other people to be able to take that data easily from calling the api.
I want to secure the api such that it can only be accessed by my react app and nothing else. How can I do that?
The only way to truly secure your API is by authenticating your app's user with something like Oauth2 and verify that credential on server-side with something like passport, and make the authorization expire with sessions. AND use SSL so none of that is easily visible through a protocol analyzer.
Sure, you can hard-code some sort of "secret key" with the app, but anyone who want it bad enough will read it off your app or sniff the packets through a packet logger until they find the key.
EDIT: Oh, and as a part of the authorization upon login, provide them with a uniquely generated "API-KEY" as part of identity, so you can validate them upon submission, and if they violate your trust, mark their API key invalid in the server so they can't use them any more.
First, if your client code and API server are running on different domains or ports, configure CORS on your API server to only honor requests that originate from the client code's domain. Second, authenticate legitimate users so that only authorized requests for data are honored. There are lots of 3rd-party libraries to help with authentication.
We're using Google App Engine (python) and Google Cloud Endpoints (OpenAPI).
We have some endpoints for admin-exclusive use. In our app.yaml we have something like this:
handlers:
- url: /_ah/api/appname/v1/admin/.*
script: main.api
login: admin
Usually I use Postman when I need to POST a special request, but for requests to login: admin endpoints, Postman redirects to and opens source of the Google Login page, which doesn't actually allow me to login.
If I open such redirect URL in the browser, I'm actually able to login, but after that, I'm redirected to the GET version of the endpoint, which doesn't help as well. I don't think the browser has a feature which easily allows me to make a custom POST requests after I've login.
So in the end I see two alternatives... one would be "importing" the authentication cookies into POSTMAN after login. The other would be use javascript in the browser's console to "manually" make the POST request I'm trying to make.
Both options seem to me somewhat overcomplicated, and I feel like I gotta be missing some easier way of doing what I'm trying to do.
So my question is what's an easy way to make a POST request to a deployed google cloud endpoint which is protected by login: admin?
Upon investigating I see you're using App Engine with Python 2.7 runtime and that the login: admin endpoint is authenticated with Google Accounts. When you protect the endpoints all requests made to it need to be properly authenticated in order to be accepted, therefore you actually need to provide your authentication credentials to the server with every request. Since the credentials posseses a Time To Live for security reasons you cannot simplify the process permanently storing them in a file.
The answer is indeed in your question, if you want to use POSTMAN you have to import the cookies after login or create a script that handles both the authentication process and the subsequent request. Here's the documentation for POSTMAN if you decide to go this way and here's Google's documentation on making authenticated API requests.
Nevertheless, there are some important things to note. Python 2.7 is sunsetting, this means that security vulnerabilities may appear and probably won't get patched which could result in huge security breaks and/or the need to rewrite your whole backend in the newer python runtime; Google's documentation encourages the switch.
Additionally, the users library will be deprecated at some point too, so you might consider changing your authentication scheme to use Auth0 with the advantage that this option is available for both runtimes.
The cookie associated with authenticating this kind of endpoint protection is called SACSID, and the domain is your own application domain, that is, in the format project.appspot.com.
You should be able to call an endpoint using the browser, which will redirect you to the google login page, and after authenticating you should be able to copy the cookie into Postman, allowing you to make authenticated endpoint calls from there.
When adding it in Postman, you'll need the domain, the cookie name and its value.
For a project that I'm currently developing, I need to expose a servlet (hosted on Google App Engine) to a Java executable which is hosted on Google Compute Engine (in the same project). Such servlet performs some maintenance tasks, so it should never be triggered by non-authorized users. So, the goal is to authorize the requests coming from the Google Compute Engine instance that is running the JAR executable.
In the past I've solved the same issue by having the servlet exposed on HTTPS and rely on a "shared secret", known both to AppEngine application and to the JAR running on the Compute Engine instance. In that way, the instance calls the specific servlet (which is public), then the servlet verifies if the secret is correct, and if so, the request is allowed.
I don't like this approach. For sure we can do something better using challenge-response authentication or by using some other authentication procedure (probably via asymmetric crypto signing). However, this is not what I want to do.
My preferred way of acheiving the same result would be by using the Compute Engine Default Service Account. I am pretty sure there is a way of creating a HTTP POST request on the compute engine and authenticate that via the default service account key. Then, on the servlet, I would rely on the UserService to check whether the request is coming from the ComputeEngine default service account, and if so, I would accept that.
However, I have not seen any documentation or code example that explains how to do that. I suspect there might be possible to perform an authenticated HTTPRequest using the default Compute Engine Service Sccount (maybe adding the Bearer JWT token as Authorization header?).
Has anyone tried something like that?
You have several options; OAuth is likely your best bet.
I have a requirement to use Google Cloud Pub/Sub API directly from Browser ( similar to GA script). I was wondering how can in handle Auth without requiring going through a back-end server.
I want to invoke the Cloud Pub/Sub API directly from the browser. I tried and it says i need to authenticate first , my issue is how to secure the Auth Token.
Is there any javascript library that is available which i can use in Browser ( not backend) to invoke the Google Pub/Sub API.
Thanks in advance
The general approach in Javascript for authorizing and making authorized requests to Google APIs is shown at https://developers.google.com/api-client-library/javascript/samples/samples#AuthorizingandMakingAuthorizedRequests -- it's not specific to the Cloud Pubsub API, but it should work for all Google APIs. Similarly, https://developers.google.com/api-client-library/javascript/start/start-js for general Javascript access to Google APIs.
This is quite an old topic, but I've been recently assessing if it's possible. The simple answer is - yes, it is possible to send messages into PubSub topics directly from a browser application. The solution is as follows:
You need to post a message via REST API using fetch()
You need to send the Authorization header
Authorization header has to contain oAuth2.0 token identifying the user; it can be an anonymous authenticated user or fully authenticated, using firebase authentication library for example.
To have all three above working perfectly, you'd have to write a lot of code. It is not practical at all and architecturally not nice. Unless you absolutely need to do it that way, not another (I can't see why though), the simplified but involving a bit more components solution is as follows:
Authenticate user in-browser via firebase - can be either anonymous or full user
Do simple GET or POST to your cloud/firebase function with the required payload
In function validate the incoming request which will have authenticated user token
If validation is good then publish message into the topic
This way it's still secure, much more maintainable and clearly separated into functional components. Less code, a bit more infrastructure.