Ghost: The Go Secret Toolkit
I am struggling to come up with a witty introduction to this topic, so let me just start by telling the origin story. Several years ago, I added some tooling to help me manage secrets related to my dotfiles. The first incarnation was a simple .netrc
type solution where I stored the secrets in plain text in files that only my user could read. I added some scripts to help reading and managing these a bit. Over time this evolved into a complex system that allowed me to pull down secrets from my online password vault and store them in a local encrypted Keepass database. This created a new problem: every time a script used a secret, I needed to enter a password. Eventually, I added a small password service to help keep track of the key used to unlock the various password stores so I wouldn’t have to type the password every time.1
The original code was all written in Perl and a little shell script. Then, I partially ported it to Golang adding more features. Some of my dotfiles tools depended on the older code, some on the newer, I didn’t implement some things very well and it was generally becoming a mess. As such, some fairly annoying bugs had crept into the code.
A few months back I decided to spend a few hours each week gathering up all the code and replacing it with a comprehensive system. This version 2.0 of my system went through a few iterations and I soon realized I’d implemented a more or less complete application. I dropped all that into a new project named ghost
and here is me announcing it, in case someone other than myself may find it interesting.
The ghost
tool provides two primary features:
- Keeping Secrets. It provides a command-line tool and Golang packages for maintaining one or more secret databases, reading and writing individual secrets, in a number of backends, including Keepass and LastPass, using a single interface.
- Synchronizing Secrets. It provides a command-line tool and Golang packages for copying secrets from one database to another, allowing me to create complete backups of my online password vaults directly into a secure destination.
Keeping Secrets
I will gloss over the configuration and other bits here. If you want to understand the full details of how to use this tool, you can read the README yourself.
Once configured, writing your secrets is as simple as:
ghost set --name "github.com" --username "email@example.com" --prompt
That will prompt you to enter your password using a pinentry dialog.
And then you can read the secret back via:
ghost get --name "github.com"
and it will output something like:
github.com:
ID: 348737777372782
Location: Personal
Username: email@example.com
Password: <hidden>
URL: https://github.com/login
Modified: 2023-08-14 07:23:33 -0500 CDT
Type:
If you want to use this with a script. you might only be interested in the secret itself:
ghost get --name "github.com" --show-password --output password
You can also output bash-style shell variables, YAML, or JSON. It will not output a password without the --show-password
option.
Synchronize Secrets
The other really important use for me is the ability to synchronize my secrets securely. If I have a configuration that looks something like this in my ~/.ghost.yaml
file:
keepers:
lastpass:
type: lastpass
username: email@example.com
password:
__SECRET__:
keeper: masterPasswordsServer
secret: LastPass Password
field: password
lastpass-backup:
type: keepass
path: ~/.lastpass-backup.kdbx
master_password:
__SECRET__:
keeper: masterPasswordsServer
secret: Keepass Master Password
field: password
pinentry:
type: human
questions:
- id: LastPass Password
ask_for: [ password ]
You can copy all the passwords from the LastPass database to a local Keepass database using this command:
ghost sync lastpass lastpass-backup
Now, this might take minutes or hours to finish running due to rate limits inteh LastPass API, but it will eventually make a full copy of your LastPass database. (Though, you must either ensure your LastPass database contains no secrets with duplicate name, username, and location values or you pass the --ignore-duplciates
option, which will skip older duplicates.)
Bonus Feature: Password Generator
In addition to the tools mentioned above, it provides a tool for generating a random password. It has two basic modes: random list of characters and the correct-horse-battery-staple method.
ghost random-password
will output something like:
v46znNDzH1W6uiUe>C;y
You can control the length with -n
, and the frequency of various characters by increasing or decreasing their weights with various options.
To use the correct-horse-battery-staple mode you run the command like so:
ghost random-password --correct-horse-battery-staple
And it will pull random words from your /usr/share/dict/words
file until it exceeds the requested length (again set with -n
and defaulting to 20):
sick gibbet capons boldly
The default dictionary is not super helpful for password creation, so I need to see about coming up with a better dictionary or better way of filtering the dictionary, but it’s a start.
Other Features
There are a number of other tools provided as well. There’s a password service built in that lets you run a daemon that can temporarily store passwords and enforce secret lifetime policies. It can read and write passwords from the system keyring. It can request passwords from the person at the keyboard. It can store your passwords in an plain text file, if that’s useful to you for some reason (e.g., some API keys are not so important to keep super secure).
Final Notes
I am really happy with how this is working now, so I hope to be able to continue expanding, fixing, and tweaking it as needed. If you find it useful, but that it’s lacking something, patches welcome.
This software is brand new and I intend to use it from the command-line and from other programs I run directly and attended. I recommend against using it for production use of any kind. I won’t call it beta or anything, but it’s like pizza straight from the oven, I won’t be surprised if it burns a hole through my mouth if I’m not careful. That said, please let me know if you find it useful.
Cheers.
-
All of this history is what I remember happening and this history is spread over several years. I’m not going to go mine my
git log
to tease out what really happened and the full timeline. This is not that kind of retelling. ↩︎