Browse Source

Automatic deterministic backups (#188)

time-fix
Luke Childs 4 years ago
committed by GitHub
parent
commit
473380e930
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      db/umbrel-seed/.gitkeep
  2. 3
      docker-compose.yml
  3. 5
      events/triggers/backup
  4. 126
      scripts/backup/backup
  5. 41
      scripts/backup/decoy-trigger
  6. 69
      scripts/backup/monitor
  7. 8
      scripts/start

0
db/umbrel-seed/.gitkeep

3
docker-compose.yml

@ -15,6 +15,8 @@ services:
- ${PWD}/tor/torrc:/etc/tor/torrc - ${PWD}/tor/torrc:/etc/tor/torrc
- ${PWD}/tor/data:/var/lib/tor/ - ${PWD}/tor/data:/var/lib/tor/
- ${PWD}/tor/run:/var/run/tor/ - ${PWD}/tor/run:/var/run/tor/
ports:
- "127.0.0.1:9150:29050"
networks: networks:
net: net:
ipv4_address: 10.11.5.1 ipv4_address: 10.11.5.1
@ -97,6 +99,7 @@ services:
DOCKER_COMPOSE_DIRECTORY: $PWD DOCKER_COMPOSE_DIRECTORY: $PWD
DEVICE_HOST: ${DEVICE_HOST:-http://umbrel.local} DEVICE_HOST: ${DEVICE_HOST:-http://umbrel.local}
MIDDLEWARE_API_URL: "http://10.11.2.2" MIDDLEWARE_API_URL: "http://10.11.2.2"
UMBREL_SEED_FILE: "/db/umbrel-seed/seed"
UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE: "/var/lib/tor/web/hostname" UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE: "/var/lib/tor/web/hostname"
BITCOIN_P2P_HIDDEN_SERVICE_FILE: "/var/lib/tor/bitcoin-p2p/hostname" BITCOIN_P2P_HIDDEN_SERVICE_FILE: "/var/lib/tor/bitcoin-p2p/hostname"
BITCOIN_P2P_PORT: $BITCOIN_P2P_PORT BITCOIN_P2P_PORT: $BITCOIN_P2P_PORT

5
events/triggers/backup

@ -0,0 +1,5 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
"${UMBREL_ROOT}/scripts/backup/backup"

126
scripts/backup/backup

@ -0,0 +1,126 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
BACKUP_FOLDER="backup"
BACKUP_ROOT="${UMBREL_ROOT}/${BACKUP_FOLDER}"
BACKUP_FILE="${UMBREL_ROOT}/backup.tar.gz.pgp"
check_dependencies () {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
check_dependencies openssl tar gpg shuf curl
# Deterministically derives 128 bits of cryptographically secure entropy
derive_entropy () {
identifier="${1}"
umbrel_seed=$(cat "${UMBREL_ROOT}/db/umbrel-seed/seed") || true
if [[ -z "$umbrel_seed" ]] || [[ -z "$identifier" ]]; then
>&2 echo "Missing derivation parameter, this is unsafe, exiting."
rm -f "${UMBREL_ROOT}/statuses/backup-in-progress"
exit 1
fi
# We need `sed 's/^.* //'` to trim the "(stdin)= " prefix from some versions of openssl
printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${umbrel_seed}" | sed 's/^.* //'
}
# Make sure an update is not in progres
if [[ -f "${UMBREL_ROOT}/statuses/backup-in-progress" ]]; then
echo "A backup is already in progress. Exiting now."
exit 1
fi
echo "Creating lock..."
touch "${UMBREL_ROOT}/statuses/backup-in-progress"
[[ -f "${UMBREL_ROOT}/.env" ]] && source "${UMBREL_ROOT}/.env"
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
[[ -d "${BACKUP_ROOT}" ]] && rm -rf "${BACKUP_ROOT}"
[[ -f "${BACKUP_FILE}" ]] && rm -f "${BACKUP_FILE}"
echo "Deriving keys..."
backup_id=$(derive_entropy "umbrel_backup_id")
encryption_key=$(derive_entropy "umbrel_backup_encryption_key")
echo "Creating backup..."
mkdir -p "${BACKUP_ROOT}"
cp --archive "${UMBREL_ROOT}/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" "${BACKUP_ROOT}/channel.backup"
# We want to back up user settings too, however we currently store the encrypted
# mnemonic in this file which is not safe to backup remotely.
# Uncomment this in the future once we've ensured there's no critical data in
# this file.
# cp --archive "${UMBREL_ROOT}/db/user.json" "${BACKUP_ROOT}/user.json"
echo "Adding random padding..."
# Up to 10KB of random binary data
# This prevents the server from being able to tell if the backup has increased
# decreased or stayed the sme size. Combined with random interval decoy backups
# this makes a (already very difficult) timing analysis attack to correlate backup
# activity with channel state changes practically impossible.
padding="$(shuf -i 0-10240 -n 1)"
dd if=/dev/urandom bs="${padding}" count=1 > "${BACKUP_ROOT}/.padding"
echo "Creating encrypted tarball..."
tar \
--create \
--gzip \
--verbose \
--directory "${UMBREL_ROOT}" \
"${BACKUP_FOLDER}" \
| gpg \
--batch \
--symmetric \
--cipher-algo AES256 \
--passphrase "${encryption_key}" \
--output "${BACKUP_FILE}"
# To decrypt:
# cat "${BACKUP_FILE}" | gpg \
# --batch \
# --decrypt \
# --passphrase "${encryption_key}" \
# | tar \
# --extract \
# --verbose \
# --gzip
BACKUP_API_URL="https://pvf3ozmmfl.execute-api.us-east-1.amazonaws.com/prod/v1/upload"
if [[ $BITCOIN_NETWORK == "testnet" ]]; then
BACKUP_API_URL="https://as0ot0lg7h.execute-api.us-east-1.amazonaws.com/dev/v1/upload"
fi
if [[ $BITCOIN_NETWORK == "regtest" ]]; then
BACKUP_API_URL="https://5fxwqbum7g.execute-api.us-east-1.amazonaws.com/dev/v1/upload"
fi
echo "Uploading backup..."
curl --socks5 localhost:9150 -F "file=@/${BACKUP_FILE}" "${BACKUP_API_URL}/${backup_id}"
echo
rm -rf "${BACKUP_ROOT}"
rm -f "${BACKUP_FILE}"
echo "Removing lock..."
rm -f "${UMBREL_ROOT}/statuses/backup-in-progress"
echo "============================="
echo "===== Backup successful ====="
echo "============================="
exit 0

41
scripts/backup/decoy-trigger

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "decoy trigger is already running"
exit 1
fi
}
check_dependencies () {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
check_if_not_already_running
check_dependencies shuf
main () {
while true; do
minutes_in_seconds="60"
hours_in_seconds="$((${minutes_in_seconds} * 10))"
max_interval="$((8 * ${hours_in_seconds}))"
delay="$(shuf -i 0-${max_interval} -n 1)"
echo "Sleeping for ${delay} seconds..."
sleep $delay
echo "Triggering decoy backup..."
touch "${UMBREL_ROOT}/events/signals/backup"
done
}
main

69
scripts/backup/monitor

@ -0,0 +1,69 @@
#!/usr/bin/env bash
check_root () {
if [[ $UID != 0 ]]; then
echo "Error: This script must be run as root."
exit 1
fi
}
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "backup monitor is already running"
exit 1
fi
}
check_dependencies () {
for cmd in "$@"; do
if ! command -v $cmd >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
check_root
check_if_not_already_running
check_dependencies fswatch readlink dirname
UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/../.."
monitor_file () {
local file_path="${1}"
echo "Monitoring $file_path"
echo
if [[ ! -e "${file_path}" ]]; then
echo "$file_path doesn't exist, waiting for it to be created..."
echo
until [[ -e "${file_path}" ]]; do
echo "Nope, $file_path still doesn't exist..."
sleep 1
done
touch "${UMBREL_ROOT}/events/signals/backup"
fi
fswatch -0 --event Updated $file_path | xargs -0 -n 1 -I {} touch "${UMBREL_ROOT}/events/signals/backup"
}
if [[ ! -d "${UMBREL_ROOT}" ]]; then
echo "Root dir does not exist '$UMBREL_ROOT'"
exit 1
fi
[[ -f "${UMBREL_ROOT}/.env" ]] && source "${UMBREL_ROOT}/.env"
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
# Monitor LND channel.backup
monitor_file "${UMBREL_ROOT}/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" &
# Monitor db/user.json
# We want to back up user settings too, however we currently store the encrypted
# mnemonic in this file which is not safe to backup remotely.
# Uncomment this in the future once we've ensured there's no critical data in
# this file.
# monitor_file "${UMBREL_ROOT}/db/user.json" &

8
scripts/start

@ -63,6 +63,14 @@ echo "Starting karen..."
echo echo
./karen & ./karen &
echo "Starting backup monitor..."
echo
./scripts/backup/monitor &
echo "Starting decoy backup trigger..."
echo
./scripts/backup/decoy-trigger &
echo echo
echo "Starting Docker services..." echo "Starting Docker services..."
echo echo

Loading…
Cancel
Save