Synopsis #
Ansible can manage OpenBSD over SSH, but most modules require a packaged Python interpreter on each managed node. Privileged tasks also require an explicit doas(1)
policy. This guide prepares one node, tests access without root privileges, configures doas, and installs a package with a playbook that can be run repeatedly.
Ansible is not an OpenBSD base-system component. The controller, ansible-core, and collections have their own support cycles. Confirm their current OpenBSD notes before upgrading an automation environment.
Define the Trust Boundary #
An automation controller can distribute commands and files to every managed node. Protect its SSH keys, inventory, vault material, logs, and backups accordingly. Use a dedicated named account on managed nodes and do not permit direct root SSH login.
Before automating tasks, set up:
- a working console or independent recovery path;
- a verified named administrator account;
- a host-key verification process;
- a backup of every file the first playbook will manage;
- a small initial host group rather than the entire fleet.
Create an Administrator Account and Configure Secure Remote Access provide the OpenBSD access baseline.
Install Python on the Managed Node #
Ansible’s raw module can operate without Python, but ordinary modules cannot. On the managed OpenBSD node, search the current package set and install the Python 3 meta-package selected for that release:
pkg_info -Q python
# Inspect available Python package stems
doas pkg_add python%3
# Install the current Python 3 package selected by the package tools
/usr/local/bin/python3 --version
# Confirm the stable interpreter path used by inventory
If package selection is ambiguous, choose the maintained Python 3 package explicitly rather than hard-coding a minor version from an older guide. Keep Python updated with the rest of the installed packages.
Create the Inventory #
On the Ansible controller, create an inventory file such as inventory.ini:
[openbsd]
router1.example.net ansible_user=automation
[openbsd:vars]
ansible_python_interpreter=/usr/local/bin/python3
ansible_become=true
ansible_become_method=doas
ansible_become_user=root
The host name, account, and grouping are examples. Use verified DNS or addresses and retain SSH host-key checking. Do not use ansible_ssh_common_args to disable host-key verification.
Test SSH before invoking Ansible:
ssh automation@router1.example.net
Then test the Python module path without privilege elevation:
ansible openbsd -i inventory.ini -m ansible.builtin.ping -e ansible_become=false
Ansible’s ping module tests module execution; it does not send ICMP echo requests.
Configure doas Permissions #
Ansible normally executes temporary module code as the target user. A command-specific doas.conf rule cannot authorize arbitrary privileged modules safely. Choose one of these models:
- Require the automation account’s password and run playbooks with
--ask-become-pass. - Grant passwordless elevation to a tightly protected automation account and treat its SSH key as a root credential.
- Avoid general elevation and use narrowly designed
rawtasks that match command-specific rules, accepting that some module features may not work.
For the interactive-password model, add this rule to /etc/doas.conf on the managed node:
permit automation as root
Parse the policy and test the exact identity with doas(1) :
doas -C /etc/doas.conf
# Parse the complete policy without executing a command
doas -u root id
# Confirm authentication and target identity from the automation account
Run privileged playbooks with --ask-become-pass. An unrestricted nopass account can act as root. Protect it with dedicated SSH keys, restrict its source addresses where practical, and monitor its use.
Install the OpenBSD Collection Support #
The community.general.openbsd_pkg module is distributed in the community.general collection and is not part of ansible-core. Install and record the collection on the controller:
ansible-galaxy collection install community.general
ansible-galaxy collection list community.general
Pin collection versions in the automation project’s requirements file when repeatable controller builds are required.
Run an Idempotent Package Playbook #
Create openbsd-baseline.yml on the controller:
---
- name: Maintain a small OpenBSD package baseline
hosts: openbsd
gather_facts: true
become: true
tasks:
- name: Ensure rsync is installed
community.general.openbsd_pkg:
name: rsync--
state: present
- name: Record the managed node release
ansible.builtin.command: uname -a
register: openbsd_uname
changed_when: false
- name: Display the managed node release
ansible.builtin.debug:
var: openbsd_uname.stdout
Use rsync-- to select the package without optional flavors, so the task does not prompt for a choice.
Run syntax and dry-run checks first:
ansible-playbook -i inventory.ini openbsd-baseline.yml --syntax-check
# Parse inventory and playbook structure
ansible-playbook -i inventory.ini openbsd-baseline.yml --check --diff --ask-become-pass --limit router1.example.net
# Preview the first managed node
ansible-playbook -i inventory.ini openbsd-baseline.yml --ask-become-pass --limit router1.example.net
# Apply only after the preview and recovery plan are acceptable
Check mode is a prediction and not every module can model all changes. Keep the first production run limited and inspect the resulting package and log state on the node.
Manage Configuration Safely #
For each managed file:
- render to a temporary path or use module validation where available;
- preserve owner, group, and mode explicitly;
- notify a handler only when content changes;
- validate daemon syntax before reload;
- keep the previous remote session and recovery route available;
- avoid overwriting package examples or local secrets without a documented merge policy.
Start with low-risk changes, such as installing a package, before automating sysupgrade, PF replacement, disk operations, or SSH authentication. Test repeated runs, limit the initial host group, and establish a recovery procedure before changing more hosts.
Diagnose Failures #
Use -vvv only in protected logs because output can contain paths, variables, command lines, and remote data. Separate SSH, Python interpreter, become, module, and target-command failures. Confirm the same SSH account and doas action manually on the affected node before changing playbook logic.