Deploy with Ansible
This page shows one way to deploy lasboltd with Ansible. The playbook is an example. Change the names and paths to fit your hosts. It does not set up the TLS proxy: use TLS and proxy for that, with your own proxy role.
The example follows the pattern of a real deployment:
- It builds the programs on your computer, not on the server. The server needs no compiler.
- It keeps the key (KEK) and the public backup key in Ansible Vault. The settings file has no secret.
- It uses the
filekey provider, so the service starts by itself after a reboot. Read Sealing and keys for the trade-off. - A run that changes nothing restarts nothing.
Layout
- inventory.ini
- lasbolt.yml
- lasboltd
- lasboltctl
- lasboltd.env.j2
- lasboltd.service.j2
- vault.yml
Steps
Build the programs for the server
On your computer, in the Lasbolt source folder:
GOOS=linux GOARCH=amd64 make build
cp bin/lasboltd bin/lasboltctl /path/to/deploy/files/Use arm64 for an ARM server. The build has no cgo, so it runs on any Linux of that CPU.
Make the secrets
head -c 32 /dev/urandom | base64 # the key (KEK)
lasboltd backup keygen --out backup.key # the backup key pairKeep backup.key (private) in a safe place away from the server and from Ansible. Put the other two values in the vault:
ansible-vault create group_vars/lasbolt/vault.ymlvault_lasbolt_kek: "<the base64 line>"
vault_lasbolt_backup_recipient: "<the contents of backup.key.pub>"Store a copy of the KEK outside the server too. Without it, you cannot open the database or the backups.
Add the inventory
[lasbolt]
vault.example.org ansible_user=deployThe user needs sudo rights.
Add the playbook and the templates
lasbolt.yml:
---
# Deploy lasboltd on a Linux host with systemd.
# Run: ansible-playbook -i inventory.ini lasbolt.yml --ask-vault-pass
- name: Deploy the Lasbolt server
hosts: lasbolt
become: true
vars:
lasbolt_port: 8080
lasbolt_public_url: "https://vault.example.org"
lasbolt_backup_interval: 24h
lasbolt_backup_keep: 14
# Built on your computer with: GOOS=linux GOARCH=amd64 make build
lasbolt_bin_dir: "{{ playbook_dir }}/files"
# Secrets come from an Ansible Vault file (group_vars/lasbolt/vault.yml):
# vault_lasbolt_kek: "<output of: head -c 32 /dev/urandom | base64>"
# vault_lasbolt_backup_recipient: "<contents of backup.key.pub>"
# Never put the private backup key in this file.
tasks:
- name: Make the lasbolt group
ansible.builtin.group:
name: lasbolt
system: true
- name: Make the lasbolt user
ansible.builtin.user:
name: lasbolt
group: lasbolt
system: true
shell: /usr/sbin/nologin
home: /var/lib/lasbolt
create_home: false
- name: Make the data folder
ansible.builtin.file:
path: /var/lib/lasbolt
state: directory
owner: lasbolt
group: lasbolt
mode: "0700"
- name: Make the settings folder
ansible.builtin.file:
path: /etc/lasbolt
state: directory
owner: root
group: lasbolt
mode: "0750"
- name: Install the programs
ansible.builtin.copy:
src: "{{ lasbolt_bin_dir }}/{{ item }}"
dest: "/usr/local/bin/{{ item }}"
owner: root
group: root
mode: "0755"
loop: [lasboltd, lasboltctl]
notify: Restart lasboltd
- name: Install the backup public key
ansible.builtin.copy:
content: "{{ vault_lasbolt_backup_recipient | trim }}\n"
dest: /etc/lasbolt/backup.key.pub
owner: root
group: lasbolt
mode: "0644"
notify: Restart lasboltd
# The key goes in on stdin, so it is not in the process list. The file
# is replaced only if it differs. lasboltd refuses a mode wider than 0600.
- name: Install the key file (KEK)
ansible.builtin.shell:
executable: /bin/bash
cmd: |
set -euo pipefail
umask 077
tmp=$(mktemp /etc/lasbolt/.kek.XXXXXX)
trap 'rm -f "$tmp"' EXIT
base64 -d > "$tmp"
[ "$(stat -c %s "$tmp")" -eq 32 ] || { echo "key is not 32 bytes" >&2; exit 1; }
if [ -f /etc/lasbolt/kek ] && cmp -s "$tmp" /etc/lasbolt/kek; then
echo kept
else
install -o lasbolt -g lasbolt -m 0400 "$tmp" /etc/lasbolt/kek
echo replaced
fi
stdin: "{{ vault_lasbolt_kek }}"
stdin_add_newline: true
register: lasbolt_kek
changed_when: lasbolt_kek.stdout == 'replaced'
no_log: true
notify: Restart lasboltd
- name: Install the settings file
ansible.builtin.template:
src: lasboltd.env.j2
dest: /etc/lasbolt/lasboltd.env
owner: root
group: lasbolt
mode: "0640"
notify: Restart lasboltd
- name: Install the systemd unit
ansible.builtin.template:
src: lasboltd.service.j2
dest: /etc/systemd/system/lasboltd.service
owner: root
group: root
mode: "0644"
notify: Restart lasboltd
- name: Enable and start lasboltd
ansible.builtin.systemd_service:
name: lasboltd
enabled: true
state: started
daemon_reload: true
- name: Run the handlers now
ansible.builtin.meta: flush_handlers
- name: Wait until lasboltd answers
ansible.builtin.uri:
url: "http://127.0.0.1:{{ lasbolt_port }}/"
status_code: [200, 303]
follow_redirects: none
register: lasbolt_health
until: lasbolt_health.status is defined and lasbolt_health.status in [200, 303]
retries: 15
delay: 1
handlers:
- name: Restart lasboltd
ansible.builtin.systemd_service:
name: lasboltd
state: restarted
daemon_reload: truetemplates/lasboltd.env.j2:
# {{ ansible_managed }}
# Settings of lasboltd. No secret is in this file.
LASBOLT_ADDR=127.0.0.1:{{ lasbolt_port }}
LASBOLT_DATA_DIR=/var/lib/lasbolt
LASBOLT_TRUSTED_PROXY=127.0.0.1/32
LASBOLT_KEY_PROVIDER=file
LASBOLT_KEY_FILE=/etc/lasbolt/kek
LASBOLT_BACKUP_DIR=/var/lib/lasbolt/backups
LASBOLT_BACKUP_RECIPIENT=/etc/lasbolt/backup.key.pub
LASBOLT_BACKUP_INTERVAL={{ lasbolt_backup_interval }}
LASBOLT_BACKUP_KEEP={{ lasbolt_backup_keep }}
LASBOLT_CTL_SOCKET=/run/lasbolt/ctl.sock
LASBOLT_PUBLIC_URL={{ lasbolt_public_url }}templates/lasboltd.service.j2:
# {{ ansible_managed }}
[Unit]
Description=Lasbolt server (lasboltd)
After=network-online.target
Wants=network-online.target
[Service]
User=lasbolt
Group=lasbolt
EnvironmentFile=/etc/lasbolt/lasboltd.env
ExecStart=/usr/local/bin/lasboltd
Restart=on-failure
RestartSec=2
UMask=0077
# Sandbox. See "Hardening".
NoNewPrivileges=true
CapabilityBoundingSet=
AmbientCapabilities=
ProtectSystem=strict
ReadWritePaths=/var/lib/lasbolt
RuntimeDirectory=lasbolt
RuntimeDirectoryMode=0700
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible
ProcSubset=pid
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
[Install]
WantedBy=multi-user.targetCheck and run
ansible-playbook -i inventory.ini lasbolt.yml --syntax-check
ansible-playbook -i inventory.ini lasbolt.yml --check --diff --ask-vault-pass
ansible-playbook -i inventory.ini lasbolt.yml --ask-vault-passThe last task waits until http://127.0.0.1:8080/ answers with 303 on the server.
Make the first owner
Open https://<your server>/register and register. See First owner and users.
Update the server
Build the new programs, copy them to files/ and run the playbook again. The copy task notices the new binaries and restarts the service. Make a backup first: see Upgrades.
Notes
--checkskips the shell task that installs the key. Do a real run to test it.- The task that installs the key uses
stat -c, which GNUstaton Linux supports. It does not run on macOS or BSD hosts. - On a host with SELinux, label the binary with
restorecon -v /usr/local/bin/lasboltd. If a proxy on the same host connects tolasboltd, set the booleanhttpd_can_network_connect. - Add a task for your proxy (Caddy or nginx). Use the examples in TLS and proxy.
- To keep the secrets in a cloud secret store and not in Ansible Vault, read them with a lookup plugin in the same task. Keep the
no_log: trueline.