Vaultwarden part 4/4: Backup and Restore

Keep your passwords in case of a disaster

Backup and Restore Procedures for Vaultwarden

Backing up Vaultwarden is crucial to ensure the security and availability of your sensitive data. Vaultwarden, being a self-hosted password manager, stores all your passwords, personal information, and other sensitive credentials. In the event of data corruption, accidental deletion or hardware failure, having a reliable backup can mean the difference between a minor inconvenience and a catastrophic data loss.

To create backups I chose Duplicati. As explained in part 1, is a versatile backup solution that supports a wide range of destinations where data can be securely stored.

Automated backup solutions, like using Duplicati with Vaultwarden, streamline the backup process, making it more reliable and less prone to human error, thereby enhancing your overall data protection strategy and ensuring business continuity.

To have a predictable, consistent, repeatable process, the installation of Duplicati is also automated with an Ansible playbook.

The Duplicati playbook

Much like the Vaultwarden playbook it also refers to vault.yml. But everything is already configure.

It will perform these actions:

  • Ensure Podman is installed.
  • Ensure the Duplicati port is disabled in the firewall. We don’t want this port to be accessible. We will create an SSH tunnel to access the portal.
  • Enable and start firewalld.
  • Create Quadlet folder location.
  • Create and configure Quadlet for Duplicati, based on a template file templates/duplicati.j2
  • Enable user lingering if needed. This allows the container to run even if the user is not logged on.
  • Inform Systemd about the new services.
  • Start the Duplicati service.
# Ansible script to run Duplicati with Podman on RHEL and derivate systems

- hosts: localhost
  remote_user: root

  vars_files:
    - vars/vault.yml

  tasks:
  - name: Packages | Ensure podman is installed
    ansible.builtin.package:
      name: podman
      state: present
    become: true

  - name: "Firewall | Ensure Duplicati {{ DUPLICATI_PORT }}/tcp is disabled"
    ansible.posix.firewalld:
      port: "{{ DUPLICATI_PORT }}/tcp"
      permanent: true
      state: disabled
    become: true
    notify:
      - Restart firewalld

  - name: Systemd | Ensure firewalld is enabled and started
    ansible.builtin.systemd_service:
      name: firewalld
      enabled: true
      state: started
    become: true

  - name: Users | Create vaultwarden user
    ansible.builtin.user:
      name: "{{ VAULTWARDEN_USER }}"
      state: present
    become: true

  - name: Vaultwarden | Create Quadlet folder location
    ansible.builtin.file:
      path: ~/.config/containers/systemd
      state: directory
    become_user: "{{ VAULTWARDEN_USER }}"
    become: true

  - name: Duplicati | Create Quadlet
    ansible.builtin.template:
      src: templates/duplicati.j2
      dest: ~/.config/containers/systemd/duplicati.container
      mode: '0600'
    become_user: "{{ VAULTWARDEN_USER }}"
    become: true

  - name: Systemd | Check if user is lingering
    ansible.builtin.stat:
      path: "/var/lib/systemd/linger/{{ VAULTWARDEN_USER }}"
    register: user_lingering

  - name: Systemd | Enable lingering is needed
    ansible.builtin.command: "loginctl enable-linger {{ VAULTWARDEN_USER }}"
    when:
      - not user_lingering.stat.exists
    become: true

  - name: Systemd | Inform Systemd about the new services
    ansible.builtin.systemd_service:
      daemon_reload: true
      scope: user
    become_user: "{{ VAULTWARDEN_USER }}"
    become: true

  - name: Systemd | Start the Duplicati service
    ansible.builtin.systemd_service:
      name: "{{ DUPLICATI_CONTAINER_NAME }}"
      daemon_reload: true
      state: started
      scope: user
    become_user: "{{ VAULTWARDEN_USER }}"
    become: true

## Finished message
  - name: Finished
    debug:
      msg:
        - "Finished installing duplicati"
        - "Access to the Duplicati admin port is blocked ({{ DUPLICATI_PORT }}/tcp)"
        - "You can access the Duplicati admin portal the through an SSH tunnel:"
        - "ssh -N -L localhost:{{ DUPLICATI_PORT }}:<server>:{{ DUPLICATI_PORT }} <user>@<server> -i <key>"

  handlers:
    - name: Restart firewalld
      ansible.builtin.service:
        name: firewalld
        state: restarted
      become: true

To run the playbook

ansible-playbook ./duplicati.yml --ask-vault-pass

Enter the vault password. The Playbook will take a few minutes to finish.

alt duplicati1

To verify, we can look at the ports in use. We know that Vaultwarden listens on port 8200.

alt duplicati2

We can also use curl to access the site.

alt duplicati3

Access the Duplicati portal

Duplicati is now running in a container as an unprivileged user. But all ports are blocked. Nothing is allowed through the firewall. How can we get to the Duplicati web interface?

The answer is to create an SSH tunnel. I’ve written an article about that. If you want a read up here it is. We want to set up a local port forward.

The syntax is:

ssh -L localhost:localport:remoteserver:remoteport ssh_user@ssh_address

You can combine with the -N switch to not execute a remote command and -i to specify a private key.

Now browse to http://localhost:8200.

alt duplicati4

We can set a password if we want to. But this interface is only accessible through an SSH tunnel.

Create a backup

The duplicati.yml prepared a volume to easily backup the data from Vaultwarden. The Vaultwarden volume is mapped inside the Duplicati container as tobackup.

Let’s create a backup. Click Add Backup, select Configure a new backup and click Next.

alt duplicati5

On the next page, fill the name and choose a password to encrypt the backup. Click Next.

alt duplicati6

One of the main reasons why I chose Duplicati as a backup solution is the sheer amount of suported backup destinations.

alt duplicati7

I chose Google Drive. If you choose anything else, the following screens will be different for you.

alt duplicati8

When I click on AuthID, a new window pops up to authenticate against Google.

alt duplicati9

After following onscreen instructions and clicking the Test connection button.

alt duplicati10

On the next window, we need to select what to backup. As mentioned earlier, select the folder tobackup. This folder contains all the data from Vaultwarden. Click Next.

alt duplicati11

Configure the Schedule as you find appropriate. Click Next.

alt duplicati12

Finaly set the remote volume size and the backup retention. The default of 50 MB seems reasonable. For the backup retention I choose the Smart backup retention.

alt duplicati13

There you have it. A daily backup of the sensitive data contained in Vaultwarden.

Restore

The last step is to be able to restore everything. Because we set everything up with playbooks, there is no doubt. We don’t need to think anymore.

There are two playbooks. vaultwarden.yml and duplicati.yml. The idea is that in a disater recovery, you first run the duplicati.yml playbook. This will allow us to restore the data used by the Vaultwarden container. After that we can run the vaultwarden.yml playbook.

So the recovery procedure is:

  • Get a system, install the OS, Ansible and Git.
  • Clone the repo.
  • Run the Duplicati playbook first.
  • Create an SSH tunnel with local portforwarding.
  • Open the web interface with http://localhost:8200.
  • Click the Restore option.
  • Fill in the appropriate backup location and password.
  • Select the date to restore from and select the tobackup folder.

alt duplicati14 - Click restore. The files are now recovered in the location where the Vaultwarden container expects them to be. - Run the Vaultwarden playbook. - Configure the access from the Internet. - Have a beer.

This concludes the series. I’ve written this mainly for myself in the future. But I hope someone else finds it usefull too.

Avatar
Sven de Windt
Systems Administrator

Systems engineer with an intrest in automation

Related

comments powered by Disqus