Hard pass#
Sat, 14 Aug 2021 21:31:26 +0000
I've got the key
I've got the secret
– From the Urban Cookie Collective's guide to password management
For reasons that seemed good at the time, I've written a password manager. It's a lot like pass ("the standard unix password manager") - which I have been using up 'til now - but it uses age instead of GPG to do the heavy lifting.
moss, the Maybe-Ok Secrets Store, is a 400-line Ruby script that uses only libraries provided by a default Ruby installation, plus 520 lines of testing code (Cucumber and RSpec).
Some random observations follow:
- almost all of it was test-driven, but the tests are for the most part end-to-end Cucumber tests that run the script in a subshell instead of calling classes/functions/methods directly. I was not expecting to get that far with end-to-end tests, but when all's said and done it's a pretty small system.
- There are certain deficiencies in the Cucumber step definition language that get more annoying as the test suite grows. Most notably, the use of ad hoc
@var
to pass state from one step to the next. I don't have a solution here, I'm just complaining about the problem. - I don't claim to be a security expert, which might make me not a good person to attempt this kind of project. I have carefully avoided rolling my own crypto by using a third party program for that, and I am quite pleased with my use of the various affordances of
Kernel.system
,IO.popen
,Kernel.spawn
andTempfile
to avoid even passing plaintext or passphrases in or out of the Ruby process. There's a lot of flexibility in that family of methods if you read the docs. More information about my security-related design choices in the relevant section of the README - I am simultaneously proud and ashamed of the command line parser I created by using various metaprogramming features in ways that may or may not be generally recommended. (Don't try this at work, kids - at least, not if you work for my employer, metaprogramming in company code is firmly in the "presumption of bad" bucket).
- I didn't know, but now I do, that there is support in the Ruby standard library ('Fiddle') for calling C libraries without having to write extensions and needing a C compiler. I didn't actually need to - I thought I was going to need mlock(2) before I realised it wouldn't help and found a way to use a temporary file instead - but it's nice to know it's there.
- It's a single script with no dependencies other than a Ruby installation, and this is intentional: it means you can easily install it anywhere just by copying the script. I might reconsider the "single script" constraint if/when it passes 500 lines. I am less likely to renege on "no external dependencies", because I've too often had trouble running programs that require Bundler from inside a project with its own Gemfile.
It's been a long time since I wrote more than about 5 lines of Ruby for anything outside of a work context: for 'fun' projects I tend to pick languages which I don't get a chance to use 9-5. Ruby for this task was definitely less than awful, though.