Personal accounting automation, and password caching on Linux

Personal accounting management is a tedious task: logging in to each bank’s web portal, checking for new PDF documents to download, then downloading for each account a “computer-friendly” record of transactions since the previous visit. And this is only to get the data…

All banks seem to agree on using the PDF format for “official communication”. For files intended to be imported into accounting software, however, anything is possible: csv, qif, ofx, or even nothing (that’s Oney…). Besides, the downloading process is not always satisfactory: the HTML forms to fill may be a bit buggy, the downloaded files sometimes have the wrong encoding, the included labels may not be as informative as those found on the web site…

Then I discovered boobank, from the Weboob software collection. This awesome tool can automatically login to each bank, and then convert the web site’s paged list of transactions into a standard format, such as ofx!

Secure handling of credentials

boobank is able to retrieve logins and passwords using external commands, as long these commands provide the needed information on their standard output. This is much better than storing the credentials in boobank’s own configuration files.

All my credentials are stored in a KeePass kdbx database. I manage this database on Linux with KeePassXC, which happens to provide a command-line interface!

KeePassXC’s CLI is too verbose, but a simple shell wrapper fixes that:

#!/bin/bash
# $1: Field to retrieve (eg. “UserName” or “Password”), or empty for everything
# $2: Path in the `kdbx` file (eg. “Me/Web/main bank”)

source "$(dirname "$0")/sessionKeyring.inc.sh"

pass="$(fromSessionKeyring kdbx_master_password 600)"

if [ -z "$pass" ]; then
  parent=$(tr '\0' ' ' </proc/$PPID/cmdline)
  if [ -n "$DISPLAY" -a -n "$(which zenity)" ]; then
    pass="$(zenity --entry --title="KeePass Password" --text="For $parent:")"
  else
    read -e -p "KeePass Password for $parent:" -r -s pass
  fi \
  && \
  toSessionKeyring kdbx_master_password "$pass" 600
fi

/usr/bin/keepassxc-cli show \
  -k /path/to/my/secret/key \
  -q /path/to/my/database.kdbx "$2" <<<"$pass" \
| sed -n "s/^${1:+$1: }//p"

Thanks to this script, each of my bank modules is configured with settings like this:

[backend]
_module = backend
login = `getFromKeepass.sh 'UserName' 'Me/Web/main bank'`
password = `getFromKeepass.sh 'Password' 'Me/Web/main bank'`

There is one small issue, though: querying the kdbx database requires a master password, and it would be cumbersome to type this password for each use of the above wrapper script. This is why this script makes use of two function defined in sessionKeyring.inc.sh, for caching the master password:

# $1: key name
# $2: timeout renewal in seconds (optional)
fromSessionKeyring() {…}

# $1: key name
# $2: secret
# $3: timeout in seconds (optional)
toSessionKeyring() {…}

Caching of the master password

As expressed by the signature of the above two functions, my wish is to temporarily store the master password, at most until the end of the user session, and at best for a given number of seconds (I chose 10 minutes).

My initial idea was to use the Secret Service API, which is available in both Gnome and Plasma, and usable through the secret-tool command:

# $1: key name
# $2: timeout renewal in seconds (optional)
fromSessionKeyring() {
  secret-tool search --all "$1" "$1" </dev/null 2>/dev/null \
  | sed -n 's/^secret = //p'
}

# $1: key name
# $2: secret
# $3: timeout in seconds (optional)
toSessionKeyring() {
  printf '%s' "$2" \
  | secret-tool store --label="KeePass master password" "$1" "$1"
}

While secret-tool’s usage is simple enough, it obviously does not handle any kind of timeout, even though the Gnome documentation for its implementation of the Secret Service API (Gnome Keyring) hints at this capacity

After a lot of searching on the Internet, I finally found what I needed: Linux’ in-kernel “key management”, which is a versatile, secure, and featureful! I wonder why it is not more used outside of kernel-related needs…
This keyring system is controlled using the well-documented keyctl tool, for which Matthew Garrett wrote a nice usage illustration.

The final code for my keyring-access functions is thus:

# $1: key name
# $2: timeout renewal in seconds (optional)
fromSessionKeyring() {
  local key
  key=$(keyctl search @us user "$1")
  [ $? -eq 0 ] || return 1
  # renew timeout and return the value
  if [ -n "$2" ]; then
    keyctl timeout $key $2
  fi
  keyctl pipe $key
}

# $1: key name
# $2: secret
# $3: timeout in seconds (optional)
toSessionKeyring() {
  local key
  key=$(printf '%s' "$2" | keyctl padd user "$1" @s)
  [ $? -eq 0 ] || return 1
  # transfer control from the program scope to the current user session, with the given timeout:
  keyctl setperm $key 0x3f3f0000
  keyctl link $key @us
  keyctl unlink $key @s
  if [ -n "$3" ]; then
    keyctl timeout $key $3
  fi
}

Conclusion

Now, I can regularly launch my main download script, which looks more or less like this (replace backend with cragr, oney, whatever…):

#!/bin/bash
#
# $1: download start date (1 month ago by default)
# $2: download path (current path by default)
#
# Format of dates: YYYY-MM-DD.

# … for each boobank backend …
declare -A accounts_backend=(
  [first_label]=first_account_number
  […]=…
  [last_label]=last_account_number
)

currentDate=$(date +%F)
downloadDate=${1:-$(date +%F -d '1 month ago')}

# … for each boobank backend …
for c in "${!accounts_backend[@]}"; do
  boobank -q -f ofx history "${accounts_backend[$c]}@backend" "$downloadDate" \
    >"${2:-.}/${downloadDate}_${currentDate}_$c.ofx"
done

I type KeePassXC’s main password only once, and there only remains to import the generated files into GnuCash!

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

La discussion continue ailleurs

URL de rétrolien : http://yalis.fr/cms/index.php/trackback/101

Fil des commentaires de ce billet