Setting up a https backend using Go and Let’s Encrypt

I recently needed to set up a dynamic web backend to both serve dynamically generated files as well as HTTP POST forms. Thus, I thought long and deeply of a solution that is both modern and versatile yet hassle-free to set up — since my systems administration skills are rather sparse.
The solution I found is to write a Go webserver; implicitly concurrent and enabling a high level of control.

Of course, nowadays you cannot run a (esp. dynamic) website without https; every browser worth its salt will display potential visitors a message framing you as a ruthless criminal. Fortunately, Let’s Encrypt gifts you the required bits: an SSL certificate!
Since I need a URL for the upcoming snippet and am personally thinking of switching my own homepage from a webserver to a self-hosted VPS setup, I chose to use http://www.jfrech.com as an example domain herein.

% # ... installing certbot (for example via `apt update && apt install certbot`) ...
% certbot certonly --standalone --preferred-challenges http -d www.jfrech.com
% # ... cli certbot interaction ...

% # ... installing Go ... (for example via `apt install golang`) ...

% # ... periodically renewing certificates ...

Once certbot has issued a certificate (the URL has to have a DNS entry linked to the current VPS and port 80 has to be unoccupied), incorporating it into the server is made straight forward by http.ListenAndServeTLS:

func handle(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, `<!doctype html><html><body><p>ip <code>` + template.HTMLEscapeString(r.RemoteAddr) + `</code> requesting <code>` + template.HTMLEscapeString(r.URL.Path) + `</code></p></body></html>`)
}
func main() {
    http.HandleFunc("/", handle)
    log.Fatal(http.ListenAndServeTLS(":443", "/etc/letsencrypt/live/www.jfrech.com/fullchain.pem", "/etc/letsencrypt/live/www.jfrech.com/privkey.pem", nil))
}

Since now the server only listens on port 443, one can add a http-to-https redirection on port 80:

func handleHTTP(w http.ResponseWriter, r *http.Request) {
    target := "https://" + r.Host + r.URL.Path
    if len(r.URL.RawQuery) > 0 {
        target += "?" + r.URL.RawQuery
    }
    http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}
func main() {
    http.HandleFunc("/", handle)
    go http.ListenAndServe(":80", http.HandlerFunc(handleHTTP))
    log.Fatal(http.ListenAndServeTLS(":443", "/etc/letsencrypt/live/www.jfrech.com/fullchain.pem", "/etc/letsencrypt/live/www.jfrech.com/privkey.pem", nil))
}

One advantage of writing one’s one web server is full control of how the website behaves. One implication, which can be seen as a downside, is that you have to do everything; even the most basic of logging. However, writing your own log files enables you to confidently follow the logging policy you employ.
From a security standpoint, you can exactly control which files are served and which result in a 404 response; avoiding accidently exposing the whole server’s directory structure for the world to see.

Something quite amusing about seeing every http request to a publicly available URL are the attempts of information or identity theft; only running a freshly registered URL (with fresh DNS entries to a fresh VPS), I got URL requests from all over the world (geolocations were deduced from the ips using dbip, yet are not shown):

  • /manager/html
  • /cgi-bin/ViewLog.asp
  • /solr/admin/info/system?wt=json
  • /?XDEBUG_SESSION_START=phpstorm
  • /?a=fetch&content=die(@md5(HelloThinkCMF))
  • /index.php?s=/Index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP
  • /api/jsonws/invoke
  • //httpbin.org:443
  • //g.alicdn.com:443
  • //sm.bdimg.com:443
  • /muieblackcat
  • //phpMyAdmin/scripts/setup.php
  • //phpmyadmin/scripts/setup.php
  • //pma/scripts/setup.php
  • //myadmin/scripts/setup.php
  • //MyAdmin/scripts/setup.php
  • /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
  • /olux.php
  • /.git/HEAD
  • /cgi-bin/mainfunction.cgi
  • /cgi-bin/ViewLog.asp
  • /UPnP/IGD.xml
  • /config/getuser?index=0

Especially the attempt to sniff out all git repositories on a server is scary, since it is known to work on a lot of smaller git servers.
Not being a system administrator, I cannot say much to most of the request listed above other than to be happy knowing that my Go server responds to each one with a 404 page.

The full server’s source code only contains the bare minimum of functionality. This was a conscious design decision; logging, 404 page serving (ref. w.WriteHeader(http.StatusNotFound)) and general behavior have to be built on top, maximizing its overall utility.
Full source code: setting-up-a-https-backend-using-go-and-lets-encrypt.go

Sources

    • Alan A. A. Donovan, Brain W. Kernighan: The Go Programming Language. New York: Addison-Wesley, 2016.
    • Using certbot in standalone-mode: DigitalOcean [2020-08-08]
    • An autocert example: blog.kowalcyk [2020-08-09]
    • Redirecting http requests: d-schmidt’s gist [2020-08-08]
    • Open git servers: c’t article [2020-08-08]

Measly Mazes

I wanted to create a maze generator for quite some while now and recently picked up the project again, using a naive approach consisting of applying a randomized depth-first search algorithm on a given rectangle. Thus, the resulting maze’s internal path structure is quite shallow, with most path forks having one degenerated short section.
Nevertheless, mazes are generated:

jt maze --ppm 32 32 | convert - -sample 1000% maze.png

You can generate your own mazes either by building maze.c natively or by using my newly developed package manager jt.

Java’s terseness

Whilst pondering the lost control one has over pastebin posts as a guest; the inability to remove a text one has published themselves and the entailed virtually temporally unbounded availability to anyone of this text, I decided to look at pastebin’s “archive” site — a chronologically sorted collection of the most recent public pastebin posts.

One interesting post was titled Filter – Stringrid – Delphi and appears to be a Delphi program with German comments accomplishing some two-dimensional array manipulation task.

However, when looking further down the archive, a post published around two minutes earlier caught my attention — exmp1[the paste unfortunately has been removed as of the 17th of May 2020 with its author not having the above mentioned limitations; you can, however, still view it on TIO.] This innocuously titled Java source file upon closer contains inspection an impressive 349 lines of code. Now, source files of such a line count are not unreasonable (especially when writing Java), however it is not the typical size of an example — as this paste’s title suggests.
Thus, I decided to read it to know what it is meant to accomplish and how it is written — the source’s line count is highly misleading regarding its functionality.

Continue reading