Securing my Clojure photo gallery: Let’s Encrypt certs on NFSN

January 10th, 2017

I have a photo gallery site written in Clojure, hosted at NearlyFreeSpeech.NET. I just spent less than an hour learning about ACME and successfully hooked up my app with TLS! Here's what I did.

In my case, the Clojure application is packaged up as a .jar file, and is run as a daemon behind NFSN's reverse proxy, which is capable of terminating TLS. They also provide a script,, that can do the ACME challenge-response. However, it does this by dropping the response files in the Apache-served public directory, which isn't exposed in my configuration.

I chose to modify my application to proxy the acme-challenge dir. It has risks; if you screw up filesystem proxying, you risk path traversal attacks that allow an attacker to read anything on your filesystem! Anyway, it takes very little code, and it worked on the first try, so I would encourage people to use this method.

Below is the meat of the commit that implements this. It's not very much!

(ns org.timmc.pellucida.res.acme
  "Pass through ACME requests to filesystem. (ACME: Automated
Certificate Management Environment)"
   [compojure.core :refer [defroutes GET]]
   [org.timmc.pellucida.settings :as cnf]))

(defn is-token-shaped?
  "Does this string look like an ACME http-01 token? In particular,
does it *not* look like a path traversal attack?"
  [^String s]
  (boolean (re-matches #"[a-zA-Z0-9\-_=]+" s)))

(defn token-response
  "Answer with the contents of the ACME challenge file."
  ;; The ACME http-01 token value is used as the filename as well
  ;; as the value of a field within the JSON file.
  (when (is-token-shaped? magic-token)
    (let [resp-path (str (:acme-challenge-dir @cnf/config) "/" magic-token)]
      {:status 200
       :headers {"Content-Type" "text/plain"} ;; Spec: This or none
       :body ( resp-path)})))

(defroutes acme-routes
  (GET ["/.well-known/acme-challenge/:magic-token"] [magic-token :as r]
       (when (:acme-challenge-dir @cnf/config)
         (token-response magic-token))))

Incidentally, there were a few other options I thought of, but did not pursue or fully evaluate:

  • Hook: uses dehydrated to implement ACME, and you can hook into it for various events, including challenge generation. I could use that to notify the app to do something.
  • Proxy: I already configure the NFSN site to proxy the root path to my app's port 8080. Could I add another proxy that just mirrors /home/public/.well-known/acme-challenge? Not sure. From the forums, it sounds like if I change the site type to "Apache2.4 generic" instead of "Custom", I could possibly do this. Ah well, the code is already written!

A coding story: Dead reckoning vs. diffs

December 29th, 2015

Once upon a time, I wrote a photo gallery in PHP, accompanied by a set of PHP and SQL scripts to populate the site from a KimDaBa (now KPhotoAlbum) database. The code is *terrible*, and when it came time to adjust how I managed photo tags, I couldn't stomach editing the PHP. Slowly but surely, I've been rewriting my photo gallery software. The website is in Clojure, and mostly up to feature parity. The tricky part is the updater -- because of an odd quirk of KPhotoAlbum.

This is the story of trying to solve a problem, finding out a flaw, and then seeing the problem in another light. The intended audience is unclear. :-)

Read full entry »

A pack o’ flickers!

March 30th, 2008

Did you know that flickers travel in packs? Me neither! Found about 7 hanging out in the yard this morning.

Actually, this post is just an excuse to play around with my new personal photo gallery website:

yellow-shafted flicker

I've written a web app that can read my KPhotoAlbum database and build a public gallery out of it. The images are served by Amazon's S3. I'll be releasing the source code under the GPL just as soon as I can get s3sync beaten into submission.