Dedicated OpenBSD-WireGuard Server; Part Two

· 9min · Dan F.

Welcome to part two of using WireGuard on OpenBSD! The first post was about the initial release of the project; This followup is about one new role added to the playbook. Now in the initial release, I wasn't attempting to compile wg or wireguard within the playbook itself. I had just planned to update the binaries every day/week with a cronjob run on one of my servers. However, thank you reddit user techsnapp for pointing out that there is actually a script that wireguard provides to assist in compiling the software on OpenBSD. This post will go over the new role written to reliably download and compile wg, wg-quick, and wireguard-go.

As many may know, there is no precompiled WireGuard binary package available in OpenBSD, currently. Luckily with the script that WireGuard has provided, compiling the software on OpenBSD is easy. However, while the script obviously does the job, I would prefer create an ansible roles for such matters. Ansible provides a very modular method for configuring servers, if roles are written properly. I strive to write ansible roles that are as independent as possible, so that roles can be swapped easily between new and old playbooks. This makes creating new playbooks extremely simple, once a handful of core roles have been created.

From WireGuard's script, comes our WireGuard binary playbook.

[Edit 4/27/19]: The original link to the wireguard playbook has been removed. The wireguard playbook has been integrated into my OpenBSD Dev repo, found here, and can be installed using the following command, which will pull down the repo, and install the wireguard binaries. This is meant to run on a new install of OpenBSD, as it could clobber previously installed configs:

ftp -o - https://raw.githubusercontent.com/findelabs/openbsd-ansible-deploy/master/bootstraps/bootstrap_wireguard.sh | sh

wireguard-binaries/vars/main.yml

+++
required_go_version: 1.11
build_utils:
  - bash
  - gmake
  - git
  - xz
  - gtar-1.30p0

Since the rest of the tasks may rely on these variables, we should go over these first. The required go version was pulled directly from the wireguard script. If the script ever gets updated, that var will have to be updated. The build_utils are the packages required for compiling Go on OpenBSD.

wireguard-binaries/tasks/main.yml

+++
# Required packages are installed with a loop, so that any packages newly installed can be identified at the end of the
# role for removal. These packages are used for both Go (if compiled), and for compiling the wireguard binaries. 
- name: Install packages required to build binaries
  openbsd_pkg:
    name: "{{ item }}"
    state: present
  register: build_list
  loop: "{{ build_utils }}"

- import_tasks: install-go.yml

# The distro list downloaded is simply a list of all versions of wireguard currently available for all systems.
- name: Get wireguard distro list
  shell: ftp -v -o - https://build.wireguard.com/distros.txt
  register: distros
  ignore_errors: true
  changed_when: false

# Any possible files that may remain should be removed
- name: Clean up any existing files
  file:
    path: /usr/src/wireguard
    state: absent

# Since the folder was removed, we need to recreate it
- name: Create /usr/src/wireguard
  file:
    path: /usr/src/wireguard
    state: directory
    owner: root
    group: wheel
    mode: 0755

# Import both tasks, one for kmodtools, and one for wireguard-go
- import_tasks: build-wireguard-kmodtools.yml
- import_tasks: build-wireguard-go.yml

# Remove all files created during build
- name: Clean up any remaining files
  file:
    path: /usr/src/wireguard
    state: absent

# Remove any packages installed at the beginning of this role. This was a frustrating task to get working.
- name: Remove packages installed during build
  openbsd_pkg:
    name: "{{ item.name }}"
    state: absent
  ignore_errors: true
  when: item.changed == true
  loop: "{{ build_list.results }}"

wireguard-binaries/tasks/install-go.yml

+++
# Use pkg_info to find the latest binary version of Go available
- name: Check current binary packaged go version
  shell: pkg_info -Q go | grep "^go-[0-9]" | cut -d- -f 2 | awk '{print $1}'
  register: packaged_go_version
  ignore_errors: true
  changed_when: false

# Find the current snapshot version
- name: Find most current go version available from source
  shell: ftp -v -o - https://golang.org/src/ | sed -E -n 's/.*goVersion = "go([0-9.]+)";.*/\1/p'
  register: current_go_version
  ignore_errors: true
  changed_when: false

# Check to see if Go is installed, and get the version
- name: Check installed go version
  shell: "command -v go >/dev/null && (go version | sed -E -n 's/^go version go([^ ]+) .*/\\1/p') || echo 1.00"
  register: installed_go_version
  ignore_errors: true
  changed_when: false

# Install Go with pkg_add, if the binary version matches or is greater than required Go version
- name: Install go binary package
  openbsd_pkg: 
    name: "{{ item }}"
    state: present
  register: install_go
  with_items:
    - go
  when: 
    - installed_go_version.stdout is version(required_go_version, '<')
    - packaged_go_version.stdout is version(required_go_version, '>=')

# If the installed version is less that the required version, and if the packaged version is not new enough, 
# compile Go from source
- import_tasks: compile-go.yml
  when: 
    - installed_go_version.stdout is version(required_go_version, '<')
    - packaged_go_version.stdout is version(required_go_version, '<')

wireguard-binaries/tasks/compile-go.yml

+++
# A lesser version of Go is required to compile this version of Go from source.
# This will get removed at the end
- name: Temporarily install go binary package
  openbsd_pkg:
    name: "{{ item }}"
    state: present
  register: install_go
  with_items:
    - go

- name: Download go source
  get_url:
    url: "https://dl.google.com/go/go{{ current_go_version.stdout }}.src.tar.gz"
    dest: /tmp
  changed_when: false

- name: Extract go source
  unarchive:
    src: "/tmp/go{{ current_go_version.stdout }}.src.tar.gz"
    dest: /usr/src/
    remote_src: yes

# Make the binary
- name: Compile go
  shell: cd /usr/src/go/src && ./make.bash
  changed_when: false

# And finally, let's remove the binary Go package
- name: Remove go binary package
  openbsd_pkg: 
    name: "{{ item }}"
    state: absent
  with_items:
    - go

# Copy the Go binary over /usr/local/bin
- name: Install go to /usr/local/bin/
  copy:
    src: /usr/src/go/bin/go
    dest: /usr/local/bin/
    remote_src: yes
    mode: 0755

You may notice that I do not remove the src folder that Go was compiled in. This was a slight compromise, as I typically do not let remnant code remain behind. However, whenever the wireguard binaries will be updated with this playbook, Go will be required. And as the packaged version of Go is not up to snuff, I would rather let the compiled binary remain instead of compiling Go every time the playbook is ran. Luckily, at this time the packaged binary is current enough.

wireguard-binaries/tasks/build-wireguard-kmodtools.yml

+++
# Most of these tasks were cut directly out of the script. Here we get the current version available
- name : Get current kmodtools version
  shell: "echo \"{{ distros.stdout }}\" | grep upstream.*kmodtools | cut -f 3"
  register: kmodtools_version
  ignore_errors: true
  changed_when: false

- name: Download WireGuard source
  get_url:
    url: "https://git.zx2c4.com/WireGuard/snapshot/WireGuard-{{ kmodtools_version.stdout }}.tar.xz"
    dest: /tmp/

- name: Extract WireGuard source
  unarchive:
    src: "/tmp/WireGuard-{{ kmodtools_version.stdout }}.tar.xz"
    dest: /usr/src/wireguard/

# Again, copied from the script
- name: Prepare wg and wg-quick Makefile for OpenBSD
  shell: "cd /usr/src/wireguard && sed -i 's/install -v/install/g;s/@install/install/g' \"WireGuard-{{ kmodtools_version.stdout }}/src/tools/Makefile\""
  changed_when: false

# Here's where I mix some things up. We do not run make install here.
- name: Build wg and wg-quick
  shell: "cd /usr/src/wireguard && gmake -j$(sysctl -n hw.ncpu) -C \"WireGuard-{{ kmodtools_version.stdout }}/src/tools\" WITH_WGQUICK=yes PREFIX=/usr/local"
  changed_when: false

# Instead, we check here if the wireguard binary is different than the version installed.
# Let's not replace binaries that do not need replacing
- name: Check if new wg binary is different than installed version
  shell: "test $(md5 -q /usr/src/wireguard/WireGuard-{{ kmodtools_version.stdout }}/src/tools/wg) == $(md5 -q /usr/local/bin/wg)"
  register: wg_md5
  ignore_errors: true
  changed_when: false

# Same goes here, check if the wg-quick is different than what is installed currently
- name: Check if new wg-quick script is different than installed version
  shell: "test $(md5 -q /usr/src/wireguard/WireGuard-{{ kmodtools_version.stdout }}/src/tools/wg-quick/openbsd.bash) == $(md5 -q /usr/local/bin/wg-quick)"
  register: wgquick_md5
  ignore_errors: true
  changed_when: false

# Here we only install the binaries if either of the previous two files were different
- name: Install wg and wg-quick
  shell: "cd /usr/src/wireguard && gmake -j$(sysctl -n hw.ncpu) -C \"WireGuard-{{ kmodtools_version.stdout }}/src/tools\" WITH_WGQUICK=yes PREFIX=/usr/local install"
  changed_when: false
  when: wg_md5.rc != 0 or wgquick_md5.rc != 0

wireguard-binaries/tasks/build-wireguard-go.yml

+++
# This info is pulled out of the distro info gathered in main.yml
- name : Get current wireguard version
  shell: "echo \"{{ distros.stdout }}\" | grep upstream.*go | cut -f 3"
  register: wireguard_version
  ignore_errors: true
  changed_when: false

- name: Download WireGuard-go source
  get_url:
    url: "https://git.zx2c4.com/wireguard-go/snapshot/wireguard-go-{{ wireguard_version.stdout }}.tar.xz"
    dest: /tmp/

- name: Extract WireGuard-go source
  unarchive:
    src: "/tmp/wireguard-go-{{ wireguard_version.stdout }}.tar.xz"
    dest: /usr/src/wireguard/

# This is also pulled directly out of the script
- name: Prepare WireGuard-go Makefile for OpenBSD
  shell: "cd /usr/src/wireguard && sed -i 's/install -v/install/g;s/@install/install/g' \"wireguard-go-{{ wireguard_version.stdout }}/Makefile\""
  changed_when: false

# This matches kmodtools. Do not install unless the binary is different.
- name: Build wireguard-go
  shell: "cd /usr/src/wireguard && gmake -j$(sysctl -n hw.ncpu) -C \"wireguard-go-{{ wireguard_version.stdout }}\" PREFIX=/usr/local"
  changed_when: false

- name: Check if new wireguard-go binary is different than installed version
  shell: "test $(md5 -q /usr/src/wireguard/wireguard-go-{{ wireguard_version.stdout }}/wireguard-go) == $(md5 -q /usr/local/bin/wireguard-go)"
  register: wireguardgo_md5
  ignore_errors: true
  changed_when: false

# Only install the binary if the new binary is different than the installed binary
- name: Install wireguard-go
  shell: "cd /usr/src/wireguard && gmake -j$(sysctl -n hw.ncpu) -C \"wireguard-go-{{ wireguard_version.stdout }}\" PREFIX=/usr/local install"
  changed_when: false
  when: wireguardgo_md5.rc != 0

I am sometimes shocked at how complicated ansible playbooks can be compared to shell scripts. Personally, it is much easier for me to compose offhand in shell than in ansible. Though, as stated in the beginning, I believe ansible has the advantage of being more powerful in the grand scheme. Using the entire OpenBSD-WireGuard playbook, I can deploy new wireguard VM's within minutes, anywhere in the world with Vultr.

Has been tested on OpenBSD 6.4 and 6.5