Vaultwarden part 2/4: Installation

Installation

Automated Installation of Vaultwarden

Overview of what will be covered

In this article, we will dive into the automated installation process of Vaultwarden using Ansible on Red Hat-like systems such as AlmaLinux, CentOS, or Rocky Linux. This includes setting up Vaultwarden securely within Podman containers, configuring essential parameters like user management and environment variables, and ensuring seamless integration with systemd using Quadlets. We will explore how automation streamlines the deployment process, enhances consistency, and reduces the likelihood of errors. By automating the installation of Vaultwarden, administrators can efficiently deploy and maintain a secure password management solution, ensuring reliable access to encrypted credentials across various platforms and devices.

Benefits of automation

Automation offers several key benefits in the deployment of Vaultwarden

  • Consistency: Ensures that Vaultwarden installations are standardized across multiple environments, reducing configuration drift and operational discrepancies.
  • Efficiency: Streamlines deployment tasks, minimizing manual intervention and allowing administrators to focus on strategic initiatives rather than routine maintenance.
  • Reliability: Automates error-prone manual processes, thereby enhancing reliability and ensuring that Vaultwarden operates consistently and predictably.
  • Security: Implements best practices consistently, such as secure configurations and access controls, to bolster the security posture of Vaultwarden deployments.

Let’s get started

Everything discussed in these articles is published on Github on https://github.com/svendewindt/autovaultwarden. Ansible and Git were installed in the previous article. The first thing to do is to clone the git repo to your machine.

git clone https://github.com/svendewindt/autovaultwarden.git

This will copy the project to the folder autovaultwarden on you machine.

├── LICENSE
├── README.md
├── duplicati.yml
├── templates
│   ├── duplicati.j2
│   └── vaultwarden.j2
├── vars
│   └── demo.yml
└── vaultwarden.yml

The file vaultwarden.yml is the actual playbook to install Vaultwarden. It refers to an Ansible vault vault.yml. This allows you to keep your secrets safe. The file demo.yml is an unencrypted example.

Copy demo.yml to vault.yml.

cp ./vars/demo.yml ./vars/vault.yml

Edit vault.yml

# Duplicati
DUPLICATI_CONTAINER_NAME: duplicati
DUPLICATI_IMAGE: docker.io/duplicati/duplicati
DUPLICATI_PORT: 8200
DUPLICATI_VOLUME: duplicati-data
DUPLICATI_AUTOUPDATE: registry

# Vaultwarden
VAULTWARDEN_USER: vw-user
VAULTWARDEN_CONTAINER_NAME: vaultwarden
VAULTWARDEN_IMAGE: "docker.io/vaultwarden/server"
VAULTWARDEN_AUTOUPDATE: registry # possible values registry, local
VAULTWARDEN_PORT: 8080
VAULTWARDEN_VOLUME: vw-data
VAULTWARDEN_SIGNUPS_ALLOWED: false
VAULTWARDEN_TIMEZONE: "Europe/Amsterdam"
VAULTWARDEN_IP_HEADER: X-Forwarded-For # possible values: X-Forwarded-For, X-Real-IP
ADMIN_TOKEN: "$argon2id$v=19$m=65540,t=3,p=4$TzhXcWox..."
DOMAIN: "https://<domainname>"

# SMTP
SMTP_HOST: smtp.gmail.com
SMTP_USERNAME: <username>
SMTP_PORT: 587
SMTP_PASSWORD: <smtp password>

There are three sections in the file. The Duplicati section refers to the parameters for Duplicati. As explained in part 1, this will be used to backup and restore the vault in case of disaster. The Vaultwarden section refers to Vaultwarden and the SMTP section refers to some SMTP parameters used by Vaultwarden. Vaultwarden needs to send invites to new users.

It is important to change these lines in vault.yml, the others are less important.

ADMIN_TOKEN: "$argon2id$v=19$m=65540,t=3,p=4$TzhXcWox..."

The ADMIN_TOKEN is an argon2 secret. This token is required to enter the Admin section of Vaultwarden.

sudo dnf install argon2 -y
echo -n "MySecretPassword" | argon2 "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4

Paste the resulting string after the ADMIN_TOKEN.

DOMAIN: "https://<domainname>"

DOMAIN refers to the FQDN where your Vaultwarden instance is reachable. In my case this could be https://pw.memosforadmins.com.

# SMTP
SMTP_HOST: smtp.gmail.com
SMTP_USERNAME: <username>
SMTP_PORT: 587
SMTP_PASSWORD: <smtp password>

I use Gmail as an SMTP server, but you could use any mailserver available. To use Gmail, you need to create an app password. More info here.

alt exampleVault

When your file is ready, it’s time to encrypt it.

ansible-vault encrypt ./vars/vault.yml

Enter a password twice. The secrets in the vault are now secured with AES256 encryption. If you are interested in encryption, you can read up on AES256 strength here.

The Vaultwarden playbook

Let’s go over what the playbook actually does. As mentioned before it refers to the secrets in vault.yml.

It will perform these actions:

  • Ensure Podman is installed.
  • Ensure the Vaultwarden port is disabled in the firewall. We wil publish the port with Cloudflare.
  • Enable and start firewalld, to limit access to the machine.
  • Create Vaultwarden user, to run the container. Podman allows containers to be run as users in stead of root. This provides an extra layer of security.
  • Create Quadlet folder location. Quadlets allow Podman to integrate with systemd in an elegant way.
  • Create and configure the Quadlet file, based on a template file templates/vaultwarden.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 Vaultwarden service.
# Ansible script to run Vaultwarden with Podman on RHEL and derivate systems
# Don't open any ports, we will use a Cloudflare tunnel to publish Vaultwarden.

- 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 Vaultwarden {{ VAULTWARDEN_PORT }}/tcp is disabled"
    ansible.posix.firewalld:
      port: "{{ VAULTWARDEN_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: Vaultwarden | Create Quadlet
    ansible.builtin.template:
      src: templates/vaultwarden.j2
      dest: ~/.config/containers/systemd/vaultwarden.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 Vaultwarden service
    ansible.builtin.systemd_service:
      name: "{{ VAULTWARDEN_CONTAINER_NAME }}"
      daemon_reload: true
      state: started
      scope: user
    become_user: "{{ VAULTWARDEN_USER }}"
    become: true

## Finished message
  - name: Finished
    debug:
      msg:
        - "Finished installing Vaultwarden"
        - "Publish vaultwarden on http://localhost:{{ VAULTWARDEN_PORT }}"
  
  handlers:
    - name: Restart firewalld
      ansible.builtin.service:
        name: firewalld
        state: restarted
      become: true

To run the playbook

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

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

alt

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

[root@localhost autovaultwarden]# sudo ss -tulpn | grep 8080
tcp   LISTEN 0      4096               *:8080            *:*    users:(("rootlessport",pid=54991,fd=12))

We can also use curl to access the site

[root@localhost autovaultwarden]# curl http://localhost:8080
<!doctype html><html class="theme_light"><head><meta charset="utf-8"/><meta name="viewport" content="width=1010"/><meta name="theme-color" content="#175DDC"/><title page-title>Vaultwarden Web</title><link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png"/><link rel="mask-icon" href="images/safari-pinned-tab.svg" color="#175DDC"/><link rel="manifest" href="cca56971e438d22818d6.json"/><script defer="defer" src="theme_head.4cb181fc19f2a308ba73.js"></script><link href="app/main.a8e776a6a06b65b2bab0.css" rel="stylesheet"></head><body class="layout_frontend"><app-root><div class="mt-5 d-flex justify-content-center"><div><img class="mb-4 logo logo-themed" alt="Vaultwarden"/><p class="text-center"><i class="bwi bwi-spinner bwi-spin bwi-2x text-muted" title="Loading" aria-hidden="true"></i></p></div></div></app-root><script defer="defer" src="app/polyfills.2fbe6d3186140d959b38.js"></script><script defer="defer" src="app/vendor.5fbde6cbb10a6f680a29.js"></script><script defer="defer" src="app/main.930ad6391bde0ab2fc73.js"></script></body></html>

Vaultwarden is now installed and configured, but not accessible from outside the vm. We will discuss in part 3 how to publish Vaultwarden.

Avatar
Sven de Windt
Systems Administrator

Systems engineer with an intrest in automation

Related

comments powered by Disqus