Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use ansible with two 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.

like image 794
Harshil Mathur Avatar asked Apr 16 '14 16:04

Harshil Mathur


People also ask

How do you authenticate Ansible?

To authenticate a user executing Ansible modules, you can prompt for the user's credentials when you execute the playbook. For example, you can define an interactive prompt in the playbook, or you can execute the playbook with the -k or --ask-pass command-line option to prompt for the password.

Is SSH necessary for Ansible?

Ansible can use a variety of connection methods beyond SSH. You can select any connection plugin, including managing things locally and managing chroot, lxc, and jail containers.

What are the three authentication methods available for MFA?

Most MFA authentication methodology is based on one of three types of additional information: Things you know (knowledge), such as a password or PIN. Things you have (possession), such as a badge or smartphone. Things you are (inherence), such as a biometric like fingerprints or voice recognition.


2 Answers

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 [email protected] 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
like image 173
duvduv Avatar answered Oct 22 '22 01:10

duvduv


Solution using a Bastion Host

Even using an ssh bastion host it took me quite a while to get this working. In case it helps anyone else, here's what I came up with. It uses the ControlMaster ssh config options and since ansible uses regular ssh it can be configured to use the same ssh features and re-use the connection to the bastion host regardless of how many connections it opens to remote hosts. I've seen these Control options recommended in general (presumably for performance reasons if you have a lot of hosts) but not in the context of 2FA to a bastion host.

With this approach you don't need any sshd config changes, so you'll want AuthenticationMethods publickey,keyboard-interactive as the only authentication method setting on the bastion server, and publickey only for all your other servers that you're proxying through the bastion to get to. Since the bastion host is the only one that accepts external connections from the internet, it's the only one that requires 2FA, and internal hosts rely on agent forwarding for public key authentication but don't use 2FA.

On the client, I created a new ssh config file for my ansible environment in the top-level directory that I run ansible from (so sibling of ansible.cfg) called ssh.config. It contains:

Host bastion-persistent-connection
  HostName <bastion host>
  ForwardAgent yes
  IdentityFile ~/.ssh/my-key
  ControlMaster auto
  ControlPath ~/.ssh/ansible-%r@%h:%p
  ControlPersist 10m

Host 10.0.*.*
  ProxyCommand ssh -W %h:%p bastion-persistent-connection -F ./ssh.config
  IdentityFile ~/.ssh/my-key

Then in ansible.cfg I have:

[ssh_connection]
ssh_args = -F ./ssh.config

A few things to note:

  • My private subnet in this case is 10.0.0.0/16 which maps to the host wildcard option above. The bastion proxies all ssh connections to servers on this subnet.

  • This is a bit brittle in that I can only run my ssh or ansible commands in this directory, because of the ProxyCommand passing the local path to this config file. Unfortunately I don't think there's an ssh variable that maps to the current config file being used so that I could pass the same config file to the ProxyCommand automatically. Depending on your environment it might be better to use an absolute path for this.

The one gotcha is it makes running ansible more complex. Unfortunately, from what I can tell ansible has no support whatsoever for 2FA. So if you have no existing ssh connection to the bastion, ansible will print out Verification code: once for every private server it's connecting to, but it's not actually listening for the input so no matter what you do the connections will fail.

So I first run: ssh -F ssh.config bastion-persistent-connection

This creates the socket file in ~/.ssh/ansible-*, and the ssh agent locally will close & remove that socket after the configurable time (what I have set to 10m).

Once the socket is open I can run ansible commands like normal, e.g. ansible all -m ping and they succeed.

like image 24
danny Avatar answered Oct 22 '22 00:10

danny