Sunday, 9 November 2014

haste-server Base URL Hack/Patch

Recently I came across haste-server, a server behind hastebin, which is a pastebin clone written in node.js. The application is minimalistic, fairly simple and works really well, except for one rather major glitch - it takes over the root directory of the whole website.

I've noticed that several people raised an issue on GitHub asking the author for help, but so far nobody shared fully working solution. Some people tried to work reverse proxy magi, others tried to patch the code - with moderate success. Instead of adding to the problem area I thought I'll try to offer a solution - keep in mind I don't know JavaScript ;-)



haste-server architecture

This is quite simple - we have haste-server backend implemented in server.js file that handles all dynamic interactions and HTML/CSS/jQuery frontend running int the browser only to call the backend and presents the results. The backend handles all dynamic aspects of the application - key generation, storing and fetching the documents. URIs for actions are fixed in the backend code as well as in the frontend - this is where we are by default.

The idea of adding a configuration parameter to haste-server (as suggested on GitHub) will not help because the configuration affects only backend process and will not inform the frontend about changes - we would have to generate frontend files on the flight which is an unnecessary complexity.
As we are using a reverse proxy in front of haste-server, we don't need to modify backend code at all. Our reverse proxy will take care of mapping our directory path to the correct URL within node.js. The only changes we need to do are in frontend, that is application.js and index.html to call the URI path we want.

The trickiest bit was to make it universal - I want my install to run under /paste, you may want /haste and another person will run it under /tools/coding/snippets. The easies way to make it portable was to write a patch generator, so you can apply/revert patch as needed. Pull request coming up, but in the meantime the code is available in my repo.

haste@localhost:~haste-server/patches/baseurl_change_via_reverseproxy$ ./haste-baseurl-patch-generator.pl haste
Patch file basepath.patch generated - your hastebin will reside in http://<servername>/haste/
Please change the directory into haste-server and apply the patch:
    Install:    patch -p0 < baseurl.patch
    Uninstall:  patch -p0 -R < baseurl.patch

That's all, now off to configure your proxy of choice... and to make it easier, here's very basic setup for the 4 most common ones I've seen/used, setting it up for /haste.

Please note, in all cases the URL will be ending with / to ensure we have a strict match (otherwise any word starting with your prefix - here haste - would match).

HAProxy
frontend shared-http-frontend
        mode http
        bind 0.0.0.0:80
        default_backend main_website
        # ACLs for request routing
        acl acl_haste path_beg /haste/
        use_backend haste_backend if acl_haste
backend haste_backend
        server haste 127.0.0.1:7777
        reqirep ^([^\ :]*)\ /haste/(.*)   \1\ /\2 
backend main_website
        server main_web 127.0.0.1:8000

Nginx
location ^~ /haste/ {
        proxy_buffering  off;
        rewrite                /haste/(.*) /$1 break;
        proxy_pass         http://127.0.0.1:7777/;
        proxy_redirect    default;
}

Lighttpd

This one comes with a caveat - we have to use 2 listeners because as Eric Bouchut pointed out here, Lighttpd can't do reverse proxy and URL rewriting at the same time. Luckily Eric was also kind enough to show us how to do it - here's working code:
server.modules   += ( "mod_rewrite", "mod_proxy" ) 
# Matching Proxy
#
$HTTP["url"] =~ "(^/haste/)" {
  proxy.server  = ( "" => (
    "servername:80" => # name
      ( "host" => "127.0.0.1",
        "port" => 82
      )
    )
  )
}

# URL Rewriting Proxy
#
$SERVER["socket"] == "127.0.0.1:82" {
  url.rewrite-once = ( "^/haste/(.*)$" => "/$1" )
  proxy.server  = ( "" => (
    "servername:82" => # name
      ( "host" => "127.0.0.1", # Set the IP address of servername
        "port" => 7777
      )
    )
  )
}
Please note, the socket listener on port 82 binds to localhost only. If we had it as ":82" as Eric did, we would have another port open to the world, so either firewall it off... or simply listen or localhost as done above.

Apache

First we need to enable mod_proxy and mod_proxy_http:
a2enmod proxy
a2enmod proxy_http
Then add to the vhost configuration file:
ProxyPass /haste/ http://127.0.0.1:7777/
ProxyPassReverse /haste/ http://127.0.0.1:7777/
That's all in principle, just remember to properly set up mod_proxy or you may end up becoming completely open proxy and your machine will be used in DDoS attacks or as proxy for up-voting some crap on the Internet (if you get lucky - it can quickly get worse).

If you use Apache for that, you should really read about Apache Proxy Abuse and possibly this StackOverflow thread.

That said, it's your machine, your problem. You've been warned ;-)

Finally, hat tip to @herkii for discussing ideas.