How to use ansible with two factor authentication

ansibleansible-playbooksshtwo-factor-authentication

I have enabled two factor authentication for ssh using duosecurity (using this playbook https://github.com/CoffeeAndCode/ansible-duo ).

How can I use ansible to manage the server now. The SSH calls fail at gathering facts because of this. I want the person running the playbook to enter the two factor code before the playbook is run.

Disabling two factor for the deployment user is a possible solution but creates a security issue which I would I like to avoid.

Best Answer

It's a hack, but you can tunnel a non-2fac Ansible SSH connection through a 2fac-enabled SSH connection.

Overview

We will setup two users: ansible will be the user Ansible will use. It should be authenticated in a way that's supported by Ansible (i.e., not 2fac). This user will be restricted so it cannot connect from anywhere but 127.0.0.1, so it is not accessible from outside the machine.

The second user, ansible_tunnel will be open to the outside world, but will be authenticated by two factors, and will only allow tunneling of SSH connections to the local machine.

You must be able to configure 2-factor authentication only for some users (not all).

Some info on SSH tunnels.

On the target machine:

  1. Create two users: ansible and ansible_tunnel
  2. Put your public key in ~/.ssh/authorized_keys of both users
  3. Set the shell of ansible_tunnel to /bin/false, or lock the user - it will be used for tunneling exclusively, not running commands
  4. Add the following to /etc/ssh/sshd_config:

    AllowTcpForwarding no
    
    AllowUsers ansible@127.0.0.1 ansible_tunnel
    
    Match User ansible_tunnel
      AllowTcpForwarding yes
      PermitOpen 127.0.0.1:22
      ForceCommand echo 'This account can only be used for tunneling SSH sessions'
    
  5. Setup 2-factor authentication only for ansible_tunnel
  6. Restart sshd

On the machine running Ansible:

  1. Before running Ansible, run the following (on the Ansible machine, not the target):

    ssh -N -L 8022:127.0.0.1:22 ansible_tunnel@<host>
    

    You will be authenticated using two factors.

  2. Once the tunnel is up (check with netstat), run Ansible with ansible_ssh_user=ansible, ansible_ssh_port=8022 and ansible_ssh_host=localhost.

Recap

  • Only ansible_tunnel can connect from the outside, and it will be authenticated using two factors
  • Once the tunnel is set up, connecting to port 8022 on the local machine is the same as connecting to sshd on the remote machine
  • We're allowing ansible to connect over SSH only when it is done through the localhost, so only connections that are tunneled are allowed

Scale

This will not scale well for multiple server, due to the need to open a separate tunnel for each machine, which requires manual action. However, if you've chosen 2-factor authentication for your servers you're already willing to do some manual action to connect to each server, and this solution will only add a little overhead with some script-wrapping.

[EDITED TO ADD]

Bonus

For convenience, we may want to log into the maintenance account directly to do some manual work, without going through the process of setting up a tunnel. We can configure SSH to require 2fac authentication in this case, while maintaining the ability to connect without 2fac through the tunnel:

# All users must authenticate using two factors
AuthenticationMethods publickey,keyboard-interactive

# Allow both maintenance user and tunnel user with no restrictions
AllowUsers ansible ansible_tunnel

# The maintenance user is allowed to authenticate using a single factor only
# when connecting from a local address - it should be impossible to connect to
# this user using a single factor from the outside (the only way to do that is
# having an existing access to the machine, or use the two-factor tunnel)
Match User ansible Address 127.0.0.1
  AuthenticationMethods publickey