Overview
In this blog post, we will go through how to create a simple Ansible Playbook to configure a Juniper SRX Firewall. This blog post assumes prior knowledge of Ansible and Junos OS. If you are not familiar with Ansible, please check out my other blog post here
Ansible can help create and manage configuration changes centrally by pushing them out to all/or some devices and requires no extra software to be installed on the devices. One of Ansible's greatest strengths is idempotence where the same configuration is maintained even if we run the playbook multiple times.
Our goal here is to configure a Juniper SRX Firewall using Ansible. The configuration includes time zone, hostname, L3 interfaces, static route, security zones and NAT.
Prerequisite
Initial Configurations
Before we start using Ansible, we need to ensure that Ansible can successfully establish an SSH connection to the device. Configuring username, password and management IP are some of the prerequisites before running Ansible successfully.
The following are the minimum required configurations on the SRX.
root# show | compare
[edit system]
+ root-authentication {
+ encrypted-password "$6$vbMCkv58$.hoAjcSyOvn.3qT0ardbMBhqcTRXnKGgLambzUeVl7748ItC9UPpO6VGFumn1sUaCbqNn6Y0PYWVAp..ph9Zr."; ## SECRET-DATA
+ }
+ domain-name packet.lan;
[edit interfaces fxp0 unit 0]
+ family inet {
+ address 10.10.50.16/16;
+ }
Junos Ansible Collection
Please ensure that you install the Junos ansible collection by using the following command.
sureshv@mac:~/Documents|⇒ ansible-galaxy collection install junipernetworks.junos
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/junipernetworks-junos-4.1.0.tar.gz to /Users/sureshv/.ansible/tmp/ansible-local-703e7nkjwm9/tmp0hb84cjz/junipernetworks-junos-4.1.0-lbwqptx9
Installing 'junipernetworks.junos:4.1.0' to '/Users/sureshv/.ansible/collections/ansible_collections/junipernetworks/junos'
junipernetworks.junos:4.1.0 was installed successfully
Skipping 'ansible.netcommon:4.1.0' as it is already installed
Skipping 'ansible.utils:2.7.0' as it is already installed
Enable netconf SSH Subsystem
When you look at Junos Ansible modules, you will notice that one of the requirements is defined as "this module requires the netconf system service be enabled on the remote device being managed"
To enable access to the netconf ssh subsystem using the default SSH port (22), configure the following.
[edit]
root@srx# set system services netconf ssh
File Structure
I've created the following directories and files in my project folder called ansible
. We will break them down one by one shortly.
sureshv@mac:~/Documents/ansible|⇒ tree
.
├── ansible.cfg
├── inventory
│ ├── group_vars
│ │ └── srx.yml
│ ├── host_file.ini
│ └── host_vars
│ ├── branch_srx.yml
└── playbook.yml
ansible.cfg
Certain settings in Ansible are adjustable via a configuration file ansible.cfg
I always prefer to create a new ansible.cfg
file on the directory that I'm working on rather than editing the default file which is located at /etc/ansible/
. The configuration file that is located in the current working directory always takes precedence over the default file.
I've made the following changes to the config file to suit my lab environment.
host_key_checking
- I set the host key check to false, otherwise, Ansible will complain that the host key is not trusted.inventory
- Referencing the location of the inventory file
[defaults]
host_key_checking = False
inventory = /Users/sureshv/Documents/ansible/inventory/host_file.ini
Ansible Inventory File host_file.ini
An inventory file is a configuration file that defines the hosts and the mapping of hosts into groups. The hosts are the devices that we want Ansible to manage. The default inventory file is located at /etc/ansible/hosts
.
You can specify a different inventory file at the command line using the -i <path>
option or reference it in the ansible.cfg
file as we did in this example.
In our example, I've added the SRX to a group called srx
and added its IP and name to the inventory file host_file.ini
#host_file.ini
[srx]
branch_srx ansible_host=10.10.50.16
Variables
Now that we have defined our inventory, where do we specify the variables such as interface names, IP addresses, usernames, passwords etc? Although you can store variables directly in the main inventory file, storing them on a separate host and group variables files may help you organize your variables more easily.
Ansible loads host and group variable files by searching paths relative to the inventory file or the playbook file.
For example, SRX firewall belongs to the group srx
so, if our inventory file is located at /Documents/ansible/inventory
it will use variables in YAML files at the following locations.
/Documents/ansible/inventory/host_vars/branch_srx.yml #host_vars
/Documents/ansible/inventory/grouop_vars/srx.yml #group_vars
So, we specify variables specific to a particular device under host_vars
such as IP address, hostname etc. We can also specify variables that are common to a group of devices under group_vars
such as DNS servers, NTP servers, banners, time-zones etc.
host_vars branch_srx.yml
Here we specify variables that are unique to branch_srx
only
---
interfaces:
- name: ge-0/0/0
ip: 116.85.10.1/29
zone: wan
description: WAN
- name: ge-0/0/1
ip: 10.16.1.1/24
zone: users
description: Users
- name: ge-0/0/2
ip: 10.16.2.1/24
zone: applications
description: Apps and Servers
- name: ge-0/0/3
ip: 10.16.9.1/24
zone: dmz
description: DMZ
group_vars srx.yml
Here we specify variables that are common to a group of devices. For example, if you were to add another SRX to this group, it will inherit the variables automatically.
---
ansible_connection: ansible.netcommon.netconf
ansible_network_os: junipernetworks.junos.junos
ansible_user: admin
ansible_password: Cisco123
- ansible_connection - Ansible uses the ansible-connection setting to determine how to connect to a remote device. When working with network devices, we need to set this to an appropriate network connection option, so Ansible treats the remote node as a network device with a limited execution environment. Without this setting, Ansible would attempt to use ssh to connect to the remote and execute the Python script on the network device, which would fail because Python generally isn’t available on network devices.
- ansible_network_os - Informs Ansible which Network platform these hosts correspond to. This is required when using the
ansible.netcommon.*
connection options.
Playbook
The final piece to the puzzle is the actual playbook. You can find all the available Junos Ansible modules here - https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html
---
- name: vSRX Playbook
hosts: srx
gather_facts: no
collections:
- junipernetworks.junos
tasks:
- name: Time Zone
junipernetworks.junos.junos_config:
lines:
- set system time-zone Europe/London
tags: ntp
- name: Hostname
junipernetworks.junos.junos_hostname:
config:
hostname: 'branch_srx'
tags:
- system
- name: L3 Interfaces
junipernetworks.junos.junos_l3_interfaces:
config:
- name: "{{ item.name }}"
ipv4:
- address: "{{ item.ip }}"
with_items: "{{ interfaces }}"
tags:
- l3
- name: Static Routes
junipernetworks.junos.junos_static_routes:
config:
- address_families:
- afi: ipv4
routes:
- dest: 0.0.0.0/0
next_hop:
- forward_router_address: 116.85.10.6
tags:
- route
- name: Security Zones and Allow Ping
junipernetworks.junos.junos_config:
lines:
- set security zones security-zone "{{ item.zone }}" interfaces "{{ item.name }}" host-inbound-traffic system-services ping
with_items: "{{ interfaces }}"
tags:
- l3
- name: Source NAT
junipernetworks.junos.junos_config:
lines:
- set security nat source rule-set users_to_wan from zone users
- set security nat source rule-set users_to_wan to zone wan
- set security nat source rule-set users_to_wan rule snat_pat match source-address 10.16.1.0/24
- set security nat source rule-set users_to_wan rule snat_pat match destination-address 0.0.0.0/0
- set security nat source rule-set users_to_wan rule snat_pat then source-nat interface
tags:
- nat
"{{ ........ }}"
Any values that you see inside the "{{ }}"
curly braces are variables. When you run the playbook, Ansible will obtain the value of the variables from the host and group var files.
When you run the playbook, Ansible will obtain the value of the variables from the host and group var files. We have four interfaces under the parent variable interfaces
.
On the first iteration, "{{ item.name }}"
becomes ge-0/0/0
and "{{ item.ip }}"
becomes 116.85.10.1/29
On the second iteration, "{{ item.name }}"
becomes ge-0/0/1
and "{{ item.ip }}"
becomes 10.16.1.1/24
and so on and so forth.
The benefit of this is, if you were to add a new interface, all you have to do is add the interface name and IP to the host_vars
file.
tags
If you have a large playbook, it may be useful to run only specific parts of it instead of running the entire playbook. You can do this with Ansible tags. You can use tags to execute or skip selected tasks.
- Add tags to your tasks
- Select or skip tags when you run your playbook by using the
--tags
argument.
For example, let's say we've added another static route and want to run the playbook to push the new static route to the devices. If you run the playbook without any tags, it will run all of your tasks. It is not a big deal with just a few devices, but if you have a larger playbook, it will take a long time to run the entire playbook.
Running the playbook will result in the following commands being sent out to the device.
set system time-zone Europe/London
set system host-name branch_srx
set interfaces ge-0/0/0 unit 0 family inet address 116.85.10.1/29
set interfaces ge-0/0/1 unit 0 family inet address 10.16.1.1/24
set interfaces ge-0/0/2 unit 0 family inet address 10.16.2.1/24
set interfaces ge-0/0/3 unit 0 family inet address 10.16.9.1/24
set security zones security-zone wan interfaces ge-0/0/0.0 host-inbound-traffic system-services ping
set security zones security-zone users interfaces ge-0/0/1.0 host-inbound-traffic system-services ping
set security zones security-zone applications interfaces ge-0/0/2.0 host-inbound-traffic system-services ping
set security zones security-zone dmz interfaces ge-0/0/3.0 host-inbound-traffic system-services ping
set security nat source rule-set users_to_wan from zone users
set security nat source rule-set users_to_wan to zone wan
set security nat source rule-set users_to_wan rule snat_pat match source-address 10.16.1.0/24
set security nat source rule-set users_to_wan rule snat_pat match destination-address 0.0.0.0/0
set security nat source rule-set users_to_wan rule snat_pat then source-nat interface
Unsupported Modules
You must have noticed that I've used set
commands to configure both Security Zones and NAT. Even though Ansible has a module to create Security Zones, I couldn't get it to work hence using the set
commands. Please let me know in the comments if you managed to create the zones via the Ansible module.
Ansible on the other hand doesn't have a module to configure NAT so, I had to manually configure them via the set
commands.
References
https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html