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.