Restic Backup

Backup-Infrastruktur mit Restic und SaltStack aufbauen

Restic ist ein einfaches aber sehr mächtiges Backup-Programm. Gepaart mit Salt können Sie Backups vollautomatisch durchführen und über den Salt-Master zentral steuern. Ein einfacher SSH-Server dient als zentraler Speicher für die Backups.

Backup-Server vorbereiten

Restic kann auf einer Vielzahl von Zielen die Backups ablegen. SFTP bzw. SSH gehört dazu, was mit jedem Linux-System einfach zu realisieren ist.

Grundsätzlich kann jeder Benutzer-Account verwendet werden, ein eigener Benutzer resticist empfohlen, um das System „ordentlich“ zu halten. Restic authentifiziert sich ausschließlich per SSH-Key. Legen Sie einen neuen Benutzer an und generieren Sie SSH Schlüssel.

mkdir /srv/salt/restic
cd /srv/salt/restic
ssh-keygen -N "" -t ed25519 -C "restic@salt-master.local" -f ./id_ed25519

Backup-Software ausrollen

/srv/salt/restic/base.sls
#
# Create a restic user
#
restic-group:
  group.present:
    - name: restic

restic:
  user.present:
    - fullname: Restic Backup
    - shell: /bin/bash
    - home: /var/lib/restic
    - createhome: true
    - system: true
    - groups:
      - restic
    - require:
      - group: restic-group

bzip2:
  pkg.installed: []
#
# Download restic from github (the debian packages are quite outdated)
#
{% set version = '0.16.2' %}
download-restic:
  file.managed:
    - name: /var/tmp/restic_{{ version }}_linux_amd64.bz2
    - source: https://github.com/restic/restic/releases/download/v{{ version }}/restic_{{ version }}_linux_amd64.bz2
      - user: restic
    - group: restic
    - mode: 0755
    - require:
      - bzip2

#
# Install restic by unpacking the compressed binary
#
install-restic:
  cmd.run:
    - name: |
        set -e
        which bunzip2
        bunzip2 -k /var/tmp/restic_{{ version }}_linux_amd64.bz2 -c > restic
        chmod 0700 restic
        ./restic version
        rm -f /var/tmp/restic_{{ version }}_linux_amd64.bz2
    - cwd: /var/lib/restic
    - runas: restic
    - unless:
      - /var/lib/restic/restic version|grep -q {{ version }}
    - require:
      - file: download-restic
      - pkg: bzip2

/var/lib/restic/restic:
  file.managed:
    - mode: 0700
    - user: restic
    - require:
      - install-restic

/var/lib/restic/.ssh/:
  file.directory:
    - user: restic
    - group: restic
    - mode: 0700

😯 Probleme:

  1. Wenn Sie Downloads aus dem Internet ungeprüft in Ihr System übernehmen, kann Schadcode ins System gelangen.

  2. Der State download-restic speichert dauerhaft das komprimierte Archiv oder lädt es erneut herunter, wenn es auf dem temporären Verzeichnis gelöscht wurde.

🧩 Aufgaben: Optimieren Sie den State. Nutzen Sie eine der folgenden Optionen:

  • Erweitern Sie den State mit einer Prüfsummen-Prüfung. Der Download darf nur dann verwendet werden, wenn die Prüfsumme stimmt. Suchen Sie dazu in der Dokumentation nach dem Stichwort source_hash.

  • Fügen Sie eine unless Bedingung ein, damit der Download nicht mehr ausgeführt wird, sobald restic erfolgreich installiert wurde.

  • Laden Sie das komprimierte Archiv zuerst auf den Salt-Master herunter, entpacken Sie es und verteilen Sie es von dort.

Backup-Server ausrollen

Restic kann die Sicherungskopien auf einer Vielzahl von Fileservern abspeichern. In einem Intranet ist der in SSH integrierte SFTP-Server die einfachste Lösung. Das bedeutet, Sie benötigen keine spezielle Server-Software. Der Server muss lediglich den passenden SSH-Private-Key nutzen, der zum Public-Key der Clients passt.

/srv/salt/restic/server.sls
include:
  - .base

openssh-server:
  pkg.installed: []

sshd:
  service.running:
    - enable: true
    
authorized_keys:
  file.managed:
    - name: /var/lib/restic/.ssh/authorized_keys
    - source: salt://restic/id_ed25519.pub
    - user: restic
    - group: restic
    - mode: 0600

Backup-Clients ausrollen

/srv/salt/restic/client.sls
include:
  - .base

#
# Set package names and dependencies according to the disctribution
# 
{% set restic_deps = salt['grains.filter_by']({
    'Debian': {'libcap':'libcap2-bin','cron':'cron','crond':'cron'},
    'RedHat': {'libcap':'libcap','cron':'cronie','crond':'crond'},
    'Suse': {'libcap':'libcap-progs','cron':'cron','crond':'cron'},
}) %}

#
# Deploy the private key need for the backup transfer over SFTP
#
priv-key:
  file.managed:
    - name: /var/lib/restic/.ssh/id_ed25519
    - source: salt://restic/id_ed25519
    - user: restic
    - group: restic
    - mode: 0600

# Install the setcap utility
libcap2-bin:
  pkg.installed:
    - name: {{ restic_deps.libcap }}

# Allow restic do read alls files without root-rights
set-cap:
  cmd.run: 
    - name: /usr/sbin/setcap cap_dac_read_search=+ep /var/lib/restic/restic
    - unless: /usr/sbin/getcap /var/lib/restic/restic|grep -q cap_dac_read_search+ep
    - require:
      - pkg: libcap2-bin

#
# Create a SSH Client config
#
ssh-client-conf:
  file.managed:
    - name: /var/lib/restic/.ssh/config
    - contents: |
        HashKnownHosts no
        Host restic-server
            StrictHostKeyChecking No
            Hostname {{ pillar['restic_server'] }}
            User restic
            Port 22

#
# Create a password used for the backup encryption
#
password-create:
  cmd.run:
    - name: openssl rand -hex 20 > .restic-password && chmod 0600 .restic-password
    - cwd: /var/lib/restic
    - runas: restic
    - creates:
      - /var/lib/restic/.restic-password

#
# Backup the genrated password to the salt master
# to restore if the machine gets completely lost
#
password-backup:
  module.run:
    - name: cp.push
    - path: /var/lib/restic/.restic-password

#
# Initialize the remote repository where the backups will be stored
#
repo-init:
  cmd.run:
    - name: ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} init
    - runas: restic
    - cwd: /var/lib/restic
    - unless: ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} stats
    - require:
      - cmd: password-create
      - module: password-backup

#
# Optionally store the password on the backup server 
# for direct access to unencrypted backups
#
sftp-backup-to-restic-server:
  cmd.run:
    - name: |
        sftp restic-server <<EOF
        cd {{ grains['id'] }}
        pwd
        PUT .restic-password
        ls -a
        EOF
    - runas: restic
    - cwd: /var/lib/restic
    - require:
      - cmd: repo-init
    
/var/lib/restic/data:
  file.directory:
    - user: restic
    - group: restic
    - mode: 0700

#
# Create a script that will execute the backup
#
/var/lib/restic/run.sh:
  file.managed:
    - user: restic
    - group: restic
    - mode: 0700
    - contents: |
        #!/bin/bash
        set -e
        cd /var/lib/restic
        # Do the backup
        ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} backup --files-from ./files_to_backup
        # Remove old backups
        ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} forget --keep-last 7 --prune

#
# Create the list of files or folders to backup
#
/var/lib/restic/files_to_backup:
  file.managed:
    - user: restic
    - groupd: restic
    - mode: 0600
    - contents: |
        /root
        /etc
        /var/lib/restic/data

#
# Create the cronjob
#
install-cron:
  pkg.installed:
    - name: {{ restic_deps.cron }}

run-cron:
  service.running:
    - name: {{ restic_deps.crond }}
    - enable: true

restic-cron:
  cron.present:
    - name: |
       /var/lib/restic/run.sh > /var/lib/restic/last.log 2>&1
    - user: restic
    - minute: '*/5'
    - identifier: RESTIC
    - require:
      - file: /var/lib/restic/files_to_backup
      - file: /var/lib/restic/run.sh
      - pkg: install-cron

😯 Probleme:

  1. Die Liste der Ordner, welche gesichert werden, ist „hart kodiert“. Was machen Sie, wenn Systeme unterschiedliche Ordner sichern sollen?

🧩 Aufgabe:

  • Speichern Sie die Backup-Ordner in einem Pillar und erweitern Sie den State, sodass /var/lib/restic/files_to_backup aus einem Pillar gelesen wird.

Backups prüfen

Prüfen Sie, wann das letzte Backup erstellt wurde, z. B. mit einem State:

/srv/salt/restic/report.sls
restic-report:
  cmd.run:
    - name: |
        ./restic -p .restic-password -r sftp:restic-server:./{{grains.id}} snapshots
    - cwd: /var/lib/restic
    - runas: restic

Zurücksichern

Oder prüfen Sie die Backups direkt auf dem Backup-Server, indem Sie die verschlüsselte Backupdatei auf dem Backup-Server mounten.

# Change to the restic user account
root@server:~# su - restic

# Create a folder to mount a backup
restic@server:~$ mkdir -p mnt/<MINION_ID>  
# Mount a backup                   
restic@server:~$ ./restic -r <MINION_ID> mount mnt/<MINION_ID>/ -p <MINION_ID>/.restic-password

Das zum Mounten benötigte Password entnehmen Sie der Datei /var/cache/salt/master/minions/<CLIENT>/files/var/lib/restic/.restic-password auf dem Salt-Master.

Weitere Information zum Zurücksichern in der 📖 Restic Dokumentation.

😯 Problem:

Falls Sie das Backup nicht mounten können, ist das fuse Paket nicht installiert. Erweitern Sie den restic.server State und installieren Sie darüber das benötigte Paket.

Backups überwachen

Optional können Sie mit dem kostenlosen Dienst Betteruptime.com ein Monitoring der Backups einrichten.

/srv/salt/restic/monitoring.sls
#
# Create a monitoring at betteruptime.com
#
{% set token='xxx' %}
register-monitoring:
  cmd.run:
    - runas: restic
    - cwd: /var/lib/restic
    - name: |
        curl -sf --request POST \
        -o monitoring-conf.json \
        --url https://betteruptime.com/api/v2/heartbeats \
        --header 'Authorization: Bearer {{ token }}' \
        --header 'Content-Type: application/json' \
        --data '{
            "name": "{{ grains['id'] }} Restic Backup",
            "period": 86400,
            "grace": 3600
        }'
    - creates: /var/lib/restic/monitoring-conf.json

Mit diesem State wird ein Monitoring Item auf dem Dashboard von Betteruptime registriert. Dieses Item erwartet alle 24 Stunden eine positive Rückmeldung, andernfalls wird ein Alarm ausgelöst. Erweitern Sie das Skript, welches das Backup durchführt, mit folgender Zeile:

curl -fs $(jq -r .data.attributes.url < monitoring-conf.json)

Last updated