Ansible Automation (System Updates)

Problem: I have several systems with various operating systems within the starlabs (Brian's home lab) network that I need to keep up to date.

Solution: I automated this process so that I can update all of them from the control plane node.

Tools utilized for this project

  • ansible
  • dnf
  • apt

Setup

On Control Plane Node: Be sure you copy the config and enable logging

root@ironman6:~# mv /etc/ansible/ansible.cfg /etc/ansible/ansible-default.cfg.bak
root@ironman6:~# cp -a /etc/ansible/ansible-sample.cfg /etc/ansible/ansible.cfg
root@ironman6:~# grep log_path /etc/ansible/ansible.cfg
;log_path=
root@ironman6:~# vim /etc/ansible/ansible.cfg
root@ironman6:~# grep log_path /etc/ansible/ansible.cfg
log_path=/var/log/ansible.log
root@ironman6:~# touch /var/log/ansible.log

Inventory

On Control Plane Node: I added my systems to my inventory here, at the bottom of the file

/etc/ansible/hosts

(truncated output)
[starlabs]
veronica ansible_host=172.16.0.8 ansible_port=22 ansible_user=ansible
ironman6 ansible_host=127.0.0.1
ironman9 ansible_host=172.16.0.8 ansible_port=2222
tardis ansible_host=172.16.0.17

[all:vars]
ansible_python_interpreter=/usr/bin/python3

On Control Plane Node: Here is my update inventory with run counts.

root@ironman6:~# history | grep -v grep | grep -v "#" | grep ansible-playbook\ \-v | grep update | awk '{print $2,$3,$4,$5,$6,$7}' | sort | uniq -c | sort -rnk1 | head -5
     33 ansible-playbook -v /playbooks/update-veronica.starlabs.us.yml --become -u ansible
     22 ansible-playbook -v /playbooks/update-ironman6.starlabs.us.yml --become -u ansible
     17 ansible-playbook -v /playbooks/update-ironman9.starlabs.us.yml --become -u ansible
     14 ansible-playbook -v /playbooks/update-tardis.starlabs.us.yml --become -u ansible

Ubuntu Setup

On Server: Add the ansible user

root@ironman6:~# adduser ansible
Adding user `ansible' ...
Adding new group `ansible' (1006) ...
Adding new user `ansible' (1006) with group `ansible' ...
Creating home directory `/home/ansible' ...
Copying files from `/etc/skel' ...
New password: 
Retype new password: 
passwd: password updated successfully
Changing the user information for ansible
Enter the new value, or press ENTER for the default
        Full Name []: 
        Room Number []: 
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] y
root@ironman6:~# usermod -aG sudo ansible

On Server: Make the ssh key

Note: For extra security, set a passphrase. But then you'll have to enter that passphrase everytime you run ansible. I prefer to just be sure my control plane node never ever gets compromised.

root@ironman6:~# su - ansible
ansible@ironman6:~$ mkdir .ssh
ansible@ironman6:~$ ssh-keygen -t rsa -b 4096 -f "/home/ansible"/.ssh/ssh_key.key -C "starlabs-$(date -u +%Y-%m-%d-%H:%M:%S%z)"; v_KEY_CREATED=true
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ansible/.ssh/ssh_key.key
Your public key has been saved in /home/ansible/.ssh/ssh_key.key.pub
The key fingerprint is:
SHA256:GT59w4qz0K6Hx9gWZJKVTGsOERdQvNXE4VzuRXOJPtk starlabs-2023-06-05-05:42:49+0000
The key's randomart image is:
+---[RSA 4096]----+
|      +B=o +o.o.+|
|       o=..oo+ oo|
|      .o+o  + + .|
|      o=++ . = E |
|       +S . + o  |
|       ..o o .   |
|      .=+..      |
|      oo*o       |
|      .=o        |
+----[SHA256]-----+

On Server: Authorize the key

ansible@ironman6:~$ touch ~/.ssh/authorized_keys
ansible@ironman6:~$ chmod 600 ~/.ssh/authorized_keys
ansible@ironman6:~$ cat /home/ansible/.ssh/ssh_key.key.pub > /home/ansible/.ssh/authorized_keys

On Server: Get the private key

ansible@ironman6:~$ cat /home/ansible/.ssh/ssh_key.key
-----BEGIN OPENSSH PRIVATE KEY-----
hidden output
-----END OPENSSH PRIVATE KEY-----

On Control Plane Node: Copy the private key to the control plane node

root@ironman6:~# vim ~/.ssh/ironman6.starlabs.us.key
root@ironman6:~# chmod 600 ~/.ssh/ironman6.starlabs.us.key

On Control Plane Node: Test out the private key

root@ironman6:~# ssh ansible@ironman6.starlabs.us -i /root/.ssh/ironman6.starlabs.us.key -p22
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-1107-raspi aarch64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro
New release '22.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.


Last login: Fri May  3 10:18:17 2024 from 127.0.0.1
ansible@ironman6:~$

On Control Plane Node: Test remotely running a command on the server.

root@ironman6:~# SERVER=ironman6;ansible $SERVER -m shell -a 'cat /etc/os-release' -u ansible --key-file ~/.ssh/$SERVER.starlabs.us.key
ironman6 | CHANGED | rc=0 >>
NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

On Control Plane Node: Add the update playbook

Note: You can add multiple hosts in the "hosts:" spot, seperated by spaces.

/playbooks/update-ironman6.starlabs.us.yml

---
- hosts: ironman6
  vars:
    ansible_ssh_private_key_file: ~/.ssh/ironman6.starlabs.us.key
  tasks:
    - name: Update and upgrade apt packages
      become: true
      apt:
              upgrade: yes
              update_cache: yes
              #cache_valid_time: 86400 #One day

    - name: Update snap packages
      become: true
      command: snap refresh

On Server: Remove the sudo password from the ansible user by adding to the bottom of /etc/sudoers

ansible ALL=(ALL) NOPASSWD: ALL

It should be below the #includedir /etc/sudoers.d part

You can use visudo to do this

root@ironman6:~# visudo
# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

^G Get Help      ^O Write Out     ^W Where Is      ^K Cut Text      ^J Justify       ^C Cur Pos       M-U Undo         M-A Mark Text    M-] To Bracket   M-Q Previous
^X Exit          ^R Read File     ^\ Replace       ^U Paste Text    ^T To Spell      ^_ Go To Line    M-E Redo         M-6 Copy Text    ^Q Where Was     M-W Next

root@ironman6:~# tail -2 /etc/sudoers
#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

On Control Plane Node: Remotely run updates for the server.

root@ironman6:~# ansible-playbook -v /playbooks/update-ironman6.starlabs.us.yml --become -u ansible
Using /etc/ansible/ansible.cfg as config file

PLAY [ironman6] ******************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************
ok: [ironman6]

TASK [Update and upgrade apt packages] *******************************************************************************************************
changed: [ironman6] => {"changed": true, "msg": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculat
(truncated output)
TASK [Update snap packages] ******************************************************************************************************************
changed: [ironman6] => {"changed": true, "cmd": ["snap", "refresh"], "delta": "0:01:09.182339", "end": "2024-05-25 13:53:42.117539", "msg": ""
PLAY RECAP ***********************************************************************************************************************************
ironman6                   : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Debian Setup

On Server: Add the ansible user

root@veronica:~# adduser ansible
Adding user `ansible' ...
Adding new group `ansible' (1006) ...
Adding new user `ansible' (1006) with group `ansible' ...
Creating home directory `/home/ansible' ...
Copying files from `/etc/skel' ...
New password: 
Retype new password: 
passwd: password updated successfully
Changing the user information for ansible
Enter the new value, or press ENTER for the default
        Full Name []: 
        Room Number []: 
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] y
root@veronica:~# usermod -aG sudo ansible

On Server: Make the ssh key

Note: For extra security, set a passphrase. But then you'll have to enter that passphrase everytime you run ansible. I prefer to just be sure my control plane node never ever gets compromised.

root@veronica:~# su - ansible
ansible@veronica:~$ mkdir .ssh
ansible@veronica:~$ ssh-keygen -t rsa -b 4096 -f "/home/ansible"/.ssh/ssh_key.key -C "starlabs-$(date -u +%Y-%m-%d-%H:%M:%S%z)"; v_KEY_CREATED=true
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ansible/.ssh/ssh_key.key
Your public key has been saved in /home/ansible/.ssh/ssh_key.key.pub
The key fingerprint is:
SHA256:GT59w4qz0K6Hx9gWZJKVTGsOERdQvNXE4VzuRXOJPtk starlabs-2023-06-05-05:42:49+0000
The key's randomart image is:
+---[RSA 4096]----+
|      +B=o +o.o.+|
|       o=..oo+ oo|
|      .o+o  + + .|
|      o=++ . = E |
|       +S . + o  |
|       ..o o .   |
|      .=+..      |
|      oo*o       |
|      .=o        |
+----[SHA256]-----+

On Server: Authorize the key

ansible@veronica:~$ touch ~/.ssh/authorized_keys
ansible@veronica:~$ chmod 600 ~/.ssh/authorized_keys
ansible@veronica:~$ cat /home/ansible/.ssh/ssh_key.key.pub > /home/ansible/.ssh/authorized_keys

On Server: Get the private key

ansible@veronica:~$ cat /home/ansible/.ssh/ssh_key.key
-----BEGIN OPENSSH PRIVATE KEY-----
hidden output
-----END OPENSSH PRIVATE KEY-----

On Control Plane Node: Copy the private key to the control plane node

root@ironman6:~# vim ~/.ssh/veronica.starlabs.us.key
root@ironman6:~# chmod 600 ~/.ssh/veronica.starlabs.us.key

On Control Plane Node: Test out the private key

root@ironman6:~# ssh ansible@veronica.starlabs.us -i /root/.ssh/veronica.starlabs.us.key -p22
Linux veronica.starlabs.us 4.19.0-26-amd64 #1 SMP Debian 4.19.304-1 (2024-01-09) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri May 10 20:38:38 2024 from 172.16.0.13
ansible@veronica:~$

On Control Plane Node: Test remotely running a command on the server.

root@ironman6:~# SERVER=veronica;ansible $SERVER -m shell -a 'cat /etc/os-release' -u ansible --key-file ~/.ssh/$SERVER.starlabs.us.key
veronica | CHANGED | rc=0 >>
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

On Control Plane Node: Add the update playbook

Note: You can add multiple hosts in the "hosts:" spot, seperated by spaces.

/playbooks/update-veronica.starlabs.us.yml

---
- hosts: veronica
  vars:
    ansible_ssh_private_key_file: ~/.ssh/veronica.starlabs.us.key
  tasks:
    - name: Update and upgrade apt packages
      become: true
      apt:
              upgrade: yes
              update_cache: yes
              #cache_valid_time: 86400 #One day

#    - name: Update snap packages
#      become: true
#      command: snap refresh

On Server: Remove the sudo password from the ansible user by adding to the bottom of /etc/sudoers

ansible ALL=(ALL) NOPASSWD: ALL

It should be below the #includedir /etc/sudoers.d part

You can use visudo to do this

root@veronica:~# visudo
# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

^G Get Help      ^O Write Out     ^W Where Is      ^K Cut Text      ^J Justify       ^C Cur Pos       M-U Undo         M-A Mark Text    M-] To Bracket   M-Q Previous
^X Exit          ^R Read File     ^\ Replace       ^U Paste Text    ^T To Spell      ^_ Go To Line    M-E Redo         M-6 Copy Text    ^Q Where Was     M-W Next

root@veronica:~# tail -2 /etc/sudoers
#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

On Control Plane Node: Remotely run updates for the server.

root@ironman6:~# ansible-playbook -v /playbooks/update-veronica.starlabs.us.yml --become -u ansible
Using /etc/ansible/ansible.cfg as config file

PLAY [veronica] ******************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************
ok: [veronica]

TASK [Update and upgrade apt packages] *******************************************************************************************************
changed: [veronica] => {"changed": true, "msg": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculat
(truncated output)
TASK [Update snap packages] ******************************************************************************************************************
changed: [veronica] => {"changed": true, "cmd": ["snap", "refresh"], "delta": "0:01:09.182339", "end": "2024-05-25 13:53:42.117539", "msg": ""
PLAY RECAP ***********************************************************************************************************************************
veronica                   : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

AlmaLinux 8 (and RHEL 8) Setup

On Server: Add the ansible user

[root@ironman9 ~]# adduser ansible
[root@ironman9 ~]# usermod -aG wheel ansible

On Server: Make the ssh key

Note: For extra security, set a passphrase. But then you'll have to enter that passphrase everytime you run ansible. I prefer to just be sure my control plane node never ever gets compromised.

[root@ironman9 ~]# su - ansible
[ansible@ironman9 ~]$ mkdir .ssh
[ansible@ironman9 ~]$ ssh-keygen -t rsa -b 4096 -f "/home/ansible"/.ssh/ssh_key.key -C "starlabs-$(date -u +%Y-%m-%d-%H:%M:%S%z)"; v_KEY_CREATED=true
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ansible/.ssh/ssh_key.key
Your public key has been saved in /home/ansible/.ssh/ssh_key.key.pub
The key fingerprint is:
SHA256:GT59w4qz0K6Hx9gWZJKVTGsOERdQvNXE4VzuRXOJPtk starlabs-2023-06-05-05:42:49+0000
The key's randomart image is:
+---[RSA 4096]----+
|      +B=o +o.o.+|
|       o=..oo+ oo|
|      .o+o  + + .|
|      o=++ . = E |
|       +S . + o  |
|       ..o o .   |
|      .=+..      |
|      oo*o       |
|      .=o        |
+----[SHA256]-----+

On Server: Authorize the key

[ansible@ironman9 ~]$ touch ~/.ssh/authorized_keys
[ansible@ironman9 ~]$ chmod 600 ~/.ssh/authorized_keys
[ansible@ironman9 ~]$ cat /home/ansible/.ssh/ssh_key.key.pub > /home/ansible/.ssh/authorized_keys

On Server: Get the private key

[ansible@ironman9 ~]$ cat /home/ansible/.ssh/ssh_key.key
-----BEGIN OPENSSH PRIVATE KEY-----
hidden output
-----END OPENSSH PRIVATE KEY-----

On Control Plane Node: Copy the private key to the control plane node

root@ironman6:~# vim ~/.ssh/ironman9.starlabs.us.key
root@ironman6:~# chmod 600 ~/.ssh/ironman9.starlabs.us.key

On Control Plane Node: Test out the private key

root@ironman6:~# ssh ansible@ironman9.starlabs.us -i /root/.ssh/ironman9.starlabs.us.key -p2222
Last login: Mon Jan  1 08:38:57 2024 from 172.16.0.13
[ansible@ironman9 ~]$ 

On Control Plane Node: Test remotely running a command on the server.

root@ironman6:~# SERVER=ironman9;ansible $SERVER -m shell -a 'cat /etc/os-release' -u ansible --key-file ~/.ssh/$SERVER.starlabs.us.key
ironman9 | CHANGED | rc=0 >>
NAME="AlmaLinux"
VERSION="8.8 (Sapphire Caracal)"
ID="almalinux"
ID_LIKE="rhel centos fedora"
VERSION_ID="8.8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="AlmaLinux 8.8 (Sapphire Caracal)"
ANSI_COLOR="0;34"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:almalinux:almalinux:8::baseos"
HOME_URL="https://almalinux.org/"
DOCUMENTATION_URL="https://wiki.almalinux.org/"
BUG_REPORT_URL="https://bugs.almalinux.org/"

ALMALINUX_MANTISBT_PROJECT="AlmaLinux-8"
ALMALINUX_MANTISBT_PROJECT_VERSION="8.8"
REDHAT_SUPPORT_PRODUCT="AlmaLinux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.8"

On Control Plane Node: Add the update playbook

Note: You can add multiple hosts in the "hosts:" spot, seperated by spaces.

/playbooks/update-ironman9.starlabs.us.yml

---
- hosts: ironman9
  vars:
    ansible_ssh_private_key_file: ~/.ssh/ironman9.starlabs.us.key
  tasks:
    - name: Update all packages
      become: true
      dnf:
        state: latest
#    - name: Restart services
#      service:
#        state: restarted
#        name: "{{ item }}"
#      loop:
#        - httpd
#        - mysql 
#    - name: Update snap packages
#      become: true
#      command: snap refresh

On Server: Remove the sudo password from the ansible user by adding to the bottom of /etc/sudoers

ansible ALL=(ALL) NOPASSWD: ALL

It should be below the #includedir /etc/sudoers.d part

You can use visudo to do this

root@ironman9:~# visudo
## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment)
#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

root@ironman9:~# tail -2 /etc/sudoers
#includedir /etc/sudoers.d
ansible ALL=(ALL) NOPASSWD: ALL

On Control Plane Node: Remotely run updates for the server.

[root@ironman9 ~]# ansible-playbook -v /playbooks/update-ironman9.starlabs.us.yml --become -u ansible
Using /etc/ansible/ansible.cfg as config file

PLAY [ironman9] ******************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************
ok: [ironman9]

TASK [Update and upgrade apt packages] *******************************************************************************************************
changed: [ironman9] => {"changed": true, "msg": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculat
(truncated output)
TASK [Update snap packages] ******************************************************************************************************************
changed: [ironman9] => {"changed": true, "cmd": ["snap", "refresh"], "delta": "0:01:09.182339", "end": "2024-05-25 13:53:42.117539", "msg": ""
PLAY RECAP ***********************************************************************************************************************************
ironman9                   : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0