Bootstrap FreeKB - Ansible - Getting Started with SSH
Ansible - Getting Started with SSH

Updated:   |  Ansible articles

By default, Ansible uses SSH to connect to managed nodes (e.g. target systems). For this reason, Ansible typically requires relatively easy configuration to run against Linux servers, since most Linux servers are setup with SSH. On the other hand, Windows servers can take a bit of planning, setup, and configuration since typically WinRM (Windows Remote Management) is used instead of SSH for Windows hosts.

 

The ansible-doc command can be used to display the different type of protocols that can be used, such as SSH, Paramiko SSH, local, and so on. The --connection command line option can be used to specify the connection plugin that is used.

ansible-playbook foo.yml --connection=ssh

 

Or you could define the connection plugin to use in your default inventory file.

[all:vars]
ansible_connection=paramiko_ssh

 


SSH user

When using the ansible-playbook command, there are numerous ways to define the user that will make the SSH connection.

If none of the above are used, the user that invokes the ansible-playbook command will be the user that is used for the SSH connection.


Known hosts

When making an SSH connection to a server, if the SSH key of the server (server1.example.com in this example) is not listed in the /etc/ssh/ssh_known_hosts or /home/username/.ssh/known_hosts file on the control node (your Ansible server), a prompt will appear stating The authenticity of host 'hostname (ip address)' can't be established, like this.

The authenticity of host 'server1.example.com (10.115.55.189)' can't be established
DSA key fingerprint is BB37 83F2 5E3A 7A4C 6C84  F047 D97B DD4E 38BB 2082
Are you sure you want to continue connecting (yes/no)?

 

Typing yes and pressing enter will append the key of the managed node to the known hosts file on the control node. Or, the known_hosts module can be used to append the managed nodes key to your known hosts file.

 


Authentication

SSH has a couple different authentication method.

  • Password authentication
  • Public/Private key authentication

It's important to recognize that the SSH connection will be made as a certain user. In this example, since remote_user: john.doe is included, then the SSH authentication will be for john.doe.

---
- hosts: all
  remote_user: john.doe
  tasks:
  - file:
      path: /tmp/foo.txt
      state: touch
...

 

If you attempt to make a connection to a managed node and Ansible has not been configured for SSH, UNREACHABLE most likely be returned. Notice in this example that the SSH server can accept john.doe SSH key (publickey) or john.doe SSH password.

server1.example.com | UNREACHABLE! => {	
    "changed": false,
    "msg": "john.doe@server1.example.com Failed to connect to the host via ssh: Permission denied (publickey, password).",
    "unreachable": true 
}

 

Password authentication

If the SSH server is configured to accept password authentication, the following options can be used.

Passwordless authentication

If the SSH server is configured to accept passwordless authentication, and the managed nodes are a Linux distribution, and OpenSSH is being used on each managed node, the ssh-keygen command can be used to create your users private key (such as id_rsa) and public certificate (such as id_rsa.pub) on the control node. While you could use the openssh_keypair module or user module with the delegate_to: localhost parameter to create your users keypair, this usually doesn't make much since, since the generation of your users keypair is typically a one time task.

ssh-keygen

 

Then the authorized_key module with the --ask-pass can be used to append the users public key to the managed nodes authorized_keys file.

[root@control ~] ansible-playbook ssh.yml --ask-pass

 

If you are not root, you may need to use the become command line flags.

[john.doe@control ~] ansible-playbook ssh.yml --ask-pass --become --become-method=sudo --become-user=root --ask-become-pass

 

By default, this will look for SSH keys in the hidden .ssh directory of the user invoking the playbook, such as /home/john.doe/.ssh/. The --private-key option can be used to specify an SSH key to use.

[john.doe@control ~] ansible-playbook ssh.yml --private-key /usr/local/keys/id_rsa

 

Or ansible_ssh_private_key_file can be used.

---
- hosts: aws
  remote_user: ec2-user
  vars:
    ansible_ssh_private_key_file: /usr/local/keys/id_rsa
  tasks:
  - name: touch /tmp/foo.txt
    file:
      path: /tmp/foo.txt
      state: touch
...

 

If you are connecting to a managed node as some other user, you may need to include remote_user in the playbook.

---
- hosts: aws
  remote_user: ec2-user
  tasks:
  - name: touch /tmp/foo.txt
    file:
      path: /tmp/foo.txt
      state: touch
...

 

After this has been done, then passwordless SSH connections can be made to the managed node without having to use the --ask-pass flag.

[root@control ~] ansible-playbook ssh.yml

 


Under the Hood

It is also really helpful to understand what Ansible is doing under the hood. Let's say you have two systems, the Ansible controller (that's the system Ansible is installed and running on) and a target server.

And let's say you have the following playbook, which uses the ansible.builtin.file module to create and then delete the /tmp/foo.txt file on the target system.

---
- hosts: all
  tasks:
  - ansible.builtin.file:
      path: /tmp/foo.txt
      state: "{{ item }}"
    with_items:
    - touch
    - absent
...

 

Let's run the ansible-playbook command with the --user option and the -vvv (very verbose) flag.

ansible-playbook example.yml --inventory server1.example.com, --user john.doe --ask-pass -vvv

 

So, underneath the hood, a directory is created on the target server, something like this.

/home/john.doe/.ansible/tmp/ansible-tmp-1713408593.1331322-16539-164674748277391

 

This can been seen in the output with the -vvv flag. The output should have something like this.

<server1.example.com> SSH: EXEC \
sshpass -d12 \
ssh -C \
-o ControlMaster=auto \
-o ControlPersist=60s \
-o 'User="john.doe"' \
-o ConnectTimeout=10 \
-o 'ControlPath="/home/john.doe/.ansible/cp/da9b18fba3"' \
server1.example.com \
'/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/john.doe/.ansible/tmp `"&& mkdir "` echo /home/john.doe/.ansible/tmp/ansible-tmp-1713408596.274309-16590-237914960550324 `" && echo ansible-tmp-1713408596.274309-16590-237914960550324="` echo /home/john.doe/.ansible/tmp/ansible-tmp-1713408596.274309-16590-237914960550324 `" ) && sleep 0'"'"''

 

It is important to recognize that the directory will be removed, so for this excercise, I would set the ANSIBLE_KEEP_REMOTE_FILES environment variable to 1 which instructs the Ansible controller to not delete the tmp files and directories that get created on the target server.

export ANSIBLE_KEEP_REMOTE_FILES=1

 

And then the AnsiballZ_file.py Python script is PUT from the Ansible controller to the target server.

<server1.example.com> PUT /home/admin/.ansible/tmp/ansible-local-1681708tty8e7/tmpclmqm36v TO /home/john.doe/.ansible/tmp/ansible-tmp-1713405726.6487887-17223-276200849623319/AnsiballZ_file.py

 

Ansible uses the sshpass command to connect to the target server and then ssh command and the /bin/sh (shell) CLI with the -c (command) flag to run a command on the target server. The command that is run on the target server is almost always /home/john.doe/.ansible/tmp/ansible-tmp/AnsiballZ_<module name>.py, a Python script. Since this command is being run on the target server, this uses the Python CLI on the target server, not Python on the Ansible controller.

<server1.example.com> SSH: EXEC \
sshpass -d12 \
ssh -C \
-o ControlMaster=auto \
-o ControlPersist=60s \
-o 'User="john.doe"' \
-o ConnectTimeout=10 \
-o 'ControlPath="/home/john.doe/.ansible/cp/da9b18fba3"' \
server1.example.com \
'/bin/sh -c '"'"'chmod u+x /home/john.doe/.ansible/tmp/ansible-tmp-1713405726.6487887-17223-276200849623319/ /home/john.doe/.ansible/tmp/ansible-tmp-1713405726.6487887-17223-276200849623319/AnsiballZ_file.py && sleep 0'"'"''

 

So really, Ansible is simply creating Python scripts on the target server and then running the Python script on the target server to do something, which in this example to create and then delete the /tmp/foo.txt file.

[john.doe@server1 ~]$ head -1 /home/webproc/.ansible/tmp/ansible-tmp-1713408594.9606252-16563-253322922038816/AnsiballZ_file.py 
#!/usr/bin/python3

 




Did you find this article helpful?

If so, consider buying me a coffee over at Buy Me A Coffee



Comments


Add a Comment


Please enter d4b8df in the box below so that we can be sure you are a human.