Proxmox - A Network Inside Network With VyOS - PART 3 - Configuring Vyos with Ansible
In Part 2 we went over initial configuration of VyOS including setting up:
- Interfaces
- SSH
- Static routes
- DNS
- DHCP servers
- DNS forwarding
- SNAT
- Firewall rules
Now we want I want to do this in a more automated fashion, my goto is Ansible for something like this configuration management.
VyOS documentation on Automation with Ansible is a bit sparse, I may put a PR in to add some of these examples.
There are some good modules in the vyos.vyos
Ansible collection which I may migrate a lot of this to use If they work well enough, but for now we
will focus on the vyos_config and using a jinja
file for templating.
If you are new to Ansible, then this post is not an introduction to Ansible, so I sugest you check out Jeff Geerlings Ansible 101, this is a fantastic introduction to Ansible and how powerful it can be then come back here.
Step 1 - Ansible directory structure
You can pull from my Github - vyos-lab to get you started.
I will be creating a new branch for each post and my main
branch is what is running in my lab at
the moment so feel free to peek ahead.
.
├── ansible.cfg
├── group_vars
│ ├── all
│ │ └── defaults.yml
│ └── vyos.yml
├── hosts
├── Pipfile
├── Pipfile.lock
├── README.md
├── requirements.yml
├── roles
│ ├── vyos_base
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── base.j2
│ └── vyos_users
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── users.j2
├── vars
│ └── users.yml
├── vyos_base.yml
└── vyos_users.yml
Step 2 - Initial setup
I have used pipenv
to create a python environment so I dont have to install ansible on my local
machine, you can follow along with your installed version of ansible or you can install pipenv
with your OS package manager*.
*Obviously you are using Linux the best OS
Then simply run pipenv install
and when that is complete jump into your virtual python environment
with pipenv shell
.
I also have created a nix flake
devshell if you are feeling that way which is in main
You should have the latest version of ansible
installed along with ansible-lint
and ansible-pylibssh
which replaces paramiko
.
Now you have ansible, you need to configure your hosts
file, I have set up DNS entries on my pi-hole
servers for these so I can use just the hostname, you may need to add extra bits but heres my hosts
file.
# ./hosts
[vyos:children] # So we have a `vyos` group
lab
# This is to create a lab group so we can share variables for other
devices in our lab
[lab]
firewall.lab # ansible_ssh_host=192.168.1.251 <- Add this if you can't
ssh to your vyos machine using hostname only
Now we will set some ansible defaults in ansible.cfg
[defaults]
inventory = hosts # This tells ansible to use the hosts file
we just created as the inventory
# These are just some other defaults I throw in.
host_key_checking = no
retry_files_enabled = false
ANSIBLE_INVENTORY_UNPARSED_FAILED = true
Next we want to set up our group vars group_vars/all/defaults.yml
, This will be used in our jinja
templating to set name-servers
and our static route
to our HOME
router as well as the url
for
adding image updates.
# ./group_vars/all/defaults.yml
name_servers:
- 192.168.1.114 # Set this to whatever dns servers you want
- 192.168.1.115
default_gateway: 192.168.1.254 # Set this to your home router
rolling: true # This sets that we are using rolling vyos not LTS
rolling_url: "https://raw.githubusercontent.com/vyos/vyos-nightly-build/refs/heads/current/version.json"
# Will probalby change this var to just `update_url:` so it can be used
for rolling and LTS
After this we need to tell ansible how it is going to connect to our VyOS
machines, as this uses
network_cli
for its connection. We can set this up in group_vars/vyos.yml
which is why the vyos
group is
in our inventory file as I may be adding a mikrotik
router to my lab to do some more complex networking.
# ./group_vars/vyos.yml
---
ansible_connection: network_cli
ansible_network_os: vyos
ansible_become: true
ansible_become_method: enable
ssh_port: 22
ansible_user: YOUR-USERNAME
ansible_python_interpereter: /usr/bin/env python3
Nearly there, Now we need to set up our user in users.yml
I put this in ./vars/
# ./vars/users.yml
users:
- name: YOUR-USERNAME
ssh_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAATRIMED
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAATRIMED
Go grab or generate an ssh
key pair and add your public key/s to the ssh_keys
list, you can add
multiple keys as the template will loop over them and add them.
We are now all set up and now can create our roles
.
Part 3 - Roles
Base role
I wanted to make my ansible modular and use roles for each area of VyOS
, the first role I want to
look at is the base
role, this sets up the basic settings such as:
- hostname
- ssh
- port
- disable-password auth
- set name-servers
- set static route out
- delete vyos user
- set update config
So if we look at our directory structure we have three directories under each role
. handlers
,tasks
and templates
.
templates
is where all the magic happens, lets have a look at our template for our base role.
# ./roles/vyos_base/templates/base.j2
{# Set hostname #}
set system host-name {{ inventory_hostname_short }}
{# Set SSH #}
set service ssh port {{ ssh_port }}
set service ssh disable-password-authentication
{# Set Nameservers #}
{% for nameserver in name_servers %}
set system name-server {{ nameserver }}
{% endfor %}
{# Set Static route #}
set protocols static route 0.0.0.0/0 next-hop {{ default_gateway }}
{# Remove vyos user #}
delete system login user vyos
{# Set rolling update url #}
{% if rolling %}
set system update-check url '{{ rolling_url }}'
{% endif %}
I have added comments for what each bit does, first we set the hostname
then set ssh
then name-servers
followed by static route
, we then remove the vyos
user then set the update url
Now we don’t want to run this first as if you haven’t already set up a user other than vyos then this
will either fail or lock you out as you will delete the vyos
user.
User role
Lets now have a look at our user role template, again same format as the previous role structure.
{# Set up user and ssh key #}
{% for user in users %}
{% for key in user.ssh_keys %}
system login user {{ user.name }} authentication public-keys {{ key.split()[2] }} key '{{ key.split()[1] }}'
system login user {{ user.name }} authentication public-keys {{ key.split()[2] }} type '{{ key.split()[0] }}'
{% endfor %}
{% endfor %}
In this loop we could add multiple users and multiple keys if we wanted to.
Tasks
Now for each role we have a tasks/main.yml
which all follow the same format
# roles/ROLE_NAME/tasks/main.yml
- name: "Set up ROLE_NAME"
vyos.vyos.vyos_config:
src: CONFIG_NAME.j2
notify: Save config
Handlers
Each role has a handler handlers/main.yml
which is the same and this just saves the config.
---
- name: Save config
vyos.vyos.vyos_config:
save: true
Playboook
The final piece, to be able to run the roles we need a playbook. I put these in the root of the directory. Generally I name them the same as the role to make it easier.
./vyos_base.yml
---
- name: Set up VyOS Base
hosts: vyos
gather_facts: true
order: inventory
roles:
- vyos_base
./vyos_users.yml
---
- name: Set up VyOS Users
hosts: vyos
gather_facts: false
order: inventory
vars_files:
- ./vars/users.yml
roles:
- vyos_users
You will notice I add vars_files:
this is so we can use the users.yml
as I see it as a gobal
variable and not a group
or host
var.
Part 4 - Running the playbook
Users
Now, If we should be in a position to run our playbooks, Again important to note we want to add our
user first then run the base
role.
Now since I have set up my vyos user and vyos password to be vyos from Part 1 the first run I will have to use a command line variable. So I will run:
ansible-playbook vyos_users.yml -e'ansible_user=vyos ansible_password=vyos' --diff
I use the --diff
flag to show what has changed, helps when debugging when adding --check
which
will run in check
mode and not actually make any changes, but show what changes
would be made.
For demonstration, I added a new user called blog
with same keys as my user. My output looks like
this:
NOTE: adjusted output formatting for better reading
PLAY [Set up VyOS Users] *********************************************
TASK [vyos_users : Set up Users] *************************************
[system login user]
+ blog {
+ authentication {
+ public-keys oliverkelly@laptop {
+ key "AAAAC3NzaC1lZDI1NTE5AAAATRIMED"
+ type "ssh-ed25519"
+ }
+ }
+ }
[edit]
[WARNING]:To ensure idempotency and correct diff the input configuration
lines should be similar to how they appear if present in the running
configuration on device including the indentation
changed: [firewall.lab]
RUNNING HANDLER [vyos_users : Save config] ***************************
changed: [firewall.lab]
PLAY RECAP ***********************************************************
firewall.lab: ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
Base
For demonstration purposes I deleted some config so that the ansible would add it back in.
Heres the output of ansible-playbook vyos_base.yml --diff
NOTE: adjusted output formatting for better reading
PLAY [Set up VyOS Base] **********************************************
TASK [Gathering Facts] ***********************************************
ok: [firewall.lab]
TASK [vyos_base : Set up base configuration] *************************
[service ssh]
+ disable-password-authentication
[system login user]
- vyos {
- }
[system]
+ host-name "firewall"
+ update-check {
+ url "https://raw.githubusercontent.com/vyos/vyos-nightly-build/refs/heads/current/version.json"
+ }
[edit]
[WARNING]:To ensure idempotency and correct diff the input configuration
lines should be similar to how they appear if present in the running
configuration on device including the indentation
changed: [firewall.lab]
RUNNING HANDLER [vyos_base : Save config] *****************************
changed: [firewall.lab]
PLAY RECAP ************************************************************
firewall.lab : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
As you can see, this is a nice way to have our config for vyos as code and this is just the first bit. In Part 4 we will look at creating our networks with a new role.