TwiN / gatus

⛑ Automated developer-oriented status page
https://gatus.io
Apache License 2.0
6.41k stars 426 forks source link

web.context-root is needed if the URL can not be rewritten. #88

Open chi-bd opened 3 years ago

chi-bd commented 3 years ago

At a1679ddc5e6c36e58fcfc75ece86bbe557e27709, web.context-root is deleted. But this option is needed in the environment which the URL can not be rewritten, for example Amazon ALB ingress controller. Would you please revive it?

TwiN commented 3 years ago

As mentioned in v2.0.0's release notes:

Removed web.context-root (Vue.js' router doesn't easily support context roots. May be re-implemented in the future if enough people ask for it)

The main reason for removing it is due to my inability to make a dynamic context root work with Vue.js' router (without ugly hacks) despite several attempts.

If you want to have a go at it, feel free, but unless a few more people request this, I won't personally try to re-implement it.

That being said, for your specific issue, most people create a separate CNAME (e.g. status.example.com) for their status page. Is there something preventing you from doing this?

KarlMW commented 3 years ago

What about this? https://stackoverflow.com/questions/43879418/deploy-vuejs-app-in-a-subdirectory It seems that it can be done with an edit of web/app/vue.config.js like this:

module.exports = {
  ...
  publicPath: "./"
  ...
};

(bear in mind that I know nothing of what I write - I just googled the problem - hopefully not wasting your time here)

In terms of the usefulness of being able to host content in a sub-directory - it's one of the first things I look for in a service I want to privately self-host (ie. purely for my own use). Being in a subdirectory means that its existence is not visible, whereas the existence of subdomains is publicly displayed in DNS.

KarlMW commented 3 years ago

I hope that's not one of the "ugly" hacks you refer to :-)

TwiN commented 3 years ago

@KarlMW Hahaha no, if it was just that, it couldn't even be called a hack.

In fact, this would work fine if we weren't using Vue's router for the service detail page.

Long story short, one of publicPath's limitations is that a relative path cannot be used in conjunction with history.pushState, which is unfortunately used by Vue.js' router. I could fix this by using hash history (e.g. http://localhost:8080/#/services/core_twinnation-sitemap), but I just can't bring myself to like that style of routing.

reference:

Limitations of relative publicPath

Relative publicPath has some limitations and should be avoided when:

  • You are using HTML5 history.pushState routing;
  • You are using the pages option to build a multi-paged app.

I tried several different methods, but none worked in a clean way.

I didn't want to specify what I meant by "ugly" hacks, because they're really ugly, but the one way I found that works would be to read the generated static script (https://github.com/TwinProduction/gatus/blob/master/web/static/js/app.js) on application start, replace the value of a constant by the defined context root and overwrite the default script with the new script that has the appropriate context root. Coupling that with the base parameter of VueRouter (https://router.vuejs.org/api/#base) would be sufficient, but well, it's hacky.


By the way, it is not possible to do a reverse lookup to retrieve the CNAMEs of a domain (unless you have access to the zone files).

For instance, if I create the CNAME potato.twinnation.org, it's basically impossible for anybody to know unless I tell them, or unless they bruteforce every single strings and find it (or they just happen to try it - but don't bother, I don't have that cname 🤣).

This is known as denial of existence.

Even if a malicious user had access to the zone files, you could still hide it from them by using a wildcard CNAME domain record, and by pointing that to a reverse proxy which would have the necessary configuration telling it to route requests with a given host to the appropriate application, you'd be the only person privy to the existence of that application.

That being said, this is still security by obscurity, which is IMO not enough if you don't want prying eyes to have access to your application, which is why you can also configure basic authentication on top of that: https://github.com/TwinProduction/gatus#basic-authentication

KarlMW commented 3 years ago

Thanks for the detailed reply. You've made gatus very easy for people to build in docker themselves - thanks for that, too :-) I tried setting publicPath: '/gatus/' in web/app/vue.config.js and then rebuilt it, hoping that a static root would work. Sadly it failed - it was still looking for its assets under /

w.r.t. CNAME reverse lookups, I'm not sure how it's done, but my never-used-publicly CNAMEs are findable on the web, eg. via https://www.nmmapper.com/sys/tools/subdomainfinder/

Whether it's some sort of active probing, or whether they just monitor certificate transparency logs, I don't know. I generally use client certificates (handled by traefik) for my private domains (as well as passwords), so I'm reasonably confident that no-one can get in, but I like a bit of obscurity as well :-)

MiddleMan5 commented 2 years ago

A lot of our deployments use path routing to separate out environments, so we really do need this feature. Allowing us to configure the full external path would also be acceptable. You may still need to modify a constant on the client side (our backends usually write the configured value to <base href=PATH /> which our frontend's read into a variable in our path building utils on load)

IE: external-root: https://health.my-domain.com/region-1/

All relative links accessed by the client would be replaced by the full external root path, although I'm not sure how this plays with pushState.

Edit:

Another thing I noticed is that the html resource uris are all full path, these might be better specified as relative imports unless the html renderer is doing something fancy with them. Ideally we don't have to do path rewriting and the app just supports being mounted on a subdirectory image

Obviously changing uris from ie /js/app.js to js/app,js has other implications, but it's an option that allows the browser to request resources relative to the current path

TwiN commented 2 years ago

@MiddleMan5 If you or anybody else can get it to work with the front-end, feel free to do so. I'm completely fine with using a different approach for routing -- if pushState has to go, then so be it.

I do want to rewrite a good part of the UI at some point and make it a little less minimalist, but I don't know when that will happen.

michaelkrieger commented 1 year ago

w.r.t. CNAME reverse lookups, I'm not sure how it's done, but my never-used-publicly CNAMEs are findable on the web, eg. via https://www.nmmapper.com/sys/tools/subdomainfinder/

They get this data from SSL Transparency. Looking at it from a number of domains I control, it includes only those that exist in SSL Transparency records (even a typo that existed for 1 minute) that show up there. Unless you have an misconfigured (or old pre RFC8482 server that still answers ANY queries, this is the only place to get the values.

I'm sure the big names (8.8.8.8 and 1.1.1.1 or major ISPs) could probably assemble it from DNS query logging though. It should be assumed that it's out there (and hence have basic auth or something like authelia in front of it). I agree that subdomains are a better place for a lot of services, but this same problem exists with many VUE apps.

ansilh commented 1 year ago

Got stuck in this issue when I configured Gatus behind a Google's LB backed by IAP authentication. we use a common FQDN and uses route based redicretion via URL map rewrite, but the Gatus web app always respond with default root ("/") instead of the called base URL ("https://common.ingress.dom//") Now the only option for left is to create an Ingress rule to route the traffic via a new DNS entry and I've to duplicate the GCP ingress configuration only for Gatus :(

Would be great if we can call this out in the README to avoid surprise at the end.

TwiN commented 1 year ago

@ansilh Could you make a PR for this? A section in the FAQ should suffice

akozlins commented 1 year ago

after some tries, here is what worked for me:

use relative path '.' for SERVER_URL in main.js

RUN sed -i \ -e "s|export const SERVER_URL = .*|export const SERVER_URL = '.'|" \ web/app/src/main.js

use relative path '.' for resources in index.html

RUN sed -i \ -e 's|="/|="./|g' \ web/app/public/index.html


- and reverse proxy (only relevant lines from docker-compose)

build: { dockerfile: "Dockerfile", args: [ "VUE_PUBLIC_PATH=/gatus", ] } labels:

rokiden commented 1 year ago

I'm homelab enthusiast with free domain from DDNS provider, subdomain option unavailable for me. Subpath would be very useful, otherwise I can use Gatus only through SSH tunnel :( (but its very uncomfortable from mobile device)

lpil commented 1 year ago

I'm in the same situation as @rokiden here! Adding new domains to the infra in question is not possible.

If the paths for the CSS and JS and badges had the preceding / removed from the start of the paths then Gatus would work so long as folks navigated to homepage first. This is not ideal, but it takes Gatus from broken to usable for in this situation.

serGrey commented 4 months ago

after some tries, here is what worked for me:

  • rebuild gatus fronend (npm --prefix web/app run build) with following changes
# set publicPath in vue.config.js
ARG VUE_PUBLIC_PATH
RUN sed -i \
    -e "s|publicPath: .*|publicPath: '${VUE_PUBLIC_PATH}'|" \
    web/app/vue.config.js

# use relative path '.' for SERVER_URL in main.js
RUN sed -i \
    -e "s|export const SERVER_URL = .*|export const SERVER_URL = '.'|" \
    web/app/src/main.js

# use relative path '.' for resources in index.html
RUN sed -i \
    -e 's|="/|="./|g' \
    web/app/public/index.html
  • and reverse proxy (only relevant lines from docker-compose)
build: { dockerfile: "Dockerfile", args: [ "VUE_PUBLIC_PATH=/gatus", ] }
labels:
- "traefik.http.middlewares.gatus.stripprefix.prefixes=/gatus"
- "traefik.http.routers.gatus.middlewares=gatus"
- "traefik.http.routers.gatus.rule=PathPrefix(`/gatus/`)"

could you please help me with it