Set up a New OpenBSD Server with Ansible

Updated on February 28, 2022
Set up a New OpenBSD Server with Ansible header image

Introduction

This guide will show you how to automate the initial OpenBSD 7 server configuration with Ansible. Ansible is a software tool that automates the configuration of one or more remote nodes from a local control node. The local node can be another Linux system, a Mac, or a Windows PC. If you are using a Windows PC, you can install Linux using the Windows Subsystem for Linux. This guide will focus on using a Mac as the control node to set up a fresh Vultr OpenBSD 7 server.

The OpenBSD 7 Ansible setup playbook is listed at the end of this guide. In addition, instructions are provided on how to install and use it.

It takes a little work to set up and start using Ansible, but once it is set up and you become familiar with it, using Ansible will save a lot of time and effort. For example, you may want to experiment with different applications. Using the Ansible setup playbook described in this guide, you can quickly reinstall your OpenBSD 7 instance and then run the playbook to configure your base server. This playbook is a good base for installing web servers, database servers, or email server.

We will use a single Ansible playbook that will do the following:

  • Apply all the available OpenBSD system patches.
  • Upgrade the installed OpenBSD packages.
  • Reboot the server if needed and wait for it to become active.
  • Install a base set of useful software packages.
  • Set a fully qualified domain name (FQDN).
  • Set the timezone.
  • Set the SSH port number.
  • Disable SSH password authentication for root.
  • Disable tunneled clear-text passwords.
  • Configure /etc/doas.conf.
  • Create regular user group.
  • Create a regular user with doas privileges.
  • Install SSH Keys for the regular user.
  • Ensure authorized key for root user is installed.
  • Update/Change the root password.
  • Create a .vimrc resource file that disables vi visual mode for root and the user.
  • Create a 2-line prompt and bash ls aliases for the user.
  • Create ksh ls aliases for root.
  • Configure the OpenBSD PF firewall.

Packet Filter (PF) is the OpenBSD system for creating a network firewall and for doing Network Address Translation. In this guide, we will show how to use PF to create a basic firewall. Refer to the OpenBSD PF User's Guide for more details. The PF configuration also provides brute force protection for SSH. It will ban host IPs that try to make five or more SSH connection attempts within a three-second interval.

OpenBSD uses ksh as the default shell for both root and regular users. This ansible playbook will install the bash shell and configure it for the regular user. The root user continues to use the OpenBSD ksh environment.

Prerequisites

  • A Vultr server with a freshly installed OpenBSD 7.0 instance.
  • A local Mac, Windows with WSL, or Linux system. This guide focuses on Mac, but the procedures are similar for any Linux control node.
  • If using a Mac, Homebrew should be installed.
  • A previously generated SSH Key for the Vultr host, and the SSH public key should be installed for the root user.
  • Ansible 2.9.x, or later stable version. This guide is tested with Ansible version 2.9.27 on a Mac, installed via Homebrew.

1. Install Ansible on the Local System

For this guide, we are using the Ansible 2.9.x Red Hat released version.

Using a Mac with Homebrew installed:

$ brew install ansible@2.9
$ brew link --force --overwrite ansible@2.9

This will install Ansible along with all the required dependencies, including python version 3.9.x. You can quickly test you your installation by doing:

$ ansible --version
ansible 2.9.27
  config file = /Users/george/.ansible.cfg
  configured module search path = ['/Users/george/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible@2.9/2.9.27_2/libexec/lib/python3.9/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.9.10 (main, Jan 15 2022, 11:48:00) [Clang 13.0.0 (clang-1300.0.29.3)]

Create a Simple Ansible Configuration

Create the .ansible.cfg configuration file in the user home directory. This tells Ansible how to locate the host's inventory file.

Add the following content to ~/.ansible.cfg:

[defaults]
inventory  = /Users/user/ansible/hosts.yml
interpreter_python = auto

Be sure to replace user with your actual user name.

Create the folder to store the hosts.yml hosts inventory file:

$ mkdir ~/ansible
$ cd ~/ansible

Of course, you can put it anywhere you want to and give it any name. Just make sure that your .ansible.cfg file points to the correct location.

Add the following content to ~/ansible/hosts.yml:

all:
  vars:
    ansible_python_interpreter: /usr/bin/python3
    ansible_become: yes
    ansible_become_method: sudo 
  children:
    vultr:
      hosts:
        host.example.com:
          user: user
          user_passwd: "{{ host_user_passwd }}"
          root_passwd: "{{ host_root_passwd }}"
          ssh_pub_key: "{{ lookup('file', '~/.ssh/host_ed25519.pub')  }}"
          ansible_become_pass: "{{ host_user_passwd }}"
          ansible_python_interpreter: /usr/local/bin/python3
    vmware:
      hosts:
        debian1.local:
          user: george
          user_passwd: "{{ db1_user_passwd }}"
          root_passwd: "{{ db1_root_passwd }}"
          ssh_pub_key: "{{ lookup('file', '~/.ssh/db1_ed25519.pub')  }}"
          ansible_become_pass: "{{ db1_user_passwd }}"

The first block defines ansible variables that are global to the host's inventory file. Hosts are listed under children groups.

Replace host with your actual host name. The vmware group shows a working example for setting up a VMware host on my Mac.

The user is the regular user to be created. The host_user_passwd and host_root_passwd are the user and root passwords that are stored in an ansible vault, described below. ssh_pub_key points to the SSH public key for the Vultr host. The ansible_become lines provide the ability for the newly created user to execute doas commands in future ansible playbooks.

The python3 executable location is different for OpenBSD, so the "child" ansible_python_interpreter defines the OpenBSD python3 location.

Using the Ansible Vault

Create the directory for the Ansible password vault and set-up playbook:

$ mkdir -p ~/ansible/openbsd
$ cd ~/ansible/openbsd

Create the Ansible password vault:

$ ansible-vault create passwd.yml
New Vault password: 
Confirm New Vault password:

This will start up your default system editor. Add the following content:

host_user_passwd: ELqZ9L70SSOTjnE0Jq
host_root_passwd: tgM2Q5h8WCeibIdJtd

Replace host with your actual hostname. Generate your own secure passwords. Save and exit your editor. This creates an encrypted file that only Ansible can read. You can add other host passwords to the same file.

pwgen is a very handy tool that you can use to generate secure passwords. Install it on a Mac via Homebrew: brew install pwgen. Use it as follows:

$ pwgen -s 18 2
ELqZ9L70SSOTjnE0Jq tgM2Q5h8WCeibIdJtd

You can view the contents of the ansible-vault file with:

$ ansible-vault view passwd.yml
Vault password:

You can edit the the file with:

$ ansible-vault edit passwd.yml                
Vault password: 

2. Create an SSH Config File for the Vultr Host

Next, we need to define the Vultr hostname and SSH port number that Ansible will use to connect to the remote host.

The SSH configuration for the server host is stored in ~/.ssh/config. An example configuration on a Mac looks like:

Host *
  AddKeysToAgent yes
  UseKeychain yes
  IdentitiesOnly yes
  AddressFamily inet

Host host.example.com host
  Hostname host.example.com
  Port 22
  User user
  IdentityFile ~/.ssh/host_ed25519

Using the SSH config file, you can change the default SSH port number if changed by the ansible playbook. The playbook is always executed the first time with SSH port 22. If the SSH port number is changed by the playbook, then the SSH port number in the SSH config file needs to be changed after the playbook runs.

With this SSH configuration file, you can use a shorthand host name to log into the server.

For the user login:

$ ssh host

For the root login:

$ ssh root@host

UserKeychain is specific to macOS. It stores the SSH public key in the macOS key chain.

host.example.com is your Vultr server FQDN (Fully Qualified Domain Name) that needs to be defined in your DNS or /etc/hosts file on your local system. Port 22 is optional, but required if you define a non-standard SSH port.

Important: Install your SSH Key for the root user if you have not done so already:

$ ssh-copy-id -i ~/.ssh/host_ed25519 root@host

and verify that you can login without using a password.

Note: If you reinstall your Vultr instance, be sure to delete your Vultr hostname from ~/.ssh/known_hosts on your local control node. Otherwise, you will see an SSH error when you try to log into your reinstalled host. The hostname is added to this file the during the first login attempt:

$ ssh root@ap1
The authenticity of host 'np1.example.com (216.128.149.25)' can't be established.
ECDSA key fingerprint is SHA256:oNczYD+xuXx0L6CM17Ciy+DWu3jOEbfVclIj9wUT7Y8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

Answer yes to the question. If you don't delete the hostname from this file after reinstalling your instance, you will see an error like:

$ ssh root@ap1
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
o o o

If this happens, just delete the line entered for your hostname in the known_hosts file and run the ssh command again.

3. Test Your SSH/Ansible Configuration

Before trying to run the setup ansible playbook, we need to verify that Ansible is working correctly, that you can access your Ansible vault, and connect to your Vultr host. First, verify that Ansible is installed correctly on a Mac:

$ ansible --version
ansible 2.9.27 
  config file = /Users/user/.ansible.cfg
  o o o

This is the latest versions of Ansible on a Mac/Homebrew when this guide was written.

Run this command to test your Ansible configuration (also, your SSH configuration):

$ cd ~/ansible/openbsd
$ ansible -m ping --ask-vault-pass --extra-vars '@passwd.yml' host.example.com -u root
Vault password: 
host.example.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

If you see the above output, then everything is working fine. If not, go back and double-check all your SSH and Ansible configuration settings. Start by verifying that you can execute:

$ ssh root@host

and login in without a password, because you have installed your SSH key for root.

4. Running the Ansible OpenBSD Server Setup Playbook

Note: See Section 6. OpenBSD 7.0 PF and Ansible Setup Playbook Listings for the PF and playbook file listing details.

Copy and paste the Ansible PF configuration file into ~/ansible/openbsd/templates/etc/pf.conf.j2.

Copy and paste the OpenBSD setup playbook into ~/ansible/openbsd/setup-pb.yml.

You are ready to run the playbook. When you execute the playbook, you will be prompted for your vault password. The playbook will execute a number of tasks with a PLAY RECAP at the end. You can rerun the playbook multiple times; for example, you may want to change the SSH port number. It will only execute tasks when needed. Be sure to update variables at the beginning of the playbook, such as your SSH port number and your timezone.

Be sure that you are in the ~/ansible/openbsd directory. This is the command to run:

$ ansible-playbook --ask-vault-pass --extra-vars '@passwd.yml' setup-pb.yml -l host.example.com -u root
Vault password:

Depending on the speed of your Mac, it make take a few seconds to start up. If it completes successfully, you will see PLAY RECAP like:

PLAY RECAP *************************************************************************************************************************
np1.nimbusplace.com          : ok=38   changed=27   unreachable=0    failed=0    skipped=5    rescued=0    ignored=0 

The most important thing to note is that there should be no failed tasks.

Next, are some basic tests that you can run to verify your server setup.

5. OpenBSD 7.0 Server Verification

After you have successfully executed the Ansible setup playbook, here are some basic tests that you can execute to verify your sever setup. Here are some real-life examples with the server host that was used to test the setup playbook (the hostname is np1.nimbusplace.com and the user name is george).

Verify your user login

Verify that you can log into your new user account using your host's public SSH key:

╭─george@imac1 ~/ansible/openbsd 
╰─$ ssh np1
Last login: Tue Feb  8 11:38:49 2022 from 72.34.15.207
OpenBSD 7.0 (GENERIC.MP) #5: Mon Jan 31 09:09:02 MST 2022

Welcome to OpenBSD: The proactively secure Unix-like operating system.

Please use the sendbug(1) utility to report bugs in the system.
Before reporting a bug, please try to reproduce it with the latest
version of the code. With bug reports, please try to ensure that
enough information to reproduce the problem is enclosed, and if a
known fix for it exists, include that as well.

george@np1:~
$ 

Note the two-line prompt. The first line shows user@host and the current directory.

Now, note how the l, la, and ls LS aliases work and the presence of the .vimrc file:

george@np1:~
$ touch tmpfile
george@np1:~
$ l
tmpfile
george@np1:~
$ la
.Xdefaults      .bashrc         .cvsrc          .mailrc         .ssh/           tmpfile
.bash_history   .cshrc          .login          .profile        .vimrc
george@np1:~
$ ll
total 48
drwxr-xr-x  3 george  george  512 Feb 17 14:04 ./
drwxr-xr-x  3 root    wheel   512 Feb 17 13:17 ../
-rw-r--r--  1 george  george   87 Sep 30 15:00 .Xdefaults
-rw-------  1 george  george    5 Feb 17 13:25 .bash_history
-rw-r--r--  1 george  george   54 Feb 17 13:17 .bashrc
-rw-r--r--  1 george  george  769 Sep 30 15:00 .cshrc
-rw-r--r--  1 george  george  101 Sep 30 15:00 .cvsrc
-rw-r--r--  1 george  george  359 Sep 30 15:00 .login
-rw-r--r--  1 george  george  175 Sep 30 15:00 .mailrc
-rw-r--r--  1 george  george  388 Feb 17 13:17 .profile
drwx------  2 george  george  512 Feb 17 13:17 .ssh/
-rw-r--r--  1 george  george   13 Feb 17 13:17 .vimrc
-rw-r--r--  1 george  george    0 Feb 17 14:04 tmpfile
george@np1:~
$ cat .vimrc
set mouse-=a

The .vimrc set mouse-=a option turns off the VI visual mode, which makes it possible to use your mouse to select and copy a block of text in a VI window.

Verify the root login

You can still login to the root account via the host SSH key. Logging in via SSH using a password is disabled. You can still login as root with a password when using the Vultr VNC console.

$ ssh root@np1
Last login: Thu Feb 17 14:50:42 2022
OpenBSD 7.0 (GENERIC.MP) #5: Mon Jan 31 09:09:02 MST 2022
 o o o
np1#

The default root environment still uses ksh. The only changes were to add the l and ll LS aliases and the .vimrc VI configuration file:

np1# l
.Xdefaults      .bash_history   .cvsrc          .login          .ssh/
.ansible/       .cshrc          .kshrc          .profile        .vimrc

np1# ll
total 48
drwx------   4 root  wheel  512 Feb 17 14:12 ./
drwxr-xr-x  13 root  wheel  512 Feb 17 13:16 ../
-rw-r--r--   1 root  wheel   87 Sep 30 15:00 .Xdefaults
drwx------   3 root  wheel  512 Feb 17 13:15 .ansible/
-rw-------   1 root  wheel   14 Feb 17 14:13 .bash_history
-rw-r--r--   1 root  wheel  578 Sep 30 15:00 .cshrc
-rw-r--r--   1 root  wheel   94 Sep 30 15:00 .cvsrc
-rw-r--r--   1 root  wheel   36 Feb 17 13:17 .kshrc
-rw-r--r--   1 root  wheel  328 Sep 30 15:00 .login
-rw-r--r--   1 root  wheel  611 Feb 17 13:17 .profile
drwx------   2 root  wheel  512 Feb  7 23:28 .ssh/
-rw-r--r--   1 root  wheel   13 Feb 17 13:17 .vimrc

np1# cat .vimrc
set mouse-=a

Verify your user password

Even though you use an SSH public key to login to your user account, you still need to use your user password with the doas command. For example, use the doas command to change to the root account (enter your user password when prompted):

george@np1:~
$ doas -s
doas (george@np1.nimbusplace.com) password: 
bash-5.1# pwd
/home/george
bash-5.1# exit
exit

Verify the root password

While in your user account, you can also use su - to change to the root account. One difference is that you will have to enter your root password:

george@np1:~
$ su -
Password:
np1# 

Verify your hostname

While we are in the root account, lets verify our hostname and some other features that the playbook set up for us:

np1# hostname -s
np1

np1# hostname
np1.nimbusplace.com

np1# date
Thu Feb 17 14:18:21 CST 2022

Here we verified both the short and FQDN host names. With the date command, verify that the timezone is set correctly.

Verify that the PF firewall rule set is protecting the SSH port

A complete discussion of PF is beyond the scope of this guide, but here are some examples to start with.

To see the current installed PF rule set:

george@np1:~
$ doas pfctl -sr
doas (george@np1.nimbusplace.com) password: 
match in all scrub (no-df random-id max-mss 1440)
block drop in quick on ! egress inet from 66.42.112.0/23 to any
block drop in quick on ! egress inet6 from 2001:19f0:5c01:1a41::/64 to any
block drop in quick on vio0 inet6 from fe80::5400:3ff:fed4:1b5c to any
block drop in quick inet6 from 2001:19f0:5c01:1a41:5400:3ff:fed4:1b5c to any
block drop in quick on ! vio0 inet6 from 2001:19f0:5c01:1a41::/64 to any
block drop in quick inet from 66.42.112.216 to any
block drop in quick on ! vio0 inet from 66.42.112.0/23 to any
block return quick from <bruteforce> to any
block return in quick on egress from <martians> to any
block return out quick on egress from any to <martians>
block return all
pass in log on vio0 proto tcp from any to any port = 22 flags S/SA keep state (source-track rule, max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global, src.track 3) label "ssh-traffic"
pass out quick all flags S/SA
pass in on egress inet6 proto ipv6-icmp all icmp6-type echoreq
pass in on egress inet6 proto ipv6-icmp all icmp6-type unreach
pass in on egress inet6 proto ipv6-icmp all icmp6-type routeradv
pass in on egress inet6 proto ipv6-icmp all icmp6-type neighbrsol
pass in on egress inet6 proto ipv6-icmp all icmp6-type neighbradv
pass in on egress inet proto icmp all icmp-type echoreq
pass in on egress inet proto icmp all icmp-type unreach

To see the contents of the martians PF table:

george@np1:~
$ doas pfctl -t martians -Ts
   0.0.0.0/8
   10.0.0.0/8
   100.64.0.0/10
   127.0.0.0/8
   169.254.0.0/16
   172.16.0.0/12
   192.0.0.0/24
   192.0.2.0/24
   192.88.99.0/24
   192.168.0.0/16
   198.18.0.0/15
   198.51.100.0/24
   203.0.113.0/24
   224.0.0.0/3
   255.255.255.255
   ::/96
   ::1
   ::ffff:0.0.0.0/96
   100::/64
   2001:2::/48
   2001:10::/28
   2001:db8::/32
   3ffe::/16
   fc00::/7
   fec0::/10

To see the contents of the SSH bruteforce table:

george@np1:~
$ doas pfctl -t bruteforce -Ts
    12.34.56.78

These are hosts that have made multiple SSH connection attempts in a short interval. You can test that it's working by trying to rapidly log into your host multiple times with an invalid password and no SSH key. Be careful to not lock yourself out.

If you accidentally lock yourself out:

george@imac1:~/ansible/openbsd
$ ssh np1
ssh: connect to host np1.nimbusplace.com port 22: Connection refused

Log into your OpenBSD host as root via the Vultr VNC Console and execute:

np1# pfctl -t bruteforce -T delete 12.34.56.78
1/1 addresses deleted.

Then you will be able to SSH into your host.

6. OpenBSD 7.0 PF and Ansible Setup Playbook Listings

Here are the listings for the PF configuration file and the Ansible setup playbook.

The Ansible PF Configuration File (pf.conf.j2)

This PF configuration will create a basic firewall that allows SSH and ICMP traffic. All other external traffic is blocked. Internal traffic is allowed.

Create the Ansible PF configuration file directory:

mkdir -p ~/ansible/openbsd/templates/etc

Create the Ansible PF configuration file:

cd ~/ansible/openbsd/templates/etc

and add the following to pf.conf.j2:

wan0 = "{{ ansible_default_ipv4.interface }}"
icmp_types = "{ echoreq unreach }"

# pfctl -t "table" -T show
#   Display table contents.
# pfctl -t bruteforce -T expire 86400
#   Remove bruteforce table entries older than 86400 seconds.
# pfctl -t bruteforce -T delete 12.34.56.78
#   Immediately delete bruteforce table entry
table <bruteforce> persist
table <martians> {
  0.0.0.0/8 10.0.0.0/8 100.64.0.0/10            \
  127.0.0.0/8 169.254.0.0/16 172.16.0.0/12      \
  192.0.0.0/24 192.0.2.0/24 192.88.99.0/24      \
  192.168.0.0/16 198.18.0.0/15 198.51.100.0/24  \
  203.0.113.0/24 224.0.0.0/3 255.255.255.255/32 \
  ::/128 ::/96 ::1/128 ::ffff:0:0/96 100::/64   \
  2001:10::/28 2001:2::/48 2001:db8::/32        \
  3ffe::/16 fec0::/10 fc00::/7 }

set block-policy return
set loginterface egress
set skip on lo0

match in all scrub (no-df random-id max-mss 1440)
antispoof quick for { egress $wan0 }

block quick from <bruteforce>
block in quick on egress from <martians> to any
block return out quick on egress from any to <martians>
block all

# SSH
# Add brute force hosts (5 attempts within 3 seconds)
# to the bruteforce table.
pass in log on $wan0 proto tcp to port { {{ ssh_port }} } \
    keep state (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global) label ssh-traffic

pass out quick

# ICMP
pass in on egress inet proto icmp all icmp-type $icmp_types
pass in on egress inet6 proto icmp6 all icmp6-type $icmp_types
pass in on egress inet6 proto icmp6 all \
  icmp6-type { routeradv neighbrsol neighbradv }

Create the OpenBSD Setup Playbook

cd ~/ansible/openbsd

and add the following to setup-pb.yml:

# Initial server setup
#
---
- hosts: all
  become: true
  vars:
    ssh_port: "22"
    tmzone: America/Chicago
    user_shell: /usr/local/bin/bash
    doas: |
      permit persist :wheel
    vimrc: |
      set mouse-=a

    dot_profile: |
      export PATH HOME TERM
      
      # This is the standard OpenBSD PATH,
      # defined for easy customization.
      PATH=/bin:/sbin:/usr/bin:/usr/sbin
      PATH=$PATH:/usr/X11R6/bin
      PATH=$PATH:/usr/local/bin:/usr/local/sbin
      PATH=$PATH:/usr/games
      PATH=$HOME/bin:$PATH
      
      # include .bashrc if it exists
      if [ -f "$HOME/.bashrc" ]; then
          . "$HOME/.bashrc"
      fi
      
      # Set two-line user prompt
      PS1="\e[0;32m\u@\h\e[m:\w\n$ "

    dot_root_profile: |
      # include .kshrc if it exists
      if [ -f "$HOME/.kshrc" ]; then
          . "$HOME/.kshrc"
      fi

    dot_bashrc: |
      alias l='ls -CF'
      alias la='ls -AF'
      alias ll='ls -alF'

    dot_kshrc: |
      alias l='ls -CF'
      alias ll='ls -alF'

  tasks:
    # Update and install the base software
    - name: Apply all available system patches.
      command: syspatch
      register: syspatch
      failed_when: syspatch.rc != 0 and syspatch.rc != 2
      changed_when: syspatch.rc == 0

    - name: Update all packages on the system.
      command: pkg_add -u

    - name: Reboot the server if needed.
      reboot:
        msg: "Reboot initiated by Ansible because of syspatch updates."
        connect_timeout: 5
        reboot_timeout: 600
        pre_reboot_delay: 0
        post_reboot_delay: 15
        test_command: whoami
      when: syspatch.rc == 0

    - name: Install a base set of software packages.
      openbsd_pkg:
        name:
          - bash
          - curl
          - git
          - htop
          - pwgen
          - rsync--
          - vim--no_x11
        state: present

    # Host Setup
    - name: Set static hostname.
      hostname:
        name: "{{ inventory_hostname }}"

    - name: Add IPv4 FQDN to /etc/hosts.
      lineinfile:
        dest: /etc/hosts
        regexp: '^127\.0\.0\.1'
        line: "127.0.0.1 {{ inventory_hostname }}"
        state: present

    - name: Add IPv6 FQDN to /etc/hosts.
      lineinfile:
        dest: /etc/hosts
        regexp: '^\:\:1'
        line: "::1       {{ inventory_hostname }}"
        state: present

    - name: Add FQDN to /etc/myname.
      lineinfile:
        dest: /etc/myname
        regexp: '^(.*)'
        line: "{{ inventory_hostname }}"
        state: present

    - name: Set timezone.
      timezone:
        name: "{{ tmzone }}"
      notify:
        - restart cron

    - name: Set ssh port port number.
      lineinfile:
        dest: /etc/ssh/sshd_config
        regexp: 'Port '
        line: 'Port {{ ssh_port }}'
        state: present
      notify:
        - restart sshd

    - name: Disable root password login via SSH.
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PermitRootLogin'
        line: 'PermitRootLogin prohibit-password'
      notify:
        - restart sshd

    - name: Disable tunneled clear-text passwords.
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'
      notify:
        - restart sshd

    - name: Configure /etc/doas.conf.
      copy:
        dest: /etc/doas.conf
        content: "{{ doas }}"
        owner: root
        group: wheel
        mode: 0644

    - name: Create regular user group.
      group:
        name: "{{ user }}"
        state: present

    - name: Create regular user with doas privileges.
      user:
        name: "{{ user }}"
        group: "{{ user }}"
        password: "{{ user_passwd | password_hash('bcrypt') }}"
        groups: wheel
        append: true
        shell: /usr/local/bin/bash

    - name: Ensure authorized keys for remote user is installed.
      authorized_key:
        user: "{{ user }}"
        key: "{{ ssh_pub_key }}"

    - name: Ensure authorized key for root user is installed.
      authorized_key:
        user: root
        key: "{{ ssh_pub_key }}"

    - name: Update root user password.
      user:
        name: root
        password: "{{ root_passwd | password_hash('bcrypt') }}"

    - name: Configure user .vimrc.
      copy:
        dest: /home/{{ user }}/.vimrc
        content: "{{ vimrc }}"
        owner: "{{ user }}"
        group: "{{ user }}"
        mode: 0644

    - name: Configure root .vimrc.
      copy:
        dest: /root/.vimrc
        content: "{{ vimrc }}"
        owner: root
        group: wheel
        mode: 0644

    - name: Configure user .profile,
      copy:
        dest: /home/{{ user }}/.profile
        content: "{{ dot_profile }}"
        owner: "{{ user }}"
        group: "{{ user }}"
        mode: 0644

    - name: Configure user .bashrc,
      copy:
        dest: /home/{{ user }}/.bashrc
        content: "{{ dot_bashrc }}"
        owner: "{{ user }}"
        group: "{{ user }}"
        mode: 0644

    - name: Update root .profile
      blockinfile:
        path: /root/.profile
        content: "{{ dot_root_profile }}"
        owner: root
        group: wheel
        mode: 0644

    - name: Configure root .kshrc.
      copy:
        dest: /root/.kshrc
        content: "{{ dot_kshrc }}"
        owner: root
        group: wheel
        mode: 0644

    - name: Configure pf firewall.
      template:
        src: etc/pf.conf.j2
        dest: /etc/pf.conf
        owner: root
        group: wheel
        mode: 0600
      notify:
        - reload pf.conf

    - meta: end_play

  handlers:
    - name: restart cron
      service:
        name: cron
        state: restarted

    - name: restart sshd
      service:
        name: sshd
        state: restarted

    - name: reload pf.conf
      command: pfctl -f /etc/pf.conf

You can read the Ansible Documentation to learn more about Ansible.

You should only have to update the vars: section to change the settings for your specific situation. Most likely, you may want to update the SSH port number and timezone.

Conclusion

In this guide, we have introduced Ansible for automating the initial OpenBSD 7.0 server setup. This is very useful for deploying or redeploying a server after testing an application. It also creates a solid foundation for creating a web, database, or email server.