Use GCP Compute Engine as proxy for Authenticated Cloud Run Service - nginx

I currently have a dockerized web application hosted on a Google Cloud Compute Instance, which is only accessible from our companies private network. This set up has worked well over the past year, but with additional development requirements and increased usage, I find myself constantly modifying the instances size, and having to restart the server with new updates. Also, other developers on the team have less experience with deploying this code which makes this app my responsibility.
I'd like to move this application to Cloud Run for scalability, ease of maintenance and deployments but still have it accessible only on our companies network. My idea was to move the application to an authenticated cloud run service and use the original server as an nginx proxy that would add an authentication header and forward the request to the cloud run service.
My question would be how would I use nginx to get the token (the server will have the necessary permissions), and add it to the request before passing it to the app. This is my current idea, but not sure where to go from here.
location / {
proxy_set_header Authentication "Bearer $ID_TOKEN";
proxy_pass https://the-library-clwysxi3sq-ue.a.run.app;
}

You're on the right track.
At this point, I recommend you to consider using Envoy Proxy instead of NGINX. Envoy has a well-documented protocol to fetch dynamic data such as $ID_TOKEN from an external source.
Whatever solution you choose, make sure you actually end up rewriting the "Host" header to your [...].run.app hostname, because if you preserve the hostname as is (somedomain.com), Cloud Run’s load balancer won't know which app to route it.
The remaining task is to figure out how to get the $ID_TOKEN dynamically.
Google Compute VM instance needs to retrieve an identity token (JWT) by querying the instance metadata service:
curl -H "Metadata-Flavor: Google" \
http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://hello-2wvlk7vg3a-uc.a.run.app
Make sure to replace the value of ?audience= with the targeted service's URL.
The response body of this call returns a JWT token that expires within an hour. You should cache this response (based on audience, and TTL<60 mins), or simply get a new one every time.
Note that on Cloud Run, you can only generate 50 identity tokens per second currently. However you are running on GCE (and I'm repeating myself here) I don't think there's a documented rate limit for metadata service on GCE. It's likely higher.
Then, you need to add it to the outgoing request (to Cloud Run) as an HTTP header:
Authorization: Bearer <TOKEN>
This procedure is explained at Service-to-service authentication documentation.
You can search Stack Overflow or Google on how to execute a Lua or Bash script in NGINX for every request.

Related

How can HTTP queries be run in the Azure Monitor workbooks?

How can HTTP queries be run in the Azure Monitor workbooks?
I read all the documentation here and still can not find how could I use my application health checks http endpoints to report on my application status in an Azure Monitor woorkbook.
I have an ASP.NET application if it matters. It exposes endpoints which I would like to call from the workbook and do different visualizations depending on the data returned.
You'd use the "Custom Endpoint" data source in the Query step.
https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#custom-endpoint
The endpoint needs to support CORS, because the calls would be coming from the azure portal, and also needs to support https, because the portal itself is loaded via https.
The endpoint is also expected to return JSON content, and then you can use JSONPath inside the settings of the custom endpoint's Result Settings tab to transform it into grid data.

Cloud Run mapping services by URL path (using custom domain and wildcards)

I've been trying to migrate my solution from Google Cloud App Engine to Google Cloud Run and have been struggling a lot.
I have 3 services in app engine for each staging environment, let's call them api, front1, front2. front1 has custom domain name assigned www.example.com, as well as staging subdomain www.dev.example.com. Using dispatch.yaml rules I route requests to appropriate services, for example: requests containing api/* are routed to api service, default path routes to front1, /foo* routes to front2. Everything is working there perfectly well, however, hosting the solution on Cloud Run would decrease the costs a lot.
So at the moment, I am really struggling to understand how could I fully replicate this behavior in Cloud Run. I've tried following firebase routing, but it requires to host an app on Firebase as well and doesn't route all traffic for route /api/** to my api service.
I've tried following this article, but I cannot select my service since I selected Internet Network Ednpoint Group option for backend type, which only can point to a single url. Also, I am not sure if it will support wildcard routing there.
I'd highly appreciate any help here, I am totally out of options at the moment.

Spring Cloud Gateway and NGINX

Do I still need NGINX to serve static content like JS etc and reverse proxy request to backend, or it can be done with just Spring Cloud Gateway?
Spring docs has a following image:
And I found no description there on how to return static content to the client, does it mean it's considered bad practice and I need extra step of reverse proxying adding its latency?
If not, where can I find more info on how to do that with Spring Cloud Gateway, especially if I'm going to make oauth2 authorization-code flow authentication using Spring Gateway?
I am using NGINX as reverse proxy, but i thought about the same question and i tried (same thing for me. oauth2 authorization-code flow authentication). So, you can serve static content with just Spring Cloud Gateway, it is possible.
For example if you are using React, take build and copy all build files to under resources/static/frontend-name location. Then, disable (permitAll) web security in that all frontend locations. So you can access just typing http://gatewayserver/frontend-name/index.html
However, I don't think to use it in production environment, NGINX still sounds like better idea for me. Because when I take release for frontends, why do i need to take release for gateway at the same time or vice-versa? But, if you have small project, it might be an option.

Custom domain name with SSL on Firebase Storage

I was able to get a custom domain name mapped to my Firebase Storage bucket by simply naming the bucket the same name as my domain name and then pointing the CNAME record to c.storage.googleapis.com. However, https doesn't work because the common name on the certificate is different. Is it possible for me to upload a certificate or, even better, have GCP or Firebase manage a certificate?
I'm coming a bit late to the party and this question might have been answered elsewhere. However, since this was the first result I found when googling for this feature, here goes nothing:
For starters, let's say you have a CNAME like assets.somedomain.com pointing to c.storage.googleapis.com, and you create a bucket called assets.somedomain.com.
Then you upload a file, whose public url will look like:
https://firebasestorage.googleapis.com/v0/b/assets.somedomain.com/o/arduino.png?alt=media&token=asdf
Which can be seen as:
firebasestorage.googleapis.com/v0/b/
+
assets.somedomain.com
+
/o/
+
arduino.png?alt=media&token=asdf
You should be able to view said file using:
https://assets.somedomain.com/arduino.png?alt=media&token=asdf
Which is
assets.somedomain.com/
+
arduino.png?alt=media&token=asdf
(basically, you strip the original base URL and the /o/ prefix)
But of course you get a big fat warning telling you the certificate is invalid, because it's meant for *.storage.googleapis.com.
In my case, I was able to circumvent this using cloudflare's universal SSL, which acts like a proxy that asks no questions whatsoever.
You try again, but somewhere in the middle the request becomes anonymous and you get an XML stating that you lack the storage.objects.get permission.
<Error>
<Code>AccessDenied</Code>
<Message>Access denied.</Message>
<Details>
Anonymous users does not have storage.objects.get access to object.
</Details>
</Error>
This means that even with the token included in the query string the proxyed request has no permission. Next step, then, is to make the bucket publicly readable in Google Cloud Console -> Storage.
(This can be done using gcloud cli, but I found this method easier to explain)
Pay attention to use the legacy object reader permission, which stops visitors from actually listing the bucket contents.
After that, you should be able to access the image using:
https://assets.somedomain.com/arduino.png
Note that you don't even need to include "alt=media" because cloudflare will serve the file instead of its metadata.
Currently we don't support custom domains in Cloud Storage for Firebase.
You have two options:
Use Firebase Hosting (developer generated content)
Set this up via GCS static hosting (docs)
In either case though, you'll lose the ability to use the Firebase SDKs for Cloud Storage, as well as it's authentication and authorization functionality.
Happy to learn more about the use case to see if it's something we should support in the future.
Update April 2021
Firebase 8.4.0 introduces storage().useEmulator(host, port).
You'll still need a reverse proxy, which you can do with Google Cloud Load Balancer or others.
It's actually quite simple to achieve what you need - i.e to serve your storage content under your custom domain with SSL support. But you'd take a bit different approach.
They key here is that, as I was once prompted by firebase support, storage api is meant for internal usage of a developer, including the urls that point to files and they are not meant to be exposed to end users. That sounded kind of strange to me at first, but after I gave it a bit of though it started to make sense.
So here is how I solved it using the updated perspective.
You can create a dedicated endpoint which redirects to a cloud function.
That endpoint would accept a storage url as a parameter.
Then the cloud function would read the url and just stream its content back.
That's it.
No need for complex proxies setup etc. All your content will now be served under your custom domain.
Here is a brief example of how the core logic of such a function may look like:
(req, res, next) => {
let link = req.query.url
const https = require('https');
//request the content of the link
https.get(link, response => {
if (response.statusCode < 200 || response.statusCode > 299) {
//handle error
} else {
//stream the content back to client
response.pipe(res)
res.on("close", () => response.destroy())
}
});
}
Now you can do something like this (assuming your function is hosted under 'storage/content'):
let contentUrl = https://my-custon-domain.com/storage/content?url={{put your storage url that refers to a file}}
and then for example assign that url to the iframe src
<iframe :src="contentUrl"/> //this is how one will do it in Vue.js
Opening such a link in a browser will display your file content (or download it depending on the browser's settings)
I'll post a more detailed explanation with examples if this answer gets more attention.
I perfectly agree to the previous answer and thanks a lot for that. But I am writing the instruction in a better fashion
Create a bucket with you custom domain name in google cloud platform-> Storage.
Create a permission of legacy object viewer and add it to all users.Note:you have to search legacy object viewer from the filter text
Add a DNS record in your domain service provider account with CNAME assets which will point to c.storage.googleapis.com.
Create a cloudflare account if you do not have
Add website in cloudflre where you need to put your domain name not the subdomain
Copy the nameserver details from cloudflare to your DNS service providers nameserver details
It will take some time to move all the dns records in cloudflare.
Goto page rules in cloudflare and add assets.yourdomain.com and turn on always use https
You are done
For GCloud users,
Just go to console,
Open Load Balancing
Provide an alias and handle your mapping assets.yourdomain.com
points to */images/
It will create a new balancer with ip address and it is
multi-regional don't worry.
Open Cloud CDN, give alias and select your created balancer.
Select your bucket name which is your firebase-storage's bucket
name.
Go to your domain provider like GoDaddy and put this ip address which points
assets.yourdomain.com to balancer's ip.
To sum up;
Google is handling certification progress and it gives you an ip, you add A record which points to given ip.
When you visit assets.yourdomain.com it goes to Google and Google points to your bucket.
It takes 5mins to complete but I have spent 1 week to understand how does it work :)
Using Firebase Storage means you are using a GCP Cloud Storage Bucket.
Using GCP Load Balancing feature, you can basically expose your GCP Storage Bucket to a public IPv4. And manage SSL certificates.
Then, you go to your domain provider console and add an "A record" to your Bucket IP.
Here is a great post : https://deliciousbrains.com/wp-offload-media/doc/how-to-set-up-a-custom-domain-cdn-for-google-cloud-storage/
GCP = Google CLoud Platform
Note that GCP Load Balancing is not free.

Firebase cloud function: how to deal with continuous request

When working with Firebase (Firebase cloud function in this case), we have to pay for every byte of bandwidth.
So, i wonder how can we deal with case that someone who somehow find out our endpoint then continuous request intentionally (by a script or tool)?
I did some search on the internet but don't see anything can help.
Except for this one but not really useful.
Since you didn't specify which type of request, I'm going to assume that you mean http(s)-triggers on firebase cloud functions.
There are multiple limiters you can put in place to 'reduce' the bandwidth consumed by the request. I'll write a few that comes to my mind
1) Limit the type of requests
If all you need is GET and say for example you don't need PUT you can start off by returning a 403 for those, before you go any further in your cloud function.
if (req.method === 'PUT') { res.status(403).send('Forbidden!'); }
2) Authenticate if you can
Follow Google's example here and allow only authorized users to use your https endpoints. You can simply achieve this by verifying tokens like this SOF answer to this question.
3) Check for origin
You can try checking for the origin of the request before going any further in your cloud function. If I recall correctly, cloud functions give you full access to the HTTP Request/Response objects so you can set the appropriate CORS headers and respond to pre-flight OPTIONS requests.
Experimental Idea 1
You can hypothetically put your functions behind a load balancer / firewall, and relay-trigger them. It would more or less defeat the purpose of cloud functions' scalable nature, but if a form of DoS is a bigger concern for you than scalability, then you could try creating an app engine relay, put it behind a load balancer / firewall and handle the security at that layer.
Experimental Idea 2
You can try using DNS level attack-prevention solutions to your problem by putting something like cloudflare in between. Use a CNAME, and Cloudflare Page Rules to map URLs to your cloud functions. This could hypothetically absorb the impact. Like this :
*function1.mydomain.com/* -> https://us-central1-etc-etc-etc.cloudfunctions.net/function1/$2
Now if you go to
http://function1.mydomain.com/?something=awesome
you can even pass the URL params to your functions. A tactic which I've read about in this medium article during the summer when I needed something similar.
Finally
In an attempt to make the questions on SOF more linked, and help everyone find answers, here's another question I found that's similar in nature. Linking here so that others can find it as well.
Returning a 403 or empty body on non supported methods will not do much for you. Yes you will have less bandwidth wasted but firebase will still bill you for the request, the attacker could just send millions of requests and you still will lose money.
Also authentication is not a solution to this problem. First of all any auth process (create token, verify/validate token) is costly, and again firebase has thought of this and will bill you based on the time it takes for the function to return a response. You cannot afford to use auth to prevent continuous requests.
Plus, a smart attacker would not just go for a req which returns 403. What stops the attacker from hitting the login endpoint a millions times?? And if he provides correct credentials (which he would do if he was smart) you will waste bandwidth by returning a token each time, also if you are re-generating tokens you would waste time on each request which would further hurt your bill.
The idea here is to block this attacker completely (before going to your api functions).
What I would do is use cloudflare to proxy my endpoints, and in my api I would define a max_req_limit_per_ip and a time_frame, save each request ip on the db and on each req check if the ip did go over the limit for that given time frame, if so you just use cloudflare api to block that ip at the firewall.
Tip:
max_req_limit_per_ip and a time_frame can be custom for different requests.
For example:
an ip can hit a 403 10 times in 1 hour
an ip can hit the login successfully 5 times in 20 minutes
an ip can hit the login unsuccessfully 5 times in 1 hour
There is a solution for this problem where you can verify the https endpoint.
Only users who pass a valid Firebase ID token as a Bearer token in the Authorization header of the HTTP request or in a __session cookie are authorized to use the function.
Checking the ID token is done with an ExpressJs middleware that also passes the decoded ID token in the Express request object.
Check this sample code from firebase.
Putting access-control logic in your function is standard practice for Firebase, BUT the function still has to be invoked to access that logic.
If you don't want your function to fire at all except for authenticated users, you can take advantage of the fact that every Firebase Project is also a Google Cloud Project -- and GCP allows for "private" functions.
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 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.

Resources