├── README.md ├── prtg-tools ├── img │ ├── prtg1.png │ ├── prtg2.png │ └── prtg3.png ├── prtg-db-sync.ps1 ├── prtg-sync-db-daily.yml ├── bulk_add_new_device_netbox_prtg.yml ├── README.md ├── prtg-service-checker.yml ├── bulk_add_tasks.yml ├── netbox-to-prtg-sync.yml └── prtg-change-host-to-ip.py ├── netbox-ansible-populate ├── securecrt-import-process │ ├── export1.png │ ├── export2.png │ ├── export3.png │ ├── export4.png │ └── readme.MD ├── dynamic │ ├── dynamic_netbox_ansible_inv.yml │ ├── filter │ │ └── filter.py │ ├── autopop_fortios_ips.yml │ ├── autopop_junos_ips.yml │ ├── autopop_junos_facts.yml │ └── autopop_fortios_facts.yml ├── bootstrap_inventory.ini └── netbox-ansible-junos-bootstrap.yml ├── junos-snmpv3-generator ├── templates │ └── junos-snmp.j2 ├── junos_snmp_standardize.yml └── filter_plugins │ └── filter.py └── cloud-ip-ranges-checks └── compare-google-ips-check-against-fortinet.yml /README.md: -------------------------------------------------------------------------------- 1 | # ansible-misc 2 | misc brain dump of ansible playbooks 3 | -------------------------------------------------------------------------------- /prtg-tools/img/prtg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/prtg-tools/img/prtg1.png -------------------------------------------------------------------------------- /prtg-tools/img/prtg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/prtg-tools/img/prtg2.png -------------------------------------------------------------------------------- /prtg-tools/img/prtg3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/prtg-tools/img/prtg3.png -------------------------------------------------------------------------------- /netbox-ansible-populate/securecrt-import-process/export1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/netbox-ansible-populate/securecrt-import-process/export1.png -------------------------------------------------------------------------------- /netbox-ansible-populate/securecrt-import-process/export2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/netbox-ansible-populate/securecrt-import-process/export2.png -------------------------------------------------------------------------------- /netbox-ansible-populate/securecrt-import-process/export3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/netbox-ansible-populate/securecrt-import-process/export3.png -------------------------------------------------------------------------------- /netbox-ansible-populate/securecrt-import-process/export4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaon1/ansible-misc/HEAD/netbox-ansible-populate/securecrt-import-process/export4.png -------------------------------------------------------------------------------- /prtg-tools/prtg-db-sync.ps1: -------------------------------------------------------------------------------- 1 | robocopy "E:\ProgramData\Paessler" "\\stanbbyserver\Paessler" /mir /zb /mt:16 /tbd /r:1 /w:3 /fft /np 2 | robocopy "E:\Program Files (x86)\PRTG Network Monitor" "\\stanbbyserver\PRTG Network Monitor" /mir /zb /mt:16 /tbd /r:1 /w:3 /fft /np -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/dynamic_netbox_ansible_inv.yml: -------------------------------------------------------------------------------- 1 | ## Ansible Plugin file for dynamic inventory through netbox 2 | --- 3 | plugin: netbox.netbox.nb_inventory 4 | api_endpoint: 5 | # token: "{{ lookup('env','NETBOX_API_KEY') }}" 6 | validate_certs: false 7 | config_context: true 8 | compose: 9 | ansible_network_os: platform.slug 10 | ansible_connection: custom_fields.ansible_connection 11 | device_query_filters: 12 | - status: 'active' 13 | - tag: 'tower_inv' 14 | -------------------------------------------------------------------------------- /netbox-ansible-populate/bootstrap_inventory.ini: -------------------------------------------------------------------------------- 1 | [junosinv] 2 | switch1 ansible_host=10.10.10.1 3 | switch2 ansible_host=10.10.10.2 4 | switch3 ansible_host=10.10.10.3 5 | 6 | [junosinv:vars] 7 | ansible_connection=netconf 8 | ansible_network_os=junos 9 | 10 | [fortinetinv] 11 | firewall1 ansible_host=10.10.10.11 12 | firewall2 ansible_host=10.10.10.12 13 | firewall3 ansible_host=10.10.10.13 14 | 15 | [fortinetinv:vars] 16 | ansible_connection=httpapi 17 | ansible_network_os=fortios 18 | -------------------------------------------------------------------------------- /prtg-tools/prtg-sync-db-daily.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: powershell script to sync prtg DB to standby server 3 | hosts: primary_core 4 | gather_facts: False 5 | tasks: 6 | ### the powershell script is an incremental robocopy of the "E:\ProgramData\Paessler" and "E:\Program Files (x86)\PRTG Network Monitor" drives 7 | - name: Run basic PowerShell script 8 | ansible.windows.win_command: 'cmd.exe /c "powershell.exe C:\prtg-db-sync\prtg-db-sync.ps1"' 9 | become: yes 10 | become_method: runas 11 | become_user: SYSTEM 12 | register: output 13 | failed_when: output.failed 14 | 15 | - debug: 16 | var: output -------------------------------------------------------------------------------- /prtg-tools/bulk_add_new_device_netbox_prtg.yml: -------------------------------------------------------------------------------- 1 | ### This playbook adds a "new" network device to both PRTG and Netbox 2 | ### We clone an exisiting device in PRTG and place the new object inside the corresponding Site mapped in netbox (same site ID) 3 | ### In netbox, we add the device, IP, role, type and tag it with the 'PRTG' tag 4 | ### The PRTG tag in netbox is used later to check that all devices in netbox are also in PRTG (compliance check) 5 | --- 6 | - name: Play to Bulk Add new devices to netbox and prtg 7 | hosts: localhost 8 | gather_facts: false 9 | vars: 10 | ### Grab secret keys from Ansible Tower runtime execution (inject variables) 11 | netbox_url: "{{ netbox_url }}" 12 | netbox_token: "{{ netbox_token }}" 13 | prtg_url: "{{ prtg_url }}" 14 | prtg_api_creds: "&username=api_user_networking&passhash={{ prtg_passhash }}" 15 | tasks: 16 | - name: include all devices 17 | include_tasks: bulk_add_tasks.yml 18 | with_list: "{{ device_list }}" -------------------------------------------------------------------------------- /junos-snmpv3-generator/templates/junos-snmp.j2: -------------------------------------------------------------------------------- 1 | replace: 2 | snmp { 3 | description {{ inventory_hostname }}; 4 | location {{ device_site }}; 5 | contact "some_contact_here"; 6 | v3 { 7 | usm { 8 | local-engine { 9 | user someuser { 10 | authentication-sha { 11 | authentication-key "{{ snmp_9key }}"; ## SECRET-DATA 12 | } 13 | privacy-aes128 { 14 | privacy-key "{{ snmp_9key }}"; ## SECRET-DATA 15 | } 16 | } 17 | } 18 | } 19 | vacm { 20 | security-to-group { 21 | security-model usm { 22 | security-name someuser { 23 | group somegroup; 24 | } 25 | } 26 | } 27 | access { 28 | group somegroup { 29 | default-context-prefix { 30 | security-model usm { 31 | security-level privacy { 32 | read-view view-all; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | view view-all { 41 | oid 1 include; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/filter/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Kaon Thana 7-27-2021 4 | Forked from original author: Nick Russo (Pluralsight training course) 5 | File contains custom filters for use in Ansible playbooks. 6 | https://www.ansible.com/ 7 | """ 8 | import json 9 | import jmespath 10 | from pprint import pprint 11 | #import flatten_3d 12 | 13 | 14 | def flatten_3d(list_of_lists): 15 | if len(list_of_lists) == 0: 16 | return list_of_lists 17 | if isinstance(list_of_lists[0], list): 18 | return flatten_3d(list_of_lists[0]) + flatten_3d(list_of_lists[1:]) 19 | return list_of_lists[:1] + flatten_3d(list_of_lists[1:]) 20 | 21 | class FilterModule: 22 | """ 23 | Defines a filter module object. 24 | """ 25 | 26 | @staticmethod 27 | def filters(): 28 | """ 29 | Return a list of hashes where the key is the filter 30 | name exposed to playbooks and the value is the function. 31 | """ 32 | return { 33 | 'map_ip_name': FilterModule.map_ip_name, 34 | } 35 | 36 | @staticmethod 37 | def map_ip_name(ip_list,json_data): 38 | ip_map = [] 39 | for ip in ip_list: 40 | ##skip juniper self mgmt ips that start with 128.0 41 | if ip.startswith('128.'): continue 42 | if ip.startswith('0x'): continue 43 | 44 | ip_by_name = jmespath.search( 45 | '[*].\"interface-information\"[*].\"physical-interface\"[*].\"logical-interface\"[?\"address-family\"[0].\"interface-address\"[0].\"ifa-local\"[?\"data"==`'+ip+'`]].name[*].data', 46 | json_data) 47 | 48 | ## check if IP has a mask. if it has no mask add a /32 49 | check_for_mask = ip.split('/') 50 | if len(check_for_mask) < 2: 51 | ip = ip + "/32" 52 | 53 | if flatten_3d(ip_by_name): 54 | ip_map = [{'intf_name': flatten_3d(ip_by_name)[0], 'ip': ip}] + ip_map 55 | else: 56 | ip_map = [{'intf_name': 'empty', 'ip': ip}] + ip_map 57 | return ip_map 58 | -------------------------------------------------------------------------------- /junos-snmpv3-generator/junos_snmp_standardize.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Standardize System SNMP on Junos Config 3 | # variable host 4 | hosts: 5 | gather_facts: false 6 | vars: 7 | # force python3 for legacy environments. We need python3 for the imported libraries in filter_plugins 8 | ansible_python_interpreter: /usr/bin/python3 9 | tasks: 10 | # get snmpv3 info from the box (we need to extract the engineID) 11 | - name: get snmp info 12 | junipernetworks.junos.junos_command: 13 | commands: show snmp v3 14 | display: text 15 | register: output_snmp_text 16 | changed_when: false 17 | 18 | # use regex to extract the engine id and strip out blank spaces 19 | - name: parse snmp engine id 20 | set_fact: 21 | engine_id: "{{ output_snmp_text.stdout[0] | regex_search('.*Local engine ID: (.*[0-9a-fA-F])', '\\1') | replace(' ', '') }}" 22 | changed_when: false 23 | 24 | # use the custom function gen_snmp_9key defined in filter.py (takes two inputs -- engine id and snmp plain text pass) 25 | - name: generate snmp_9key 26 | set_fact: 27 | snmp_9key: "{{ engine_id[0] | gen_snmp_9key(snmp_cred)}}" 28 | changed_when: false 29 | 30 | # generate the config snipper of code to be sent to junos. Use jinja2 template defined in directory 31 | - name: Template Lookup and Config Generation 32 | template: 33 | src: "templates/junos-snmp.j2" 34 | # create a temp candidate config file to be loaded in next task 35 | dest: "{{ inventory_hostname }}-snmp.conf" 36 | delegate_to: localhost 37 | # this task will always generate a change, don't need to see it. 38 | changed_when: false 39 | 40 | - name: Load Standard System Parameters to Juniper Device 41 | junipernetworks.junos.junos_config: 42 | # if TRUE this will be a DRY RUN (no changes) 43 | check_commit: "{{ check_commit }}" 44 | src_format: text 45 | # we use the replace flag to overwrite specific blocks of config 46 | update: replace 47 | # temp candidate config file created in previous task 48 | src: "{{ inventory_hostname }}-snmp.conf" 49 | register: result 50 | -------------------------------------------------------------------------------- /prtg-tools/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | This repo is the home for various ansible playbooks written to interact with the PRTG API and help with day-to-day operations and maintain desired monitoring state. 3 | 4 | ## Add New Device to PRTG and Netbox (Bulk) 5 | The playbook [bulk_add_new_device_netbox_prtg](bulk_add_new_device_netbox_prtg.yml) and its corresponding task list [bulk_add_tasks](bulk_add_tasks.yml) are used to add a new network device to both PRTG and Netbox. 6 | - We clone an existing device in PRTG and place the new object inside the corresponding Site mapped in netbox (same site ID) 7 | - In netbox, we add the device, IP, role, type and tag it with the 'PRTG' tag 8 | - The PRTG tag in netbox is used later to check that all devices in netbox are also in PRTG (compliance check) 9 | 10 | ## PRTG to Netbox Sync Checker 11 | The playbook [netbox-to-prtg-sync](netbox-to-prtg-sync.yml) queries the device list of PRTG and queries all devices in Netbox tagged with 'PRTG' label. 12 | 13 | It performs a diff against both lists and alerts if there is a difference. This is scheduled daily so that we know if a new device was added to PRTG (or Netbox) and not put in the corresponding system. 14 | 15 | This play helps keep our Source of Truth in sync with monitoring. 16 | 17 | ## PRTG Sync Databases to Backup Core 18 | The playbook [prtg-sync-db-daily](prtg-sync-db-daily.yml) runs daily to incrementally sync the primary core server to the backup core server. We sync the two primary drives: 19 | - "E:\ProgramData\Paessler" 20 | - "E:\Program Files (x86)\PRTG Network Monitor" 21 | - robocopy args = /mir /zb /mt:16 /tbd /r:1 /w:3 /fft /np 22 | 23 | ## PRTG Windows Service Checker 24 | The playbook [prtg-service-checker](prtg-service-checker.yml) is used to maintain core and probe server states. In ACTIVE mode, we confirm that the primary core and probe windows services are running and the standby services are manually stopped. This prevents accidental turn up of services and split-brain scenarios. In STANDBY mode, we flip the services on all the cores and probes. This is usually done for software upgrades (maintenance windows). 25 | 26 | Illustration of the playbook and architecture: 27 | 28 | ![](img/prtg3.png) 29 | 30 | ![](img/prtg1.png) 31 | 32 | ![](img/prtg2.png) 33 | 34 | -------------------------------------------------------------------------------- /netbox-ansible-populate/securecrt-import-process/readme.MD: -------------------------------------------------------------------------------- 1 | # How To Export Devices from Netbox to SecureCRT 2 | Guide on how to create a Netbox export template to prepare a secureCRT import file 3 | 4 | ## Create Netbox Custom Export Template 5 | - In Netbox go to Other --> Export Templates --> Add 6 | - Give it a name 7 | - Content types: DCIM > Device 8 | - Description 9 | - Template: 10 | ```jinja2 11 | session_name,hostname,protocol,folder 12 | {% for device in queryset %}{% set ipadd = device['primary_ip']['address']|replace("/32", "") %}{{ device.name }},{{ ipadd }},ssh2,{{ device.site.slug }} 13 | {% endfor %} 14 | ``` 15 | - file extension: .csv 16 | - download as attachment (checked) 17 | 18 | **This template would create a CSV with the columns: session_name, hostname, protocol, folder** 19 | [Reference Link](https://www.vandyke.com/support/tips/importsessions.html) 20 | 21 | ![](export1.png) 22 | 23 | ## Choose your devices to export 24 | - In Netbox go to Devices --> Filters --> Choose your filters 25 | - For example, export all **active** devices with a **primary IP** 26 | - Export the data to your **secure_crt** template 27 | - Now you will have a CSV ready for secureCRT: 28 | - session_name: The value that will be used for the secureCRT session’s name. 29 | - hostname: The hostname or IP address for the remote server. 30 | - protocol: The protocol to be used for connecting with the session (SSH2) 31 | - folder: A relative folder path for the specified session as displayed in the Session manager. In this case, the device site slug from netbox will be used as the folder. 32 | 33 | ![](export2.png) 34 | 35 | ## Import the data export into SecureCRT 36 | 1) Download the `ImportArbitraryDataFromFileToSecureCRTSessions.py` file from the [secureCRT website](https://www.vandyke.com/support/scripting/scripting-examples/import-arbitrary-data-from-file-to-securecrt-sessions.html) 37 | 2) Create a backup of your current secureCRT sessions 38 | 3) Delete all current sessions/folders in secureCRT (if you don't want duplicates) 39 | 4) In secureCRT: 40 | - go to Script --> Run --> Find `Import ImportArbitraryDataFromFileToSecureCRTSessions.py` file 41 | - Select the CSV file (that you exported from Netbox) 42 | - Import using default sessions 43 | 44 | ![](export3.png) 45 | 46 | ![](export4.png) -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/autopop_fortios_ips.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Gather Fortios IPs and Auto-Populate Netbox" 3 | hosts: "{{ var_hosts }}" 4 | gather_facts: False 5 | vars: 6 | ansible_httpapi_use_ssl: yes 7 | ansible_httpapi_validate_certs: no 8 | ansible_httpapi_port: 443 9 | ansible_network_os: fortinet.fortios.fortios 10 | tasks: 11 | - fortinet.fortios.fortios_monitor_fact: 12 | vdom: "root" 13 | selector: 'system_available-interfaces' 14 | register: fortios_facts 15 | 16 | - name: "TASK 31: NETBOX >> Add interfaces to device" 17 | netbox.netbox.netbox_device_interface: 18 | netbox_url: "{{ netbox_url }}" 19 | netbox_token: "{{ netbox_token }}" 20 | data: 21 | device: "{{ inventory_hostname }}" 22 | name: "{{ item['name'] }}" 23 | mac_address: "{{ item['mac_address'] }}" 24 | type: other 25 | state: present 26 | validate_certs: no 27 | loop: "{{ fortios_facts['meta']['results'] }}" 28 | loop_control: 29 | label: "{{ item.name }}" 30 | when: 31 | - item['status'] is defined 32 | - item['link'] is defined 33 | - item['status'] == "up" 34 | - item['link'] == "up" 35 | - item['ipv4_addresses'][0]['ip'] is defined 36 | - item['mac_address'] is defined 37 | delegate_to: localhost 38 | 39 | - name: "TASK 32: NETBOX >> ADD all IPs to IPAM and link to device" 40 | netbox.netbox.netbox_ip_address: 41 | netbox_url: "{{ netbox_url }}" 42 | netbox_token: "{{ netbox_token }}" 43 | data: 44 | address: "{{item['ipv4_addresses'][0]['ip'] }}/{{ item['ipv4_addresses'][0]['cidr_netmask'] }}" 45 | status: active 46 | assigned_object: 47 | name: "{{ item['name'] }}" 48 | device: "{{ inventory_hostname }}" 49 | state: present 50 | validate_certs: no 51 | loop: "{{ fortios_facts['meta']['results'] }}" 52 | loop_control: 53 | label: "{{ item.name }}" 54 | when: 55 | - item['status'] is defined 56 | - item['link'] is defined 57 | - item['status'] == "up" 58 | - item['link'] == "up" 59 | - item['ipv4_addresses'][0]['ip'] is defined 60 | - item['mac_address'] is defined 61 | delegate_to: localhost 62 | register: ipam_output 63 | changed_when: False -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/autopop_junos_ips.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Gather Juniper IPs and Auto-Populate Netbox" 3 | hosts: "{{ var_hosts }}" 4 | gather_facts: False 5 | tasks: 6 | - name: "Connect to Juniper Device and Collect Facts" 7 | junipernetworks.junos.junos_facts: 8 | 9 | - name: "TASK 1: Check if net_version exists" 10 | set_fact: 11 | net_version: "12" 12 | when: ansible_facts['net_version'] is undefined 13 | changed_when: False 14 | 15 | - name: "TASK 2: Assign net version" 16 | set_fact: 17 | net_version: "{{ ansible_facts['net_version'] }}" 18 | when: ansible_facts['net_version'] is defined 19 | changed_when: False 20 | 21 | - name: collect default set of facts 22 | junipernetworks.junos.junos_command: 23 | commands: show interfaces terse 24 | display: json 25 | register: junos_output 26 | when: 27 | - net_version.split('.')[0]|int > 13 28 | 29 | - name: Parse IPs 30 | set_fact: 31 | ip_list: "{{ junos_output.stdout | json_query('[*].\"interface-information\"[*].\"physical-interface\"[*].\"logical-interface\"[*].\"address-family\"[*].\"interface-address\"[*].\"ifa-local\"[*].data') | flatten }}" 32 | delegate_to: localhost 33 | when: 34 | - net_version.split('.')[0]|int > 13 35 | 36 | - name: Match IP to Int Name 37 | set_fact: 38 | name_ip_map: "{{ ip_list | map_ip_name(junos_output.stdout) }}" 39 | delegate_to: localhost 40 | when: 41 | - net_version.split('.')[0]|int > 13 42 | 43 | - name: "TASK 31: NETBOX >> Add interfaces to device" 44 | netbox.netbox.netbox_device_interface: 45 | netbox_url: "{{ netbox_url }}" 46 | netbox_token: "{{ netbox_token }}" 47 | data: 48 | device: "{{ inventory_hostname }}" 49 | name: "{{ item.intf_name }}" 50 | type: other 51 | state: present 52 | validate_certs: no 53 | loop: "{{ name_ip_map }}" 54 | delegate_to: localhost 55 | when: 56 | - net_version.split('.')[0]|int > 13 57 | 58 | - name: "TASK 32: NETBOX >> ADD all IPs to IPAM and link to device" 59 | netbox.netbox.netbox_ip_address: 60 | netbox_url: "{{ netbox_url }}" 61 | netbox_token: "{{ netbox_token }}" 62 | data: 63 | # family: 4 64 | address: "{{ item.ip }}" 65 | status: active 66 | assigned_object: 67 | name: "{{ item.intf_name }}" 68 | device: "{{ inventory_hostname }}" 69 | state: present 70 | validate_certs: no 71 | loop: "{{ name_ip_map }}" 72 | delegate_to: localhost 73 | changed_when: False 74 | when: 75 | - net_version.split('.')[0]|int > 13 -------------------------------------------------------------------------------- /prtg-tools/prtg-service-checker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play to toggle Core active/passive 3 | hosts: prtg_cores 4 | gather_facts: False 5 | vars: 6 | prtg_services: ["PRTGCoreService","PRTGProbeService"] 7 | active_standby_mode: "{{ active_standby_mode }}" 8 | tasks: 9 | - block: 10 | - name: Checking Standy Core Server Status 11 | win_service: 12 | name: "{{ item }}" 13 | state: stopped 14 | with_items: "{{ prtg_services }}" 15 | when: ansible_host == "standbyserver" 16 | register: resultall 17 | 18 | - name: Checking Active Core Server Status 19 | win_service: 20 | name: "{{ item }}" 21 | state: started 22 | with_items: "{{ prtg_services }}" 23 | when: ansible_host == "activeserver" 24 | register: resultall 25 | when: active_standby_mode == "active" 26 | 27 | - block: 28 | - name: Checking Standby Core Server Status 29 | win_service: 30 | name: "{{ item }}" 31 | state: started 32 | with_items: "{{ prtg_services }}" 33 | when: ansible_host == "standbyserver" 34 | register: resultall 35 | 36 | - name: Checking Active Core Server Status 37 | win_service: 38 | name: "{{ item }}" 39 | state: stopped 40 | with_items: "{{ prtg_services }}" 41 | when: ansible_host == "activeserver" 42 | register: resultall 43 | when: active_standby_mode == "standby" 44 | 45 | - name: Play to toggle Active Probes 46 | hosts: prtg_probes_active 47 | gather_facts: False 48 | vars: 49 | prtg_services: "PRTGProbeService" 50 | active_standby_mode: "{{ active_standby_mode }}" 51 | tasks: 52 | - name: Checking Probes Status ACTIVE MODE 53 | win_service: 54 | name: "PRTGProbeService" 55 | state: started 56 | when: active_standby_mode == "active" 57 | register: resultall 58 | 59 | - name: Checking Probes Status STANDBY MODE 60 | win_service: 61 | name: "PRTGProbeService" 62 | state: stopped 63 | when: active_standby_mode == "standby" 64 | register: resultall 65 | 66 | - name: Play to toggle Standby Probes 67 | hosts: prtg_probes_standby 68 | gather_facts: False 69 | vars: 70 | prtg_services: "PRTGProbeService" 71 | active_standby_mode: "{{ active_standby_mode }}" 72 | tasks: 73 | - name: Checking Probes Status ACTIVE MODE 74 | win_service: 75 | name: "PRTGProbeService" 76 | state: stopped 77 | when: active_standby_mode == "active" 78 | register: resultall 79 | 80 | - name: Checking Probes Status STANDBY MODE 81 | win_service: 82 | name: "PRTGProbeService" 83 | state: started 84 | when: active_standby_mode == "standby" 85 | register: resultall -------------------------------------------------------------------------------- /prtg-tools/bulk_add_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ### Tasks to be included in the bulk_add_new_device_netbox_prtg.yml main playbook 3 | - name: "TASK 0: NETBOX >> GRAB PRTG SITE ID BY NAME" 4 | uri: 5 | ### Get Device site ID from netbox (custom field). This ID Maps to PRTG Site ID 6 | url: "{{ netbox_url }}/api/dcim/sites/?slug={{ item['device_site'] }}" 7 | method: GET 8 | validate_certs: no 9 | headers: 10 | Authorization: "Token {{ netbox_token }}" 11 | register: site_id_results 12 | delegate_to: localhost 13 | 14 | - name: "TASK 1: SET PRTG SITE ID" 15 | set_fact: 16 | prtg_site_id: "{{ site_id_results.json.results[0].custom_fields.prtg_site_id }}" 17 | 18 | - name: "TASK 10: NETBOX >> ADD DEVICE TO NETBOX" 19 | netbox.netbox.netbox_device: 20 | netbox_url: "{{ netbox_url }}" 21 | netbox_token: "{{ netbox_token }}" 22 | data: 23 | name: "{{ item['device_name'] }}" 24 | device_type: "{{ item['device_type'] }}" 25 | platform: "{{ item['device_platform'] }}" 26 | site: "{{ item['device_site'] }}" 27 | device_role: "{{ item['device_role'] }}" 28 | tags: 29 | - prtg 30 | state: present 31 | validate_certs: no 32 | delegate_to: localhost 33 | 34 | - name: "TASK 11: NETBOX >> Add temporary interface for mgmt ip" 35 | netbox.netbox.netbox_device_interface: 36 | netbox_url: "{{ netbox_url }}" 37 | netbox_token: "{{ netbox_token }}" 38 | data: 39 | device: "{{ item['device_name'] }}" 40 | name: Management_Interface 41 | type: other 42 | state: present 43 | validate_certs: no 44 | delegate_to: localhost 45 | 46 | - name: "TASK 12: NETBOX >> ADD IP ADDRESS OF ANSIBLE HOST" 47 | netbox.netbox.netbox_ip_address: 48 | netbox_url: "{{ netbox_url }}" 49 | netbox_token: "{{ netbox_token }}" 50 | data: 51 | family: 4 52 | address: "{{ item['device_ip'] }}/{{ item['device_mask'] }}" 53 | status: active 54 | assigned_object: 55 | name: Management_Interface 56 | device: "{{ item['device_name'] }}" 57 | state: present 58 | validate_certs: no 59 | delegate_to: localhost 60 | 61 | - name: "TASK 13: NETBOX >> ASSOCIATE IP ADDRESS TO DEVICE" 62 | netbox.netbox.netbox_device: 63 | netbox_url: "{{ netbox_url }}" 64 | netbox_token: "{{ netbox_token }}" 65 | data: 66 | name: "{{ item['device_name'] }}" 67 | device_type: "{{ item['device_type'] }}" 68 | platform: "{{ item['device_platform'] }}" 69 | status: Active 70 | primary_ip4: "{{ item['device_ip'] }}/{{ item['device_mask'] }}" 71 | state: present 72 | validate_certs: no 73 | delegate_to: localhost 74 | 75 | - name: "TASK 14: ADD DEVICE TO PRTG" 76 | uri: 77 | url: "{{ prtg_url }}/api/duplicateobject.htm?id=2436&name={{ item['device_name'] | urlencode }}&host={{ item['device_ip'] | urlencode }}&targetid={{ prtg_site_id }}{{ prtg_api_creds }}" 78 | register: result -------------------------------------------------------------------------------- /prtg-tools/netbox-to-prtg-sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Sync netbox and prtg device list 3 | hosts: localhost 4 | gather_facts: false 5 | vars: 6 | netbox_uri: "https:///api/dcim/devices/?limit=0&status=active&tag=prtg" 7 | netbox_token: "{{ netbox_token }}" 8 | prtg: "https://prtg" 9 | api_creds: "&username=api_user_networking&passhash={{ prtg_passhash }}" 10 | get_all_devices_uri: "/api/table.json?content=devices&output=json&columns=objid,probe,group,device,host,downsens,partialdownsens,downacksens,upsens,warnsens,pausedsens,unusualsens,undefinedsens&count=5000" 11 | get_all_devices_call: "{{ prtg + get_all_devices_uri + api_creds }}" 12 | netbox_list: [] 13 | prtg_list: [] 14 | 15 | tasks: 16 | - name: Netbox GET All devices tagged with PRTG label 17 | uri: 18 | url: "{{ netbox_uri }}" 19 | method: GET 20 | validate_certs: no 21 | headers: 22 | Authorization: "Token {{ netbox_token }}" 23 | register: result 24 | tags: always 25 | 26 | - set_fact: 27 | netbox_list: "{{ netbox_list + [item.name] }}" 28 | loop: "{{ result.json.results }}" 29 | tags: always 30 | no_log: True 31 | 32 | - name: PRTG GET All devices 33 | uri: 34 | url: "{{ get_all_devices_call }}" 35 | register: result 36 | tags: always 37 | 38 | ### Ignore some devices that we don't need to check 39 | - set_fact: 40 | prtg_list: "{{ prtg_list + [item.device] }}" 41 | loop: "{{ result.json.devices }}" 42 | when: (item.device != 'Probe Device') and 43 | (item.device != 'Core Device') and 44 | (item.device != 'do_not_delete_for_cloning') and 45 | (item.device != 'sFlow Aggregator') and 46 | (not item.device | regex_search("Office Status")) 47 | tags: always 48 | no_log: True 49 | 50 | ### perform two way diffs on each list -- Netbox vs PRTG and PRTG vs Netbox 51 | - set_fact: 52 | device_diff_additions: "{{ netbox_list | difference(prtg_list) }}" 53 | tags: always 54 | 55 | - set_fact: 56 | device_diff_deletions: "{{ prtg_list | difference(netbox_list) }}" 57 | tags: always 58 | 59 | - name: LIST OF DEVICES TO ADD TO PRTG 60 | debug: 61 | var: device_diff_additions 62 | tags: always 63 | 64 | ### Fail playbook and send alert if theres something missing 65 | - name: LIST OF DEVICES NOT IN NETBOX 66 | debug: 67 | var: device_diff_deletions 68 | tags: always 69 | failed_when: device_diff_deletions|length > 0 or device_diff_additions|length > 0 70 | 71 | 72 | # - name: Add Missing Devices to PRTG 73 | # uri: 74 | # url: "{{ prtg }}/api/duplicateobject.htm?id=2436&name={{ item | urlencode }}&host={{ item | urlencode }}&targetid=2066{{ api_creds }}" 75 | # register: result 76 | # loop: "{{ device_diff_additions }}" 77 | # tags: [never, add] -------------------------------------------------------------------------------- /prtg-tools/prtg-change-host-to-ip.py: -------------------------------------------------------------------------------- 1 | ### Ad Hoc script run once to fix an architectural problem I originally implemented. 2 | ### Initially I populated all PRTG devices by Domain Name (DNS) 3 | ### Later on, I decided to monitor everything by IP (not DNS) 4 | 5 | # This python script alters all existing PRTG devices to monitor by IP instead of DNS Hostname 6 | # Populate IP from NETBOX. Calls Netbox API to grab IP of the device name 7 | # Call PRTG API '/api/setobjectproperty.htm?' to make the desired change 8 | # Kaon Thana 10-12-2021 9 | from time import sleep 10 | 11 | import requests 12 | import os 13 | 14 | 15 | # vars 16 | device_name = "" 17 | device_ip = "" 18 | 19 | # Get passwords and keys from OS ENV 20 | # To set this in OS, enter the command: 'export NETBOX_TOKEN=' 21 | netbox_token = os.getenv('NETBOX_TOKEN') 22 | prtg_token = os.getenv('PRTG_TOKEN') 23 | 24 | # URLs to hit for netbox API call 25 | netbox_api_url = "https:///api/dcim/devices/" 26 | 27 | # PRTG URLs for API Call 28 | prtg_base_url = "https://prtg" 29 | prtg_end_url = "&username=api_user_networking&passhash={}".format(prtg_token) 30 | prtg_all_devices = "/api/table.json?content=devices&output=json&columns=objid,probe,group,device,host,downsens,partialdownsens,downacksens,upsens,warnsens,pausedsens,unusualsens,undefinedsens&count=1000" 31 | 32 | def main(): # main function 33 | # main function runs to update prtg 34 | prtg_update_device_with_ip() 35 | 36 | def prtg_update_device_with_ip(): 37 | 38 | global device_name 39 | global prtg_base_url 40 | global prtg_end_url 41 | global prtg_all_devices 42 | global device_ip 43 | prtg_api_call = prtg_base_url + prtg_all_devices + prtg_end_url 44 | 45 | # prtg api call to grab all devices 46 | prtg_request_get_all_devices = requests.get(prtg_api_call, verify=False) 47 | 48 | for device in prtg_request_get_all_devices.json()['devices']: 49 | if device['device'] != "Core Device" and device['device'] != "Probe Device" and device['device'] != "do_not_delete_for_cloning": 50 | device_ip = netbox_grab_ip_from_device_name(device['device']) 51 | prtg_update_url = "/api/setobjectproperty.htm?id=" + str(device['objid']) + "&name=host&value=" + device_ip 52 | prtg_complete_api = prtg_base_url + prtg_update_url + prtg_end_url 53 | print("PRTG CURRENT VALUE: " + str(device['objid']) + " --> " + device['device'] + " --> " + device['host']) 54 | prtg_response = requests.get(prtg_complete_api, verify=False) 55 | print(prtg_response) 56 | print("PRTG NEW VALUE: " + str(device['objid']) + " --> " + device['device'] + " --> " + device_ip) 57 | sleep(.2) 58 | 59 | def netbox_grab_ip_from_device_name(device_name): 60 | # params for requests 61 | netbox_params = {'name': "{}".format(device_name)} 62 | netbox_headers = {'Authorization': "Token {}".format(netbox_token)} 63 | 64 | # netbox request to grab host info filter by device NAME 65 | netbox_request =requests.get(netbox_api_url, params=netbox_params, headers=netbox_headers, verify=False) 66 | 67 | # store result in json variable 68 | netbox_result = netbox_request.json() 69 | 70 | # retrieve IP without MASK 71 | ip_mask = netbox_result['results'][0]['primary_ip']['address'] 72 | device_ip = ip_mask.split('/')[0] 73 | 74 | return device_ip 75 | 76 | if __name__ == "__main__": 77 | main() -------------------------------------------------------------------------------- /netbox-ansible-populate/netbox-ansible-junos-bootstrap.yml: -------------------------------------------------------------------------------- 1 | ### Playbook to bootstrap netbox inventory with a list of juniper devices provided by static inventory file 2 | # Uses ansible gather_facts to grab net_version, serial number and net_model 3 | # Also perform a dig to get a FQDN which we can use as device name instead of the inventory_name 4 | 5 | --- 6 | - name: PB to Bootstrap Netbox Inventory 7 | hosts: junosinv 8 | gather_facts: True 9 | vars: 10 | ansible_user: 11 | ansible_ssh_private_key_file: 12 | netbox_url: 13 | netbox_token: 14 | platform: "{{ ansible_network_os }}" 15 | site: 16 | device_role: "access_switch" 17 | tasks: 18 | - name: "Check if net_version exists" 19 | ### If ansible_facts does not provide net_version we manually fill it in as 111 20 | set_fact: 21 | net_version: "111" 22 | when: ansible_facts['net_version'] is undefined 23 | 24 | - name: "Assign net version" 25 | set_fact: 26 | net_version: "{{ ansible_facts['net_version'] }}" 27 | when: ansible_facts['net_version'] is defined 28 | 29 | ### Optional 30 | - name: "Resolve FQDN Hostname - perform DIG" 31 | ### Perform linux DIG command to get the reverse DNS record for the IP. THis will be our new hostname for netbox 32 | raw: "dig -x {{ ansible_host }} +short | sed -e 's/.$//'" 33 | register: dig_result 34 | delegate_to: localhost 35 | 36 | ### Optional 37 | - name: "TASK 11: Assign dig result to fqdn var" 38 | ### If Reverse DNS exists, trim whhite spaces and assing to var 39 | set_fact: 40 | fqdn: "{{ dig_result.stdout_lines[0] | trim}}" 41 | when: dig_result.stdout_lines[0] is defined 42 | 43 | ### Optional 44 | - name: "TASK 12: If no dig result, assign placeholder fqdn value" 45 | ### If no reverse DNS, then set a inventory hostname and IP as the hostname 46 | set_fact: 47 | fqdn: "{{ inventory_hostname }}-no-dns-{{ ansible_host }}" 48 | when: dig_result.stdout_lines[0] is undefined 49 | 50 | - name: "Add Device to NetBox" 51 | netbox.netbox.netbox_device: 52 | netbox_url: "{{ netbox_url }}" 53 | netbox_token: "{{ netbox_token }}" 54 | data: 55 | name: "{{ fqdn }}" 56 | device_type: "{{ ansible_facts['net_model'] }}" 57 | platform: "{{ platform }}" 58 | serial: "{{ ansible_facts['net_serialnum'] }}" 59 | site: "{{ site }}" 60 | device_role: "{{ device_role }}" 61 | custom_fields: 62 | code_version: "{{ net_version }}" 63 | state: present 64 | validate_certs: no 65 | delegate_to: localhost 66 | 67 | - name: "Add a new Interface called management_interface to device" 68 | ### this interface will be used as the primary IP and interface for the device 69 | netbox.netbox.netbox_device_interface: 70 | netbox_url: "{{ netbox_url }}" 71 | netbox_token: "{{ netbox_token }}" 72 | data: 73 | device: "{{ fqdn }}" 74 | name: Management_Interface 75 | type: other 76 | state: present 77 | validate_certs: no 78 | delegate_to: localhost 79 | 80 | - name: "Add IP address of ansible host to IPAM" 81 | netbox.netbox.netbox_ip_address: 82 | netbox_url: "{{ netbox_url }}" 83 | netbox_token: "{{ netbox_token }}" 84 | data: 85 | family: 4 86 | address: "{{ ansible_host }}/32" 87 | status: active 88 | assigned_object: 89 | name: Management_Interface 90 | device: "{{ fqdn }}" 91 | state: present 92 | validate_certs: no 93 | delegate_to: localhost 94 | 95 | - name: "Assign ansible_host IP as the primary interface for the device" 96 | netbox.netbox.netbox_device: 97 | netbox_url: "{{ netbox_url }}" 98 | netbox_token: "{{ netbox_token }}" 99 | data: 100 | name: "{{ fqdn }}" 101 | device_type: "{{ ansible_facts['net_model'] }}" 102 | platform: "{{ platform }}" 103 | serial: "{{ ansible_facts['net_serialnum'] }}" 104 | status: Active 105 | primary_ip4: "{{ ansible_host }}/32" 106 | state: present 107 | validate_certs: no 108 | delegate_to: localhost 109 | -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/autopop_junos_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Gather Juniper Facts and Auto-Populate Netbox with key values" 3 | hosts: "{{ var_hosts }}" 4 | gather_facts: False 5 | tasks: 6 | - name: "Connect to Juniper Device and Collect Facts" 7 | junipernetworks.junos.junos_facts: 8 | 9 | - name: "TASK 1: Check if net_version exists" 10 | set_fact: 11 | net_version: "12" 12 | when: ansible_facts['net_version'] is undefined 13 | changed_when: False 14 | 15 | - name: "TASK 2: Assign net version" 16 | set_fact: 17 | net_version: "{{ ansible_facts['net_version'] }}" 18 | when: ansible_facts['net_version'] is defined 19 | changed_when: False 20 | 21 | - name: "Gather Virtual Chassis Data" 22 | junipernetworks.junos.junos_command: 23 | commands: show virtual-chassis 24 | display: json 25 | register: output_vc_json 26 | changed_when: False 27 | when: 28 | - net_version.split('.')[0]|int > 13 29 | 30 | - name: "FOR JUNOS Older than v13 -- CONFIRM Juniper to NETBOX" 31 | netbox.netbox.netbox_device: 32 | netbox_url: "{{ netbox_url }}" 33 | netbox_token: "{{ netbox_token }}" 34 | data: 35 | name: "{{ inventory_hostname }}" 36 | device_type: "{{ ansible_facts['net_model'] }}" 37 | serial: "{{ ansible_facts['net_serialnum'] }}" 38 | custom_fields: 39 | code_version: "{{ net_version }}" 40 | imei: "{{ custom_fields['imei'] }}" 41 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 42 | state: present 43 | validate_certs: no 44 | when: 45 | - net_version.split('.')[0]|int < 13 46 | delegate_to: localhost 47 | 48 | - name: "CONFIRM Juniper Device Main VC to NETBOX - PRIMARY NODE" 49 | netbox.netbox.netbox_device: 50 | netbox_url: "{{ netbox_url }}" 51 | netbox_token: "{{ netbox_token }}" 52 | data: 53 | name: "{{ inventory_hostname }}" 54 | device_type: "{{ ansible_facts['net_model'] }}" 55 | platform: "{{ hostvars[inventory_hostname]['platforms'][0] }}" 56 | device_role: "{{ hostvars[inventory_hostname]['device_roles'][0] }}" 57 | site: "{{ hostvars[inventory_hostname]['sites'][0] }}" 58 | serial: "{{item['member-serial-number'][0]['data']}}" 59 | custom_fields: 60 | code_version: "{{ net_version }}" 61 | imei: "{{ custom_fields['imei'] }}" 62 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 63 | state: present 64 | validate_certs: no 65 | when: 66 | - net_version.split('.')[0]|int > 13 67 | - item['member-id'][0]['data']|int == 0 68 | delegate_to: localhost 69 | loop: "{{ output_vc_json['stdout'][0]['virtual-chassis-information'][0]['member-list'][0]['member'] }}" 70 | loop_control: 71 | label: "{{ item['member-id'][0]['data'] }}" 72 | 73 | - name: "CONFIRM Juniper Device ALL VC Nodes to NETBOX - Other nodes" 74 | netbox.netbox.netbox_device: 75 | netbox_url: "{{ netbox_url }}" 76 | netbox_token: "{{ netbox_token }}" 77 | data: 78 | name: "{{ inventory_hostname }}_node_{{item['member-id'][0]['data']}}" 79 | device_type: "{{ ansible_facts['net_model'] }}" 80 | platform: "{{ hostvars[inventory_hostname]['platforms'][0] }}" 81 | device_role: "{{ hostvars[inventory_hostname]['device_roles'][0] }}" 82 | site: "{{ hostvars[inventory_hostname]['sites'][0] }}" 83 | serial: "{{item['member-serial-number'][0]['data']}}" 84 | custom_fields: 85 | code_version: "{{ net_version }}" 86 | imei: "{{ custom_fields['imei'] }}" 87 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 88 | state: present 89 | validate_certs: no 90 | when: 91 | - net_version.split('.')[0]|int > 13 92 | - item['member-id'][0]['data']|int != 0 93 | delegate_to: localhost 94 | loop: "{{ output_vc_json['stdout'][0]['virtual-chassis-information'][0]['member-list'][0]['member'] }}" 95 | loop_control: 96 | label: "{{ item['member-id'][0]['data'] }}" -------------------------------------------------------------------------------- /netbox-ansible-populate/dynamic/autopop_fortios_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Gather Fortios Facts and Auto-Populate Netbox with key values" 3 | hosts: "{{ var_hosts }}" 4 | gather_facts: False 5 | vars: 6 | ansible_httpapi_use_ssl: yes 7 | ansible_httpapi_validate_certs: no 8 | ansible_httpapi_port: 443 9 | ansible_network_os: fortinet.fortios.fortios 10 | tasks: 11 | - fortinet.fortios.fortios_monitor_fact: 12 | vdom: "root" 13 | selector: 'system_firmware' 14 | register: fortios_firmware_facts 15 | 16 | - fortinet.fortios.fortios_monitor_fact: 17 | vdom: "root" 18 | selector: 'system_ha-peer' 19 | register: fortios_ha_facts 20 | 21 | - name: "NETBOX GRAB DEVICE TYPE BY PLATFORM ID" 22 | uri: 23 | url: "{{ netbox_url }}/api/dcim/device-types/?cf_platform_id={{ fortios_firmware_facts['meta']['results']['current']['platform-id'] }}" 24 | method: GET 25 | validate_certs: no 26 | headers: 27 | Authorization: "Token {{ netbox_token }}" 28 | register: device_type_results 29 | delegate_to: localhost 30 | 31 | - name: "CONFIRM SOLO FIREWALL TO NETBOX" 32 | netbox.netbox.netbox_device: 33 | netbox_url: "{{ netbox_url }}" 34 | netbox_token: "{{ netbox_token }}" 35 | data: 36 | name: "{{ inventory_hostname }}" 37 | device_type: "{{ device_type_results['json']['results'][0]['slug']}}" 38 | platform: "{{ hostvars[inventory_hostname]['platforms'][0] }}" 39 | device_role: "{{ hostvars[inventory_hostname]['device_roles'][0] }}" 40 | site: "{{ hostvars[inventory_hostname]['sites'][0] }}" 41 | serial: "{{ fortios_ha_facts['meta']['serial'] }}" 42 | custom_fields: 43 | code_version: "{{ fortios_firmware_facts['meta']['results']['current']['version'] }}" 44 | imei: "{{ custom_fields['imei'] }}" 45 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 46 | state: present 47 | validate_certs: no 48 | when: fortios_ha_facts['meta']['results']|length < 1 49 | delegate_to: localhost 50 | 51 | - name: "CONFIRM HA PAIR TO NETBOX - PRIMARY NODE" 52 | netbox.netbox.netbox_device: 53 | netbox_url: "{{ netbox_url }}" 54 | netbox_token: "{{ netbox_token }}" 55 | data: 56 | name: "{{ inventory_hostname }}" 57 | device_type: "{{ device_type_results['json']['results'][0]['slug']}}" 58 | platform: "{{ hostvars[inventory_hostname]['platforms'][0] }}" 59 | device_role: "{{ hostvars[inventory_hostname]['device_roles'][0] }}" 60 | site: "{{ hostvars[inventory_hostname]['sites'][0] }}" 61 | serial: "{{ fortios_ha_facts['meta']['results'][0]['serial_no'] }}" 62 | custom_fields: 63 | code_version: "{{ fortios_firmware_facts['meta']['results']['current']['version'] }}" 64 | imei: "{{ custom_fields['imei'] }}" 65 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 66 | state: present 67 | validate_certs: no 68 | when: fortios_ha_facts['meta']['results']|length > 1 69 | delegate_to: localhost 70 | 71 | - name: "CONFIRM HA PAIR TO NETBOX - SECOND NODE" 72 | netbox.netbox.netbox_device: 73 | netbox_url: "{{ netbox_url }}" 74 | netbox_token: "{{ netbox_token }}" 75 | data: 76 | name: "{{ inventory_hostname }}_secondary_standby" 77 | device_type: "{{ device_type_results['json']['results'][0]['slug']}}" 78 | platform: "{{ hostvars[inventory_hostname]['platforms'][0] }}" 79 | device_role: "{{ hostvars[inventory_hostname]['device_roles'][0] }}" 80 | site: "{{ hostvars[inventory_hostname]['sites'][0] }}" 81 | serial: "{{ fortios_ha_facts['meta']['results'][1]['serial_no'] }}" 82 | custom_fields: 83 | code_version: "{{ fortios_firmware_facts['meta']['results']['current']['version'] }}" 84 | imei: "{{ custom_fields['imei'] }}" 85 | ansible_connection: "{{ custom_fields['ansible_connection'] }}" 86 | state: present 87 | validate_certs: no 88 | when: fortios_ha_facts['meta']['results']|length > 1 89 | delegate_to: localhost -------------------------------------------------------------------------------- /cloud-ip-ranges-checks/compare-google-ips-check-against-fortinet.yml: -------------------------------------------------------------------------------- 1 | ### This playbook will compare the list of known IPv4 Google Cloud IP addresses against 2 | ### the a deployed 'address_grp' object in a Fortigate Firewall 3 | ### First we grab the json data from https://www.gstatic.com/ipranges/goog.json and extract ipv4 addresses to a list 4 | ### Next we grab the firewall_addrgrp called 'google_cdn' on the firewall and put it into a list 5 | ### We run a difference of list1 vs list2. If there is a difference, the PB will throw an error and Tower sends email 6 | ### Kaon Thana 4/22/2022 7 | --- 8 | - name: PB to compare Google Cloud IPs vs current 'google_cdn' object in firewall 9 | hosts: firewall_host1 10 | gather_facts: false 11 | vars: 12 | ### Fortinet specific vars for Ansible to connect - https://galaxy.ansible.com/fortinet/fortios 13 | ansible_python_interpreter: /usr/bin/python3 14 | ansible_user: user 15 | ansible_password: password 16 | ansible_connection: httpapi 17 | ansible_httpapi_use_ssl: yes 18 | ansible_httpapi_validate_certs: no 19 | ansible_httpapi_port: 443 20 | ansible_network_os: fortinet.fortios.fortios 21 | vdom: "root" 22 | ### Variable to store list of 'google_cdn' ips as a list from the user firewall 23 | google_ip_list: [] 24 | ### Variable to store list of Google Cloud IP addresses we retrieve from the internet 25 | google_cdn_ipv4_list: [] 26 | ### Variable to store the list difference result 27 | missing_google_ips: [] 28 | tasks: 29 | ### GET request to retrieve the current json data of Google Cloud IP Ranges 30 | - name: Get all google cloud ip ranges as json result 31 | uri: 32 | url: "https://www.gstatic.com/ipranges/goog.json" 33 | method: GET 34 | validate_certs: no 35 | register: google_web_json_result 36 | ### Ansible will register a change here, we can ignore it. 37 | changed_when: false 38 | delegate_to: localhost 39 | 40 | ### Extract only IPv4 Addresses and add to a flat list 41 | - set_fact: 42 | google_cdn_ipv4_list: "{{ google_cdn_ipv4_list + [ item['ipv4Prefix'] ] }}" 43 | loop: "{{ google_web_json_result.json.prefixes }}" 44 | when: item['ipv4Prefix'] is defined 45 | changed_when: false 46 | delegate_to: localhost 47 | 48 | ### Hit the firewall once here to retrieve the object. This object does not contain IP/Mask info only names 49 | - name: Get google_cdn list of objects from firewall 50 | fortinet.fortios.fortios_configuration_fact: 51 | vdom: "{{ vdom }}" 52 | selector: "firewall_addrgrp" 53 | params: 54 | name: "google_cdn" 55 | register: google_networks_objects 56 | changed_when: false 57 | 58 | ### For each name in 'google_cdn' object we ask the firewall to give us back the IP/Mask info. Many API hits here. 59 | - name: Iterate through every Google object and extract subnet info 60 | fortinet.fortios.fortios_configuration_fact: 61 | vdom: "{{ vdom }}" 62 | selector: "firewall_address" 63 | params: 64 | name: "{{ item.name }}" 65 | register: google_item 66 | loop: "{{ google_networks_objects.meta.results[0]['member'] }}" 67 | changed_when: false 68 | 69 | ### The returned IP and Subnet info is in form 10.10.10.10 255.255.255.0. These Filters translate that to 10.10.10.10/24 70 | ### List is populated with all 'google_cdn' IP/Mask in correct format for comparison 71 | - set_fact: 72 | google_ip_list: "{{ google_ip_list + [ item.meta.results[0].subnet | replace(' ','/') | ansible.netcommon.ipaddr ] }}" 73 | loop: "{{ google_item.results }}" 74 | changed_when: false 75 | delegate_to: localhost 76 | 77 | ### Use ansible difference filter to compare list1 to list2. It shows items that are in list1 but not in list2 78 | - name: Show the difference in lists 79 | set_fact: 80 | missing_google_ips: "{{ google_cdn_ipv4_list | difference(google_ip_list) }}" 81 | changed_when: false 82 | delegate_to: localhost 83 | 84 | ### If an IP Subnet exists in the google cdn ipv4 list but not on the firewall 'google_cdn object' then 85 | ### we fail the PB and Tower will send an email with the list 86 | - debug: 87 | msg: "List of missing Google Subnets that need to be added to Firewall: {{ missing_google_ips }}" 88 | failed_when: missing_google_ips | length>0 89 | -------------------------------------------------------------------------------- /junos-snmpv3-generator/filter_plugins/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Kaon Thana 8-24-2022 4 | 5 | Filter file to generate junos compatible snmpv3 key plus encode it as a $9$ key for configuration 6 | 7 | Source #1 for the snmpv3 hashgen: 8 | https://github.com/TheMysteriousX/SNMPv3-Hash-Generator/blob/master/snmpv3_hashgen/hashgen.py 9 | 10 | Source #2 for the junos $9$ key encryption 11 | https://github.com/peering-manager/peering-manager/blob/main/devices/crypto/juniper.py 12 | 13 | https://metacpan.org/pod/Crypt::Juniper 14 | """ 15 | 16 | 17 | import hashlib 18 | import string 19 | import secrets 20 | 21 | from itertools import repeat 22 | from functools import partial 23 | 24 | P_LEN = 32 25 | E_LEN = 16 26 | 27 | import random 28 | 29 | 30 | 31 | MAGIC = "$9$" 32 | 33 | FAMILY = [ 34 | "QzF3n6/9CAtpu0O", 35 | "B1IREhcSyrleKvMW8LXx", 36 | "7N-dVbwsY2g4oaJZGUDj", 37 | "iHkq.mPf5T", 38 | ] 39 | EXTRA = {} 40 | for counter, value in enumerate(FAMILY): 41 | for character in value: 42 | EXTRA[character] = 3 - counter 43 | 44 | NUM_ALPHA = [x for x in "".join(FAMILY)] 45 | ALPHA_NUM = {NUM_ALPHA[x]: x for x in range(0, len(NUM_ALPHA))} 46 | 47 | ENCODING = [ 48 | [1, 4, 32], 49 | [1, 16, 32], 50 | [1, 8, 32], 51 | [1, 64], 52 | [1, 32], 53 | [1, 4, 16, 128], 54 | [1, 32, 64], 55 | ] 56 | 57 | class FilterModule: 58 | """ 59 | Defines a filter module object. 60 | """ 61 | 62 | @staticmethod 63 | def filters(): 64 | """ 65 | Return a list of hashes where the key is the filter 66 | name exposed to playbooks and the value is the function. 67 | """ 68 | return { 69 | 'gen_snmp_9key': FilterModule.gen_snmp_9key 70 | } 71 | @staticmethod 72 | def gen_snmp_9key(engine_id, snmp_pass): 73 | """ 74 | Takes two inputs (engine_id and snmp password). 75 | Hashes the password and engine id together to create a localized key. 76 | Then encodes the key with the juniper $9$ algorithm to be used in junos configuration 77 | """ 78 | 79 | hash = Hashgen.algs["sha1"] 80 | 81 | Kul_auth = Hashgen.derive_msg(snmp_pass, engine_id, hash) 82 | Kul_priv = Hashgen.derive_msg(snmp_pass, engine_id, hash) 83 | 84 | localized_key = hash(Kul_auth) 85 | 86 | snmp_9key = encrypt(localized_key) 87 | 88 | return snmp_9key 89 | 90 | class Hashgen(object): 91 | @staticmethod 92 | def hash(bytes, alg=hashlib.sha1, name=None, raw=False): 93 | digest = alg(bytes).digest() 94 | return digest if raw else digest.hex() 95 | 96 | @staticmethod 97 | def expand(substr, target_len): 98 | reps = target_len // len(substr) + 1 # approximation; worst case: overrun = l + len(s) 99 | return "".join(list(repeat(substr, reps)))[:target_len] 100 | 101 | @staticmethod 102 | def kdf(password, alg=None): 103 | alg = Hashgen.algs["sha1"] if alg is None else alg 104 | 105 | data = Hashgen.expand(password, 1048576).encode("utf-8") 106 | return alg(data, raw=True) 107 | 108 | @staticmethod 109 | def random_string(len=P_LEN, alphabet=(string.ascii_letters + string.digits)): 110 | return "".join(secrets.choice(alphabet) for _ in range(len)) 111 | 112 | @staticmethod 113 | def random_engine(len=E_LEN): 114 | return secrets.token_hex(len) 115 | 116 | @staticmethod 117 | def derive_msg(passphrase, engine, alg): 118 | # Parameter derivation á la rfc3414 119 | Ku = Hashgen.kdf(passphrase, alg) 120 | E = bytearray.fromhex(engine) 121 | 122 | return b"".join([Ku, E, Ku]) 123 | 124 | 125 | # Define available hash algorithms 126 | Hashgen.algs = { 127 | "md5": partial(Hashgen.hash, alg=hashlib.md5, name='md5'), 128 | "sha1": partial(Hashgen.hash, alg=hashlib.sha1, name='sha1'), 129 | "sha224": partial(Hashgen.hash, alg=hashlib.sha224, name='sha224'), 130 | "sha256": partial(Hashgen.hash, alg=hashlib.sha256, name='sha256'), 131 | "sha384": partial(Hashgen.hash, alg=hashlib.sha384, name='sha384'), 132 | "sha512": partial(Hashgen.hash, alg=hashlib.sha512, name='sha512'), 133 | } 134 | 135 | 136 | def __nibble(cref, length): 137 | nib = cref[0:length] 138 | rest = cref[length:] 139 | 140 | if len(nib) != length: 141 | raise Exception(f"Ran out of characters: hit '{nib}', expecting {length} chars") 142 | 143 | return nib, rest 144 | 145 | 146 | def __gap(c1, c2): 147 | return (ALPHA_NUM[str(c2)] - ALPHA_NUM[str(c1)]) % (len(NUM_ALPHA)) - 1 148 | 149 | 150 | def __gap_decode(gaps, dec): 151 | num = 0 152 | 153 | if len(gaps) != len(dec): 154 | raise Exception("Nibble and decode size not the same.") 155 | 156 | for x in range(0, len(gaps)): 157 | num += gaps[x] * dec[x] 158 | 159 | return chr(num % 256) 160 | 161 | 162 | def __reverse(current): 163 | reversed = list(current) 164 | reversed.reverse() 165 | return reversed 166 | 167 | 168 | def __gap_encode(pc, prev, encode): 169 | __ord = ord(pc) 170 | 171 | crypt = "" 172 | gaps = [] 173 | for mod in __reverse(encode): 174 | gaps.insert(0, int(__ord / mod)) 175 | __ord %= mod 176 | 177 | for gap in gaps: 178 | gap += ALPHA_NUM[prev] + 1 179 | prev = NUM_ALPHA[gap % len(NUM_ALPHA)] 180 | crypt += prev 181 | 182 | return crypt 183 | 184 | 185 | def __randc(counter=0): 186 | return_value = "" 187 | for _ in range(counter): 188 | return_value += NUM_ALPHA[random.randrange(len(NUM_ALPHA))] 189 | return return_value 190 | 191 | 192 | def is_encrypted(value): 193 | return value.startswith(MAGIC) 194 | 195 | 196 | def decrypt(value): 197 | if not value: 198 | return "" 199 | 200 | if not is_encrypted(value): 201 | return value 202 | 203 | chars = value.split("$9$", 1)[1] 204 | first, chars = __nibble(chars, 1) 205 | toss, chars = __nibble(chars, EXTRA[first]) 206 | previous = first 207 | decrypted = "" 208 | 209 | while chars: 210 | decode = ENCODING[len(decrypted) % len(ENCODING)] 211 | nibble, chars = __nibble(chars, len(decode)) 212 | gaps = [] 213 | for i in nibble: 214 | g = __gap(previous, i) 215 | previous = i 216 | gaps += [g] 217 | decrypted += __gap_decode(gaps, decode) 218 | 219 | return decrypted 220 | 221 | 222 | def encrypt(value, salt=None): 223 | if not value: 224 | return "" 225 | 226 | if is_encrypted(value): 227 | return value 228 | 229 | # if not salt: 230 | # salt = __randc(1) 231 | # rand = __randc(EXTRA[salt]) 232 | 233 | salt = '7' 234 | rand = '7' 235 | 236 | position = 0 237 | previous = salt 238 | crypted = MAGIC + salt + rand 239 | 240 | for x in value: 241 | encode = ENCODING[position % len(ENCODING)] 242 | crypted += __gap_encode(x, previous, encode) 243 | previous = crypted[-1] 244 | position += 1 245 | 246 | return crypted 247 | --------------------------------------------------------------------------------