Provision a new Ansible node without SSH public key and Python
Let's assume that we want to provision a new node targethost-01.tld
which will likely be a fresh VM somewhere.
The base setup resembles:
- a minimal Ubuntu environment,
- without an appropriate Python version,
- without any SSH public keys and
- no dedicated Ansible user (just
root
).
Our goals are:
- Install the required Python dependencies
- Setup users
ansible
anddev
with SSH access using public keys
We will have to create four files:
ansible.cfg
inventory
playbooks/bootstrap-python.yml
playbooks/bootstrap.yml
Furthermore we should have a folder public
which contains our SSH public keys that we want to store in the corresponding authorized_keys
file on the node. For the convenience, we store the SSH private key of the ansible
user in private/id_rsa
. IF the file should be stored somewhere else, just edit ansible.cfg
accordingly.
The ansible.cfg
contains:
[defaults]
inventory = ./inventory
remote_user = ansible
#forks = 20
#gathering = smart
#fact_caching = jsonfile
#fact_caching_connection = ./facts
#fact_caching_timeout = 600
log_path = ./ansible.log
nocows = 1
private_key_file = ./private/id_rsa
host_key_checking = false
[privilege_escalation]
become = false
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=60
control_path = %(directory)s/%%h-%%r
pipelining = True
timeout = 10
This configuration comes with sane defaults and some performance optimizations for SSH connections. For even better performance, forks
, gathering
and fact_*
settings could be enabled.
We also need an inventory
file containing:
[ubuntu]
targethost-01.tld ansible_python_interpreter=/usr/bin/python2.7
Playbook bootstrap-python.yml
:
- hosts: ubuntu
gather_facts: false
become: true
pre_tasks:
- name: Generate locals
raw: export LC_ALL="de_DE.UTF-8"; locale-gen de_DE.UTF-8
changed_when: false
- name: install python 2
raw: test -e /usr/bin/python || (apt -y update && apt -y install python-minimal)
changed_when: false
- setup: # gather facts
- hosts: alpine
gather_facts: false
become: true
pre_tasks:
- name: install python 2
raw: test -e /usr/bin/python || (apk --update add python)
changed_when: false
- setup: # gather facts
Playbook bootstrap.yml
:
---
- import_playbook: bootstrap-python.yml
- hosts: all
vars:
users:
- ansible
- dev
tasks:
- name: 'Create users with corresponding groups'
user:
name: "{{ item }}"
groups: "users"
with_items: "{{ users }}"
- name: 'Add corresponding authorized_keys to each user'
authorized_key:
user: "{{ item }}"
state: present
# Public key file has to be named according to the user,
# e.g. 'ansible.ssh.pub'
key: "{{ lookup('file', '../public/' + item + '.ssh.pub') }}"
with_items: "{{ users }}"
Execute the following command on your Ansible control machine (e.g. your local machine):
$ ansible-playbook \
--inventory-file=my-inventory \
--ask-pass \
--user root \
playbooks/bootstrap.yml \
--limit targethost-01.tld
This is what happens as a result:
- Ansible connects to the node
targethost-01.tld
via SSH using password credentials. - It bootstraps Python if the binary cannot be found. As the node is in the host group
ubuntu
, Python will be installed usingapt
. - Ansible then creates the users, adds them to their corresponding groups and provisions the
authorized_keys
with the public keys underpublic/
.
If the provisioning was successful, any subsequent run of Ansible against that node should use the SSH key:
$ ansible targethost-01.tld -m ping
About the author
Jan Beilicke is a long-time IT professional and full-time nerd. Open source enthusiast, advocating security and privacy. Sees the cloud as other people's computers. Find him on Mastodon.