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.
- --user command line option
- remote_user parameter
- ansible_user variable in your inventory file
- ansible_user variable in your group_vars file
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.
- --ask-pass command line flag
- configure the default hosts file with your SSH username and password
- create a vault encrypted file that contains your SSH username and password in group_vars or a vault encrypted file that contains your SSH username and password in vars_files
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