Skip to content
Deploy with Ansible

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 file key 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.
The key file puts the secret on the disk of the server. Use a vault that only the people who deploy can open. Keep the private backup key out of Ansible.

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 pair

Keep 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.yml
vault_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=deploy

The 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: true

templates/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.target

Check 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-pass

The 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

  • --check skips the shell task that installs the key. Do a real run to test it.
  • The task that installs the key uses stat -c, which GNU stat on 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 to lasboltd, set the boolean httpd_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: true line.
Last updated on