Making Ansible, Doas and OpenBSD play nicely

Update 11 Jan 2020: The behaviour of doas changed in OpenBSD 6.6 so the environment specification described below is unnecessary. As of 6.6, doas sets HOME and USER to reflect the target user, like sudo. For details see openbsd-tech

A little while ago, OpenBSD replaced sudo in the base system with doas. This transition follows a typical path in the OpenBSD community where a large, difficult to audit daemon or key application is replaced by an auditable and securable alternate implementation. At the same time, features are pruned if they add significant complexity. I don’t need all the features of sudo and I like the simplicity and security of doas so I wanted to switch. I use Ansible to provision and manage my small collection of servers and when version 2.0 added support for privilege escalation via doas (via the become keyword) I started to make my switch. The main use-case in my Ansible playbooks is where I run as root and become my non-admin user so that permissions are set correctly by default, and in this case doas behaves differently…

# /usr/bin/env | grep -E '^(HOME|USER)='
HOME=/root
USER=root
# sudo -u esteele /usr/bin/env | grep -E '^(HOME|USER)='
USER=esteele
HOME=/home/esteele
# doas -u esteele /usr/bin/env | grep -E '^(HOME|USER)=' 
HOME=/root
USER=root

The key difference is which environment variables are propagated from the original session. I find doas to be a little unintuitive in this respect, particularly the inability in the new session to write to the directory specified by $HOME and this behaviour causes problems with the Ansible git and the pip tasks. The git task expects $USER to correspond to the effective user id and the pip task expects $HOME to be the home directory of the effective user id. The unexpected doas environment meant the tasks were trying to write files, as my non-admin user, under /root which was unsurprisingly unsuccessful. Fortunately Ansible has a way to specify environment variables for a task and even though it’d be preferable for the become mechanism to take care of this, it’s an effective workaround (albeit a slightly dirty one).

The example below, from my web host playbook, ties the environment specification and the doas become in a block

# doas preserves USER and HOME, which is different to sudo, however by                                                                                                    
#  specifying them as environment variables we can proceed.                                                                                                               
# USER is required for git, and HOME is required for pip                                                                                                                  
- block:                                                                                                                                                                  
  - name: Checkout wordspeak repo                                                                                                                                         
    git: repo=ssh://git@github.com/edwinsteele/wordspeak.org.git                                                                                                          
         dest=/home/esteele/Code/wordspeak.org                                                                                                                            

  - name: Setup nikola virtualenv                                                                                                                                         
    pip: requirements=/home/esteele/Code/wordspeak.org/requirements.txt                                                                                                   
         virtualenv_command=/usr/local/bin/virtualenv                                                                                                                     
         virtualenv=/home/esteele/.virtualenvs/wordspeak_n7                                                                                                               

  become: True                                                                                                                                                            
  become_method: doas                                                                                                                                                     
  become_user: esteele                                                                                                                                                    
  environment:                                                                                                                                                            
    USER: esteele                                                                                                                                                         
    HOME: /home/esteele           

And having tied this together, I can remove the sudo package!

    - name: Remove sudo
      openbsd_pkg: name=sudo-- state=absent