Two days ago, Hex v1.0 was released. This is a major achievement, and all the reason to celebrate! We’re delighted that we were able to contribute to this success by implementing a new feature which is now readily available!
Hex is the de-facto standard when it comes to package
management in the Elixir ecosystem. Most developers don’t typically run it
directly but rather invoke it via Mix tasks. For instance, running mix
deps.get
for fetching dependencies is a very common thing to do – however,
it’s Hex doing all the grunt work in the background!
The recent Hex v1.0 release is a significant milestone for the project: it’s
the first time the major version got bumped after having been with the v0.y.z
version scheme for over seven
years!
Being an Elixir shop, we are of course very excited to see this degree of software maturity. However, we’re not just happy users of Open Source software: we also want to give something back. For Hex v1.0, we implemented a feature which improves the authentication and contributed it back upstream for everyone to enjoy. :-)
Our own Hex repository
At BetterDoc, we run our own local Hex repository, hosting Elixir packages with shared functionality used by different Elixir projects. We’ve been happy users of the awesome MiniRepo project for a long time. It truly makes running your own Hex-compatible repository a breeze!
However, time moves on, and we noticed that MiniRepo eventually got deprecated.
In fact, it was superseded by a new, simpler way to build a Hex repository:
mix hex.registry build
(available with Hex v0.21 and later).
Sticking with MiniRepo didn’t seem like a sustainable approach, so we started
discussing how mix hex.build
could be used to run our repository.
Building a repository using mix hex.build
The hex.registry
task is beautifully simple: given a directory full of
tarballs as created by mix
hex.build
, a single
invocation of
$ mix hex.registry build
can be used to generate the registry, i.e. the set of meta files which represents an index of the available packages. This leaves you with a plain directory of static files which can be hosted by any HTTP server to form a perfectly fine Hex repository!
At BetterDoc, we already use Amazon S3 for a few other things, so it was only natural to host the files there. However, there was one thing which got us scratching our heads:
How do we make sure that the repository is not readable by the whole wide world?
Authenticating clients
Elixir packages are plain tarballs which, among other things, contain Elixir source code. We’d rather not share that with our everyone (including our parents) right away; who knows what profanities might have found their way into the source code! 😬
Amazon S3 does have some authentication functionality built-in, but it involves
signing and authentication S3
requests.
Doing so is far from trivial: a specially crafted token needs to be created and
passed along with the HTTP request via the Authorization
HTTP header.
Unfortunately, Hex had no way good way to do this.
Thus, we needed to find a different way to authenticate clients somehow. We decided that plain simple HTTP basic access authentication would be sufficient for us. This is well-supported by Hex: simply encode the user name and password right in the URL which is registered with the mix hex.repo task – of course, you would use a strong password:
$ mix hex.repo add https://littlebobbytables:correcthorsebatterystaple@repo.yourcompany.com
Implementing this server-side was easy: instead of talking straight to a S3 bucket, we would have our repository URL point to a Amazon CloudFront distribution which uses a tiny CloudFront function to implement the authentication check. Authenticated requests are then forwarded to S3.
Storing login credentials
We had a working solution and started exchanging virtual high-fives. However, there was also some guilty conscience: we were storing sensitive login credentials in the Hex configuration file, which is typically world-readable. Also, we don’t like our credentials spread all over the place!
Unix-based systems (including macOS) used a dedicated configuration file for
this exact purpose for many decades: the .netrc
file.
This file provides a convenient place to collect login credentials to different
machines. Furthermore, the file is typically user-readable only (and many tools
processing this file enforce that).
Since we already used the .netrc
file for other purposes, we asked ourselves:
how much work would it be to make Hex read login credentials from the .netrc
file?
Rolling up the sleeves
At BetterDoc, we enjoy working on (and presenting!) our Friday projects - and contributing to Open Source software is a great project! Thus, we decided to give extending Hex a shot. A few days (and nights) later, we got ourselves a nice pull request and were eagerly waiting for feedback.
We didn’t have to wait long: Eric Meadows-Jönsson (of the Elixir core language team) and Wojtek Mach (a prominent Elixir developer working on Ecto among other things) were quick to give very friendly and constructive feedback.
Design decisions were discussed, test cases extended and error conditions addressed. It was only during this review phase that we realised the wide range of platforms supported by Hex: the v1.0 Release Workflow verifies that the test suite passes with all Elixir version since Elixir 1.0.5, covering OTP 17.x through OTP 24. Phew!
As we were rewriting our code to work with the very first Elixir versions, we really started to appreciate how far the language has come. 😊
Going Live
Eventually, our work got merged. Right in time for the big Hex v1.0 release! 🥳
We successfully scratched our own itch: basic auth credentials used for
authenticating with Hex repositories no longer need to be encoded in the URL.
Instead, we can nicely tuck them away in our trusted .netrc
file.
The project has been a very pleasurable experience; the BetterDoc concept of Friday projects gave us the freedom to work on this task and the Hex project maintainers have been extremely welcoming and friendly all along.