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.
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.
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.
Update the containers
Eventually there will come a time when you need to update the containers. The containers are set to update automatically, from the registry. We still need to invoke the update command manually. This is easy to do.
Change to the user running the containers and run the following command.
podman auto-update
In part 3 we will discuss how to publish Vaultwarden.