---
title: "Ansible KVM Router Lab Part 5"
date: 2021-10-17
draft: false
tags: ["linux", "kvm", "libvirt", "virsh", "ansible", "bash"]
authors: ["trent"]
post: 30
---
date: 2021-10-17

## Introduction
This is Part 5 of a multi-part series of blog posts for building a
[router lab](https://github.com/TrentSPalmer/router-lab){target="_blank"}
automatically using a series of bash scripts and ansible.

[Ansible KVM Router Lab Part 1](/posts/ansible-kvm-router-lab-part-1/){target="_blank"}
is an overview.

In [Ansible KVM Router Lab Part 2](/posts/ansible-kvm-router-lab-part-2/){target="_blank"},
I break down the script 
[build_vms.bash](https://github.com/TrentSPalmer/router-lab/blob/master/build_vms.bash){target="_blank"}.

In [Ansible KVM Router Lab Part 3](/posts/ansible-kvm-router-lab-part-3/){target="_blank"},
I explain
[define_bridge_networks.bash](https://github.com/TrentSPalmer/router-lab/blob/master/define_bridge_networks.bash){target="_blank"}
and
[shutdown_vms.bash](https://github.com/TrentSPalmer/router-lab/blob/master/shutdown_vms.bash){target="_blank"}
scripts which are used to construct the lab.

In [Ansible KVM Router Lab Part 4](/posts/ansible-kvm-router-lab-part-4/){target="_blank"},
I explain
[connect_vms_to_bridges.bash](https://github.com/TrentSPalmer/router-lab/blob/master/connect_vms_to_bridges.bash){target="_blank"},
[start_vms.bash](https://github.com/TrentSPalmer/router-lab/blob/master/start_vms.bash){target="_blank"},
and [rebuild_known_hosts.bash](https://github.com/TrentSPalmer/router-lab/blob/master/rebuild_known_hosts.bash){target="_blank"}
scripts which are used to construct the lab.

In this post I explain how I use Ansible to finish constructing the lab.

In [Ansible KVM Router Lab Part 6](/posts/ansible-kvm-router-lab-part-6/){target="_blank"},
I explain
[disconnect_vms_from_bridges.bash](https://github.com/TrentSPalmer/router-lab/blob/master/disconnect_vms_from_bridges.bash){target="_blank"},
[undefine_and_remove_vms.bash](https://github.com/TrentSPalmer/router-lab/blob/master/undefine_and_remove_vms.bash){target="_blank"},
and [remove_bridge_networks](https://github.com/TrentSPalmer/router-lab/blob/master/remove_bridge_networks.bash){target="_blank"}
which are used to destroy the lab.

## Setup Ansible
* Configure ansible host file
```cfg
# ~/.ansible.cfg
[defaults]
inventory = ~/router-lab/ansible/hosts.yml
```
* Setup bashrc
```bash
# ~/.bashrc
export LIBVIRT_DEFAULT_URI="qemu+ssh://<user>@<server>/system"

alias ansible-pb=anspb
anspb() {
  ANS_DIR=~/router-lab/ansible/playbooks;
  echo Changing to "${ANS_DIR}" and executing: ansible-playbook "${@}"
  (cd $ANS_DIR || exit ; ansible-playbook "${@}")
}
```
* install apps
```bash
apt install ansible ansible-lint
```

## Run Ansible
```shell
ansible-pb build_out_routers.yml -K
```
or if you want to first update all the clients
```shell
ansible-pb update_and_build.yml -K
```
## Ansible Tasks
This is an explaination of the tasks in the Ansible Playbook.
Playbooks are executed from top to bottom.
### Install `dnsmasq`, `iptables-persistent`
This task is only run against the first and second lab clients as
they are the routers.
### Install `traceroute`
Traceroute is parsed in a later task to confirm that traffic is
following the correct route.
(Also incidentally installs `needrestart` and `screen`.)
### Backup `/etc/network/interfaces`
This is a simple bash command that tests if `/etc/network/interfaces.bak`
exists, and if not creates it.
### Update Network Config
This task updates `/etc/network/interfaces` in all the lab clients
to describe the network interfaces needed to connect to each other.

For instance, here is the new `/etc/network/interfaces` file for _dnettwo_.
```cfg
# /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug enp1s0
iface enp1s0 inet dhcp

# The primary network interface
allow-hotplug enp7s0
iface enp7s0 inet dhcp

auto enp8s0
iface enp8s0 inet static
        address 10.4.4.1
        network 10.4.4.0
        netmask 255.255.255.0
        broadcast 10.4.4.255
```
### Backup `/etc/dnsmasq.conf`
This is a simple bash command that tests if `/etc/dnsmasq.conf.bak`
exists, and if not creates it. (only applies to the two router clients)
### Configure `dnsmasq`
This task copies the templates for `/etc/dnsmasq.conf` to each of
the two router clients.

`dnsmasq` is used to provide _DHCP_ (and name resolution).
For instance, here is the new `/etc/dnsmasq.conf` for _dnetone_.
```cfg
# /etc/dnsmasq.conf
dhcp-range=10.5.5.50,10.5.5.150
listen-address=127.0.0.1, 10.5.5.1
```
### Configure Network _ifup_
This applies to all the lab clients except for the first one,
changes the default route. A bash script is copied from
template to `/etc/network/if-up.d/ifup-script`.

For instance here is `ifup-script` for _dnetthree_.
```bash
#!/bin/bash
# /etc/network/if-up.d/ifup-script

default_dev="$(ip route | head -1 | awk '{print $5}')"
echo "${default_dev}"

if [ "${default_dev}" == "enp1s0" ]
then
  ip route del default via 10.55.44.1 dev enp1s0
fi

if [ "${default_dev}" != "enp7s0" ]
then
  ip route add default via 10.4.4.1 dev enp7s0
fi
```
### Restart Network and `dnsmasq`
This is sequential:

1. _enp7s0_ is restarted on _dnetone_
2. `dnsmasq` is restarted on _dnetone_, offering service on _enp7s0_
3. _enp7s0_ and _enp8s0_ are restarted on _dnettwo_, thus soliciting dhcp service on _enp7s0_, and triggering `/etc/network/if-up.d/ifup-script`
4. `dnsmasq` is restarted on _dnettwo_, offering service on _enp8s0_
5. _enp7s0_ is restarted on _dnetthree_, _dnetfour_, and _dnetfive_, thus soliciting dhcp service on _enp7s0_, and triggering `/etc/network/if-up.d/ifup-script`

### Backup `/etc/sysctl.conf`
This is a simple bash command that tests if `/etc/sysctl.conf.bak`
exists, and if not creates it. (only applies to the two router clients)

### Enable _ipv4 forwarding_
This is a simple bash command that uncomments the option for _ipv4 forwarding_
in `/etc/sysctl.conf`, applies only to the two routers.
```cfg
# /etc/sysctl.conf
...
# this
#net.ipv4.ip_forward=1
...
# becomes this
net.ipv4.ip_forward=1
...
```

### Start _ipv4 forwarding_
This simple bash command starts _ipv4 forwarding_, applies only
to the two routers.
```bash
bash -c "sysctl -w net.ipv4.ip_forward=1"
```

### Configure `iptables` _workaround_
This applies only to the two router clients.
From `iptables`'s point of view, the ansible connection isn't a RELATED INPUT
connection, thus it is necessary to bring up a firewall in a two-step
process that involves first ACCEPTING RELATED OUTPUT connections in a workaround.

From ansible template, the following is copied to `/dev/shm/iptables_workaround`
```iptables
# /dev/shm/iptables_workaround
*filter
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]

-A INPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED
-A OUTPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED

COMMIT
```

### Apply `iptables` _workaround_
This applies only to the two router clients.
The following command is dispatched to apply the above _iptables_workaround_:
```bash
bash -c "iptables-restore < /dev/shm/iptables_workaround"
```

### Configure `iptables`
This applies only to the two router clients.

From ansible template the following is copied to `/etc/iptables/rules.v4` on _dnetone_.
```iptables
*nat
-A POSTROUTING -o enp1s0 -j MASQUERADE
COMMIT

*filter
-A INPUT -i lo -j ACCEPT
# allow ssh, so that we do not lock ourselves
-A INPUT -i enp1s0 -p tcp -m tcp --dport 22 -j ACCEPT
# allow incoming traffic to the outgoing connections,
# et al for clients from the private network
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# prohibit everything else incoming
-A INPUT -i enp1s0 -j DROP
COMMIT
```

From ansible template the following is copied to `/etc/iptables/rules.v4` on _dnettwo_.
```iptables
*nat
-A POSTROUTING -o enp7s0 -j MASQUERADE
COMMIT

*filter
-A INPUT -i lo -j ACCEPT
# allow ssh, so that we do not lock ourselves
-A INPUT -i enp7s0 -p tcp -m tcp --dport 22 -j ACCEPT
# allow incoming traffic to the outgoing connections,
# et al for clients from the private network
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# prohibit everything else incoming
-A INPUT -i enp7s0 -j DROP
COMMIT
```

### Apply `iptables` firewall
This applies only to the two router clients.
The following command is dispatched to apply the above from `/etc/iptables/rules.v4`:
```bash
bash -c "iptables-restore < /etc/iptables/rules.v4"
```

### `traceroute` test
The following script is dispatched to _dnettwo_:
```bash
#!/bin/bash

RESULT="$(traceroute 8.8.8.8)"

FIRST_HOP="$(echo "${RESULT}" | head -2 | tail -1 | awk '{print $2}')"

if [ "${FIRST_HOP}" == "10.5.5.1" ]
then
  exit 0
else
  exit 1
fi
```
The following script is dispatched to _dnetthree_, _dnetfour_, and _dnetfive_:
```bash
#!/bin/bash

RESULT="$(traceroute 8.8.8.8)"

FIRST_HOP="$(echo "${RESULT}" | head -2 | tail -1 | awk '{print $2}')"

if [ "${FIRST_HOP}" != "10.4.4.1" ]
then
  exit 1
fi

SECOND_HOP="$(echo "${RESULT}" | head -3 | tail -1 | awk '{print $2}')"

if [ "${SECOND_HOP}" == "10.5.5.1" ]
then
  exit 0
else
  exit 1
fi
```

## To Be Continued
In [Ansible KVM Router Lab Part 6](/posts/ansible-kvm-router-lab-part-6/){target="_blank"},
I explain
[disconnect_vms_from_bridges.bash](https://github.com/TrentSPalmer/router-lab/blob/master/disconnect_vms_from_bridges.bash){target="_blank"},
[undefine_and_remove_vms.bash](https://github.com/TrentSPalmer/router-lab/blob/master/undefine_and_remove_vms.bash){target="_blank"},
and [remove_bridge_networks](https://github.com/TrentSPalmer/router-lab/blob/master/remove_bridge_networks.bash){target="_blank"}
which are used to destroy the lab.
