if input.tail and input.type != 'radio':
param['description'] = enforce_period(input.tail)
elif isinstance(input, lxml.html.SelectElement):
if args.verbose >= 2:
print(f'Found select element: name={input.name} value={input.value} value_options={input.value_options} multiple={input.multiple} attrib={input.attrib}')
# Strip any trailing []
input.name = re.sub(r'\[]$', '', input.name)
if input.attrib.get('class') == 'form-control' and input.attrib.get('data-toggle') == 'collapse':
args.type_param = input.name
if input.value is not None:
param['default'] = input.value
if input.value_options is not None:
if input.name == 'interface':
param['type'] = 'str'
# If a GW Group is present, allow them - this would be better to check for GW Group in the option text
# but this would require extracting that which will take a bit of work
if any('GW' in s for s in input.value_options):
param['parse'] = 'p2o_interface_with_gwgroup'
if 'p2o_interface_with_gwgroup' not in args_imports:
args_imports.append('p2o_interface_with_gwgroup')
# By default, interfaces will be parsed allowing virtual interfaces. If not allowed we need a different parser.
elif len(set(input.value_options).intersection(['enc0', 'openvpn'])) == 0:
param['parse'] = 'p2o_interface_without_virtual'
if 'p2o_interface_without_virtual' not in args_imports:
args_imports.append('p2o_interface_without_virtual')
else:
if input.multiple:
param['type'] = 'list'
param['default'] = []
for selected in input.value:
print(f'selected = {selected}')
param['default'].append(selected)
else:
param['type'] = 'str'
param['choices'] = input.value_options
param['multiple'] = input.multiple
elif input.tag == 'textarea':
param['type'] = 'str'
#
form_groups = input.xpath('./ancestor::div[@class="form-group"]')
if form_groups:
form_group = form_groups[0]
descr_elt = form_group.find('*span')
if descr_elt is not None:
if descr_elt.text:
if args.verbose >= 2:
print(f'Found descr_elt {descr_elt.tag} {descr_elt.text} {descr_elt.attrib}')
if input.get('type') == 'radio':
param['description'] = f'{descr_elt.text.strip()} of the {module_name}.'
else:
param['description'] += f'{descr_elt.text.strip()} of the {module_name}.'
if 'class' in descr_elt.attrib and descr_elt.attrib['class'] == 'element-required':
if args.verbose >= 2:
print(f'Found element-required')
param['required'] = True
else:
if args.verbose >= 3:
print(f'Could not find descriptive element for item')
help_elt = form_group.find('./div/span[@class="help-block"]')
if help_elt is not None and help_elt.text is not None:
if args.verbose >= 2:
print(f'help_elt text {help_elt.text.strip()}')
descr = enforce_period(help_elt.text.strip())
if input.get('type') == 'radio':
param['description'] = f' {descr}'
else:
param['description'] += f' {descr}'
if args.verbose >= 2:
print(f'Final param = {param}\n')
params[input.name] = param
if not args.is_config:
# Key is handled separately from other parameters so remove it
# TODO - keep the description, etc?
params.pop(module_key, None)
# Debug
if args.verbose >= 2:
print(f'Web paramters: {params.keys()}')
# Determine the type of the bool parameters
bool_style = None
bool_values = {}
for name, param in params.items():
if param.get('type') != 'bool':
continue
bool_values[name] = param['value']
if bool_style is None:
bool_style = param['value']
elif bool_style != param['value']:
# Not consistent
bool_style = 'inconsistent'
# Determine if the form produces different types of items
if args.type_param in params_full:
# The type of item is recorded in the item configuration
module_type = params_full[args.type_param]['example']
if args.type_suffix is True:
module_name += f'_{module_type}'
if isinstance(args.type_suffix, str):
module_name += f'_{args.type_suffix}'
elif (match := re.match(r'type=([^&]+)', uri.query)):
module_type = match.group(1)
module_name += f'_{module_type}'
elif args.type_param in params:
# The type of item is purely a fuction of the web form
choices = params[args.type_param].get('choices')
if choices is None:
sys.exit(f"Detected item type parameter '{args.type_param}' but no choices parameter in {params[args.type_param]}. You must set --type-suffix to something.")
if args.type_suffix not in params[args.type_param]['choices']:
sys.exit(f"Detected item type parameter '{args.type_param}' with choices {params[args.type_param]['choices']}. You must set --type-suffix to one of these.")
else:
module_type = args.type_suffix
module_name += f'_{args.type_suffix}'
params_full[args.type_param] = params[args.type_param]
args.keep_params = True
else:
module_type = None
if not args.is_config:
# Consistency
params_web_only = list(set(params.keys()) - set(params_full.keys()))
if args.verbose >= 2:
print('Web parameters not in xml: ' + str(params_web_only))
# Cleanup extra web parameters
for param in params_web_only:
# See if the items are numbered, likely maps to an unnumbered XML tag
newp = re.sub(r'0$', '', param)
if newp != param:
if newp in params_full:
if args.verbose >= 2:
print(f'Renaming {param} to {newp}')
params[newp] = params.pop(param)
continue
# See if the items are prefixed by a type, likely maps to un-prefixed XML tag
newp = re.sub(f'^{module_type}_', '', param)
if newp != param:
if newp in params_full and newp not in params:
if args.verbose >= 2:
print(f'Renaming {param} to {newp}')
params[newp] = params.pop(param)
continue
# Common renamings
for f, t in [('dst', 'destination'), ('src', 'source')]:
if param == f and t in params_full:
if args.verbose >= 2:
print(f'Renaming {f} to {t}')
params[t] = params.pop(f)
break
else:
# Otherwise, drop - probably just used to construct the final elements
if param in params and not args.keep_params:
if args.verbose >= 2:
print(f'Removing {param}')
del params[param]
params_xml_only = list(set(params_full.keys()) - set(params.keys()) - {module_key, 'refid'})
if args.verbose >= 2:
print(f'XML parameters not in web: {params_xml_only}\n')
if len(params_xml_only) > 0:
print(f'You may need to use {module_node.upper()}_MAP_PARAMS')
for param in params_xml_only:
params[param] = params_full[param]
# Create some sample descriptions
for name, param in params.items():
# TODO - wrap long descriptions
if 'description' not in param or param['description'] == '':
param['description'] = f'The {name} of the {module_node}.'
if 'example' not in param or param['example'] == '':
if name in params_full and 'example' in params_full[name]:
param['example'] = params_full[name]['example']
if 'default' in param:
param['description'] += f' Defaults to {param["default"]}.'
if args.is_config:
module_base = 'PFSenseModuleConfigBase'
# Generate PFSense module name
if package == 'core':
pfsense_module_name = 'PFSense' + ''.join([word.capitalize() for word in module_name.split('_')]) + 'Module'
else:
pfsense_module_name = f'PFSense{package}' + ''.join([word.capitalize() for word in module_name.split('_')[1:]]) + 'Module'
# Template variables
context = dict(
module_base=module_base,
module_name=module_name,
module_root=module_root,
module_node=module_node,
module_key=module_key,
pfsense_module_name=pfsense_module_name,
params=params,
params_xml_only=params_xml_only,
name_param=name_param,
bool_style=bool_style,
bool_values=bool_values,
args_imports=args_imports,
is_config=args.is_config,
is_simple_package=is_simple_package,
is_full_package=is_full_package,
package=package,
author_name=args.author_name,
author_email=args.author_email,
author_handle=args.author_handle,
php_requires=php_requires,
php_save=php_save,
php_subsystem=php_subsystem,
year=datetime.date.today().year,
)
# Render our module!
jenv = jinja2.Environment(loader=jinja2.FileSystemLoader("misc/"), trim_blocks=True, keep_trailing_newline=True)
jenv.filters['dict2items'] = dict_to_list_of_dict_key_value_elements
jenv.filters['unique'] = unique
template = jenv.get_template("pfsense_module.py.j2")
filename = f'plugins/modules/pfsense_{module_name}.py'
if os.path.isfile(filename) and not args.force:
sys.exit(f'{filename} already exists! Use --force to overwrite.')
if args.verbose > 0:
print(f'Writing module {filename} with {context}')
else:
print(f'Writing module {filename}')
f = open(f'{filename}', 'w')
f.write(template.render(context))
f.close()
================================================
FILE: misc/pytest
================================================
#!/bin/sh
misc/local2ansible
if [ ! -f .coveragerc ]; then
cp -f misc/.coveragerc .
fi
python3 -m pytest -v -r a test/units/modules/network/pfsense/ test/units/plugins/lookup/test_pfsense.py --cov --cov-report html $*
================================================
FILE: misc/run_ansible_sanity_tests
================================================
#!/bin/sh
if [ -z "${ANSIBLE_HOME}" ]
then
echo "ANSIBLE_HOME is undefined. Go into ansible directory and run 'source hacking/env-setup'"
exit 1
fi
misc/local2ansible
TO_CHECK='lib/ansible/modules/network/pfsense/
lib/ansible/module_utils/network/pfsense/
lib/ansible/plugins/lookup/pfsense.py
test/units/modules/network/pfsense/'
cd ${ANSIBLE_HOME}
ansible-test sanity --python 3.5 ${TO_CHECK} $*
ansible-test sanity --python 2.7 ${TO_CHECK} $*
================================================
FILE: misc/setup_units_tests
================================================
#!/bin/sh
if [ -z "${ANSIBLE_HOME}" ]
then
echo "ANSIBLE_HOME is undefined. Go into ansible directory and run 'source hacking/env-setup'"
exit 1
fi
rm -f test/units/compat test/units/modules/utils.py test/units/modules/utils.pyc test/units/module_utils test/units/__init__.py test/units/__init__.pyc test/units/modules/__init__.py test/units/modules/__init__.pyc
ln -s ${ANSIBLE_HOME}/test/units/compat test/units/compat
ln -s ${ANSIBLE_HOME}/test/units/modules/utils.py test/units/modules/utils.py
ln -s ${ANSIBLE_HOME}/test/units/module_utils test/units/module_utils
touch test/units/__init__.py
touch test/units/modules/__init__.py
================================================
FILE: plugins/lookup/pfsense.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: pfsense
author: Frederic Bor (@f-bor)
version_added: 0.1.0
short_description: Generate pfSense aliases, rules and rule_separators
description:
- This lookup plugin is designed to be used with pfsense_aggregate module.
It takes a yaml file and generate list of aliases and rules definitions.
The aim is to be able to easily manage a fleet of pfSenses and avoiding any
redundant work, like defining the sames hosts/ports aliases or rules
on multiples pfSenses. The plugin determine what is required to be defined
on each pfsense, leaving to the network administrator only the task of updating
the yaml definition file.
options:
file:
description: The yaml file defining the network
type:
description: What to generate
choices:
- aliases
- rules
- rule_separators
"""
EXAMPLES = """
- name: Get all aliases to be defined
debug:
aliases: "{{ lookup('pfsensible.core.pfsense', 'all_pf_defs.yml', 'aliases') }}"
- name: Get all rules to be defined
debug:
rules: "{{ lookup('pfsensible.core.pfsense', 'all_pf_defs.yml', 'rules') }}"
- name: Get all rule_separators to be defined
debug:
rule_separators: "{{ lookup('pfsensible.core.pfsense', 'all_pf_defs.yml', 'rule_separators') }}"
"""
RETURN = """
_list:
description:
- list of dictionaries with aliases, rules or rule_separators
type: list
"""
"""
To determine if a rule and corresponding aliases has to be declared on a pfsense
and on which interfaces, the plugin check if rule source or destination is
matching any local or routed network on the pfsense. To avoid having every rule
declared on a wan interface, if a rule can be declared on multiple interfaces
it is not on the ones routing 0.0.0.0/0 if there is another which is not routing it.
If a network is declared as remote (routed thru) on an interface and is also defined
as the local network on an interface of the target pfsense, the local network is preffered.
The same apply to adjacent networks, which indicates neighbor networks that are routed thru a pfSense.
Following pfSense rule definition (one host/alias per source/destination,
one port/alias per source/destination), each rule declared in the yaml is breaked
into smaller rules until having rules than can be declared.
The generated rules order follows the yaml file rules order.
Rule separators name are taken from parent rules' groups (see 'ADMIN', 'VOIP',
'MISC' or 'ACTIVE DIRECTORY' in the example below). Nested groups generate separators
names in the form 'GROUP1 - GROUP2 - ...'
You can define a default value for all rules and subrules of a separator using
the name 'options'. The parameters supported this way are gateway, log, queue, ackqueue,
in_queue, out_queue and sched. You can override those default values setting other values
on a deeper options set or inside the rule definition.
You can use an extra parameter in rules and options, filter, to restrict the rule
generation only to the pfsenses set in this parameter. Same goes for ifilter and interfaces.
The yaml file must include the following definitions to describe the network topology:
- pfsenses
- rules
- hosts_aliases
- ports_aliases
You can run the plugin alone to debug rules and aliases generation, for example:
- ./lookup_plugins/pfsense.py defs.yml pf1
- ./lookup_plugins/pfsense.py defs.yml pf1 ping_from_poc3
A typical pfsense_aggregate task using the lookup plugin will look like this:
tasks:
- name: "setup aliases & rules"
pfsense_aggregate:
purge_aliases: true
purge_rules: true
purge_rule_separators: true
aggregated_aliases: |
{{ lookup('pfsensible.core.pfsense', 'defs.yml', 'aliases') }}
aggregated_rules: |
{{ lookup('pfsensible.core.pfsense', 'defs.yml', 'rules') }}
aggregated_rule_separators: |
{{ lookup('pfsensible.core.pfsense', 'defs.yml', 'rule_separators') }}
Here is an example of yaml file:
---
pfsenses:
pf1: {
interfaces: {
lan: { ip: 192.168.1.1/24, adjacent_networks: lan_data_poc4 },
lan_100: { ip: 172.16.1.1/24 },
vpn: { remote_networks: lan_data_all lan_voip_all},
}
}
pf2: {
interfaces: {
lan: { ip: 192.168.2.1/24 },
lan_100: { ip: 172.16.2.1/24 },
vpn: { remote_networks: lan_data_all lan_voip_all},
}
}
pf3: {
interfaces: {
bridge_lan: { ip: 192.168.3.1/24, remote_networks: lan_data_all, bridge: True },
bridge_lan_100: { ip: 172.16.3.1/24, remote_networks: lan_voip_all, bridge: True },
wan: { remote_networks: 0.0.0.0/0 }
}
}
rules:
options: { log: yes }
ADMIN:
antilock_out: { src: any, dst: any, protocol: tcp, dst_port: port_ssh port_http 443 }
admin_bypass: { src: srv_admin, dst: any }
MISC:
ping_from_poc3: { src: lan_poc3_all, dst: srv_admin, protocol: icmp }
VOIP:
voip_conf_tftp: { src: all_ipbx, dst: lan_voip_all, dst_port: 69, protocol: udp }
ACTIVE DIRECTORY:
ads_to_ads_tcp: { src: all_ads, dst: all_ads, dst_port: port_dns port_ldap port_ldap_ssl, protocol: tcp }
ads_to_ads_udp: { src: all_ads, dst: all_ads, dst_port: port_dns port_ldap, protocol: udp }
DNS:
options: { log: no }
any_to_local_dns: {src: any, dst: all_ads, dst_port: port_dns, protocol: udp }
hosts_aliases:
# hosts
ipbx_poc1: { ip: 172.16.1.3 }
ipbx_poc2: { ip: 172.16.2.3 }
ipbx_poc3: { ip: 172.16.3.3 }
all_ipbx: { ip: ipbx_poc1 ipbx_poc2 ipbx_poc3 }
ad_poc1: { ip: 192.168.1.3 }
ad_poc2: { ip: 192.168.2.3 }
ad_poc3: { ip: 192.168.3.3 }
all_ads: { ip: ad_poc1 ad_poc2 ad_poc3 }
# networks
lan_voip_poc1: { ip: 172.16.1.0/24 }
lan_voip_poc2: { ip: 172.16.2.0/24 }
lan_voip_poc3: { ip: 172.16.3.0/24 }
lan_voip_all : { ip: lan_voip_poc1 lan_voip_poc2 lan_voip_poc3 }
lan_data_poc1: { ip: 192.168.1.0/24 }
lan_data_poc2: { ip: 192.168.2.0/24 }
lan_data_poc3: { ip: 192.168.3.0/24 }
lan_data_poc4: { ip: 192.168.4.0/24 }
lan_data_all : { ip: lan_data_poc1 lan_data_poc2 lan_data_poc3 lan_data_poc4 }
lan_poc3_all : { ip: lan_voip_poc3 lan_data_poc3 }
srv_admin: { ip: 192.168.1.165 }
ports_aliases:
port_ssh: { port: 22 }
port_telnet: { port: 23 }
port_dns: { port: 51 }
port_http: { port: 80 }
port_ldap: { port: 389 }
port_ldap_ssl: { port: 636 }
"""
from copy import copy, deepcopy
from collections import OrderedDict
from ansible.utils.display import Display
from functools import lru_cache
try:
from dns import resolver, exception
except ImportError as imp_exc:
DNS_IMPORT_ERROR = imp_exc
else:
DNS_IMPORT_ERROR = None
import argparse
import json
import re
import socket
import sys
import yaml
import traceback
import os
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
import ipaddress
OPTION_FIELDS = [
'gateway', 'log', 'queue', 'ackqueue', 'in_queue', 'out_queue', 'queue_error', 'icmptype', 'filter', 'efilter', 'ifilter', 'eifilter', 'sched', 'quick',
'direction', 'staticnatport', 'ipprotocol',
'associated_rule', 'natreflection',
]
OUTPUT_OPTION_FIELDS = ['gateway', 'log', 'queue', 'ackqueue', 'in_queue', 'out_queue', 'queue_error', 'icmptype', 'sched', 'quick', 'direction', 'ipprotocol']
OUTPUT_SRC_NAT_OPTION_FIELDS = ['staticnatport', 'ipprotocol']
OUTPUT_DST_NAT_OPTION_FIELDS = ['associated_rule', 'natreflection']
display = Display()
def to_unicode(string):
""" return a unicode representation of string if required """
if sys.version_info[0] >= 3:
return string
return string.decode("utf-8")
def ordered_load(stream, loader_cls=yaml.SafeLoader, object_pairs_hook=OrderedDict):
""" load and return yaml data from stream using ordered dicts """
class OrderedLoader(loader_cls):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(OrderedLoader, self).__init__(stream)
def include(self, node):
filename = os.path.join(self._root, self.construct_scalar(node))
with open(filename, 'r') as f:
return yaml.load(f, OrderedLoader)
def construct_mapping(loader, node):
loader.flatten_mapping(node)
return object_pairs_hook(loader.construct_pairs(node))
if DNS_IMPORT_ERROR:
raise AnsibleError('dns must be installed to use ordered_load from this plugin') from DNS_IMPORT_ERROR
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
construct_mapping)
OrderedLoader.add_constructor(
'!include',
OrderedLoader.include)
return yaml.load(stream, OrderedLoader)
def static_vars(**kwargs):
""" static decorator to declare static vars """
def decorate(func):
""" static decorator func """
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
@lru_cache(maxsize=None)
def to_ip_address(address):
""" convert address to IPv4Address or IPv6Address """
return ipaddress.ip_address(to_unicode(address))
@lru_cache(maxsize=None)
def to_ip_network(address, strict=True):
""" convert address to IPv4Network or IPv6Network """
return ipaddress.ip_network(to_unicode(address), strict=strict)
@lru_cache(maxsize=None)
@static_vars(
classA=ipaddress.IPv4Network((u"10.0.0.0", u"255.0.0.0")),
classB=ipaddress.IPv4Network((u"172.16.0.0", u"255.240.0.0")),
classC=ipaddress.IPv4Network((u"192.168.0.0", u"255.255.0.0")),
)
def is_private_ip(address):
""" check if ip address is class A, B or C """
if not isinstance(address, ipaddress.IPv4Address):
ip_address = to_ip_address(to_unicode(address))
else:
ip_address = address
return ip_address in is_private_ip.classA or ip_address in is_private_ip.classB or ip_address in is_private_ip.classC
@lru_cache(maxsize=None)
@static_vars(
classA=ipaddress.IPv4Network((u"10.0.0.0", u"255.0.0.0")),
classB=ipaddress.IPv4Network((u"172.16.0.0", u"255.240.0.0")),
classC=ipaddress.IPv4Network((u"192.168.0.0", u"255.255.0.0")),
)
def is_private_network(address):
""" check if network is class A, B or C """
if not isinstance(address, ipaddress.IPv4Network):
net = to_ip_network(to_unicode(address))
else:
net = address
return net.subnet_of(is_private_network.classA) or net.subnet_of(is_private_network.classB) or net.subnet_of(is_private_network.classC)
@lru_cache(maxsize=None)
@static_vars(
ip_broadcast=ipaddress.IPv4Address((u"255.255.255.255")),
net_multicast=ipaddress.IPv4Network((u"224.0.0.0", u"255.255.255.0")),
)
def is_ip_broadcast(address):
""" check if ip address is ip broadcast address """
if not isinstance(address, ipaddress.IPv4Address):
ip_address = to_ip_address(to_unicode(address))
else:
ip_address = address
return ip_address == is_ip_broadcast.ip_broadcast or ip_address in is_ip_broadcast.net_multicast
@static_vars(re_cache=None)
def is_fqdn(address):
""" check if address is a fqdn address """
if is_fqdn.re_cache is None:
is_fqdn.re_cache = re.compile(r'(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)')
return is_fqdn.re_cache.match(address) is not None
def resolve_hostname(address, dns_servers=None):
""" get ip for hostname """
if dns_servers is None:
try:
resolved_ip = socket.gethostbyname(address)
return resolved_ip
except socket.gaierror:
pass
msg = "Unable to resolve: {0}".format(address)
else:
if DNS_IMPORT_ERROR:
raise AnsibleError('dns must be installed to use ordered_load from this plugin') from DNS_IMPORT_ERROR
error = None
try:
res = resolver.Resolver()
res.timeout = 15
res.lifetime = 15
res.nameservers = dns_servers
answers = res.query(address)
if answers:
return answers[0].address
except exception.Timeout:
error = 'timeout'
msg = "Unable to resolve: {0} using {1} dns servers".format(address, ','.join(dns_servers))
if error is not None:
msg += ' ({0})'.format(error)
raise AssertionError(msg)
def is_valid_ip(address):
""" validate ip address format """
try:
to_ip_address(to_unicode(address))
return True
except ValueError:
return False
def is_valid_port(port):
""" validate port format """
if not port.isdigit():
return False
nport = int(port)
return nport >= 0 and nport <= 65535
def is_valid_port_range(port_range):
""" validate port range format """
group = re.match(r'^(\d+)-(\d+)$', port_range)
if not group:
return False
nport1 = int(group.group(1))
nport2 = int(group.group(2))
return nport1 >= 0 and nport1 <= 65535 and nport2 >= 0 and nport2 <= 65535
def is_valid_network(address):
""" validate network address format """
try:
to_ip_network(to_unicode(address))
return True
except ValueError:
return False
def rule_product_dict(tab, rule, field, out_field=None):
""" Return cartesian product between rule[field] and tab as dicts """
if field not in rule:
return tab
if not out_field:
out_field = field
out = []
for new_val in rule[field].split():
for existing_val in tab:
obj = existing_val.copy()
obj[out_field] = new_val
out.append(obj)
return out
def rule_product_ports(rule, field, field_port):
""" Return cartesian product between rule[field] and field_port as string """
if field_port not in rule:
return rule[field]
aliases = rule[field].split()
ports = rule[field_port].split()
ret = []
for alias in aliases:
added = False
for port in ports:
if port:
ret.append(alias + ":" + port)
added = True
if not added:
ret.append(alias)
return ' '.join(ret)
def get_bool(values, field):
""" Return boolean field value from values """
field_value = False
if isinstance(values[field], bool):
field_value = values[field]
elif isinstance(values[field], str):
if values[field].lower() in ['yes', 'true']:
field_value = True
elif values[field].lower() not in ['no', 'false']:
raise AnsibleError('{0} must be yes/no or true/false (got "{1}")'.format(field, values[field]))
return field_value
class PFSenseHostAlias(object):
""" Class holding structured pfsense host alias definition """
def __init__(self):
self.name = None
self.descr = None
self.definition = []
self.ips = []
self.networks = []
self.dns = None
self.fake = False
# define all interfaces on which the alias may be defined as a local source
# interfaces['gw_poc1_1'] = ['lan', 'obs']
self.local_interfaces = {}
# define all interfaces on which the alias may be defined as a routed source
self.routed_interfaces = {}
self._computed = False
def copy(self):
copy_object = PFSenseHostAlias()
copy_object.name = self.name
copy_object.descr = self.descr
copy_object.dns = self.dns
copy_object.fake = self.fake
copy_object._computed = self._computed
copy_object.definition = self.definition[:]
# ips
copy_object.ips = self.ips[:]
# networks
copy_object.networks = self.networks[:]
# local_interfaces
for k, v in self.local_interfaces.items():
copy_object.local_interfaces[k] = v.copy()
# routed_interfaces
for k, v in self.routed_interfaces.items():
copy_object.routed_interfaces[k] = v.copy()
return copy_object
def __str__(self):
return "name={0}, descr={1}, definition={2}, ips={3}, networks={4}, local_interfaces={5}, routed_interfaces={6}, fake={7}".format(
self.name, self.descr, self.definition, self.ips, self.networks, self.local_interfaces, self.routed_interfaces, self.fake)
def compute_any(self, data):
""" Do all computations for object 'any' """
# we add all interfaces of all pfsenses
for pfsense in data.pfsenses_obj.values():
for interface in pfsense.interfaces.values():
self.local_interfaces[pfsense.name].append(interface.name)
self.routed_interfaces[pfsense.name].append(interface.name)
def compute_all(self, data):
""" Do all computations """
if not self._computed:
self._computed = True
if self.name != 'any':
self.compute_addresses(data)
self.compute_local_interfaces(data)
self.compute_routed_interfaces(data)
def compute_addresses(self, data):
""" Convert all aliases to structured ip addresses or networks """
todo = []
todo.extend(self.definition)
while todo:
address = todo.pop()
# special case when (self) is used in nat rules
if address == '':
continue
# it's an ip address
try:
host_ip = to_ip_address(to_unicode(address))
self.ips.append(host_ip)
continue
except ValueError:
pass
# it's an ip network
try:
net = to_ip_network(to_unicode(address))
self.networks.append(net)
continue
except ValueError:
pass
# it's a fqdn
if address not in data.all_aliases:
if is_fqdn(address):
resolved_ip = resolve_hostname(address, self.dns)
host_ip = to_ip_address(to_unicode(resolved_ip))
self.ips.append(host_ip)
continue
raise AssertionError("Invalid address: " + address + " for " + self.name)
# it's another alias
alias = data.hosts_aliases_obj.get(address)
if alias is not None:
if not alias._computed:
alias.compute_all(data)
self.ips += alias.ips
self.networks += alias.networks
continue
todo.extend(data.all_aliases[address]['ip'].split())
def _is_in_networks(self, interface, fcheckname):
""" check if an alias is in a network of an interface """
fcheck = getattr(interface, fcheckname)
for alias_ip in self.ips:
if is_ip_broadcast(alias_ip):
continue
if not fcheck(alias_ip):
return False
for alias_net in self.networks:
if not fcheck(alias_net):
return False
return True
def is_in_local_network(self, interface):
""" check if an alias is in the local network of an interface """
return self._is_in_networks(interface, 'local_network_contains')
def is_in_remote_networks(self, interface):
""" check if an alias is in the remote networks of an interface """
return self._is_in_networks(interface, 'remote_networks_contains')
def is_in_adjacent_networks(self, interface):
""" check if an alias is in the adjacent networks of an interface """
return self._is_in_networks(interface, 'adjacent_networks_contains')
def compute_routed_interfaces(self, data):
""" Find all interfaces on all pfsense where the alias may be used as a routed source """
for pfsense in data.pfsenses_obj.values():
# if the target alias is local, we do not consider the other interfaces
self.routed_interfaces[pfsense.name] = set()
if pfsense.name in self.local_interfaces:
continue
cache = pfsense._interfaces_remote_networks_contains_cache
_compute = pfsense._interfaces_network_contains
local_ifaces = self.routed_interfaces[pfsense.name]
for address in self.ips:
cache_key = id(address)
interfaces = cache.get(cache_key)
if interfaces is None:
interfaces = _compute(address, 'remote_networks')
cache[cache_key] = interfaces
local_ifaces.update(interfaces)
for address in self.networks:
cache_key = id(address)
interfaces = cache.get(cache_key)
if interfaces is None:
interfaces = _compute(address, 'remote_networks')
cache[cache_key] = interfaces
local_ifaces.update(interfaces)
def compute_local_interfaces(self, data):
""" Find all interfaces on all pfsense where the alias may be used as a local source """
for pfsense in data.pfsenses_obj.values():
self.local_interfaces[pfsense.name] = set()
cache = pfsense._interfaces_local_networks_contains_cache
_compute = pfsense._interfaces_network_contains
local_ifaces = self.local_interfaces[pfsense.name]
for address in self.ips:
cache_key = id(address)
interfaces = cache.get(cache_key)
if interfaces is None:
interfaces = _compute(address, 'local_networks')
cache[cache_key] = interfaces
local_ifaces.update(interfaces)
for address in self.networks:
cache_key = id(address)
interfaces = cache.get(cache_key)
if interfaces is None:
interfaces = _compute(address, 'local_networks')
cache[cache_key] = interfaces
local_ifaces.update(interfaces)
def is_whole_local(self, pfsense):
""" check if all ips/networks match a local network interface in pfense """
for alias_ip in self.ips:
if is_ip_broadcast(alias_ip):
continue
if not pfsense.any_local_network_contains(alias_ip):
return False
for alias_net in self.networks:
if not pfsense.any_local_network_contains(alias_net):
return False
return True
def routed_by_interfaces(self, pfsense, use_remote_networks=True):
""" return all interfaces for which all ips/networks match an adjacent/remote network in pfsense """
all_interfaces = set()
# we always search through all interfaces to handle cases with internet
for alias_ip in self.ips:
interfaces = pfsense.interfaces_adjacent_or_remote_networks_contains(alias_ip, use_remote_networks=True)
interfaces = pfsense.hack_internet_routing(interfaces, alias_ip, use_remote_networks=True)
all_interfaces.update(interfaces)
for alias_net in self.networks:
interfaces = pfsense.interfaces_adjacent_or_remote_networks_contains(alias_net, use_remote_networks=True)
interfaces = pfsense.hack_internet_routing(interfaces, alias_net, use_remote_networks=True)
all_interfaces.update(interfaces)
# if we didn't want the remote interfaces, we only keep the adjacents
if not use_remote_networks:
interfaces = set()
for interface in all_interfaces:
if self.is_in_adjacent_networks(pfsense.interfaces[interface]):
interfaces.add(interface)
all_interfaces = interfaces
return all_interfaces
def is_adjacent_or_remote(self, pfsense):
""" check if all ips/networks are in an adjacent/remote network in pfsense """
return len(self.routed_by_interfaces(pfsense, use_remote_networks=True)) > 0
def is_adjacent(self, pfsense):
""" check if all ips/networks are in an adjacent network in pfsense """
return len(self.routed_by_interfaces(pfsense, use_remote_networks=False)) > 0
def is_ip_broadcast(self):
""" check if an alias is the ip_broadcast """
if len(self.ips) != 1 or self.networks:
return False
return is_ip_broadcast(self.ips[0])
def is_whole_in_pfsense(self, pfsense):
""" check if all ips/networks have at least one interface in pfsense """
if self.name in pfsense.is_whole_in_pfsense_cache:
return pfsense.is_whole_in_pfsense_cache[self.name]
for alias_ip in self.ips:
if is_ip_broadcast(alias_ip):
pfsense.is_whole_in_pfsense_cache[self.name] = False
return False
if not pfsense.any_network_contains(alias_ip):
pfsense.is_whole_in_pfsense_cache[self.name] = False
return False
for alias_net in self.networks:
if not pfsense.any_network_contains(alias_net):
pfsense.is_whole_in_pfsense_cache[self.name] = False
return False
pfsense.is_whole_in_pfsense_cache[self.name] = True
return True
def is_whole_not_in_pfsense(self, pfsense):
""" check if all ips/networks have at least one interface in pfsense """
if self.name in pfsense.is_whole_not_in_pfsense_cache:
return pfsense.is_whole_not_in_pfsense_cache[self.name]
for alias_ip in self.ips:
if is_ip_broadcast(alias_ip):
pfsense.is_whole_not_in_pfsense_cache[self.name] = False
return False
if pfsense.any_network_contains(alias_ip):
pfsense.is_whole_not_in_pfsense_cache[self.name] = False
return False
for alias_net in self.networks:
if pfsense.any_network_contains(alias_net):
pfsense.is_whole_not_in_pfsense_cache[self.name] = False
return False
pfsense.is_whole_not_in_pfsense_cache[self.name] = True
return True
def is_whole_in_same_routing_ifaces(self, pfsense):
""" check if all ips/networks have the same interfaces in pfense """
if self.name in pfsense.is_whole_in_same_routing_ifaces_cache:
return pfsense.is_whole_in_same_routing_ifaces_cache[self.name]
# we loop through all ips/networks in the alias
# if there is any difference in the interfaces where the address need to be defined
# then we return False so the parent function split the alias
target_ar_interfaces = None
target_local_interfaces = None
for alias_ip in self.ips:
interfaces = pfsense.interfaces_adjacent_or_remote_networks_contains(alias_ip)
interfaces = pfsense.hack_internet_routing(interfaces, alias_ip)
if target_ar_interfaces is None:
target_ar_interfaces = interfaces
elif target_ar_interfaces ^ interfaces:
pfsense.is_whole_in_same_routing_ifaces_cache[self.name] = False
return False
interfaces = pfsense.interfaces_local_networks_contains(alias_ip)
interfaces = pfsense.hack_internet_routing(interfaces, alias_ip)
if target_local_interfaces is None:
target_local_interfaces = interfaces
elif target_local_interfaces ^ interfaces:
pfsense.is_whole_in_same_routing_ifaces_cache[self.name] = False
return False
for alias_net in self.networks:
interfaces = pfsense.interfaces_adjacent_or_remote_networks_contains(alias_net)
interfaces = pfsense.hack_internet_routing(interfaces, alias_net)
if target_ar_interfaces is None:
target_ar_interfaces = interfaces
elif target_ar_interfaces ^ interfaces:
pfsense.is_whole_in_same_routing_ifaces_cache[self.name] = False
return False
interfaces = pfsense.interfaces_local_networks_contains(alias_net)
interfaces = pfsense.hack_internet_routing(interfaces, alias_net)
if target_local_interfaces is None:
target_local_interfaces = interfaces
elif target_local_interfaces ^ interfaces:
pfsense.is_whole_in_same_routing_ifaces_cache[self.name] = False
return False
pfsense.is_whole_in_same_routing_ifaces_cache[self.name] = True
return True
def match_local_interface_ip(self, pfsense):
""" Return True if the alias IP matches one interface on the pfsense """
all_local_ips = pfsense._all_local_ips
for alias_ip in self.ips:
if alias_ip in all_local_ips:
return True
return False
class PFSenseRule(object):
""" Class holding structured pfsense rule declaration """
def __init__(self):
self.name = None
self.separator = None
self.src = []
self.src_port = []
self.dst = []
self.dst_port = []
self.src_nat = []
self.dst_nat = []
self.dst_nat_port = []
self.protocol = []
self.action = "pass"
self.options = dict()
self.floating = False
self.force = False
self.asymmetric = False
self.invert_dst = False
self.invert_src = False
self.invert_dst_nat = False
self.invert_src_nat = False
self.sub_rules = []
self.interfaces = None
self.generated_names = {}
def copy(self):
copy_object = PFSenseRule()
copy_object.name = self.name
copy_object.separator = self.separator
copy_object.src = [alias.copy() for alias in self.src]
copy_object.dst = [alias.copy() for alias in self.dst]
copy_object.src_port = self.src_port[:]
copy_object.dst_port = self.dst_port[:]
copy_object.src_nat = self.src_nat
copy_object.dst_nat = self.dst_nat
copy_object.dst_nat_port = self.dst_nat_port
copy_object.protocol = self.protocol
copy_object.action = self.action
copy_object.options = self.options
copy_object.floating = self.floating
copy_object.force = self.force
copy_object.asymmetric = self.asymmetric
copy_object.invert_dst = self.invert_dst
copy_object.invert_src = self.invert_src
copy_object.invert_dst_nat = self.invert_dst_nat
copy_object.invert_src_nat = self.invert_src_nat
copy_object.sub_rules = [rule.copy() for rule in self.sub_rules]
if self.interfaces is not None:
copy_object.interfaces = self.interfaces.copy()
else:
copy_object.interfaces = None
copy_object.generated_names = self.generated_names.copy()
return copy_object
def _copy_for_decompose(self):
""" Lightweight copy for rule decomposition - shares alias objects instead of deep-copying them.
Callers always replace src or dst immediately after copying. """
copy_object = PFSenseRule()
copy_object.name = self.name
copy_object.separator = self.separator
copy_object.src = list(self.src)
copy_object.dst = list(self.dst)
copy_object.src_port = self.src_port[:]
copy_object.dst_port = self.dst_port[:]
copy_object.src_nat = self.src_nat
copy_object.dst_nat = self.dst_nat
copy_object.dst_nat_port = self.dst_nat_port
copy_object.protocol = self.protocol
copy_object.action = self.action
copy_object.options = self.options
copy_object.floating = self.floating
copy_object.force = self.force
copy_object.asymmetric = self.asymmetric
copy_object.invert_dst = self.invert_dst
copy_object.invert_src = self.invert_src
copy_object.invert_dst_nat = self.invert_dst_nat
copy_object.invert_src_nat = self.invert_src_nat
if self.interfaces is not None:
copy_object.interfaces = self.interfaces.copy()
else:
copy_object.interfaces = None
copy_object.generated_names = {}
return copy_object
def get_option(self, name):
""" return option value for name """
if name in self.options:
return self.options[name]
separator = self.separator
while separator is not None:
if separator.options is not None and name in separator.options:
return separator.options[name]
separator = separator.parent
return None
def to_json(self):
""" return JSON String containing rule """
srcs = []
for src in self.src:
srcs.append(src.name)
dsts = []
for dst in self.dst:
dsts.append(dst.name)
res = self.name + ": { src: " + " ".join(srcs) + ", dst: " + " ".join(dsts)
if self.src_port:
res += ", src_port: " + " ".join(self.src_port)
if self.dst_port:
res += ", dst_port: " + " ".join(self.dst_port)
if self.protocol:
res += ", protocol: " + " ".join(self.protocol)
if self.action != "pass":
res += ", action: " + " ".join(self.action)
for field in OUTPUT_OPTION_FIELDS:
value = self.get_option(field)
if value is not None:
res += ', {0}: {1}'.format(field, value)
res += " }"
return res
class PFSenseRuleSeparator(object):
""" Class holding structured pfsense rule separator declaration """
def __init__(self):
self.name = None
self.interface = None
self.parent = None
self.options = None
def __hash__(self):
return hash(self.name + self.interface)
def __eq__(self, other):
return self.__class__ == other.__class__ and self.name == other.name and self.interface == other.interface
class PFSenseInterface(object):
""" Class holding structured pfsense interface definition """
def __init__(self):
self.name = None
self.local_ip = None # first ip defined
self.local_network = None # first network defined
self.local_ips = set()
self.local_networks = set()
self.remote_networks = set()
self.adjacent_networks = set()
self.tags = set()
self.bridge = False
self._remote_networks_contains_cache = dict()
self._adjacent_networks_contains_cache = dict()
# Pre-split networks by private/public (populated by precompute_network_splits)
self._private_local_networks = None
self._public_local_networks = None
self._private_remote_networks = None
self._public_remote_networks = None
self._private_adjacent_networks = None
self._public_adjacent_networks = None
def precompute_network_splits(self):
""" Pre-split networks into private/public for faster containment checks """
for attr in ('local_networks', 'remote_networks', 'adjacent_networks'):
networks = getattr(self, attr)
private = []
public = []
for net in networks:
if is_private_network(net):
private.append(net)
else:
public.append(net)
setattr(self, '_private_' + attr, private)
setattr(self, '_public_' + attr, public)
@staticmethod
def _networks_contains(address, networks):
""" return true if address is in networks """
if isinstance(address, ipaddress.IPv4Address):
private_address = is_private_ip(address)
for snet in networks:
private_net = is_private_network(snet)
if private_address and private_net or not private_address and not private_net:
if address in snet:
return True
elif isinstance(address, ipaddress.IPv4Network):
private_address = is_private_network(address)
for snet in networks:
private_net = is_private_network(snet)
if private_address and private_net or not private_address and not private_net:
if address.subnet_of(snet):
return True
else:
raise AssertionError('wrong type in remote_networks_contains:' + type(address))
return False
def remote_networks_contains(self, address):
""" return true if address is defined in remote_networks of this interface """
cache_key = id(address)
res = self._remote_networks_contains_cache.get(cache_key)
if res is None:
res = self._networks_contains(address, self.remote_networks)
self._remote_networks_contains_cache[cache_key] = res
return res
def adjacent_networks_contains(self, address):
""" return true if address is defined in adjacent_networks of this interface """
cache_key = id(address)
res = self._adjacent_networks_contains_cache.get(cache_key)
if res is None:
res = self._networks_contains(address, self.adjacent_networks)
self._adjacent_networks_contains_cache[cache_key] = res
return res
def local_network_contains(self, address):
""" return true if address is in the local network of this interface """
if self.local_networks:
for local_network in self.local_networks:
if isinstance(address, ipaddress.IPv4Address):
private_address = is_private_ip(address)
private_net = is_private_network(local_network)
if private_address and private_net or not private_address and not private_net:
if address in local_network:
return True
elif isinstance(address, ipaddress.IPv4Network):
private_address = is_private_network(address)
private_net = is_private_network(local_network)
if private_address and private_net or not private_address and not private_net:
if address.subnet_of(local_network):
return True
else:
raise AssertionError('wrong type in local_network_contains:' + type(address))
return False
def are_in_same_network(self, src, dst):
""" return true if both the aliases are in the same network on the interface """
def _match(snet, alias):
for ip in alias.ips:
if ip not in snet:
return False
for net in alias.networks:
if not net.subnet_of(snet):
return False
return True
for snet in self.local_networks:
if _match(snet, src) and _match(snet, dst):
return True
return False
class PFSense(object):
""" Class holding structured pfsense definition """
def __init__(self, name, interfaces):
self.name = name
self.interfaces = interfaces
self.is_whole_in_pfsense_cache = dict()
self.is_whole_not_in_pfsense_cache = dict()
self.is_whole_in_same_routing_ifaces_cache = dict()
self._interfaces_local_networks_contains_cache = dict()
self._iface_networks_by_attr = {}
# Pre-computed set of all local IPs across all interfaces
self._all_local_ips = set()
for iface in interfaces.values():
self._all_local_ips.update(iface.local_ips)
self._interfaces_remote_networks_contains_cache = dict()
self._interfaces_adjacent_networks_contains_cache = dict()
self._hack_internet_routing_cache = dict()
def any_adjacent_networks_contains(self, address):
""" return true if address is defined in adjacent_networks of any interface """
return len(self.interfaces_adjacent_networks_contains(address)) != 0
def any_remote_networks_contains(self, address):
""" return true if address is defined in remote_networks of any interface """
return len(self.interfaces_remote_networks_contains(address)) != 0
def any_local_network_contains(self, address):
""" return true if address is defined in local network of any interface """
return len(self.interfaces_local_networks_contains(address)) != 0
def any_network_contains(self, address):
""" return true if address is defined in the local, remote or adjacent networks of any interface """
return self.any_local_network_contains(address) or self.any_remote_networks_contains(address) or self.any_adjacent_networks_contains(address)
def _get_iface_networks(self, attr):
""" return cached list of (interface_name, networks) for given attr """
result = self._iface_networks_by_attr.get(attr)
if result is None:
result = [(iface.name, getattr(iface, attr)) for iface in self.interfaces.values()]
self._iface_networks_by_attr[attr] = result
return result
def _interfaces_network_contains(self, address, networks_name):
""" return interfaces names where address is in the interface network """
res = set()
if isinstance(address, ipaddress.IPv4Address):
if is_private_ip(address):
attr = '_private_' + networks_name
else:
attr = '_public_' + networks_name
for iface_name, networks in self._get_iface_networks(attr):
for snet in networks:
if address in snet:
res.add(iface_name)
break
elif isinstance(address, ipaddress.IPv4Network):
if is_private_network(address):
attr = '_private_' + networks_name
else:
attr = '_public_' + networks_name
for iface_name, networks in self._get_iface_networks(attr):
for snet in networks:
if address.subnet_of(snet):
res.add(iface_name)
break
else:
raise AssertionError('wrong type in _interfaces_network_contains:' + type(address))
return frozenset(res)
def interfaces_local_networks_contains(self, address):
""" return interfaces names where address is in the interface local network """
cache_key = id(address)
res = self._interfaces_local_networks_contains_cache.get(cache_key)
if res is None:
res = self._interfaces_network_contains(address, 'local_networks')
self._interfaces_local_networks_contains_cache[cache_key] = res
return res
def interfaces_remote_networks_contains(self, address):
""" return interfaces names where address is in the interface remote networks """
cache_key = id(address)
res = self._interfaces_remote_networks_contains_cache.get(cache_key)
if res is None:
res = self._interfaces_network_contains(address, 'remote_networks')
self._interfaces_remote_networks_contains_cache[cache_key] = res
return res
def interfaces_adjacent_networks_contains(self, address):
""" return interfaces names where address is in the interface adjacent networks """
cache_key = id(address)
res = self._interfaces_adjacent_networks_contains_cache.get(cache_key)
if res is None:
res = self._interfaces_network_contains(address, 'adjacent_networks')
self._interfaces_adjacent_networks_contains_cache[cache_key] = res
return res
def interfaces_adjacent_or_remote_networks_contains(self, address, use_remote_networks=True):
""" return interfaces names where address is in the interface local or remote networks """
if use_remote_networks:
return self.interfaces_adjacent_networks_contains(address) | self.interfaces_remote_networks_contains(address)
return self.interfaces_adjacent_networks_contains(address)
@static_vars(internet=ipaddress.IPv4Network((u"0.0.0.0", u"0.0.0.0")))
def hack_internet_routing(self, interfaces, address, use_remote_networks=True):
""" internet (defined as route to 0.0.0.0/0) is an issue to automatically detect interfaces on which routing is done,
because every host or network match.
If multiple interfaces can be used and there is at least one specific route which match the address,
we consider the internet ones as mistakes and remove them """
if not isinstance(interfaces, frozenset):
interfaces = frozenset(interfaces)
key = (interfaces, id(address), use_remote_networks)
res = self._hack_internet_routing_cache.get(key)
if res is None:
has_non_internet = False
internet_interfaces = set()
is_net = isinstance(address, ipaddress.IPv4Network)
for interface in interfaces:
route_found = False
internet_found = False
if use_remote_networks:
for network in self.interfaces[interface].remote_networks:
if network == self.hack_internet_routing.internet:
internet_found = True
elif is_net and address.overlaps(network) or not is_net and address in network:
route_found = True
for network in self.interfaces[interface].adjacent_networks:
if network == self.hack_internet_routing.internet:
internet_found = True
elif is_net and address.overlaps(network) or not is_net and address in network:
route_found = True
if route_found:
has_non_internet = True
elif internet_found:
internet_interfaces.add(interface)
if has_non_internet:
res = interfaces - frozenset(internet_interfaces)
else:
res = interfaces
self._hack_internet_routing_cache[key] = res
return res
class PFSenseData(object):
""" Class holding all data """
def __init__(self, hosts_aliases, ports_aliases, pfsenses, rules, target_name, gendiff=False, debug=None, aggregate=True):
self._hosts_aliases = hosts_aliases
self._ports_aliases = ports_aliases
self._pfsenses = pfsenses
self._rules = rules
self._rules_separators = list()
self._target_name = target_name
self._rules_obj = OrderedDict()
self._pfsenses_obj = {}
self._hosts_aliases_obj = OrderedDict()
self._target = None
self._errors = []
self.log_errors = False
self.gendiff = gendiff
self.debug = debug
self.aggregate = aggregate
self._all_aliases = copy(self._hosts_aliases)
self._all_aliases.update(self._ports_aliases)
self._ignored_aliases = set()
self._ignored_rules = set()
self._hosts_alias_by_content = None
@property
def all_aliases(self):
""" all_aliases getter """
return self._all_aliases
@property
def hosts_aliases(self):
""" hosts_aliases getter """
return self._hosts_aliases
@property
def ignored_aliases(self):
""" ignored_aliases getter """
return self._ignored_aliases
@property
def hosts_aliases_obj(self):
""" hosts_aliases_obj getter """
return self._hosts_aliases_obj
@property
def ports_aliases(self):
""" ports_aliases getter """
return self._ports_aliases
@property
def pfsenses(self):
""" pfsenses getter """
return self._pfsenses
@property
def pfsenses_obj(self):
""" pfsenses_obj getter """
return self._pfsenses_obj
@property
def rules_obj(self):
""" rules_obj getter """
return self._rules_obj
@property
def rules(self):
""" rules getter """
return self._rules
@property
def ignored_rules(self):
""" ignored_rules getter """
return self._ignored_rules
@property
def rules_separators(self):
""" rules_separators getter """
return self._rules_separators
@property
def target_name(self):
""" target_name getter """
return self._target_name
@property
def target(self):
""" target getter """
return self._target
@target.setter
def target(self, target):
""" target setter """
self._target = target
@property
def errors(self):
""" errors getter """
return self._errors
def set_error(self, error):
""" add an error """
display.error(error)
self._errors.append(error)
@staticmethod
def is_child_def(values):
""" check if values contains more definitions """
for value in values.values():
if isinstance(value, (OrderedDict, dict, list)):
return False
return True
def unalias_ip(self, alias):
""" expand alias to its IP definition """
ret = []
todo = []
todo.extend(alias.split())
while todo:
elts = todo.pop()
if elts in self._all_aliases:
todo.extend(self._all_aliases[elts]['ip'].split())
else:
ret.append(elts)
return ret
def get_hosts_alias(self, hosts, ips, networks, _basename):
""" return an alias with all the hosts
create it if required """
# Build index on first call for O(1) lookups instead of O(N) scan
if self._hosts_alias_by_content is None:
self._hosts_alias_by_content = {}
for alias in self._hosts_aliases_obj.values():
if alias.ips or alias.networks:
key = (frozenset(alias.ips), frozenset(alias.networks))
if key not in self._hosts_alias_by_content:
self._hosts_alias_by_content[key] = alias
key = (frozenset(ips), frozenset(networks))
existing = self._hosts_alias_by_content.get(key)
if existing is not None:
return existing
obj = PFSenseHostAlias()
obj.definition = list(hosts)
obj.definition.sort()
obj.ips = list(ips)
obj.networks = list(networks)
obj.fake = False
# alias can only be 32 chars long so we truncate a bit if required
basename = _basename[0:26]
idx = 1
while True:
obj.name = 'h_{0}_{1}'.format(basename, idx)
if obj.name not in self._all_aliases:
break
idx = idx + 1
self._hosts_aliases_obj[obj.name] = obj
self._hosts_alias_by_content[key] = obj
alias = dict()
alias['ip'] = ' '.join(obj.definition)
alias['type'] = 'network'
self._all_aliases[obj.name] = alias
return obj
def get_ports_alias(self, ports, _basename):
""" return an alias with all the ports
create it if required """
for name, alias in self._ports_aliases.items():
alias_ports = set(alias['port'].split())
if not alias_ports ^ ports:
return name
# alias can only be 32 chars long so we truncate a bit if required
basename = _basename[0:26]
idx = 1
while True:
name = 'p_{0}_{1}'.format(basename, idx)
if name not in self._all_aliases:
break
idx = idx + 1
alias = dict()
alias['descr'] = name
sorted_ports = list(ports)
sorted_ports.sort()
alias['port'] = ' '.join(sorted_ports)
self._all_aliases[name] = alias
self._ports_aliases[name] = alias
return name
class PFSenseDataParser(object):
""" Class doing all data checks and pfsense objects generation """
def __init__(self, data):
self._data = data
@staticmethod
def check_alias_name(name):
""" check an alias name """
# todo: check reserved keywords (any, self, ...)
if re.match('^[a-zA-Z0-9_]+$', name) is None:
raise AnsibleError(name + ': the name of the alias may only consist of the characters "a-z, A-Z, 0-9 and _"')
def parse_host_alias(self, obj, src_name, type_name, name, allow_any, dns_servers=None):
""" Parse a host alias definition """
ret = True
value = obj[src_name]
values = str(value).split()
if not values:
self._data.set_error("Empty " + src_name + " field for " + type_name + " " + name)
return False
# we check that all exist
ip_defs = 0
net_defs = 0
fqdn_defs = 0
other_defs = 0
for value in values:
if is_valid_ip(value):
if value not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[value] = self.create_obj_host_alias(value)
ip_defs = ip_defs + 1
continue
if is_valid_network(value):
if value not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[value] = self.create_obj_host_alias(value)
net_defs = net_defs + 1
continue
if value not in self._data.hosts_aliases and (value != 'any' or not allow_any):
if is_fqdn(value):
if value not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[value] = self.create_obj_host_alias(value, dns_servers)
fqdn_defs = fqdn_defs + 1
continue
self._data.set_error(value + " is not a valid alias, ip address or network in " + type_name + " " + name)
ret = False
other_defs = other_defs + 1
if fqdn_defs and (ip_defs + net_defs + other_defs) > 0:
self._data.set_error("fqdn definitions can't be mixed with aliases, IP or networks addresses (in " + type_name + " " + name + ")")
ret = False
# if it's a real alias, we must check for mixed network definitions
if not allow_any:
if net_defs > 0:
if net_defs != len(values):
self._data.set_error("mixed network definitions and aliases or IP addresses in " + type_name + " " + name)
ret = False
else:
obj['type'] = 'network'
else:
obj['type'] = 'host'
return ret
def parse_hosts_aliases(self):
""" Parse all hosts aliases definitions """
dups = {}
ret = True
for name, alias in self._data.hosts_aliases.items():
self.check_alias_name(name)
if 'ignored' in alias and get_bool(alias, 'ignored'):
self._data.ignored_aliases.add(name)
continue
# ip field is mandatory
if 'ip' not in alias and 'host' not in alias:
self._data.set_error("No ip or host field for alias " + name)
ret = False
continue
# we check that all fields are valid
for field in alias:
if field not in ['ip', 'host', 'descr', 'dns', 'ignore_dup', 'ignored']:
self._data.set_error(field + " is not a valid field name in alias " + name)
ret = False
dns_servers = None
if 'dns' in alias:
dns_servers = alias['dns'].split()
# we check that all IPs exist and are not empty
if not self.parse_host_alias(alias, 'ip', 'alias', name, False, dns_servers):
ret = False
continue
# we check for duplicates
_alias = deepcopy(alias)
if 'descr' in _alias:
del _alias['descr']
dup = json.dumps(_alias)
if dup in dups:
display.warning("duplicate alias definition for ip " + alias['ip'] + " (" + dups[dup] + ", " + name + ")")
elif 'ignore_dup' not in alias:
dups[dup] = name
obj = PFSenseHostAlias()
obj.name = name
obj.definition = alias['ip'].split()
if 'descr' in alias:
obj.descr = alias['descr']
obj.dns = dns_servers
self._data.hosts_aliases_obj[obj.name] = obj
return ret
def check_port_alias(self, ports, src_name, type_name, name):
""" Checking a port alias definition """
ret = True
values = str(ports).split()
if src_name == 'dst_nat_port':
if '-' in ports:
self._data.set_error("There must be only one port in dst_nat_port of " + name)
return False
if len(values) > 1:
self._data.set_error("There must be only one port in {0} of {1}".format(src_name, name))
return False
if not values:
self._data.set_error("Empty " + src_name + " field for " + type_name + " " + name)
return False
# we check that all exist
for value in values:
if not is_valid_port(value) and not is_valid_port_range(value) and value not in self._data.ports_aliases:
self._data.set_error(value + " is not a valid alias, port or port range in " + type_name + " " + name)
ret = False
return ret
def parse_ports_aliases(self):
""" Checking all ports alias definitions """
dups = {}
ret = True
for name, alias in self._data.ports_aliases.items():
self.check_alias_name(name)
# port field is mandatory
if 'port' not in alias:
self._data.set_error("No port field for alias " + name)
ret = False
continue
if not isinstance(alias['port'], str):
alias['port'] = str(alias['port'])
# we check that all IPs exist and are not empty
if not self.check_port_alias(alias['port'], 'port', 'alias', name):
ret = False
continue
# we check that all fields are valid
for field in alias:
if field != 'port' and field != 'descr':
self._data.set_error(field + " is not a valid field name in alias " + name)
ret = False
# we check for duplicates
_alias = deepcopy(alias)
if 'descr' in _alias:
del _alias['descr']
dup = json.dumps(_alias)
if dup in dups:
display.warning("duplicate alias definition for port " + alias['port'] + " (" + dups[dup] + ", " + name + ")")
else:
dups[dup] = name
return ret
def create_obj_any_alias(self):
""" Create a PFSenseHostAlias object for address any (for easier processing later) """
obj = PFSenseHostAlias()
obj.name = 'any'
obj.definition = ['any']
obj.fake = True
obj.compute_any(self._data)
self._data.all_aliases['any'] = {}
self._data.all_aliases['any']['ip'] = '0.0.0.0/0'
self._data.all_aliases['any']['type'] = 'network'
return obj
def create_obj_host_alias(self, src, dns_servers=None):
""" Create a PFSenseHostAlias object from address (for easier processing later) """
obj = PFSenseHostAlias()
obj.name = src
obj.definition = [src]
obj.fake = True
obj.dns = dns_servers
if src == 'any':
return self.create_obj_any_alias()
return obj
def create_obj_rule_from_def(self, name, rule, separator):
""" Create a PFSenseRule object from yaml definition """
def _get_bool(field):
field_value = False
if isinstance(rule[field], bool):
field_value = rule[field]
elif isinstance(rule[field], str):
if rule[field].lower() in ['yes', 'true']:
field_value = True
elif rule[field].lower() not in ['no', 'false']:
self._data.set_error('{0} must be yes/no or true/false (got "{1}")'.format(field, rule[field]))
return field_value
obj = PFSenseRule()
obj.name = name
obj.separator = separator
if 'src_port' in rule:
if not isinstance(rule['src_port'], str):
obj.src_port = str(rule['src_port'])
else:
obj.src_port = rule['src_port'].split()
if 'dst_port' in rule:
if not isinstance(rule['dst_port'], str):
obj.dst_port = str(rule['dst_port'])
else:
obj.dst_port = rule['dst_port'].split()
if 'dst_nat_port' in rule:
if not isinstance(rule['dst_nat_port'], str):
obj.dst_nat_port = str(rule['dst_nat_port'])
else:
obj.dst_nat_port = rule['dst_nat_port'].split()
if 'protocol' in rule:
obj.protocol = rule['protocol'].split()
if 'action' in rule:
obj.action = rule['action']
for field in OPTION_FIELDS:
if field in rule:
obj.options[field] = rule[field]
if 'force' in rule:
obj.force = _get_bool('force')
if obj.force and not (obj.get_option('filter') and obj.get_option('ifilter')):
self._data.set_error('force must not be used without filter and ifilter')
if 'floating' in rule:
obj.floating = _get_bool('floating')
if 'quick' in rule and not obj.floating:
self._data.set_error('Quick must only be used with floating rules')
for src in rule['src'].split():
if src not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[src] = self.create_obj_host_alias(src)
target = self._data.hosts_aliases_obj[src]
obj.src.append(target)
for dst in rule['dst'].split():
if dst not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[dst] = self.create_obj_host_alias(dst)
target = self._data.hosts_aliases_obj[dst]
obj.dst.append(target)
if 'src_nat' in rule:
src = rule['src_nat']
if src not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[src] = self.create_obj_host_alias(src)
target = self._data.hosts_aliases_obj[src]
obj.src_nat.append(target)
if 'dst_nat' in rule:
dst = rule['dst_nat']
if dst not in self._data.hosts_aliases_obj:
self._data.hosts_aliases_obj[dst] = self.create_obj_host_alias(dst)
target = self._data.hosts_aliases_obj[dst]
obj.dst_nat.append(target)
if 'asymmetric' in rule:
obj.asymmetric = _get_bool('asymmetric')
if 'invert_src' in rule:
obj.invert_src = _get_bool('invert_src')
if not obj.force:
self._data.set_error('invert_src must be used with force (for now)')
if 'invert_dst' in rule:
obj.invert_dst = _get_bool('invert_dst')
if not obj.force:
self._data.set_error('invert_dst must be used with force (for now)')
if 'invert_src_nat' in rule:
obj.invert_src_nat = _get_bool('invert_src_nat')
if not obj.force:
self._data.set_error('invert_src_nat must be used with force (for now)')
if 'invert_dst_nat' in rule:
obj.invert_dst_nat = _get_bool('invert_dst_nat')
if not obj.force:
self._data.set_error('invert_dst_nat must be used with force (for now)')
return obj
def parse_rules(self, parent=None, parent_separator=None):
""" Parse all rules definitions """
ret = True
if parent is None:
parent = self._data.rules
if parent_separator is None:
parent_separator = PFSenseRuleSeparator()
for name, rule in parent.items():
# not a rule
if rule is None:
continue
if not self._data.is_child_def(rule):
separator = PFSenseRuleSeparator()
separator.parent = parent_separator
if parent_separator.name is None or not parent_separator.name:
separator.name = name
else:
separator.name = parent_separator.name + ' - ' + name
self._data.rules_separators.append(separator)
if not self.parse_rules(rule, separator):
ret = False
continue
if name == 'options':
parent_separator.options = rule
if parent_separator.options and parent_separator.options.get('invisible'):
if parent_separator.name is None or not parent_separator.name:
parent_separator.name = ''
else:
parent_separator.name = parent_separator.parent.name
continue
if 'ignored' in rule and get_bool(rule, 'ignored'):
self._data.ignored_rules.add(name)
continue
for field in ['src', 'dst']:
# src and dst field are mandatory
if field not in rule:
self._data.set_error("No {0} field for rule {1}".format(field, name))
ret = False
continue
# we check that all exist and are not empty
if not self.parse_host_alias(rule, field, 'rule', name, True):
ret = False
for field in ['src_nat', 'dst_nat']:
if field not in rule:
continue
if len(rule[field].split()) > 1:
self._data.set_error('There must be only one address in {0} field of {1}'.format(field, name))
ret = False
continue
if field == 'src_nat' and rule['src_nat'] == '(self)':
rule['src_nat'] = ''
continue
# we check that all exist and are not empty
if not self.parse_host_alias(rule, field, 'rule', name, True):
ret = False
# checking ports
for field in ['src_port', 'dst_port', 'dst_nat_port']:
if field in rule:
if not isinstance(rule[field], str):
rule[field] = str(rule[field])
if not self.check_port_alias(rule[field], field, 'rule', name) or not self.check_tcp_udp(rule, name):
ret = False
if 'dst_nat_port' in rule and 'dst_nat' not in rule:
self._data.set_error('dst_nat_port field is set on {0} without any dst_nat target'.format(name))
ret = False
continue
if 'dst_nat' in rule and 'dst_nat_port' not in rule:
self._data.set_error('dst_nat field is set on {0} without any dst_nat_port target'.format(name))
ret = False
continue
# we check that all fields are valid
valid_fields = ['src', 'dst', 'src_port', 'dst_port', 'protocol', 'action', 'floating', 'force']
valid_fields.extend(['src_nat', 'dst_nat', 'dst_nat_port', 'asymmetric', 'invert_dst', 'invert_src', 'invert_dst_nat', 'invert_src_nat', 'ignored'])
valid_fields.extend(OPTION_FIELDS)
for field in rule:
if field not in valid_fields:
self._data.set_error(field + " is not a valid field name in rule " + name)
ret = False
if name in self._data.rules_obj:
display.warning("Rule already defined: {0}".format(name))
self._data.rules_obj[name] = self.create_obj_rule_from_def(name, rule, parent_separator)
if self._data.errors:
return False
return ret
def parse_target_name(self):
""" Parse target's name definition """
if self._data.target_name not in self._data.pfsenses:
self._data.set_error(self._data.target_name + " does not exist in pfsenses section")
return False
self._data.target = self._data.pfsenses_obj[self._data.target_name]
return True
def check_tcp_udp(self, rule, name):
""" check if protocol is valid when ports are set """
if 'protocol' not in rule:
return True
protocols = str(rule['protocol']).split()
for protocol in protocols:
if protocol != 'udp' and protocol != 'tcp' and protocol != 'tcp/udp':
self._data.set_error(protocol + " protocol used with src_port or dst_port in rule " + name)
return False
return True
def check_pfsense_interfaces_objs(self, interfaces, name):
""" Checking all interfaces networks between them """
for src_name, src in interfaces.items():
for dst_name, dst in interfaces.items():
if src_name != dst_name and src.local_networks and dst.local_networks:
for src_network in src.local_networks:
for dst_network in dst.local_networks:
if src_network.overlaps(dst_network):
self._data.set_error("Local networks of " + src_name + " and " + dst_name + " overlap in " + name)
return False
return True
def create_pfsenses_aliases(self):
""" Generate useful aliases for pfsenses """
def _warn_alias(alias):
if len(alias) >= 32:
display.warning("Autogenerated alias {0} is too long and will trigger an error if used".format(alias))
# if pf_paris has lan and wan interfaces declared, we will generate
# - pf_paris_ips with all its ips
# - pf_paris_nets with all its subnets
# - pf_paris_lan_ips and pf_paris_wan_ips containing pf_paris lan and wan ips (respectivly)
# - pf_paris_lan_nets and pf_paris_wan_nets containing pf_paris lan and wan subnets (respectivly)
# - all_lan_ips and all_wan_ips containing all pfsenses lan and wan ips (respectivly)
# - all_lan_nets and all_wan_nets containing all pfsenses lan and wan subnets (respectivly)
# if lan has a tag named 'voip':
# - pf_paris_voip_ips containing pf_paris lan ips
# - pf_paris_voip_nets containing pf_paris lan subnets
# - all_voip_ips containing all pfsenses ips of interfaces with tag voip
# - all_voip_nets containing all pfsenses subnets of interfaces with tag voip
# - all_pfsenses_ips containing all pfsenses ips
# - all_pfsenses_nets containing all pfsenses subnets
# all_pfsenses
all_pfsenses_definition = list()
# all_lan, all_wan
all_pfsenses_interfaces_definition = dict()
# all_pfsenses_nets
net_all_pfsenses_definition = list()
# all_lan_nets, all_wan_nets
net_all_pfsenses_interfaces_definition = dict()
for name, pfsense in self._data.pfsenses.items():
# maybe do some options for this
ipext = '_ips'
netext = '_nets'
# interfaces field is mandatory
if 'interfaces' not in pfsense:
continue
# pf_paris_ips
pfsense_definition = list()
# pf_paris_nets
net_pfsense_definition = list()
# pf_paris_lan_ips, pf_paris_tag_ips
interfaces_definition = dict()
# pf_paris_lan_nets, pf_paris_tag_nets
net_interfaces_definition = dict()
for iname, interface in pfsense['interfaces'].items():
if 'ip' not in interface:
continue
if 'id' in interface and interface.get('id'):
interface_id = str(interface['id'])
else:
interface_id = iname
# get the tags
tags = list()
tags.append(interface_id)
if 'tags' in interface:
for tag in sorted(interface['tags'].split()):
if tag not in tags:
tags.append(tag)
# pf_paris_lan_ips, pf_paris_wan_ips
interface_definition = list()
# pf_paris_lan_nets, pf_paris_wan_nets
net_interface_definition = list()
# get the ips and networks
for ip in interface['ip'].split():
try:
local_network = to_ip_network(to_unicode(ip), False)
str_net = str(local_network)
net_interface_definition.append(str_net)
except ValueError:
# we will fail later
pass
group = re.match(r'([^\/]*)\/(\d+)', ip)
try:
if group:
ip = to_ip_address(to_unicode(group.group(1)))
str_ip = str(ip)
interface_definition.append(str_ip)
except ValueError:
# we will fail later
pass
# interface ip alias
if interface_definition:
for idx, tag in enumerate(tags):
interface_definition_name = name + '_' + tag + ipext
ikey = 'all_' + tag + ipext
if ikey not in all_pfsenses_interfaces_definition:
all_pfsenses_interfaces_definition[ikey] = list()
if interface_definition_name not in all_pfsenses_interfaces_definition[ikey]:
all_pfsenses_interfaces_definition[ikey].append(interface_definition_name)
if idx == 0:
# we only add the interface and not all the tags in the pfsense_definition
pfsense_definition.append(interface_definition_name)
interface_definition.sort()
if interface_definition_name not in interfaces_definition:
interfaces_definition[interface_definition_name] = list()
interfaces_definition[interface_definition_name].extend(interface_definition)
# interface network alias
if net_interface_definition:
for idx, tag in enumerate(tags):
interface_definition_name = name + '_' + tag + netext
ikey_net = 'all_' + tag + netext
if ikey_net not in net_all_pfsenses_interfaces_definition:
net_all_pfsenses_interfaces_definition[ikey_net] = list()
if interface_definition_name not in net_all_pfsenses_interfaces_definition[ikey_net]:
net_all_pfsenses_interfaces_definition[ikey_net].append(interface_definition_name)
if idx == 0:
# we only add the interface and not all the tags in the pfsense_definition
net_pfsense_definition.append(interface_definition_name)
net_interface_definition.sort()
if interface_definition_name not in net_interfaces_definition:
net_interfaces_definition[interface_definition_name] = list()
net_interfaces_definition[interface_definition_name].extend(net_interface_definition)
# pf_paris_lan_ips, pf_paris_tag_ips
for interface_definition_name in sorted(interfaces_definition.keys()):
interface_definition = interfaces_definition[interface_definition_name]
interface_definition = list(dict.fromkeys(interface_definition))
interface_definition.sort()
alias = dict()
alias['ip'] = ' '.join(interface_definition)
alias['ignore_dup'] = True
self._data.all_aliases[interface_definition_name] = alias
self._data.hosts_aliases[interface_definition_name] = alias
_warn_alias(interface_definition_name)
# pf_paris_lan_nets, pf_paris_tag_nets
for interface_definition_name in sorted(net_interfaces_definition.keys()):
net_interface_definition = net_interfaces_definition[interface_definition_name]
net_interface_definition = list(dict.fromkeys(net_interface_definition))
net_interface_definition.sort()
alias = dict()
alias['ip'] = ' '.join(net_interface_definition)
alias['ignore_dup'] = True
self._data.all_aliases[interface_definition_name] = alias
self._data.hosts_aliases[interface_definition_name] = alias
_warn_alias(interface_definition_name)
# pfsense ip alias
if pfsense_definition:
pfsense_definition.sort()
all_pfsenses_definition.append(name + ipext)
alias = dict()
alias['ip'] = ' '.join(pfsense_definition)
alias['ignore_dup'] = True
self._data.all_aliases[name + ipext] = alias
self._data.hosts_aliases[name + ipext] = alias
_warn_alias(name + ipext)
# pfsense network alias
if net_pfsense_definition:
net_pfsense_definition.sort()
net_all_pfsenses_definition.append(name + netext)
alias = dict()
alias['ip'] = ' '.join(net_pfsense_definition)
alias['ignore_dup'] = True
self._data.all_aliases[name + netext] = alias
self._data.hosts_aliases[name + netext] = alias
_warn_alias(name + netext)
# generate interfaces groups ip aliases
for name in sorted(all_pfsenses_interfaces_definition.keys()):
definition = all_pfsenses_interfaces_definition[name]
definition.sort()
alias = dict()
alias['ip'] = ' '.join(definition)
alias['ignore_dup'] = True
self._data.all_aliases[name] = alias
self._data.hosts_aliases[name] = alias
_warn_alias(name)
# generate interfaces groups network aliases
for name in sorted(net_all_pfsenses_interfaces_definition.keys()):
definition = net_all_pfsenses_interfaces_definition[name]
definition.sort()
alias = dict()
alias['ip'] = ' '.join(definition)
alias['ignore_dup'] = True
self._data.all_aliases[name] = alias
self._data.hosts_aliases[name] = alias
_warn_alias(name)
# generate all ip aliases
all_pfsenses_definition.sort()
alias = dict()
alias['ip'] = ' '.join(all_pfsenses_definition)
alias['ignore_dup'] = True
self._data.all_aliases['all_pfsenses' + ipext] = alias
self._data.hosts_aliases['all_pfsenses' + ipext] = alias
# generate all network aliases
net_all_pfsenses_definition.sort()
alias = dict()
alias['ip'] = ' '.join(net_all_pfsenses_definition)
alias['ignore_dup'] = True
self._data.all_aliases['all_pfsenses' + netext] = alias
self._data.hosts_aliases['all_pfsenses' + netext] = alias
def parse_pfsense_interfaces(self, pfsense, name):
""" Parse all pfsense interfaces definitions """
ret = {}
ids = set()
for iname, interface in pfsense['interfaces'].items():
# extracting & checking local network
local_ips = set()
local_networks = set()
first_local_ip = None
first_local_network = None
tags = set()
tags.add(iname)
if 'tags' in interface:
for tag in interface['tags'].split():
tags.add(tag)
for key in interface:
if key not in ['adjacent_networks', 'remote_networks', 'ip', 'tags', 'id']:
self._data.set_error("Invalid field " + key + " in " + iname + " of " + name)
return {}
if 'id' in interface and interface.get('id'):
interface_id = str(interface['id'])
else:
interface_id = iname
if interface_id in ids:
self._data.set_error("Duplicate interface id " + interface_id + " in " + iname + " of " + name)
return {}
ids.add(interface_id)
if 'ip' in interface:
for ip in interface['ip'].split():
try:
local_network = to_ip_network(to_unicode(ip), False)
if first_local_network is None:
first_local_network = local_network
except ValueError:
self._data.set_error("Invalid network " + ip + " in " + name)
return {}
if local_network.prefixlen == 32:
self._data.set_error("Invalid network prefix length for network " + ip + " in " + name)
return {}
# extracting & checking ip
group = re.match(r'([^\/]*)\/(\d+)', ip)
try:
local_ip = to_ip_address(to_unicode(group.group(1)))
if first_local_ip is None:
first_local_ip = local_ip
except ValueError:
self._data.set_error("Invalid ip " + ip + " in " + name)
return {}
local_ips.add(local_ip)
local_networks.add(local_network)
# extracting & checking remote networks
remote_networks = set()
if 'remote_networks' in interface:
networks = self._data.unalias_ip(interface['remote_networks'])
for network in networks:
try:
remote_networks.add(to_ip_network(to_unicode(network)))
except ValueError:
self._data.set_error("Invalid network " + network + " in remote_networks of " + name)
return {}
# extracting & checking adjacent networks
adjacent_networks = set()
if 'adjacent_networks' in interface:
networks = self._data.unalias_ip(interface['adjacent_networks'])
for network in networks:
try:
adjacent_networks.add(to_ip_network(to_unicode(network)))
except ValueError:
self._data.set_error("Invalid network " + network + " in adjacent_networks of " + name)
return {}
obj = PFSenseInterface()
obj.name = iname
obj.local_ip = first_local_ip
obj.local_network = first_local_network
obj.local_ips = local_ips
obj.local_networks = local_networks
obj.bridge = (interface.get('bridge'))
obj.remote_networks = remote_networks
obj.adjacent_networks = adjacent_networks
obj.tags = tags
obj.precompute_network_splits()
ret[iname] = obj
if not self.check_pfsense_interfaces_objs(ret, name):
ret = {}
return ret
def parse_pfsenses(self):
""" Checking all pfsenses definitions """
dups = {}
ret = True
for name, pfsense in self._data.pfsenses.items():
# interfaces field is mandatory
if 'interfaces' not in pfsense:
self._data.set_error("No interfaces field for pfsense " + name)
ret = False
continue
if not pfsense['interfaces']:
self._data.set_error("Empty interfaces field for pfsense " + name)
ret = False
continue
interfaces = self.parse_pfsense_interfaces(pfsense, name)
# checking interfaces
if not interfaces:
ret = False
continue
# we check that all fields are valid
for field in pfsense:
if field != 'interfaces':
self._data.set_error(field + " is not a valid field name in pfsense " + name)
ret = False
# we check for duplicates
_pfsense = deepcopy(pfsense)
if 'descr' in _pfsense:
del _pfsense['descr']
dup = json.dumps(_pfsense)
if dup in dups:
display.warning("duplicate pfsense definition (" + dups[dup] + ", " + name + ")")
else:
dups[dup] = name
obj = PFSense(name, interfaces)
self._data.pfsenses_obj[obj.name] = obj
return ret
def parse_hosts_aliases_objs(self):
""" Checking all host alias objs, addresses and finding pfsenses interfaces """
for obj in self._data.hosts_aliases_obj.values():
obj.compute_all(self._data)
return True
def parse(self):
""" Check and parse everything """
ret = True
self.create_pfsenses_aliases()
ret = ret and self.parse_hosts_aliases()
ret = ret and self.parse_ports_aliases()
ret = ret and self.parse_rules()
ret = ret and self.parse_pfsenses()
ret = ret and self.parse_target_name()
ret = ret and self.parse_hosts_aliases_objs()
return ret
class PFSenseRuleDecomposer(object):
""" Class decomposing rules into smaller rules (more suited to pfsense logic ) """
def __init__(self, data):
self._data = data
def host_separate(self, host):
""" separate aliases to remove mixed configuration
where there is a local and remote network/ip is the host
host is expanded to sub-aliases if required """
ret = []
if host.is_whole_not_in_pfsense(self._data.target):
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: is_whole_not_in_pfsense {1}'.format(host.name, self._data.target.name))
ret.append(host)
elif host.is_whole_in_pfsense(self._data.target):
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: is_whole_in_pfsense {1}'.format(host.name, self._data.target.name))
ret.append(host)
elif host.is_ip_broadcast():
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: is_ip_broadcast'.format(host.name))
ret.append(host)
else:
alias = self._data.all_aliases[host.name]
if 'ip' in alias:
for alias_ip in alias['ip'].split():
ret_n = self.host_separate(self._data.hosts_aliases_obj[alias_ip])
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: host_separate: {1}'.format(host.name, ret_n))
ret.extend(ret_n)
return ret
def host_separate_by_iface(self, host):
""" separate aliases to remove mixed configuration
where there is a local and remote network/ip is the host
host is expanded to sub-aliases if required """
ret = []
if host.is_whole_in_same_routing_ifaces(self._data.target):
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: is_whole_in_same_routing_ifaces {1}'.format(host.name, self._data.target.name))
ret.append(host)
else:
alias = self._data.all_aliases[host.name]
if 'ip' in alias:
for alias_ip in alias['ip'].split():
ret_n = self.host_separate_by_iface(self._data.hosts_aliases_obj[alias_ip])
if self._data.debug is not None and self._data.debug == host.name:
display.warning('{0}: host_separate_by_iface: {1}'.format(host.name, ret_n))
ret.extend(ret_n)
return ret
def separate_aliases(self, rule, field, attr, func):
""" Separate aliases from field using func, setting new aliases in attr """
sub_rules = []
function = getattr(self, func)
src_sep = function(field)
if len(src_sep) > 1:
for src in src_sep:
new_rule = rule._copy_for_decompose()
setattr(new_rule, attr, [src])
sub_rules.append(new_rule)
return sub_rules
def decompose_rule(self, rule):
""" Returns smaller rules from rule """
# A PFSense rule can have only one src or dst
blocking = rule.action != 'pass'
sub_rules = []
if len(rule.src) > 1 or len(rule.dst) > 1:
for src in rule.src:
for dst in rule.dst:
new_rule = rule._copy_for_decompose()
new_rule.src = [src]
new_rule.dst = [dst]
sub_rules.append(new_rule)
return sub_rules
if len(rule.src) != 1 or len(rule.dst) != 1:
raise AssertionError()
# forced rules are generated
if rule.force:
return []
src = rule.src[0]
dst = rule.dst[0]
# if it's blocking or reject rule, we don't split the destination
# since we only need the source to know how to define the rule
sub_rules = self.separate_aliases(rule, src, 'src', 'host_separate')
if not blocking and not sub_rules:
sub_rules = self.separate_aliases(rule, dst, 'dst', 'host_separate')
if not sub_rules:
sub_rules = self.separate_aliases(rule, src, 'src', 'host_separate_by_iface')
if not blocking and not sub_rules:
sub_rules = self.separate_aliases(rule, dst, 'dst', 'host_separate_by_iface')
return sub_rules
def decompose_rules(self):
""" Returns smaller rules (more suited to pfsense logic ) """
for rule in self._data.rules_obj.values():
todo = []
todo.append(rule)
while todo:
obj = todo.pop()
res = self.decompose_rule(obj)
if not res:
rule.sub_rules.append(obj)
else:
todo.extend(res)
class PFSenseAliasFactory(object):
""" Class generating aliases definitions """
def __init__(self, data):
self._data = data
def add_host_alias_rec(self, alias, aliases):
""" set aliases hosts names to define (recursive) """
if ':' in alias.name:
return
name = alias.name
aliases[name] = self._data.all_aliases[name]
for target in alias.definition:
obj = self._data.hosts_aliases_obj[target]
if obj.fake:
continue
self.add_host_alias_rec(obj, aliases)
def add_port_alias_rec(self, alias, aliases):
""" Return aliases ports names to define (recursive) """
if alias in self._data.all_aliases:
if alias not in aliases:
aliases[alias] = self._data.all_aliases[alias]
if 'port' in aliases[alias]:
for port in aliases[alias]['port'].split():
self.add_port_alias_rec(port, aliases)
def add_hosts_aliases(self, rule, aliases):
""" Return aliases hosts names to define """
for rule_aliases in [rule.src, rule.dst, rule.src_nat, rule.dst_nat]:
for alias in rule_aliases:
if alias.fake:
continue
self.add_host_alias_rec(alias, aliases)
def add_ports_aliases(self, rule, aliases):
""" Return aliases ports names to define """
for alias in rule.src_port:
self.add_port_alias_rec(alias, aliases)
for alias in rule.dst_port:
self.add_port_alias_rec(alias, aliases)
def generate_aliases(self, rule_filter=None):
""" Return aliases definitions for pfsense_aggregate """
hosts_aliases = {}
ports_aliases = {}
for name, rule in self._data.rules_obj.items():
if rule_filter is not None and name != rule_filter:
continue
for subrule in rule.sub_rules:
if not subrule.interfaces:
continue
self.add_hosts_aliases(subrule, hosts_aliases)
self.add_ports_aliases(subrule, ports_aliases)
ret = []
for name, alias in hosts_aliases.items():
definition = {}
definition['name'] = name
definition['type'] = alias['type']
definition['address'] = ' '.join(alias['ip'].split())
definition['state'] = 'present'
if 'descr' in alias:
definition['descr'] = alias['descr']
else:
definition['descr'] = ''
definition['detail'] = ''
ret.append(definition)
for name, alias in ports_aliases.items():
definition = {}
definition['name'] = name
definition['type'] = 'port'
definition['address'] = ' '.join(alias['port'].replace('-', ':').split())
definition['state'] = 'present'
if 'descr' in alias:
definition['descr'] = alias['descr']
else:
definition['descr'] = ''
definition['detail'] = ''
ret.append(definition)
return ret
@staticmethod
def output_aliases(aliases, ignored_aliases):
""" Output aliases definitions for pfsense_aggregate """
print(" #===========================")
print(" # Hosts & network aliases")
print(" # ")
definitions = list()
for alias in aliases:
if alias['type'] == 'port':
continue
definition = " - { name: \"" + alias['name'] + "\", type: \"" + alias['type'] + "\", address: \""
definition += ' '.join(alias['address'].split()) + "\""
if 'descr' in alias:
definition = definition + ", descr: \"" + alias['descr'] + "\""
definition = definition + ", state: \"present\" }"
definitions.append(definition)
definitions.sort()
print('\n'.join(definitions))
print(" #===========================")
print(" # ports aliases")
print(" # ")
definitions = list()
for alias in aliases:
if alias['type'] != 'port':
continue
definition = " - { name: \"" + alias['name'] + "\", type: \"port\", address: \"" + ' '.join(alias['address'].split()) + "\""
if 'descr' in alias:
definition = definition + ", descr: \"" + alias['descr'] + "\""
definition = definition + ", state: \"present\" }"
definitions.append(definition)
definitions.sort()
print('\n'.join(definitions))
print(" #===========================")
print(" # ignored aliases")
print(" # ")
definitions = list()
for alias in ignored_aliases:
definition = " - { name: \"" + alias + "\" }"
definitions.append(definition)
definitions.sort()
print('\n'.join(definitions))
class PFSenseRuleFactory(object):
""" Class generating rules definitions """
def __init__(self, data, display_warnings=True):
self._data = data
self._decomposer = PFSenseRuleDecomposer(data)
self._display_warnings = display_warnings
def rule_interfaces_any(self, rule_obj):
""" Return interfaces set on which the rule is needed to be defined
Manage rules with any src or dst """
src = rule_obj.src[0]
dst = rule_obj.dst[0]
# if rule is forced, we return the interface defined
if rule_obj.force:
return set(rule_obj.get_option('ifilter').split())
if src.name == 'any' and dst.name == 'any':
# we return all interfaces of target
return set(self._data.target.interfaces.keys())
elif src.name == 'any':
# if the destination is local, we return all interfaces of target
if dst.is_whole_local(self._data.target):
return set(self._data.target.interfaces.keys())
# otherwise we return all interfaces of target if the destination is adjacent/remote
# (we must be able to reach the destination to allow any src to access it)
for iface, interface in self._data.target.interfaces.items():
if dst.is_in_adjacent_networks(interface) or dst.is_in_remote_networks(interface):
return set(self._data.target.interfaces.keys())
return set()
elif rule_obj.dst[0].name == 'any':
# we allow the interfaces matching the source ip/networks
# or the adjacent/remote networks
interfaces = set()
for iface, interface in self._data.target.interfaces.items():
if src.is_in_local_network(interface) or src.is_in_adjacent_networks(interface) or src.is_in_remote_networks(interface):
interfaces.add(iface)
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning('{0}: to_any_dst interfaces={1}'.format(rule_obj.name, interfaces))
return interfaces
return None
def rule_interfaces_ip_broadcast(self, rule_obj):
""" Return interfaces set on which the rule is needed to be defined
Manage rules with src or dst ip broadcast """
src = rule_obj.src[0]
dst = rule_obj.dst[0]
src_is_bcast = src.is_ip_broadcast()
dst_is_bcast = dst.is_ip_broadcast()
if not src_is_bcast and not dst_is_bcast:
return None
if src_is_bcast and rule_obj.dst[0].is_whole_local(self._data.target):
return rule_obj.dst[0].local_interfaces[self._data.target.name] | rule_obj.dst[0].routed_interfaces[self._data.target.name]
if dst_is_bcast and rule_obj.src[0].is_whole_local(self._data.target):
return rule_obj.src[0].local_interfaces[self._data.target.name] | rule_obj.src[0].routed_interfaces[self._data.target.name]
# we return no rules for:
# - broadcast to broadcast
# - broadcast to remote
# - remote to broadcast
return []
def bridged_by_interfaces(self, routing_interfaces, dst):
""" if all the routing_interfaces are bridged and the destinations are on local bridges too
return the destination bridges """
for iface in routing_interfaces:
if not self._data.target.interfaces[iface].bridge:
return None
if self._data.target.name in dst.local_interfaces:
for iface in dst.local_interfaces[self._data.target.name]:
if not self._data.target.interfaces[iface].bridge:
return None
else:
return None
return dst.local_interfaces[self._data.target.name]
def rule_interfaces(self, rule_obj):
""" Return interfaces list on which the rule is needed to be defined """
def filter_interfaces(interfaces):
if interface_filter is not None:
interfaces = interfaces & interface_filter
if interface_efilter is not None:
interfaces = interfaces - interface_efilter
return interfaces
# if the rule has a filter, apply it
rule_filter = rule_obj.get_option('filter')
if rule_filter and self._data.target.name not in rule_filter.split():
return set()
# if the rule has an efilter, apply it
rule_efilter = rule_obj.get_option('efilter')
if rule_efilter and self._data.target.name in rule_efilter.split():
return set()
interface_efilter = rule_obj.get_option('eifilter')
if interface_efilter is not None:
interface_efilter = set(interface_efilter.split())
interface_filter = rule_obj.get_option('ifilter')
if interface_filter is not None:
interface_filter = set(interface_filter.split())
if len(rule_obj.src) != 1 or len(rule_obj.dst) != 1:
raise AssertionError()
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning('{0}: src={1} dst={2}'.format(rule_obj.name, rule_obj.src[0].name, rule_obj.dst[0].name))
# if the rule uses 'any'
interfaces = self.rule_interfaces_any(rule_obj)
if interfaces is not None:
return filter_interfaces(interfaces)
# if the rule uses broadcasts
interfaces = self.rule_interfaces_ip_broadcast(rule_obj)
if interfaces is not None:
return filter_interfaces(interfaces)
interfaces = set()
src_is_local = rule_obj.src[0].is_whole_local(self._data.target)
dst_is_local = rule_obj.dst[0].is_whole_local(self._data.target)
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning('{0}: src_is_local={1} dst_is_local={2}'.format(rule_obj.name, src_is_local, dst_is_local))
# if it's a blocking or reject rule, we only use the src
if rule_obj.action != 'pass':
if src_is_local:
interfaces = rule_obj.src[0].local_interfaces[self._data.target.name]
else:
interfaces = rule_obj.src[0].routed_by_interfaces(self._data.target, True)
return filter_interfaces(interfaces)
# if source and dst are local, return the local interface
if src_is_local and dst_is_local:
if len(rule_obj.src[0].local_interfaces[self._data.target.name]) != 1:
raise AssertionError(
'Invalid local interfaces count for {0}: {1}'
.format(rule_obj.name, len(rule_obj.src[0].local_interfaces[self._data.target.name])))
if len(rule_obj.dst[0].local_interfaces[self._data.target.name]) != 1:
raise AssertionError(
'Invalid local interfaces count for {0}: {1}'
.format(rule_obj.name, len(rule_obj.dst[0].local_interfaces[self._data.target.name])))
# if they are both on the same interface, we dont need any rule when:
# - the interface is not a bridge
# - src and dst are in the same network on the interface
# - the pfsense is not the source/destination of the rule
src_interface = ''.join(rule_obj.src[0].local_interfaces[self._data.target.name])
dst_interface = ''.join(rule_obj.dst[0].local_interfaces[self._data.target.name])
if (src_interface == dst_interface and
not self._data.target.interfaces[src_interface].bridge and
self._data.target.interfaces[src_interface].are_in_same_network(rule_obj.src[0], rule_obj.dst[0])):
if not rule_obj.src[0].match_local_interface_ip(self._data.target) and not rule_obj.dst[0].match_local_interface_ip(self._data.target):
return set()
return filter_interfaces(rule_obj.src[0].local_interfaces[self._data.target.name])
# if the destination is unreachable
if not dst_is_local and src_is_local and not rule_obj.dst[0].is_adjacent_or_remote(self._data.target):
if self._display_warnings:
display.warning(
'Destination {0} is not accessible from this pfSense for {1}.Please add the right adjacent/remote network if it\'s not an error'
.format(rule_obj.dst[0].name, rule_obj.name))
return set()
# if the source is unreachable
if not src_is_local and dst_is_local and not rule_obj.src[0].is_adjacent_or_remote(self._data.target):
if self._display_warnings:
display.warning(
'Source {0} can not access to this pfSense for {1}. Please add the right adjacent/remote network if it\'s not an error'
.format(rule_obj.src[0].name, rule_obj.name))
return set()
# we add all the interfaces the source can use to go out
if self._data.target.name in rule_obj.src[0].local_interfaces:
interfaces.update(rule_obj.src[0].local_interfaces[self._data.target.name])
# we add interfaces the source can use to get in
if not src_is_local:
src_is_adjacent = rule_obj.src[0].is_adjacent(self._data.target)
routing_interfaces = rule_obj.src[0].routed_by_interfaces(self._data.target, not src_is_adjacent)
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning('{0}: src_is_adjacent={1}, routing_interfaces={2}, src={3}'.format(
rule_obj.name, src_is_adjacent, routing_interfaces, rule_obj.src[0].name))
# if they are both not local and on the same interfaces or with an unreachable destination
# we return nothing
if not dst_is_local:
dst_is_adjacent = rule_obj.dst[0].is_adjacent(self._data.target)
# if the source is remote and the destination is adjacent, we return the source interfaces
if not src_is_adjacent and dst_is_adjacent:
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning(
'{0}: dst_is_adjacent={1}, routing_interfaces={2}, dst={3}'.format(
rule_obj.name, dst_is_adjacent, filter_interfaces(routing_interfaces), rule_obj.dst[0].name))
return filter_interfaces(routing_interfaces)
# if the source is on adjacent networks, it can get out to reach remote networks
dst_routing_interfaces = rule_obj.dst[0].routed_by_interfaces(self._data.target, src_is_adjacent)
if self._data.debug is not None and self._data.debug == rule_obj.name:
display.warning('{0}: dst_is_adjacent={1}, dst_routing_interfaces={2}, dst={3}'.format(
rule_obj.name, dst_is_adjacent, dst_routing_interfaces, rule_obj.dst[0].name))
# if the source is adjacent and the destination is remote, we return the source interfaces
if src_is_adjacent and len(dst_routing_interfaces):
return filter_interfaces(routing_interfaces)
routing_interfaces = routing_interfaces.difference(dst_routing_interfaces)
if not routing_interfaces or not dst_routing_interfaces:
return set()
# if the interfaces we would use are bridged, and the destinations are on local bridges too
# we declare the rule on the destination bridges since packets would come from there
bridge_interfaces = self.bridged_by_interfaces(routing_interfaces, rule_obj.dst[0])
if bridge_interfaces:
interfaces.update(bridge_interfaces)
else:
interfaces.update(routing_interfaces)
if not interfaces and (src_is_local or dst_is_local):
msg = 'Invalid sub-rule interfaces count ({0}), src={1}, dst={2}'.format(len(interfaces), rule_obj.src[0].name, rule_obj.dst[0].name)
raise AssertionError(msg)
return filter_interfaces(interfaces)
def generate_rule(self, name, rule_obj, interfaces, last_name):
""" Generate rules definitions for rule """
def _gen_rule_dict(rule_def, name, interface=None):
if interface is None:
interface = 'floating'
floating = True
else:
floating = False
definition = {}
definition['name'] = name
definition['action'] = rule_obj.action
for field in OUTPUT_OPTION_FIELDS:
value = rule_obj.get_option(field)
if value is not None:
definition[field] = value
if floating:
definition['floating'] = 'yes'
if 'direction' not in definition:
definition['direction'] = 'any'
definition['interface'] = ','.join(rule_interfaces)
else:
definition['interface'] = interface
definition['state'] = 'present'
if interface in last_name and last_name[interface]:
definition['after'] = last_name[interface]
else:
definition['after'] = 'top'
if rule_obj.asymmetric:
definition['statetype'] = 'sloppy state'
definition['tcpflags_any'] = True
definition.update(rule_def)
interfaces[interface].append(definition)
last_name[interface] = name
if rule_obj.invert_src and 'source' in definition:
definition['source'] = '!' + definition['source']
if rule_obj.invert_dst and 'destination' in definition:
definition['destination'] = '!' + definition['destination']
if interface not in rule_obj.generated_names:
rule_obj.generated_names[interface] = name
def _gen_src_nat_rule_dict(rule_def, name, interface, src_nat):
definition = {}
definition['descr'] = '{0}_{1}'.format(name, interface)
definition['interface'] = interface
definition['state'] = 'present'
definition['address'] = '{0}'.format(src_nat.name)
definition.update(rule_def)
for field in OUTPUT_SRC_NAT_OPTION_FIELDS:
value = rule_obj.get_option(field)
if value is not None:
definition[field] = value
for field in ['source', 'destination']:
key = field + '_port'
if key in definition:
definition[field] = '{0}:{1}'.format(definition[field], definition[key])
del definition[key]
interfaces[interface].append(definition)
def _gen_dst_nat_rule_dict(rule_def, name, interface, dst_nat, dst_nat_port):
definition = {}
definition['descr'] = '{0}_{1}'.format(name, interface)
definition['interface'] = interface
definition['state'] = 'present'
definition['target'] = '{0}:{1}'.format(dst_nat.name, dst_nat_port)
definition.update(rule_def)
for field in OUTPUT_DST_NAT_OPTION_FIELDS:
value = rule_obj.get_option(field)
if value is not None:
definition[field] = value
if 'associated_rule' not in definition:
definition['associated_rule'] = 'pass'
for field in ['source', 'destination']:
key = field + '_port'
if key in definition:
definition[field] = '{0}:{1}'.format(definition[field], definition[key])
del definition[key]
if rule_obj.invert_src_nat and 'source' in definition:
definition['source'] = '!' + definition['source']
if rule_obj.invert_dst_nat and 'destination' in definition:
definition['destination'] = '!' + definition['destination']
interfaces[interface].append(definition)
base = []
base.append({})
if len(rule_obj.src) != 1 or len(rule_obj.dst) != 1:
raise AssertionError()
rule = {}
rule['src'] = rule_obj.src[0].name
rule['dst'] = rule_obj.dst[0].name
if rule_obj.protocol:
rule['protocol'] = ' '.join(rule_obj.protocol)
if self._data.aggregate:
if rule_obj.src_port:
if len(rule_obj.src_port) == 1:
rule['src_port'] = ' '.join(rule_obj.src_port)
else:
rule['src_port'] = self._data.get_ports_alias(set(rule_obj.src_port), name)
rule_obj.src_port = [rule['src_port']]
if rule_obj.dst_port:
if len(rule_obj.dst_port) == 1:
rule['dst_port'] = ' '.join(rule_obj.dst_port)
else:
rule['dst_port'] = self._data.get_ports_alias(set(rule_obj.dst_port), name)
rule_obj.dst_port = [rule['dst_port']]
else:
if rule_obj.src_port:
rule['src_port'] = ' '.join(rule_obj.src_port)
if rule_obj.dst_port:
rule['dst_port'] = ' '.join(rule_obj.dst_port)
base = rule_product_dict(base, rule, 'src', 'source')
base = rule_product_dict(base, rule, 'dst', 'destination')
base = rule_product_dict(base, rule, 'protocol')
base = rule_product_dict(base, rule, 'src_port', 'source_port')
base = rule_product_dict(base, rule, 'dst_port', 'destination_port')
if rule_obj.floating:
rule_interfaces = list(rule_obj.interfaces)
rule_interfaces.sort()
if len(base) == 1:
_gen_rule_dict(base[0], name)
else:
rule_idx = 1
for rule_def in base:
rule_name = name + "_" + str(rule_idx)
_gen_rule_dict(rule_def, rule_name)
rule_idx = rule_idx + 1
else:
for interface in rule_obj.interfaces:
if len(base) == 1:
if rule_obj.src_nat:
_gen_src_nat_rule_dict(base[0], name, interface, rule_obj.src_nat[0])
if rule_obj.dst_nat:
_gen_dst_nat_rule_dict(base[0], name, interface, rule_obj.dst_nat[0], rule_obj.dst_nat_port[0])
if not rule_obj.src_nat and not rule_obj.dst_nat:
_gen_rule_dict(base[0], name, interface)
else:
rule_idx = 1
for rule_def in base:
rule_name = name + "_" + str(rule_idx)
if rule_obj.src_nat:
_gen_src_nat_rule_dict(rule_def, rule_name, interface, rule_obj.src_nat[0])
if rule_obj.dst_nat:
_gen_dst_nat_rule_dict(rule_def, rule_name, interface, rule_obj.dst_nat[0], rule_obj.dst_nat_port[0])
if not rule_obj.src_nat and not rule_obj.dst_nat:
_gen_rule_dict(rule_def, rule_name, interface)
rule_idx = rule_idx + 1
def aggregate_subrules(self, rule, interfaces, subrules, sub_interfaces):
""" aggregate generated subrules """
def _get_same_rule(new_rules, src, dst):
for rule in new_rules:
if rule.src[0].name == src.name and rule.dst[0].name == dst.name:
return rule
return None
def _add_list(group, objs):
for obj in objs:
group.add(obj)
def _aggregate_job(interface=None):
# we create fake alias when required
# we also use interfaces IP and NET when possible
src = subrule.src[0]
dst = subrule.dst[0]
if len(src_group_name) != 1:
src = self._data.get_hosts_alias(src_group_name, src_group_ip, src_group_net, rule.name)
elif (not rule.src_nat and not rule.floating and len(subrule.src[0].networks) == 1 and
len(subrule.src[0].ips) == 0 and
subrule.src[0].networks[0] == self._data.target.interfaces[interface].local_network):
src = subrule.src[0].copy()
src.name = "NET:{0}".format(interface)
elif (not rule.src_nat and not rule.floating and len(subrule.src[0].networks) == 0 and
len(subrule.src[0].ips) == 1 and
subrule.src[0].ips[0] == self._data.target.interfaces[interface].local_ip):
src = subrule.src[0].copy()
src.name = "IP:{0}".format(interface)
if len(dst_group_name) != 1:
dst = self._data.get_hosts_alias(dst_group_name, dst_group_ip, dst_group_net, rule.name)
elif (not rule.src_nat and not rule.floating and len(subrule.dst[0].networks) == 1 and
len(subrule.dst[0].ips) == 0 and
subrule.dst[0].networks[0] == self._data.target.interfaces[interface].local_network):
dst = subrule.dst[0].copy()
dst.name = "NET:{0}".format(interface)
elif (not rule.src_nat and not rule.floating and len(subrule.dst[0].networks) == 0 and
len(subrule.dst[0].ips) == 1 and
subrule.dst[0].ips[0] == self._data.target.interfaces[interface].local_ip):
dst = subrule.dst[0].copy()
dst.name = "IP:{0}".format(interface)
# when aggregating, we merge rules with same src/dst
existing_rule = _get_same_rule(new_rules, src, dst)
if existing_rule is None:
existing_rule = copy(subrule)
existing_rule.src[0] = src
existing_rule.dst[0] = dst
existing_rule.interfaces = set()
new_rules.append(existing_rule)
if rule.floating:
for sub_interface in sub_interfaces:
existing_rule.interfaces.add(sub_interface)
if sub_interface not in interfaces:
interfaces[sub_interface] = []
else:
existing_rule.interfaces.add(interface)
if interface not in interfaces:
interfaces[interface] = []
new_rules = list()
if rule.floating:
src_group_name = set()
dst_group_name = set()
src_group_ip = set()
dst_group_ip = set()
src_group_net = set()
dst_group_net = set()
subrule = None
for interface in sorted(sub_interfaces):
for subrule in sub_interfaces[interface]:
src_group_name.add(subrule.src[0].name)
dst_group_name.add(subrule.dst[0].name)
_add_list(src_group_ip, subrule.src[0].ips)
_add_list(src_group_net, subrule.src[0].networks)
_add_list(dst_group_ip, subrule.dst[0].ips)
_add_list(dst_group_net, subrule.dst[0].networks)
_aggregate_job()
else:
for interface in sorted(sub_interfaces):
src_group_name = set()
dst_group_name = set()
src_group_ip = set()
dst_group_ip = set()
src_group_net = set()
dst_group_net = set()
subrule = None
for subrule in sub_interfaces[interface]:
src_group_name.add(subrule.src[0].name)
dst_group_name.add(subrule.dst[0].name)
_add_list(src_group_ip, subrule.src[0].ips)
_add_list(src_group_net, subrule.src[0].networks)
_add_list(dst_group_ip, subrule.dst[0].ips)
_add_list(dst_group_net, subrule.dst[0].networks)
_aggregate_job(interface)
if rule.floating and 'floating' not in interfaces:
interfaces['floating'] = []
subrules.extend(new_rules)
def guess_rules(self, rule_filter):
""" Return interfaces, rules and rules names """
interfaces = {}
rules = list()
for name, rule in self._data.rules_obj.items():
subrules = []
sub_interfaces = dict()
# for each subrule, we guess on which interfaces the subrule needs to be generated, if any
for subrule in sorted(rule.sub_rules, key=lambda x: x.src[0].name + x.dst[0].name):
subrule.interfaces = self.rule_interfaces(subrule)
if rule_filter is not None and name != rule_filter:
continue
if not subrule.interfaces:
continue
# when aggregating, we group the rules by interface for later
# otherwise, we add the subrule
if self._data.aggregate:
for interface in subrule.interfaces:
if interface not in sub_interfaces:
sub_interfaces[interface] = []
sub_interfaces[interface].append(subrule)
else:
subrules.append(subrule)
for interface in subrule.interfaces:
if interface not in interfaces:
interfaces[interface] = []
# let's aggregate
if self._data.aggregate and sub_interfaces:
self.aggregate_subrules(rule, interfaces, subrules, sub_interfaces)
if self._data.gendiff:
rules.extend(subrules)
else:
for subrule in subrules:
rules.append((name, subrule))
# we only keep the subrules having interfaces
rule.sub_rules = subrules
return (interfaces, rules)
def generate_rules(self, rule_filter=None):
""" Return rules definitions for pfsense_aggregate
if rule_filter, process only rules matching rule_filter
"""
filter_rules = []
src_nat_rules = []
dst_nat_rules = []
# first, we break rules in small parts (one src, one dst)
self._decomposer.decompose_rules()
# then, we guess the rules which are required on the target
(interfaces, rules) = self.guess_rules(rule_filter)
# last, we generate each required rule
last_name = dict()
if self._data.gendiff:
for rule in rules:
self.generate_rule(rule.name, rule, interfaces, last_name)
else:
for (name, rule) in rules:
self.generate_rule(name, rule, interfaces, last_name)
# since nat is not separated by interface, we manage the order here
last_src_nat = 'top'
last_dst_nat = 'top'
for name in sorted(interfaces.keys()):
interface = interfaces[name]
for rule in interface:
if 'address' in rule:
rule['after'] = last_src_nat
last_src_nat = rule['descr']
src_nat_rules.append(rule)
elif 'target' in rule:
rule['after'] = last_dst_nat
last_dst_nat = rule['descr']
dst_nat_rules.append(rule)
else:
filter_rules.append(rule)
return (filter_rules, src_nat_rules, dst_nat_rules)
def output_rules(self, rules, ignored_rules):
""" Output rules definitions for pfsense_aggregate """
print(" #===========================")
print(" # Rules")
print(" # ")
interfaces = list(self._data.target.interfaces.keys())
interfaces.append('floating')
definitions = list()
for interface in interfaces:
for rule in rules:
if interface == rule['interface'] or interface == 'floating' and rule.get('floating'):
definition = ' - { name: "%s", source: "%s", ' % (rule['name'], rule['source'])
if 'source_port' in rule:
definition += 'source_port: "{0}", '.format(rule['source_port'])
definition += 'destination: "{0}", '.format(rule['destination'])
if 'destination_port' in rule:
definition += 'destination_port: "{0}", '.format(rule['destination_port'])
definition += 'interface: "{0}", action: "{1}"'.format(rule['interface'], rule['action'])
if rule.get('protocol'):
definition += ", protocol: \"" + rule['protocol'] + "\""
if rule.get('descr'):
definition += ", descr: \"" + rule['descr'] + "\""
for field in OUTPUT_OPTION_FIELDS:
value = rule.get(field)
if value is not None:
definition += ', {0}: {1}'.format(field, value)
if rule.get('floating'):
definition += ", floating: True"
if rule.get('statetype') is not None:
definition += ", statetype: '{0}'".format(rule.get('statetype'))
if rule.get('tcpflags_any'):
definition += ", tcpflags_any: True"
if rule.get('after') and not self._data.gendiff:
definition += ", after: \"" + rule['after'] + "\""
definition += ", state: \"present\" }"
if self._data.gendiff:
definitions.append(definition)
else:
print(definition)
definitions.sort()
print('\n'.join(definitions))
print(" #===========================")
print(" # ignored rules")
print(" # ")
definitions = list()
for rule in ignored_rules:
definition = " - { name: \"" + rule + "\" }"
definitions.append(definition)
definitions.sort()
print('\n'.join(definitions))
def output_src_nat_rules(self, rules):
""" Output outbound definitions for pfsense_aggregate """
print(" #===========================")
print(" # Nat outbound rules")
print(" # ")
interfaces = list(self._data.target.interfaces.keys())
interfaces.append('floating')
definitions = list()
for interface in sorted(interfaces):
for rule in rules:
if interface == rule['interface']:
definition = ' - { descr: "%s", source: "%s", ' % (rule['descr'], rule['source'])
if 'source_port' in rule:
definition += 'source_port: "{0}", '.format(rule['source_port'])
definition += 'destination: "{0}", '.format(rule['destination'])
if 'destination_port' in rule:
definition += 'destination_port: "{0}", '.format(rule['destination_port'])
definition += 'interface: "{0}", address: "{1}"'.format(rule['interface'], rule['address'])
for field in OUTPUT_SRC_NAT_OPTION_FIELDS:
value = rule.get(field)
if value is not None:
definition += ', {0}: {1}'.format(field, value)
if rule.get('protocol'):
definition += ", protocol: \"" + rule['protocol'] + "\""
if rule.get('descr'):
definition += ", descr: \"" + rule['descr'] + "\""
if rule.get('after') and not self._data.gendiff:
definition += ", after: \"" + rule['after'] + "\""
definition += ", state: \"present\" }"
if self._data.gendiff:
definitions.append(definition)
else:
print(definition)
definitions.sort()
print('\n'.join(definitions))
def output_dst_nat_rules(self, rules):
""" Output outbound definitions for pfsense_aggregate """
print(" #===========================")
print(" # Nat port forward rules")
print(" # ")
interfaces = list(self._data.target.interfaces.keys())
interfaces.append('floating')
definitions = list()
for interface in sorted(interfaces):
for rule in rules:
if interface == rule['interface']:
definition = ' - { descr: "%s", source: "%s", ' % (rule['descr'], rule['source'])
definition += 'destination: "{0}", '.format(rule['destination'])
definition += 'interface: "{0}", target: "{1}"'.format(rule['interface'], rule['target'])
for field in OUTPUT_SRC_NAT_OPTION_FIELDS:
value = rule.get(field)
if value is not None:
definition += ', {0}: {1}'.format(field, value)
if rule.get('descr'):
definition += ", descr: \"" + rule['descr'] + "\""
if rule.get('after') and not self._data.gendiff:
definition += ", after: \"" + rule['after'] + "\""
definition += ", state: \"present\" }"
if self._data.gendiff:
definitions.append(definition)
else:
print(definition)
definitions.sort()
print('\n'.join(definitions))
class PFSenseRuleSeparatorFactory(object):
""" Class generating rule separators definitions """
def __init__(self, data):
self._data = data
def _find_first_separator_rule(self, separator):
""" return the name of the first rule in the separator """
for rule in self._data.rules_obj.values():
for subrule in rule.sub_rules:
if subrule.separator.name == separator.name and separator.interface in subrule.generated_names:
return subrule.generated_names[separator.interface]
return None
def generate_rule_separators(self, rule_filter=None):
""" Return rule_separators definitions for pfsense_aggregate """
separators = OrderedDict()
for name, rule in self._data.rules_obj.items():
if rule_filter is not None and name != rule_filter:
continue
for subrule in rule.sub_rules:
if subrule.separator is None or subrule.separator.name is None:
continue
for interface in subrule.interfaces:
separator = PFSenseRuleSeparator()
separator.name = subrule.separator.name
if rule.floating:
separator.interface = 'floating'
else:
separator.interface = interface
if separator not in separators:
separators[separator] = separator
ret = []
for separator in separators.values():
definition = {}
definition['name'] = separator.name
if separator.interface == 'floating':
definition['floating'] = True
else:
definition['interface'] = separator.interface
definition['before'] = self._find_first_separator_rule(separator)
if definition['before'] is None:
# for now we don't manage empty separators
continue
definition['state'] = 'present'
ret.append(definition)
return ret
def output_rule_separators(self, separators):
""" Output rule separators definitions for pfsense_aggregate """
print(" #===========================")
print(" # Rule separators")
print(" # ")
interfaces = list(self._data.target.interfaces.keys())
interfaces.append('floating')
definitions = list()
for interface in interfaces:
for separator in separators:
if 'interface' in separator and interface == separator['interface'] or interface == 'floating' and 'floating' in separator:
definition = " - { name: \"" + separator['name'] + "\", "
if interface == 'floating':
definition += "floating: True, "
else:
definition += "interface: \"" + separator['interface'] + "\", "
definition += "before: \"" + separator['before'] + "\", state: \"present\" }"
definitions.append(definition)
definitions.sort()
print('\n'.join(definitions))
def _create_target_data(cached_data, target_name):
""" Create a lightweight per-target copy from cached global data.
Shares immutable state (aliases, pfsense objects with their caches),
copies only what generate_rules mutates (rules_obj, alias/port dicts). """
data = PFSenseData.__new__(PFSenseData)
# Shared immutable state (read-only after global parse)
data._hosts_aliases = cached_data._hosts_aliases
data._pfsenses = cached_data._pfsenses
data._pfsenses_obj = cached_data._pfsenses_obj
data._rules = cached_data._rules
data._rules_separators = cached_data._rules_separators
data._ignored_aliases = cached_data._ignored_aliases
data._ignored_rules = cached_data._ignored_rules
data.log_errors = False
data.gendiff = cached_data.gendiff
data.debug = cached_data.debug
data.aggregate = cached_data.aggregate
data._errors = []
# Per-target copies (get_hosts_alias/get_ports_alias add entries during generation)
data._all_aliases = dict(cached_data._all_aliases)
data._hosts_aliases_obj = OrderedDict(cached_data._hosts_aliases_obj)
data._ports_aliases = dict(cached_data._ports_aliases)
data._hosts_alias_by_content = None
# Copy rules (sub_rules, generated_names, src_port/dst_port get mutated by generate_rules)
data._rules_obj = OrderedDict()
for name, rule in cached_data._rules_obj.items():
data._rules_obj[name] = rule._copy_for_decompose()
# Set target
data._target_name = target_name
data._target = data._pfsenses_obj[target_name]
return data
# Module-level cache: parsed global data persists across Ansible lookup invocations
# (all hosts share the same Python process on the controller)
_PARSED_CACHE = {}
class LookupModule(LookupBase):
""" Lookup module generating pfsense definitions """
def get_hostname(self):
""" Just for easier mock """
myvars = getattr(self._templar, '_available_variables', {})
return myvars['inventory_hostname']
@staticmethod
def get_definitions(from_file):
""" Just for easier mock """
return ordered_load(open(from_file), yaml.SafeLoader)
def load_data(self, from_file):
""" Load and return pfsense data """
fvars = self.get_definitions(from_file)
if fvars is None:
raise AnsibleError("No usable data found in {0}".format(from_file))
for section in ['hosts_aliases', 'ports_aliases', 'pfsenses', 'rules']:
if section not in fvars:
raise AnsibleError("Missing {0} section in {1}".format(section, from_file))
data = PFSenseData(
hosts_aliases=fvars['hosts_aliases'],
ports_aliases=fvars['ports_aliases'],
pfsenses=fvars['pfsenses'],
rules=fvars['rules'],
target_name=self.get_hostname()
)
return data
def _run(self, terms, variables, **kwargs):
""" Main function """
if len(terms) != 2:
raise AnsibleError("pfsense lookup requires a filename and another parameter in [aliases, rules, rule_separators, all_definitions]")
from_file = terms[0]
target_name = self.get_hostname()
# Check module-level cache for parsed global data
abs_path = os.path.abspath(from_file)
try:
mtime = os.path.getmtime(abs_path)
except OSError:
mtime = 0
cache_key = (abs_path, mtime)
cached_data = _PARSED_CACHE.get(cache_key)
if cached_data is not None:
# Cache hit: validate target and create lightweight per-target copy
if target_name not in cached_data._pfsenses_obj:
raise AnsibleError(target_name + " does not exist in pfsenses section")
data = _create_target_data(cached_data, target_name)
else:
# Cache miss: full parse
data = self.load_data(from_file)
parser = PFSenseDataParser(data)
if not parser.parse():
raise AnsibleError("Error checking pfsense data")
# Cache the fully-parsed global state (before generate_rules mutates anything)
_PARSED_CACHE[cache_key] = data
# Create per-target copy (generate_rules will mutate it, not the cached original)
data = _create_target_data(data, target_name)
alias_factory = PFSenseAliasFactory(data)
rule_factory = PFSenseRuleFactory(data, display_warnings=(terms[1] == 'rules'))
rule_separator_factory = PFSenseRuleSeparatorFactory(data)
(rules, src_nat_rules, dst_nat_rules) = rule_factory.generate_rules()
rule_separators = rule_separator_factory.generate_rule_separators()
aliases = alias_factory.generate_aliases()
if terms[1] == 'aliases':
return [aliases]
elif terms[1] == 'rules':
return [rules]
elif terms[1] == 'nat_outbounds':
return [src_nat_rules]
elif terms[1] == 'nat_port_forwards':
return [dst_nat_rules]
elif terms[1] == 'rule_separators':
return [rule_separators]
elif terms[1] == 'all_definitions':
res = {}
res['aggregated_aliases'] = aliases
res['aggregated_rules'] = rules
res['aggregated_rule_separators'] = rule_separators
res['aggregated_nat_outbounds'] = src_nat_rules
res['aggregated_nat_port_forwards'] = dst_nat_rules
res['ignored_rules'] = list(data.ignored_rules)
res['ignored_aliases'] = list(data.ignored_aliases)
return [res]
return []
def run(self, terms, variables, **kwargs):
""" Entry point for main function (to properly catch & display exceptions stacktrace)"""
trace = None
res = []
try:
res = self._run(terms, variables, **kwargs)
except AnsibleError:
raise
except AssertionError:
raise
except Exception:
trace = traceback.format_exc()
finally:
if trace is not None:
raise AnsibleError(trace)
return res
def unit_test_helper(filename, pfname):
""" Unit test helper """
rule_filter = None
fvars = ordered_load(open(filename), yaml.SafeLoader)
data = PFSenseData(
hosts_aliases=fvars['hosts_aliases'],
ports_aliases=fvars['ports_aliases'],
pfsenses=fvars['pfsenses'],
rules=fvars['rules'],
target_name=pfname,
)
parser = PFSenseDataParser(data)
if not parser.parse():
return False
alias_factory = PFSenseAliasFactory(data)
rule_factory = PFSenseRuleFactory(data)
rule_separator_factory = PFSenseRuleSeparatorFactory(data)
(rules, src_nat_rules, dst_nat_rules) = rule_factory.generate_rules(rule_filter)
rule_separators = rule_separator_factory.generate_rule_separators()
aliases = alias_factory.generate_aliases(rule_filter)
alias_factory.output_aliases(aliases, data.ignored_aliases)
rule_factory.output_rules(rules, data.ignored_rules)
rule_factory.output_src_nat_rules(src_nat_rules)
rule_factory.output_dst_nat_rules(dst_nat_rules)
rule_separator_factory.output_rule_separators(rule_separators)
return (aliases, rules)
def main():
""" Output debug helper """
parser = argparse.ArgumentParser()
parser.add_argument("file", help="input file")
parser.add_argument("pfsense", help="target_fw")
parser.add_argument('filter', help="rule_name", nargs='?')
parser.add_argument("-a", "--dont-aggregate", action="store_false", help="dont generate aliases to aggregate rules")
parser.add_argument("-g", "--gendiff", action="store_true", help="output more suitable for diffs (debugging)")
parser.add_argument("-d", "--debug-rule", action="store", help="debug rule")
args = parser.parse_args()
rule_filter = None
if args.filter:
rule_filter = args.filter
print('Loading data...')
fvars = ordered_load(open(args.file), yaml.SafeLoader)
data = PFSenseData(
hosts_aliases=fvars['hosts_aliases'],
ports_aliases=fvars['ports_aliases'],
pfsenses=fvars['pfsenses'],
rules=fvars['rules'],
target_name=args.pfsense,
gendiff=args.gendiff,
debug=args.debug_rule,
aggregate=args.dont_aggregate,
)
parser = PFSenseDataParser(data)
print('Parsing data...')
if not parser.parse():
return
alias_factory = PFSenseAliasFactory(data)
rule_factory = PFSenseRuleFactory(data)
rule_separator_factory = PFSenseRuleSeparatorFactory(data)
print('Generating rules...')
(rules, src_nat_rules, dst_nat_rules) = rule_factory.generate_rules(rule_filter)
if rule_filter is None:
print('Generating rule separators...')
rule_separators = rule_separator_factory.generate_rule_separators(rule_filter)
else:
print('Filter set. Skipping rule separators...')
print('Generating aliases...')
aliases = alias_factory.generate_aliases(rule_filter)
alias_factory.output_aliases(aliases, data.ignored_aliases)
rule_factory.output_rules(rules, data.ignored_rules)
rule_factory.output_src_nat_rules(src_nat_rules)
rule_factory.output_dst_nat_rules(dst_nat_rules)
if rule_filter is None:
rule_separator_factory.output_rule_separators(rule_separators)
if __name__ == '__main__':
profile = False
if profile:
import cProfile
import pstats
profiler = cProfile.Profile()
profiler.enable()
main()
profiler.disable()
stats = pstats.Stats(profiler).sort_stats('tottime')
stats.print_stats()
else:
main()
================================================
FILE: plugins/module_utils/__init__.py
================================================
================================================
FILE: plugins/module_utils/alias.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018-2024, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
ALIAS_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
state=dict(default='present', choices=['present', 'absent']),
type=dict(required=False, choices=['host', 'network', 'port', 'urltable', 'urltable_ports']),
address=dict(default=None, required=False, type='str'),
url=dict(default=None, required=False, type='str'),
descr=dict(default=None, required=False, type='str'),
detail=dict(default=None, required=False, type='str'),
updatefreq=dict(default=None, required=False, type='int'),
)
ALIAS_MUTUALLY_EXCLUSIVE = [
('address', 'url'),
]
ALIAS_REQUIRED_IF = [
["state", "present", ["type"]],
["type", "host", ["address"]],
["type", "network", ["address"]],
["type", "port", ["address"]],
["type", "urltable", ["updatefreq"]],
["type", "urltable_ports", ["updatefreq"]],
# When "address" deprecation period is over
# ["type", "urltable", ["updatefreq", "url"]],
# ["type", "urltable_ports", ["updatefreq", "url"]],
]
ALIAS_MAP_PARAM_IF = [
["type", "urltable", ("address", "url")],
["type", "urltable_ports", ("address", "url")],
]
ALIAS_CREATE_DEFAULT = dict(
descr='',
detail='',
)
ALIAS_PHP_COMMAND_SET = """
require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('aliases'); }
"""
class PFSenseAliasModule(PFSenseModuleBase):
""" module managing pfsense aliases """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return ALIAS_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseAliasModule, self).__init__(module, pfsense, root='aliases', node='alias', key='name', update_php=ALIAS_PHP_COMMAND_SET,
map_param_if=ALIAS_MAP_PARAM_IF, create_default=ALIAS_CREATE_DEFAULT)
# Override for use with aggregate
self.argument_spec = ALIAS_ARGUMENT_SPEC
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
self.pfsense.check_name(params['name'], 'alias')
if params['state'] == 'present':
# the GUI does not allow to create 2 aliases with same name and differents types
alias_elt = self.pfsense.find_alias(params['name'])
if alias_elt is not None:
if params['type'] not in ['host', 'network'] or alias_elt.find('type').text not in ['host', 'network']:
if params['type'] != alias_elt.find('type').text:
self.module.fail_json(msg='An alias with this name and a different type already exists: \'{0}\''.format(params['name']))
# Aliases cannot have the same name as an interface description
if self.pfsense.get_interface_by_display_name(params['name']) is not None:
self.module.fail_json(msg='An interface description with this name already exists: \'{0}\''.format(params['name']))
# updatefreq is for urltable only
if params['updatefreq'] is not None and params['type'] != 'urltable' and params['type'] != 'urltable_ports':
self.module.fail_json(msg='updatefreq is only valid with type urltable or urltable_ports')
details = params['detail'].split('||') if params['detail'] is not None else []
if params['address'] is not None:
# check details count
addresses = params['address'].split(' ')
if len(details) > len(addresses):
self.module.fail_json(msg='Too many details in relation to addresses')
# warn if address is used with urltable to urltable_ports
if params['type'] in ['urltable', 'urltable_ports']:
self.module.warn('Use of "address" with {type} is depracated, please use "url" instead'.format(type=params['type']))
# pfSense GUI rule
for detail in details:
if detail.startswith('|') or detail.endswith('|'):
self.module.fail_json(msg='Vertical bars (|) at start or end of descriptions not allowed')
================================================
FILE: plugins/module_utils/arg_route.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# module_base _params_to_obj currently does not call thse functions if
# params[name] is None.
def p2o_cert(self, name, params, obj):
obj[name] = self.pfsense.get_certref(params[name])
def p2o_interface(self, name, params, obj):
obj[name] = self.pfsense.parse_interface(params[name], with_virtual=True)
def p2o_interface_with_gwgroup(self, name, params, obj):
obj[name] = self.pfsense.parse_interface(params[name], with_virtual=False, with_gwgroup=True)
def p2o_interface_without_virtual(self, name, params, obj):
obj[name] = self.pfsense.parse_interface(params[name], with_virtual=False)
def p2o_port(self, name, params, obj):
obj[name] = self.pfsense.parse_port(params[name], with_virtual=True)
def p2o_strip(self, name, params, obj):
if params[name] is not None:
obj[name] = params[name].strip()
================================================
FILE: plugins/module_utils/arg_validate.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2025, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# TODO - allow specifying type of cert, e.g. HTTPS
def validate_cert(self, cert):
if self.pfsense.get_certref(cert) is None:
raise ValueError(f"Unknown certificate '{cert}'.")
================================================
FILE: plugins/module_utils/default_gateway.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2023, Nicolas Zagulajew
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
DEFAULT_GATEWAY_ARGUMENT_SPEC = dict(
gateway=dict(type='str'),
ipprotocol=dict(default='inet', choices=['inet', 'inet6']),
)
class PFSenseDefaultGatewayModule(PFSenseModuleBase):
""" module managing pfsense default gateways """
@staticmethod
def get_argument_spec():
""" return argument spec """
return DEFAULT_GATEWAY_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseDefaultGatewayModule, self).__init__(module, pfsense, root='gateways')
self.name = "pfsense_default_gateway"
self.target_elt = self.root_elt
self.interface_elt = None
self.read_only = False
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params
gateway required, str
ipprotocol default : inet, choice inet/inet6
"""
params = self.params
obj = dict()
# Modification
if params["gateway"]:
my_defaultgw = self._gw2machine(params['gateway'])
if params['ipprotocol'] == "inet":
obj['defaultgw4'] = my_defaultgw
self.result["defaultgw4"] = params["gateway"]
elif params['ipprotocol'] == "inet6":
obj['defaultgw6'] = my_defaultgw
self.result["defaultgw6"] = params["gateway"]
else:
self.module.fail_json(msg='Please specify a valid ipprotocol (inet/inet6)')
return obj
def _validate_params(self):
""" do some extra checks on input parameters
gateway required, str
ipprotocol default : inet, choice inet/inet6
"""
params = self.params
gateway_list = ["none", "automatic"] + [gw["Name"] for gw in self.pfsense.find_active_gateways()]
# get list of current default gateways and append gateway_groups to list
for elt in self.root_elt:
if elt.tag in ["gateway_group"]:
gateway_list.append(elt.find("name").text)
elif elt.tag == "defaultgw4":
self.result["defaultgw4"] = self._gw2human(elt.text)
elif elt.tag == "defaultgw6":
self.result["defaultgw6"] = self._gw2human(elt.text)
if params["gateway"]:
if str(params["gateway"]) not in gateway_list:
self.module.fail_json(msg="Unknown gateway %s : %s" % (params["gateway"], gateway_list))
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
if self.params["ipprotocol"] == "inet":
return self.pfsense.new_element('defaultgw4')
elif self.params["ipprotocol"] == "inet6":
return self.pfsense.new_element('defaultgw6')
##############################
# Utilities
#
@staticmethod
def _gw2machine(gateway):
"""
Translates special gateway to machine-readable
"-" means none
"" means automatic
"""
if gateway is not None:
if gateway.lower() == "automatic":
return ""
elif gateway.lower() == "none":
return "-"
return gateway
@staticmethod
def _gw2human(gateway):
"""
Translates special gateway as human-readable
"-" means none
"" means automatic
"""
if gateway is None:
return "automatic"
elif gateway == "-":
return "none"
else:
return gateway
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return []
##############################
def run(self, params):
""" process input params to add/update/delete """
self.params = params
self._check_deprecated_params()
self._check_onward_params()
self._validate_params()
self.obj = self._params_to_obj()
if params["gateway"]:
self._add()
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''
require_once("filter.inc");
$retval = 0;
$retval |= system_routing_configure();
$retval |= system_resolvconf_generate();
$retval |= filter_configure();
/* reconfigure our gateway monitor */
setup_gateways_monitor();
/* Dynamic DNS on gw groups may have changed */
send_event("service reload dyndnsall");
if ($retval == 0) clear_subsystem_dirty('staticroutes');
''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return ""
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if self.params["ipprotocol"] == "inet":
values += self.format_updated_cli_field(self.obj, before, 'defaultgw4', add_comma=values)
elif self.params["ipprotocol"] == "inet6":
values += self.format_updated_cli_field(self.obj, before, 'defaultgw6', add_comma=values)
return values
================================================
FILE: plugins/module_utils/dhcp_server.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2024, David Rosado
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ipaddress import ip_address, ip_network
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
DHCPSERVER_ARGUMENT_SPEC = dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
interface=dict(required=True, type='str'),
enable=dict(type='bool', default=True),
range_from=dict(type='str'),
range_to=dict(type='str'),
failover_peerip=dict(type='str'),
defaultleasetime=dict(type='int'),
maxleasetime=dict(type='int'),
netmask=dict(type='str'),
gateway=dict(type='str'),
domain=dict(type='str'),
domainsearchlist=dict(type='str'),
ddnsdomain=dict(type='str'),
ddnsdomainprimary=dict(type='str'),
ddnsdomainkeyname=dict(type='str', no_log=False),
ddnsdomainkeyalgorithm=dict(type='str', default='hmac-md5', choices=['hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512']),
ddnsdomainkey=dict(type='str', no_log=True),
mac_allow=dict(type='list', elements='str'),
mac_deny=dict(type='list', elements='str'),
ddnsclientupdates=dict(type='str', default='allow', choices=['allow', 'deny', 'ignore']),
tftp=dict(type='str'),
ldap=dict(type='str'),
nextserver=dict(type='str'),
filename=dict(type='str'),
filename32=dict(type='str'),
filename64=dict(type='str'),
rootpath=dict(type='str'),
numberoptions=dict(type='str'),
winsserver=dict(type='list', elements='str'),
dnsserver=dict(type='list', elements='str'),
ntpserver=dict(type='list', elements='str'),
ignorebootp=dict(type='bool'),
denyunknown=dict(type='str', choices=['disabled', 'enabled', 'class']),
nonak=dict(type='bool'),
ignoreclientuids=dict(type='bool'),
staticarp=dict(type='bool'),
dhcpinlocaltime=dict(type='bool'),
statsgraph=dict(type='bool'),
disablepingcheck=dict(type='bool'),
)
class PFSenseDHCPServerModule(PFSenseModuleBase):
""" module managing pfsense DHCP server settings """
@staticmethod
def get_argument_spec():
"""return argument spec"""
return DHCPSERVER_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseDHCPServerModule, self).__init__(module, pfsense)
self.name = "pfsense_dhcp_server"
self.obj = dict()
self.root_elt = self.pfsense.get_element('dhcpd', create_node=True)
self.target = None
self.network = None
##############################
# params processing
#
def _get_logical_interface(self, interface):
"""Find the logical interface name"""
for iface in self.pfsense.interfaces:
# Check if it matches the logical name (e.g., 'lan', 'wan', 'opt1')
if iface.tag.lower() == interface.lower():
return iface.tag
# Check if it matches the physical interface name (e.g., 'em0', 'igb0')
if_elt = iface.find('if')
if if_elt is not None and if_elt.text.strip().lower() == interface.lower():
return iface.tag
# Check if it matches the interface description
descr_elt = iface.find('descr')
if descr_elt is not None and descr_elt.text.strip().lower() == interface.lower():
return iface.tag
return None
def _is_valid_netif(self, netif):
for nic in self.pfsense.interfaces:
if nic.tag == netif:
if nic.find('ipaddr') is not None:
ipaddr = nic.find('ipaddr').text
if ipaddr is not None:
if nic.find('subnet') is not None:
subnet = int(nic.find('subnet').text)
if subnet < 31:
self.network = ip_network(u'{0}/{1}'.format(ipaddr, subnet), strict=False)
return True
return False
def _is_valid_macaddr(self, macaddr):
return bool(re.fullmatch(r'(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}', macaddr, re.I))
def _params_to_obj(self):
"""return a dict from module params"""
params = self.params
obj = dict()
self.obj = obj
if params['state'] == 'present':
self._get_ansible_param(obj, 'range', force_value={}, force=True)
self._get_ansible_param(obj['range'], 'range_from', fname='from', force=True)
self._get_ansible_param(obj['range'], 'range_to', fname='to', force=True)
# Forced options
for option in ['failover_peerip', 'defaultleasetime', 'maxleasetime',
'netmask', 'gateway', 'domain', 'domainsearchlist',
'ddnsdomain', 'ddnsdomainprimary', 'ddnsdomainkeyname',
'ddnsdomainkeyalgorithm', 'ddnsdomainkey', 'mac_allow',
'mac_deny', 'ddnsclientupdates', 'tftp', 'ldap',
'nextserver', 'filename', 'filename32', 'filename64',
'rootpath', 'numberoptions']:
self._get_ansible_param(obj, option, force=True)
for option in ['mac_allow', 'mac_deny']:
if params[option] is None:
params[option] = ""
self._get_ansible_param(obj, ','.join(params[option]))
# Non-forced options
for option in ['winsserver', 'dnsserver', 'ntpserver']:
self._get_ansible_param(obj, option)
for option in ['enable', 'ignorebootp', 'nonak', 'ignoreclientuids',
'staticarp', 'disablepingcheck']:
self._get_ansible_param_bool(obj, option, value='')
for option in ['dhcpinlocaltime', 'statsgraph']:
self._get_ansible_param_bool(obj, option, value='yes')
self._get_ansible_param(obj, 'denyunknown')
if obj.get('denyunknown') == 'disabled':
del obj['denyunknown']
# Defaulted options
self._get_ansible_param(obj, 'ddnsdomainkeyalgorithm', force_value='hmac-md5', force=True)
return obj
def _validate_params(self):
"""do some extra checks on input parameters"""
params = self.params
self.target = self._get_logical_interface(params['interface'])
if self.target is None or self.target.lower() == "wan":
self.module.fail_json(msg=f"The specified interface {params['interface']} is not a valid logical interface or cannot be mapped to one")
if not self._is_valid_netif(self.target):
self.module.fail_json(msg=f"The specified interface {params['interface']} is not a valid logical interface")
if params['state'] == 'present' and params['enable']:
if params.get('range_from') is None or params.get('range_to') is None:
self.module.fail_json(msg=f"The specified interface {params['interface']}'requires an IP range")
if not self.pfsense.is_ipv4_address(params['range_from']):
self.module.fail_json(msg="The 'range_from' address is not a valid IPv4 address")
if not self.pfsense.is_ipv4_address(params['range_to']):
self.module.fail_json(msg="The 'range_to' address is not a valid IPv4 address")
if not ip_address(params['range_from']) in self.network or not ip_address(params['range_to']) in self.network:
self.module.fail_json(msg=f"The IP address must lie in the {params['interface']} subnet")
if ip_address(params['range_from']) >= ip_address(params['range_to']):
self.module.fail_json(msg=f"The interface {params['interface']} must have a valid IP range pool")
if params.get('gateway'):
if not self.pfsense.is_ipv4_address(params['gateway']):
self.module.fail_json(msg="The 'gateway' is not a valid IPv4 address")
if params.get('mac_allow'):
for macaddr in params["mac_allow"]:
is_valid = self._is_valid_macaddr(macaddr)
if not is_valid:
self.module.fail_json(msg=f"The MAC address {macaddr} is invalid")
if params.get('mac_deny'):
for macaddr in params["mac_deny"]:
is_valid = self._is_valid_macaddr(macaddr)
if not is_valid:
self.module.fail_json(msg=f"The MAC address {macaddr} is invalid")
if params.get('denyunknown') not in [None, 'disabled', 'enabled', 'class']:
self.module.fail_json(msg=f"The option {params['denyunknown']} is invalid, use 'disabled', 'enabled' or 'class'")
##############################
# XML processing
#
def _get_params_to_remove(self):
"""returns the list of params to remove if they are not set"""
params = ['enable', 'ignorebootp', 'nonak', 'ignoreclientuids', 'staticarp', 'disablepingcheck', 'dhcpinlocaltime', 'statsgraph']
if self.params.get('denyunknown') == 'disabled':
params.append('denyunknown')
return params
def _create_target(self):
"""create the XML target_elt"""
return self.pfsense.new_element(self.target)
def _find_target(self):
"""find the XML target_elt"""
return self.pfsense.get_element(self.target, root_elt=self.root_elt)
##############################
# Logging
#
def _get_obj_name(self):
"""return obj's name"""
return f"'{self.target}'"
def _log_fields(self, before=None):
"""generate pseudo-CLI command fields parameters to create an obj"""
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'enable', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.obj["range"], 'from', fname="range_from")
values += self.format_cli_field(self.obj["range"], 'to', fname="range_to")
values += self.format_cli_field(self.obj, 'failover_peerip')
values += self.format_cli_field(self.obj, 'defaultleasetime')
values += self.format_cli_field(self.obj, 'maxleasetime')
values += self.format_cli_field(self.obj, 'netmask')
values += self.format_cli_field(self.obj, 'gateway')
values += self.format_cli_field(self.obj, 'domain')
values += self.format_cli_field(self.obj, 'domainsearchlist')
values += self.format_cli_field(self.obj, 'ddnsdomain')
values += self.format_cli_field(self.obj, 'ddnsdomainprimary')
values += self.format_cli_field(self.obj, 'ddnsdomainkeyname')
values += self.format_cli_field(self.obj, 'ddnsdomainkeyalgorithm')
values += self.format_cli_field(self.obj, 'ddnsdomainkey')
values += self.format_cli_field(self.obj, 'mac_allow')
values += self.format_cli_field(self.obj, 'mac_deny')
values += self.format_cli_field(self.obj, 'ddnsclientupdates')
values += self.format_cli_field(self.obj, 'tftp')
values += self.format_cli_field(self.obj, 'ldap')
values += self.format_cli_field(self.obj, 'nextserver')
values += self.format_cli_field(self.obj, 'filename')
values += self.format_cli_field(self.obj, 'filename32')
values += self.format_cli_field(self.obj, 'filename64')
values += self.format_cli_field(self.obj, 'rootpath')
values += self.format_cli_field(self.obj, 'numberoptions')
values += self.format_cli_field(self.obj, 'denyunknown')
else:
values += self.format_updated_cli_field(self.obj, before, 'enable', fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj["range"], before["range"], 'from', fname="range_from")
values += self.format_updated_cli_field(self.obj["range"], before["range"], 'to', fname="range_to")
values += self.format_updated_cli_field(self.obj, before, 'failover_peerip')
values += self.format_updated_cli_field(self.obj, before, 'defaultleasetime')
values += self.format_updated_cli_field(self.obj, before, 'maxleasetime')
values += self.format_updated_cli_field(self.obj, before, 'netmask')
values += self.format_updated_cli_field(self.obj, before, 'gateway')
values += self.format_updated_cli_field(self.obj, before, 'domain')
values += self.format_updated_cli_field(self.obj, before, 'domainsearchlist')
values += self.format_updated_cli_field(self.obj, before, 'ddnsdomain')
values += self.format_updated_cli_field(self.obj, before, 'ddnsdomainprimary')
values += self.format_updated_cli_field(self.obj, before, 'ddnsdomainkeyname')
values += self.format_updated_cli_field(self.obj, before, 'ddnsdomainkeyalgorithm')
values += self.format_updated_cli_field(self.obj, before, 'ddnsdomainkey')
values += self.format_updated_cli_field(self.obj, before, 'mac_allow')
values += self.format_updated_cli_field(self.obj, before, 'mac_deny')
values += self.format_updated_cli_field(self.obj, before, 'ddnsclientupdates')
values += self.format_updated_cli_field(self.obj, before, 'tftp')
values += self.format_updated_cli_field(self.obj, before, 'ldap')
values += self.format_updated_cli_field(self.obj, before, 'nextserver')
values += self.format_updated_cli_field(self.obj, before, 'filename')
values += self.format_updated_cli_field(self.obj, before, 'filename32')
values += self.format_updated_cli_field(self.obj, before, 'filename64')
values += self.format_updated_cli_field(self.obj, before, 'rootpath')
values += self.format_updated_cli_field(self.obj, before, 'numberoptions')
values += self.format_updated_cli_field(self.obj, before, 'denyunknown')
return values
##############################
# run
#
def _update(self):
"""make the target pfsense reload"""
return self.pfsense.phpshell("""
require_once("util.inc");
require_once("services.inc");
services_dhcpd_configure();
""")
def _pre_remove_target_elt(self):
self.diff['after'] = {}
if self.target_elt is not None:
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
else:
self.diff['before'] = {}
================================================
FILE: plugins/module_utils/gateway.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from ipaddress import ip_address, ip_network
GATEWAY_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True, type='str'),
interface=dict(required=False, type='str'),
ipprotocol=dict(default='inet', choices=['inet', 'inet6']),
gateway=dict(required=False, type='str'),
descr=dict(default='', type='str'),
disabled=dict(default=False, type='bool'),
monitor=dict(required=False, type='str'),
monitor_disable=dict(default=False, type='bool'),
action_disable=dict(default=False, type='bool'),
force_down=dict(default=False, type='bool'),
weight=dict(default=1, required=False, type='int'),
losslow=dict(required=False, type='int'),
losshigh=dict(required=False, type='int'),
nonlocalgateway=dict(default=False, type='bool'),
)
GATEWAY_REQUIRED_IF = [
["state", "present", ["interface", "gateway", "weight"]],
]
class PFSenseGatewayModule(PFSenseModuleBase):
""" module managing pfsense gateways """
@staticmethod
def get_argument_spec():
""" return argument spec """
return GATEWAY_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseGatewayModule, self).__init__(module, pfsense, root='gateways', create_root=True, node='gateway_item', key='name')
self.name = "pfsense_gateway"
self.interface_elt = None
self.dynamic = False
##############################
# params processing
#
def _check_gateway_groups(self):
""" check if gateway is in use in gateway groups """
for elt in self.root_elt:
if (elt.tag == 'defaultgw4' or elt.tag == 'defaultgw6') and (elt.text is not None and elt.text == self.params['name']):
return False
if elt.tag != 'gateway_group':
continue
items = elt.findall('.//item')
for item in items:
fields = item.text.split('|')
if fields and fields[0] == self.params['name']:
return False
return True
def _check_routes(self):
""" check if gateway is in use in static routes """
routes = self.pfsense.get_element('staticroutes')
if routes is None:
return True
for elt in routes:
if elt.find('gateway').text == self.params['name']:
return False
return True
def _check_subnet(self):
""" check if addr lies into interface subnets """
def _check_vips():
virtualips = self.pfsense.get_element('virtualip')
if virtualips is None:
return False
for vip_elt in virtualips:
if vip_elt.find('interface').text != self.interface_elt.tag or vip_elt.find('mode').text != 'other' or vip_elt.find('type').text != 'network':
continue
subnet = ip_network(u'{0}/{1}'.format(vip_elt.find('subnet').text, vip_elt.find('subnet_bits').text), strict=False)
if addr in subnet:
return True
return False
if self.params['ipprotocol'] == 'inet':
inet_type = 'IPv4'
f1_elt = self.interface_elt.find('ipaddr')
f2_elt = self.interface_elt.find('subnet')
else:
inet_type = 'IPv6'
f1_elt = self.interface_elt.find('ipaddrv6')
f2_elt = self.interface_elt.find('subnetv6')
if f1_elt is None or f1_elt.text is None or f2_elt is None or f2_elt.text is None:
self.module.fail_json(msg='Cannot add {0} Gateway Address because no {0} address could be found on the interface.'.format(inet_type))
try:
if self.params['nonlocalgateway']:
return
addr = ip_address(u'{0}'.format(self.params['gateway']))
subnet = ip_network(u'{0}/{1}'.format(f1_elt.text, f2_elt.text), strict=False)
if addr in subnet or _check_vips():
return
self.module.fail_json(msg="The gateway address {0} does not lie within one of the chosen interface's subnets.".format(self.params['gateway']))
except ValueError:
self.module.fail_json(msg='Cannot add {0} Gateway Address because no {0} address could be found on the interface.'.format(inet_type))
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['name'] = params['name']
if params['state'] == 'present':
obj['interface'] = self.pfsense.parse_interface(params['interface'])
self.interface_elt = self.pfsense.get_interface_elt(obj['interface'])
self._get_ansible_param(obj, 'ipprotocol')
self._get_ansible_param(obj, 'gateway')
self._get_ansible_param(obj, 'descr')
self._get_ansible_param(obj, 'monitor')
self._get_ansible_param(obj, 'weight')
self._get_ansible_param(obj, 'losslow')
self._get_ansible_param(obj, 'losshigh')
self._get_ansible_param_bool(obj, 'disabled', value=None)
self._get_ansible_param_bool(obj, 'monitor_disable', value=None)
self._get_ansible_param_bool(obj, 'action_disable', value=None)
self._get_ansible_param_bool(obj, 'force_down', value=None)
self._get_ansible_param_bool(obj, 'nonlocalgateway', value=None)
if not self.dynamic:
self._check_subnet()
elif self.target_elt.find('interface').text != obj['interface']:
self.module.fail_json(msg="The gateway use 'dynamic' as a target. You can not change the interface")
elif self.target_elt.find('ipprotocol').text != params['ipprotocol']:
self.module.fail_json(msg="The gateway use 'dynamic' as a target. You can not change ipprotocol")
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
self.target_elt = self.pfsense.find_gateway_elt(params['name'], dhcp=True, vti=True)
if self.target_elt is not None and self.target_elt.find('gateway').text == 'dynamic':
self.dynamic = True
if params['state'] == 'present':
# check weight
if params.get('weight') is not None and (params['weight'] < 1 or params['weight'] > 30):
self.module.fail_json(msg='weight must be between 1 and 30')
# check loss thresholds
for param in ['losslow', 'losshigh']:
if params.get(param) is not None and (params[param] < 1 or params[param] > 100):
self.module.fail_json(msg=f'{param} must be between 1 and 100')
if self.dynamic:
if params['gateway'] != 'dynamic':
self.module.fail_json(msg="The gateway use 'dynamic' as a target. This is read-only, so you must set gateway as dynamic too")
else:
self.pfsense.check_ip_address(params['gateway'], params['ipprotocol'], 'gateway', fail_ifnotip=True)
if params.get('monitor') is not None and params['monitor'] != '':
self.pfsense.check_ip_address(params['monitor'], params['ipprotocol'], 'monitor', fail_ifnotip=True)
self.pfsense.check_name(params['name'], 'gateway')
else:
if self.dynamic:
self.module.fail_json(msg="The gateway use 'dynamic' as a target. You can not delete it")
if not self._check_gateway_groups() or not self._check_routes():
self.module.fail_json(msg="The gateway is still in use. You can not delete it")
##############################
# XML processing
#
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['disabled', 'monitor', 'monitor_disable', 'action_disable', 'force_down', 'nonlocalgateway']
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''
require_once("filter.inc");
$retval = 0;
$retval |= system_routing_configure();
$retval |= system_resolvconf_generate();
$retval |= filter_configure();
/* reconfigure our gateway monitor */
setup_gateways_monitor();
/* Dynamic DNS on gw groups may have changed */
send_event("service reload dyndnsall");
if ($retval == 0) clear_subsystem_dirty('staticroutes');
''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['name'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'interface')
values += self.format_cli_field(self.obj, 'ipprotocol', default='inet')
values += self.format_cli_field(self.obj, 'gateway')
values += self.format_cli_field(self.obj, 'descr', default='')
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.obj, 'monitor')
values += self.format_cli_field(self.params, 'monitor_disable', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'action_disable', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'force_down', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.obj, 'weight', default='1')
values += self.format_cli_field(self.obj, 'losslow')
values += self.format_cli_field(self.obj, 'losshigh')
values += self.format_cli_field(self.params, 'nonlocalgateway', fvalue=self.fvalue_bool, default=False)
else:
fbefore = dict()
fbefore['interface'] = self.pfsense.get_interface_display_name(before['interface'])
values += self.format_updated_cli_field(self.params, fbefore, 'interface', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ipprotocol', default='inet', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'gateway', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'descr', default='', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor_disable', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'action_disable', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'force_down', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'weight', default='1', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'losslow', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'losshigh', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'nonlocalgateway', fvalue=self.fvalue_bool)
return values
================================================
FILE: plugins/module_utils/haproxy_backend.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
HAPROXY_BACKEND_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True, type='str'),
balance=dict(default='none', choices=['none', 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri']),
balance_urilen=dict(required=False, type='int'),
balance_uridepth=dict(required=False, type='int'),
balance_uriwhole=dict(required=False, type='bool'),
connection_timeout=dict(required=False, type='int'),
server_timeout=dict(required=False, type='int'),
check_type=dict(default='none', choices=['none', 'Basic', 'HTTP', 'Agent', 'LDAP', 'MySQL', 'PostgreSQL', 'Redis', 'SMTP', 'ESMTP', 'SSL']),
check_frequency=dict(required=False, type='int'),
retries=dict(required=False, type='int'),
log_checks=dict(required=False, type='bool'),
httpcheck_method=dict(required=False, choices=['OPTIONS', 'HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE']),
monitor_uri=dict(required=False, type='str'),
monitor_httpversion=dict(required=False, type='str'),
monitor_username=dict(required=False, type='str'),
monitor_domain=dict(required=False, type='str'),
)
class PFSenseHaproxyBackendModule(PFSenseModuleBase):
""" module managing pfsense haproxy backends """
@staticmethod
def get_argument_spec():
""" return argument spec """
return HAPROXY_BACKEND_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseHaproxyBackendModule, self).__init__(module, pfsense)
self.name = "pfsense_haproxy_backend"
self.obj = dict()
pkgs_elt = self.pfsense.get_element('installedpackages')
self.haproxy = pkgs_elt.find('haproxy') if pkgs_elt is not None else None
self.root_elt = self.haproxy.find('ha_pools') if self.haproxy is not None else None
if self.root_elt is None:
self.module.fail_json(msg='Unable to find backends XML configuration entry. Are you sure haproxy is installed ?')
##############################
# params processing
#
def _params_to_obj(self):
""" return a backend dict from module params """
obj = dict()
obj['name'] = self.params['name']
if self.params['state'] == 'present':
self._get_ansible_param(obj, 'balance', force=True)
if obj['balance'] == 'none':
obj['balance'] = None
self._get_ansible_param(obj, 'balance_urilen', force=True)
self._get_ansible_param(obj, 'balance_uridepth', force=True)
self._get_ansible_param(obj, 'connection_timeout', force=True)
self._get_ansible_param(obj, 'server_timeout', force=True)
self._get_ansible_param(obj, 'check_type', force=True)
self._get_ansible_param(obj, 'check_frequency', fname='checkinter', force=True)
self._get_ansible_param(obj, 'retries', force=True)
self._get_ansible_param_bool(obj, 'log_checks', fname='log-health-checks', force=True)
self._get_ansible_param_bool(obj, 'balance_uriwhole', force=True)
self._get_ansible_param(obj, 'httpcheck_method', force=True)
self._get_ansible_param(obj, 'monitor_uri', force=True)
self._get_ansible_param(obj, 'monitor_httpversion', force=True)
self._get_ansible_param(obj, 'monitor_username', force=True)
self._get_ansible_param(obj, 'monitor_domain', force=True)
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
# check name
if re.search(r'[^a-zA-Z0-9\.\-_]', self.params['name']) is not None:
self.module.fail_json(msg="The field 'name' contains invalid characters.")
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
server_elt = self.pfsense.new_element('item')
self.obj['id'] = self._get_next_id()
return server_elt
def _find_target(self):
""" find the XML target_elt """
for item_elt in self.root_elt:
if item_elt.tag != 'item':
continue
name_elt = item_elt.find('name')
if name_elt is not None and name_elt.text == self.obj['name']:
return item_elt
return None
def _get_next_id(self):
""" get next free haproxy id """
max_id = 99
id_elts = self.haproxy.findall('.//id')
for id_elt in id_elts:
if id_elt.text is None:
continue
ha_id = int(id_elt.text)
if ha_id > max_id:
max_id = ha_id
return str(max_id + 1)
##############################
# run
#
def _update(self):
""" make the target pfsense reload haproxy """
return self.pfsense.phpshell('''require_once("haproxy/haproxy.inc");
$result = haproxy_check_and_run($savemsg, true); if ($result) unlink_if_exists($d_haproxyconfdirty_path);''')
##############################
# Logging
#
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'balance')
values += self.format_cli_field(self.params, 'balance_urilen')
values += self.format_cli_field(self.params, 'balance_uridepth')
values += self.format_cli_field(self.params, 'balance_uriwhole', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'connection_timeout')
values += self.format_cli_field(self.params, 'server_timeout')
values += self.format_cli_field(self.params, 'check_type')
values += self.format_cli_field(self.params, 'check_frequency')
values += self.format_cli_field(self.params, 'retries')
values += self.format_cli_field(self.params, 'log_checks', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'httpcheck_method')
values += self.format_cli_field(self.params, 'monitor_uri')
values += self.format_cli_field(self.params, 'monitor_httpversion')
values += self.format_cli_field(self.params, 'monitor_username')
values += self.format_cli_field(self.params, 'monitor_domain')
else:
for param in ['balance', 'log-health-checks', 'balance_uriwhole']:
if param in before and before[param] == '':
before[param] = None
values += self.format_updated_cli_field(self.obj, before, 'balance', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'balance_urilen', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'balance_uridepth', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'balance_uriwhole', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'connection_timeout', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'server_timeout', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'check_type', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'checkinter', add_comma=(values), fname='check_frequency')
values += self.format_updated_cli_field(self.obj, before, 'retries', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'log-health-checks', add_comma=(values), fname='log_checks', fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'httpcheck_method', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor_uri', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor_httpversion', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor_username', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'monitor_domain', add_comma=(values))
return values
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['name'])
================================================
FILE: plugins/module_utils/haproxy_backend_server.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
HAPROXY_BACKEND_SERVER_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
backend=dict(required=True, type='str'),
name=dict(required=True, type='str'),
mode=dict(default='active', choices=['active', 'backup', 'disabled', 'inactive']),
forwardto=dict(required=False, type='str'),
address=dict(required=False, type='str'),
port=dict(required=False, type='int'),
ssl=dict(required=False, type='bool'),
checkssl=dict(required=False, type='bool'),
weight=dict(required=False, type='int'),
sslserververify=dict(required=False, type='bool'),
verifyhost=dict(required=False, type='str'),
ca=dict(required=False, type='str'),
crl=dict(required=False, type='str'),
clientcert=dict(required=False, type='str'),
cookie=dict(required=False, type='str'),
maxconn=dict(required=False, type='int'),
advanced=dict(required=False, type='str'),
istemplate=dict(required=False, type='str'),
)
HAPROXY_BACKEND_SERVER_MUTUALLY_EXCLUSIVE = [
['forwardto', 'address'],
['forwardto', 'port'],
]
class PFSenseHaproxyBackendServerModule(PFSenseModuleBase):
""" module managing pfsense haproxy backend servers """
@staticmethod
def get_argument_spec():
""" return argument spec """
return HAPROXY_BACKEND_SERVER_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseHaproxyBackendServerModule, self).__init__(module, pfsense)
self.name = "pfsense_haproxy_backend_server"
self.root_elt = None
self.obj = dict()
pkgs_elt = self.pfsense.get_element('installedpackages')
self.haproxy = pkgs_elt.find('haproxy') if pkgs_elt is not None else None
self.backends = self.haproxy.find('ha_pools') if self.haproxy is not None else None
if self.backends is None:
self.module.fail_json(msg='Unable to find backends XML configuration entry. Are you sure haproxy is installed ?')
self.backend = None
self.servers = None
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['name'] = params['name']
if params['state'] == 'present':
obj['status'] = params['mode']
for param in ['ssl', 'checkssl', 'sslserververify']:
self._get_ansible_param_bool(obj, param)
self._get_ansible_param(obj, 'forwardto')
self._get_ansible_param(obj, 'address')
self._get_ansible_param(obj, 'port')
self._get_ansible_param(obj, 'weight')
self._get_ansible_param(obj, 'verifyhost')
if 'ca' in params and params['ca'] is not None and params['ca'] != '':
ca_elt = self.pfsense.find_ca_elt(params['ca'])
if ca_elt is None:
self.module.fail_json(msg='%s is not a valid certificate authority' % (params['ca']))
obj['ssl-server-ca'] = ca_elt.find('refid').text
if 'crl' in params and params['crl'] is not None and params['crl'] != '':
crl_elt = self.pfsense.find_crl_elt(params['crl'])
if crl_elt is None:
self.module.fail_json(msg='%s is not a valid certificate revocation list' % (params['crl']))
obj['ssl-server-crl'] = crl_elt.find('refid').text
if 'clientcert' in params and params['clientcert'] is not None and params['clientcert'] != '':
cert = self.pfsense.find_cert_elt(params['clientcert'])
if cert is None:
self.module.fail_json(msg='%s is not a valid certificate' % (params['clientcert']))
obj['ssl-server-clientcert'] = cert.find('refid').text
self._get_ansible_param(obj, 'cookie')
self._get_ansible_param(obj, 'maxconn')
self._get_ansible_param(obj, 'advanced')
self._get_ansible_param(obj, 'istemplate')
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
if re.search(r'[^a-zA-Z0-9\.\-_]', params['name']) is not None:
self.module.fail_json(msg="The field 'name' contains invalid characters")
if len(params['name']) < 2:
self.module.fail_json(msg="The field 'name' must be at least 2 characters")
self.backend = self._find_backend(params['backend'])
if self.backend is None:
self.module.fail_json(msg="The backend named '{0}' does not exist".format(params['backend']))
self.root_elt = self.backend.find('ha_servers')
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('ha_servers')
self.backend.append(self.root_elt)
if 'forwardto' in params and params['forwardto'] is not None:
frontend_elt = None
frontends = self.haproxy.find('ha_backends')
for item_elt in frontends:
if item_elt.tag != 'item':
continue
name_elt = item_elt.find('name')
if name_elt is not None and name_elt.text == params['forwardto']:
frontend_elt = item_elt
break
if frontend_elt is None:
self.module.fail_json(msg="The frontend named '{0}' does not exist".format(params['forwardto']))
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
server_elt = self.pfsense.new_element('item')
self.obj['id'] = self._get_next_id()
return server_elt
def _find_backend(self, name):
""" return the target backend_elt if found """
for item_elt in self.backends:
if item_elt.tag != 'item':
continue
name_elt = item_elt.find('name')
if name_elt is not None and name_elt.text == name:
return item_elt
return None
def _find_target(self):
""" find the XML target_elt """
for item_elt in self.root_elt:
if item_elt.tag != 'item':
continue
name_elt = item_elt.find('name')
if name_elt is not None and name_elt.text == self.obj['name']:
return item_elt
return None
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
params = ['ssl', 'checkssl', 'sslserververify', 'forwardto', 'address', 'port', 'weight', 'istemplate', 'verifyhost']
params += ['ssl-server-crl', 'ssl-server-ca', 'ssl-server-clientcert', 'cookie', 'maxconn', 'advanced']
return params
def _get_next_id(self):
""" get next free haproxy id """
max_id = 99
id_elts = self.haproxy.findall('.//id')
for id_elt in id_elts:
if id_elt.text is None:
continue
ha_id = int(id_elt.text)
if ha_id > max_id:
max_id = ha_id
return str(max_id + 1)
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''require_once("haproxy/haproxy.inc");
$result = haproxy_check_and_run($savemsg, true); if ($result) unlink_if_exists($d_haproxyconfdirty_path);''')
##############################
# Logging
#
def _get_ref_names(self, before):
""" get cert and ca names """
if 'ssl-server-ca' in before and before['ssl-server-ca'] is not None and before['ssl-server-ca'] != '':
elt = self.pfsense.find_ca_elt(before['ssl-server-ca'], 'refid')
if elt is not None:
before['ca'] = elt.find('descr').text
if 'ca' not in before:
before['ca'] = None
if 'ssl-server-crl' in before and before['ssl-server-crl'] is not None and before['ssl-server-crl'] != '':
elt = self.pfsense.find_crl_elt(before['ssl-server-crl'], 'refid')
if elt is not None:
before['crl'] = elt.find('descr').text
if 'crl' not in before:
before['crl'] = None
if 'ssl-server-clientcert' in before and before['ssl-server-clientcert'] is not None and before['ssl-server-clientcert'] != '':
elt = self.pfsense.find_cert_elt(before['ssl-server-clientcert'], 'refid')
if elt is not None:
before['clientcert'] = elt.find('descr').text
if 'clientcert' not in before:
before['clientcert'] = None
def _get_obj_name(self):
""" return obj's name """
return "'{0}' on '{1}'".format(self.obj['name'], self.params['backend'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'mode', fname='status')
values += self.format_cli_field(self.params, 'forwardto')
values += self.format_cli_field(self.params, 'address')
values += self.format_cli_field(self.params, 'port')
values += self.format_cli_field(self.params, 'ssl', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'checkssl', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'weight')
values += self.format_cli_field(self.params, 'sslserververify', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'ca')
values += self.format_cli_field(self.params, 'crl')
values += self.format_cli_field(self.params, 'clientcert')
values += self.format_cli_field(self.params, 'cookie')
values += self.format_cli_field(self.params, 'maxconn')
values += self.format_cli_field(self.params, 'advanced')
values += self.format_cli_field(self.params, 'istemplate')
else:
for param in ['ssl', 'checkssl', 'sslserververify']:
if param in before and before[param] == '':
before[param] = None
self._get_ref_names(before)
values += self.format_updated_cli_field(self.obj, before, 'status', add_comma=(values), fname='mode')
values += self.format_updated_cli_field(self.obj, before, 'forwardto', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'address', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'port', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ssl', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'checkssl', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'weight', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'sslserververify', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'verifyhost', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'ca', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'crl', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'clientcert', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'cookie', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'maxconn', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'advanced', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'istemplate', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/interface.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2021-2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule
try:
from ipaddress import ip_network
except ImportError:
from ansible_collections.community.general.plugins.module_utils.compat.ipaddress import ip_network
INTERFACE_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
descr=dict(required=True, type='str'),
interface=dict(required=False, type='str'),
interface_descr=dict(required=False, type='str'),
enable=dict(default=False, type='bool'),
ipv4_type=dict(default='none', choices=['none', 'static', 'dhcp']),
ipv6_type=dict(default='none', choices=['none', 'static', 'slaac']),
mac=dict(required=False, type='str'),
mtu=dict(required=False, type='int'),
mss=dict(required=False, type='int'),
speed_duplex=dict(default='autoselect', required=False, type='str'),
ipv4_address=dict(required=False, type='str'),
ipv4_prefixlen=dict(default=24, required=False, type='int'),
ipv4_gateway=dict(required=False, type='str'),
ipv6_address=dict(required=False, type='str'),
ipv6_prefixlen=dict(default=128, required=False, type='int'),
ipv6_gateway=dict(required=False, type='str'),
blockpriv=dict(required=False, type='bool'),
blockbogons=dict(required=False, type='bool'),
slaacusev4iface=dict(required=False, type='bool'),
)
INTERFACE_REQUIRED_IF = [
["state", "present", ["ipv4_type", "ipv6_type"]],
["ipv4_type", "static", ["ipv4_address", "ipv4_prefixlen"]],
["ipv6_type", "static", ["ipv6_address", "ipv6_prefixlen"]],
]
INTERFACE_MUTUALLY_EXCLUSIVE = [['interface', 'interface_descr']]
class PFSenseInterfaceModule(PFSenseModuleBase):
""" module managing pfsense interfaces """
@staticmethod
def get_argument_spec():
""" return argument spec """
return INTERFACE_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseInterfaceModule, self).__init__(module, pfsense)
self.name = "pfsense_interface"
# Override for use with aggregate
self.argument_spec = INTERFACE_ARGUMENT_SPEC
self.obj = dict()
self.root_elt = self.pfsense.interfaces
self.setup_interface_cmds = ""
self.setup_interface_pre_cmds = ""
##############################
# params processing
#
def _check_overlaps(self, ipfield, netfield):
""" check new address does not overlaps with one existing """
if not self.obj.get(ipfield) or self.obj.get(netfield) is None:
return
our_addr = ip_network(u'{0}/{1}'.format(self.obj[ipfield], self.obj[netfield]), strict=False)
for iface in self.root_elt:
if iface == self.target_elt:
continue
ipaddr_elt = iface.find(ipfield)
subnet_elt = iface.find(netfield)
if ipaddr_elt is None or subnet_elt is None or ipaddr_elt.text in ['dhcp', None] or ipaddr_elt.text in ['dhcpv6', None]:
continue
other_addr = ip_network(u'{0}/{1}'.format(ipaddr_elt.text, subnet_elt.text), strict=False)
if our_addr.overlaps(other_addr):
descr_elt = iface.find('descr')
if descr_elt is not None and descr_elt.text:
ifname = descr_elt.text
else:
ifname = iface.tag
msg = 'IP address {0}/{1} is being used by or overlaps with: {2} ({3}/{4})'.format(
self.obj[ipfield],
self.obj[netfield],
ifname,
ipaddr_elt.text,
subnet_elt.text
)
self.module.fail_json(msg=msg)
def _params_to_obj(self):
""" return an interface dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['descr'] = params['descr']
if params['state'] == 'present':
obj['if'] = params['interface']
for param in ['enable', 'blockpriv', 'blockbogons']:
self._get_ansible_param_bool(obj, param, value='')
self._get_ansible_param(obj, 'mac', fname='spoofmac', force=True)
self._get_ansible_param(obj, 'mtu')
self._get_ansible_param(obj, 'mss')
self._get_ansible_param(obj, 'speed_duplex', fname='media', exclude='autoselect')
if params['ipv4_type'] == 'static':
self._get_ansible_param(obj, 'ipv4_address', fname='ipaddr')
self._get_ansible_param(obj, 'ipv4_prefixlen', fname='subnet')
self._get_ansible_param(obj, 'ipv4_gateway', fname='gateway')
elif params['ipv4_type'] == 'dhcp':
obj['ipaddr'] = 'dhcp'
if params['ipv6_type'] == 'static':
self._get_ansible_param(obj, 'ipv6_address', fname='ipaddrv6')
self._get_ansible_param(obj, 'ipv6_prefixlen', fname='subnetv6')
self._get_ansible_param(obj, 'ipv6_gateway', fname='gatewayv6')
if params['ipv6_type'] == 'slaac':
obj['ipaddrv6'] = 'slaac'
self._get_ansible_param_bool(obj, 'slaacusev4iface', value='')
# get target interface
self.target_elt = self._find_matching_interface()
self._check_overlaps('ipaddrv6', 'subnetv6')
self._check_overlaps('ipaddr', 'subnet')
# check gateways
if self.obj.get('gateway') and not self.pfsense.find_gateway_elt(self.obj['gateway'], self.target_elt.tag, 'inet'):
self.module.fail_json(msg='Gateway {0} does not exist on {1}'.format(self.obj['gateway'], self.obj['descr']))
if self.obj.get('gatewayv6') and not self.pfsense.find_gateway_elt(self.obj['gatewayv6'], self.target_elt.tag, 'inet6'):
self.module.fail_json(msg='Gateway {0} does not exist on {1}'.format(self.obj['gatewayv6'], self.obj['descr']))
else:
self.target_elt = self._get_interface_elt_by_display_name(self.obj['descr'])
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
if re.match('^[a-zA-Z0-9_]+$', params['descr']) is None:
self.module.fail_json(msg='The name of the interface may only consist of the characters "a-z, A-Z, 0-9 and _"')
if params['state'] == 'present':
if params.get('mac') and re.match('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$', params['mac']) is None:
self.module.fail_json(msg='MAC address must be in the following format: xx:xx:xx:xx:xx:xx (or blank).')
# todo can't change mac address on vlan interface
if params.get('ipv4_prefixlen') is not None and params['ipv4_prefixlen'] < 1 or params['ipv4_prefixlen'] > 32:
self.module.fail_json(msg='ipv4_prefixlen must be between 1 and 32.')
if params.get('ipv6_prefixlen') is not None and params['ipv6_prefixlen'] < 1 or params['ipv6_prefixlen'] > 128:
self.module.fail_json(msg='ipv6_prefixlen must be between 1 and 128.')
if params.get('mtu') is not None and params['mtu'] < 1:
self.module.fail_json(msg='mtu must be above 0')
if params.get('mss') is not None and params['mtu'] < 1:
self.module.fail_json(msg='mtu must be above 0')
interfaces = self._get_interface_list()
if params.get('interface') is not None:
if params['interface'] not in interfaces.keys():
self.module.fail_json(
msg='{0} can\'t be assigned. Interface may only be one the following: {1}'.format(params['interface'], list(interfaces.keys())))
elif params.get('interface_descr') is not None:
for interface, attributes in interfaces.items():
if 'descr' in attributes and attributes['descr'] == params['interface_descr']:
if params.get('interface') is not None:
self.module.fail_json(msg='Multiple interfaces found for "{0}"'.format(params['interface_descr']))
else:
params['interface'] = interface
else:
self.module.fail_json(msg='one of the following is required: interface, interface_descr')
media_modes = set(self._get_media_mode(params['interface']))
media_modes.add('autoselect')
if params.get('speed_duplex') and params['speed_duplex'] not in media_modes:
self.module.fail_json(msg='For this interface, media mode may only be one the following: {0}'.format(media_modes))
if params['ipv4_type'] == 'static':
if params.get('ipv4_address') and not self.pfsense.is_ipv4_address(params['ipv4_address']):
self.module.fail_json(msg='{0} is not a valid IPv4 address'.format(params['ipv4_address']))
if params['ipv6_type'] == 'static':
if params.get('ipv6_address') and not self.pfsense.is_ipv6_address(params['ipv6_address']):
self.module.fail_json(msg='{0} is not a valid IPv6 address'.format(params['ipv6_address']))
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.obj
self.setup_interface_cmds += "interface_configure('{0}', true);\n".format(self.target_elt.tag)
self.result['ifname'] = self.target_elt.tag
def _copy_and_update_target(self):
""" update the XML target_elt """
(before, changed) = super(PFSenseInterfaceModule, self)._copy_and_update_target()
if changed:
if self.params['enable']:
self.setup_interface_cmds += "interface_bring_down('{0}', false);\n".format(self.target_elt.tag)
self.setup_interface_cmds += "interface_configure('{0}', true);\n".format(self.target_elt.tag)
else:
self.setup_interface_cmds += "interface_bring_down('{0}', true);\n".format(self.target_elt.tag)
self.result['ifname'] = self.target_elt.tag
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
# wan can't be deleted, so the first interface we can create is lan
if self.pfsense.get_interface_elt('lan') is None:
interface_elt = self.pfsense.new_element('lan')
self.root_elt.insert(1, interface_elt)
return interface_elt
# lan is used, so we must create an optX interface
i = 1
while True:
interface = 'opt{0}'.format(i)
if self.pfsense.get_interface_elt(interface) is None:
interface_elt = self.pfsense.new_element(interface)
# i + 1 = i + (lan and wan) - 1
self.root_elt.insert(i + 1, interface_elt)
return interface_elt
i = i + 1
def _get_interface_elt_by_port_and_display_name(self, interface_port, name):
""" return pfsense interface_elt """
for iface in self.root_elt:
descr_elt = iface.find('descr')
if descr_elt is None:
continue
if iface.find('if').text.strip() == interface_port and descr_elt.text.strip().lower() == name.lower():
return iface
return None
def _get_interface_elt_by_display_name(self, name):
""" return pfsense interface by name """
for iface in self.root_elt:
descr_elt = iface.find('descr')
if descr_elt is None:
continue
if descr_elt.text.strip().lower() == name.lower():
return iface
return None
def _get_interface_display_name_by_port(self, interface_port):
""" return pfsense interface physical name """
for iface in self.root_elt:
if iface.find('if').text.strip() == interface_port:
descr_elt = iface.find('descr')
if descr_elt is not None:
return descr_elt.text.strip()
return iface.tag
return None
def _get_interface_elt_by_port(self, interface_port):
""" find pfsense interface by port name """
for iface in self.root_elt:
if iface.find('if').text.strip() == interface_port:
return iface
return None
def _find_matching_interface(self):
""" return target interface """
# we first try to find an interface having same port and display name
interface_elt = self._get_interface_elt_by_port_and_display_name(self.obj['if'], self.obj['descr'])
if interface_elt is not None:
return interface_elt
# we then try to find an existing interface with the same display name
interface_elt = self._get_interface_elt_by_display_name(self.obj['descr'])
if interface_elt is not None:
# we check the target port can be used
used_by = self._get_interface_display_name_by_port(self.obj['if'])
if used_by is not None:
self.module.fail_json(msg='Port {0} is already in use on interface {1}'.format(self.obj['if'], used_by))
return interface_elt
# last, we try to find an existing interface with the port (interface will be renamed)
return self._get_interface_elt_by_port(self.obj['if'])
def _find_target(self):
""" find the XML target_elt """
return self.target_elt
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
params = ['mtu', 'mss', 'gateway', 'enable', 'mac', 'media', 'ipaddr', 'subnet', 'ipaddrv6', 'subnetv6', 'gatewayv6', 'blockpriv', 'blockbogons']
return params
def _pre_remove_target_elt(self):
""" processing before removing elt """
super(PFSenseInterfaceModule, self)._pre_remove_target_elt()
self.obj['if'] = self.target_elt.find('if').text
ifname = self.target_elt.tag
if self.pfsense.ifgroups is not None:
for ifgroup_elt in self.pfsense.ifgroups.findall("ifgroupentry"):
if ifgroup_elt.find('members') is not None:
members = ifgroup_elt.find('members').text.split()
if ifname in members:
self.module.fail_json(msg='The interface is part of the group {0}. Please remove it from the group first.'.format(
ifgroup_elt.find('ifname').text))
self._remove_all_separators(ifname)
self._remove_all_rules(ifname)
self.setup_interface_pre_cmds += "interface_bring_down('{0}');\n".format(ifname)
self.result['ifname'] = ifname
def _remove_all_rules(self, interface):
""" delete all interface rules """
# we use the pfsense_rule module to delete the rules since, at least for floating rules,
# it implies to recalculate separators positions
# if we have to just remove the deleted interface of a floating rule we do it ourselves
todel = []
for rule_elt in self.pfsense.rules:
if rule_elt.find('floating') is not None:
interfaces = rule_elt.find('interface').text.split(',')
old_ifs = ','.join([self.pfsense.get_interface_display_name(old_interface) for old_interface in interfaces])
if interface in interfaces:
if len(interfaces) > 1:
interfaces.remove(interface)
new_ifs = ','.join([self.pfsense.get_interface_display_name(new_interface) for new_interface in interfaces])
rule_elt.find('interface').text = ','.join(interfaces)
cmd = 'update rule \'{0}\' on \'floating({1})\' set interface=\'{2}\''.format(rule_elt.find('descr').text, old_ifs, new_ifs)
self.result['commands'].append(cmd)
continue
todel.append(rule_elt)
else:
continue
else:
iface = rule_elt.find('interface')
if iface is not None and iface.text == interface:
todel.append(rule_elt)
if todel:
pfsense_rules = PFSenseRuleModule(self.module, self.pfsense)
for rule_elt in todel:
params = {}
params['state'] = 'absent'
params['name'] = rule_elt.find('descr').text
params['interface'] = rule_elt.find('interface').text
if rule_elt.find('floating') is not None:
params['floating'] = True
pfsense_rules.run(params)
if pfsense_rules.result['commands']:
self.result['commands'].extend(pfsense_rules.result['commands'])
def _remove_all_separators(self, interface):
""" delete all interface separators """
todel = []
separators = self.pfsense.rules.find('separator') or []
for interface_elt in separators:
if interface_elt.tag != interface:
continue
for separator_elt in interface_elt:
todel.append(separator_elt)
for separator_elt in todel:
cmd = 'delete rule_separator \'{0}\', interface=\'{1}\''.format(separator_elt.find('text').text, interface)
self.result['commands'].append(cmd)
interface_elt.remove(separator_elt)
separators.remove(interface_elt)
break
##############################
# run
#
def _get_interface_list(self):
return self.pfsense.php(
"require_once('/etc/inc/interfaces.inc');"
"$portlist = get_interface_list();"
""
"/* add wireless clone interfaces */"
"if (is_array($config['wireless']) && is_array($config['wireless']['clone']) && count($config['wireless']['clone']))"
" foreach ($config['wireless']['clone'] as $clone) $portlist[$clone['cloneif']] = $clone;"
""
"/* add VLAN interfaces */"
"if (is_array($config['vlans']) && is_array($config['vlans']['vlan']) && count($config['vlans']['vlan']))"
" foreach ($config['vlans']['vlan'] as $vlan) $portlist[$vlan['vlanif']] = $vlan;"
""
"/* add Bridge interfaces */"
"if (is_array($config['bridges']) && is_array($config['bridges']['bridged']) && count($config['bridges']['bridged']))"
" foreach ($config['bridges']['bridged'] as $bridge) $portlist[$bridge['bridgeif']] = $bridge;"
""
"/* add GIF interfaces */"
"if (is_array($config['gifs']) && is_array($config['gifs']['gif']) && count($config['gifs']['gif']))"
" foreach ($config['gifs']['gif'] as $gif) $portlist[$gif['gifif']] = $gif;"
""
"/* add GRE interfaces */"
"if (is_array($config['gres']) && is_array($config['gres']['gre']) && count($config['gres']['gre']))"
" foreach ($config['gres']['gre'] as $gre) $portlist[$gre['greif']] = $gre;"
""
"/* add LAGG interfaces */"
"if (is_array($config['laggs']) && is_array($config['laggs']['lagg']) && count($config['laggs']['lagg']))"
" foreach ($config['laggs']['lagg'] as $lagg) {"
" $portlist[$lagg['laggif']] = $lagg;"
" /* LAGG members cannot be assigned */"
" $lagifs = explode(',', $lagg['members']);"
" foreach ($lagifs as $lagif)"
" if (isset($portlist[$lagif])) unset($portlist[$lagif]);"
" }"
""
"/* add QinQ interfaces */"
"if (is_array($config['qinqs']) && is_array($config['qinqs']['qinqentry']) && count($config['qinqs']['qinqentry']))"
" foreach ($config['qinqs']['qinqentry'] as $qinq) {"
" $portlist[\"{$qinq['vlanif']}\"] = $qinq;"
" /* QinQ members */"
" $qinqifs = explode(' ', $qinq['members']);"
" foreach ($qinqifs as $qinqif) $portlist[\"{$qinq['vlanif']}.{$qinqif}\"] = $qinqif;"
" }"
""
"/* add PPP interfaces */"
"if (is_array($config['ppps']) && is_array($config['ppps']['ppp']) && count($config['ppps']['ppp']))"
" foreach ($config['ppps']['ppp'] as $pppid => $ppp) $portlist[$ppp['if']] = $ppp;"
""
"if (is_array($config['openvpn'])) {"
" if (is_array($config['openvpn']['openvpn-server']))"
" foreach ($config['openvpn']['openvpn-server'] as $s) $portlist[\"ovpns{$s['vpnid']}\"] = $s;"
" if (is_array($config['openvpn']['openvpn-client']))"
" foreach ($config['openvpn']['openvpn-client'] as $c) $portlist[\"ovpnc{$c['vpnid']}\"] = $c;"
"}"
""
"$ipsec_descrs = interface_ipsec_vti_list_all();"
"foreach ($ipsec_descrs as $ifname => $ifdescr) $portlist[$ifname] = array('descr' => $ifdescr);"
""
"echo json_encode($portlist, JSON_PRETTY_PRINT);")
def _get_media_mode(self, interface):
""" Find all possible media options for the interface """
return self.pfsense.php(
'$mediaopts_list = array();\n'
'exec("/sbin/ifconfig -m ' + interface + ' | grep \'media \'", $mediaopts);\n'
'foreach ($mediaopts as $mediaopt) {\n'
' preg_match("/media (.*)/", $mediaopt, $matches);\n'
' if (preg_match("/(.*) mediaopt (.*)/", $matches[1], $matches1)) {\n'
' // there is media + mediaopt like "media 1000baseT mediaopt full-duplex"\n'
' array_push($mediaopts_list, $matches1[1] . " " . $matches1[2]);\n'
' } else {\n'
' // there is only media like "media 1000baseT"\n'
' array_push($mediaopts_list, $matches[1]);\n'
' }\n'
'}\n'
'echo json_encode($mediaopts_list);')
def get_pre_update_cmds(self):
""" build and return php commands to setup interfaces before changing config """
cmd = 'require_once("filter.inc");\n'
cmd += 'require_once("interfaces.inc");\n'
if self.setup_interface_pre_cmds != "":
cmd += self.setup_interface_pre_cmds
return cmd
def get_update_cmds(self):
""" build and return php commands to setup interfaces """
cmd = 'require_once("filter.inc");\n'
cmd += 'require_once("interfaces.inc");\n'
cmd += 'require_once("services.inc");\n'
cmd += 'require_once("gwlb.inc");\n'
cmd += 'require_once("rrd.inc");\n'
cmd += 'require_once("shaper.inc");\n'
if self.setup_interface_cmds != "":
cmd += self.setup_interface_cmds
cmd += 'services_snmpd_configure();\n'
cmd += 'setup_gateways_monitor();\n'
cmd += "clear_subsystem_dirty('interfaces');\n"
cmd += "filter_configure();\n"
cmd += "enable_rrd_graphing();\n"
cmd += "if (is_subsystem_dirty('staticroutes') && (system_routing_configure() == 0)) clear_subsystem_dirty('staticroutes');"
return cmd
def _pre_update(self):
""" tasks to run before making config changes """
return self.pfsense.phpshell(self.get_pre_update_cmds())
def _update(self):
""" make the target pfsense reload interfaces """
return self.pfsense.phpshell(self.get_update_cmds())
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['descr'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'if', fname='port')
values += self.format_cli_field(self.obj, 'enable', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'ipv4_type', default='none')
values += self.format_cli_field(self.obj, 'ipaddr', fname='ipv4_address')
values += self.format_cli_field(self.obj, 'subnet', fname='ipv4_prefixlen')
values += self.format_cli_field(self.obj, 'gateway', fname='ipv4_gateway')
values += self.format_cli_field(self.params, 'ipv6_type', default='none')
if self.obj.get('ipaddrv6') != 'slaac':
values += self.format_cli_field(self.obj, 'ipaddrv6', fname='ipv6_address')
values += self.format_cli_field(self.obj, 'subnetv6', fname='ipv6_prefixlen')
values += self.format_cli_field(self.obj, 'gatewayv6', fname='ipv6_gateway')
values += self.format_cli_field(self.params, 'mac')
values += self.format_cli_field(self.obj, 'mtu')
values += self.format_cli_field(self.obj, 'mss')
values += self.format_cli_field(self.obj, 'blockpriv', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.obj, 'blockbogons', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'speed_duplex', fname='speed_duplex', default='autoselect')
else:
# todo: - detect before ipv4_type for proper logging
values += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(values), fname='interface')
values += self.format_updated_cli_field(self.obj, before, 'if', add_comma=(values), fname='port')
values += self.format_updated_cli_field(self.obj, before, 'enable', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'ipv4_type', add_comma=(values), log_none='True')
values += self.format_updated_cli_field(self.obj, before, 'ipaddr', add_comma=(values), fname='ipv4_address')
values += self.format_updated_cli_field(self.obj, before, 'subnet', add_comma=(values), fname='ipv4_prefixlen')
values += self.format_updated_cli_field(self.obj, before, 'gateway', add_comma=(values), fname='ipv4_gateway')
if self.obj.get('ipaddrv6') == 'slaac' and before.get('ipaddrv6') != 'slaac':
res = "ipv6_type=slaac"
if values:
values += ", " + res
else:
values += res
else:
values += self.format_updated_cli_field(self.obj, before, 'ipv6_type', add_comma=(values), log_none='True')
values += self.format_updated_cli_field(self.obj, before, 'ipaddrv6', add_comma=(values), fname='ipv6_address')
values += self.format_updated_cli_field(self.obj, before, 'subnetv6', add_comma=(values), fname='ipv6_prefixlen')
values += self.format_updated_cli_field(self.obj, before, 'gatewayv6', add_comma=(values), fname='ipv6_gateway')
values += self.format_updated_cli_field(self.obj, before, 'spoofmac', add_comma=(values), fname='mac')
values += self.format_updated_cli_field(self.obj, before, 'mtu', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'mss', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'media', add_comma=(values), fname='speed_duplex')
values += self.format_updated_cli_field(self.obj, before, 'blockpriv', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'blockbogons', add_comma=(values), fvalue=self.fvalue_bool)
return values
def _log_update(self, before):
""" generate pseudo-CLI command to update an interface """
log = "update {0} '{1}'".format(
self._get_module_name(True),
# pfSense doesn't enforce a descr on an interface, especially on
# first-run so fallback to interface specifier if not known
before.get('descr', before['if']),
)
values = self._log_fields(before)
self.result['commands'].append(log + ' set ' + values)
================================================
FILE: plugins/module_utils/interface_group.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule
INTERFACE_GROUP_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True, type='str'),
descr=dict(type='str'),
members=dict(type='list', elements='str'),
)
INTERFACE_GROUP_REQUIRED_IF = [
['state', 'present', ['members']],
]
INTERFACE_GROUP_PHP_COMMAND = '''
require_once("interfaces.inc");
{0}
interface_group_setup($ifgroupentry);'''
class PFSenseInterfaceGroupModule(PFSenseModuleBase):
""" module managing pfsense interfaces """
@staticmethod
def get_argument_spec():
""" return argument spec """
return INTERFACE_GROUP_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseInterfaceGroupModule, self).__init__(module, pfsense, root='ifgroups', create_root=True, node='ifgroupentry', key='ifname')
self.name = "pfsense_interface_group"
##############################
# params processing
#
def _params_to_obj(self):
""" return an interface dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['ifname'] = params['name']
if params['state'] == 'present':
obj['descr'] = params['descr']
members = []
for interface in params['members']:
if self.pfsense.is_interface_display_name(interface):
members.append(self.pfsense.get_interface_by_display_name(interface))
elif self.pfsense.is_interface_port(interface):
members.append(interface)
else:
self.module.fail_json(msg='Unknown interface name "{0}".'.format(interface))
obj['members'] = ' '.join(members)
self.result['member_ifnames'] = members
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
if re.match('^[a-zA-Z0-9_]+$', params['name']) is None:
self.module.fail_json(msg='The name of the interface group may only consist of the characters "a-z, A-Z, 0-9 and _"')
if len(params['name']) > 15:
self.module.fail_json(msg='Group name cannot have more than 15 characters.')
if re.match('[0-9]$', params['name']) is not None:
self.module.fail_json(msg='Group name cannot end with a digit.')
# Make sure list of interfaces is a unique set
if params['state'] == 'present':
if len(params['members']) > len(set(params['members'])):
self.module.fail_json(msg='List of members is not unique.')
# TODO - check that name isn't in use by any interfaces
##############################
# XML processing
#
def _remove_all_rules(self, interface):
""" delete all interface rules """
# we use the pfsense_rule module to delete the rules since, at least for floating rules,
# it implies to recalculate separators positions
# if we have to just remove the deleted interface of a floating rule we do it ourselves
todel = []
for rule_elt in self.pfsense.rules:
if rule_elt.find('floating') is not None:
interfaces = rule_elt.find('interface').text.split(',')
old_ifs = ','.join([self.pfsense.get_interface_display_name(old_interface) for old_interface in interfaces])
if interface in interfaces:
if len(interfaces) > 1:
interfaces.remove(interface)
new_ifs = ','.join([self.pfsense.get_interface_display_name(new_interface) for new_interface in interfaces])
rule_elt.find('interface').text = ','.join(interfaces)
cmd = 'update rule \'{0}\' on \'floating({1})\' set interface=\'{2}\''.format(rule_elt.find('descr').text, old_ifs, new_ifs)
self.result['commands'].append(cmd)
continue
todel.append(rule_elt)
else:
continue
else:
iface = rule_elt.find('interface')
if iface is not None and iface.text == interface:
todel.append(rule_elt)
if todel:
pfsense_rules = PFSenseRuleModule(self.module, self.pfsense)
for rule_elt in todel:
params = {}
params['state'] = 'absent'
params['name'] = rule_elt.find('descr').text
params['interface'] = rule_elt.find('interface').text
if rule_elt.find('floating') is not None:
params['floating'] = True
pfsense_rules.run(params)
if pfsense_rules.result['commands']:
self.result['commands'].extend(pfsense_rules.result['commands'])
def _remove_all_separators(self, interface):
""" delete all interface separators """
todel = []
separators = self.pfsense.rules.find('separator')
for interface_elt in separators:
if interface_elt.tag != interface:
continue
for separator_elt in interface_elt:
todel.append(separator_elt)
for separator_elt in todel:
cmd = 'delete rule_separator \'{0}\', interface=\'{1}\''.format(separator_elt.find('text').text, interface)
self.result['commands'].append(cmd)
interface_elt.remove(separator_elt)
separators.remove(interface_elt)
break
##############################
# run
#
def _update(self):
""" make the target pfsense reload interfaces """
return self.pfsense.phpshell(INTERFACE_GROUP_PHP_COMMAND.format(self.pfsense.dict_to_php(self.obj, 'ifgroupentry')))
##############################
# Logging
#
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'descr')
values += self.format_cli_field(self.obj, 'members')
else:
values += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'members', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/ipsec.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
IPSEC_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
descr=dict(required=True, type='str'),
iketype=dict(choices=['ikev1', 'ikev2', 'auto'], type='str'),
protocol=dict(default='inet', choices=['inet', 'inet6', 'both']),
interface=dict(required=False, type='str'),
remote_gateway=dict(required=False, type='str'),
nattport=dict(required=False, type='int'),
disabled=dict(required=False, type='bool'),
authentication_method=dict(choices=['pre_shared_key', 'rsasig']),
mode=dict(required=False, choices=['main', 'aggressive']),
myid_type=dict(default='myaddress', choices=['myaddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'dyn_dns', 'auto']),
myid_data=dict(required=False, type='str'),
peerid_type=dict(default='peeraddress', choices=['any', 'peeraddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'auto']),
peerid_data=dict(required=False, type='str'),
certificate=dict(required=False, type='str'),
certificate_authority=dict(required=False, type='str'),
preshared_key=dict(required=False, type='str', no_log=True),
lifetime=dict(default=28800, type='int'),
rekey_time=dict(required=False, type='int'),
reauth_time=dict(required=False, type='int'),
rand_time=dict(required=False, type='int'),
disable_rekey=dict(required=False, type='bool'),
margintime=dict(required=False, type='int'),
startaction=dict(default='', choices=['', 'none', 'start', 'trap']),
closeaction=dict(default='', choices=['', 'none', 'start', 'trap']),
disable_reauth=dict(default=False, type='bool'),
mobike=dict(default='off', choices=['on', 'off']),
gw_duplicates=dict(required=False, type='bool'),
splitconn=dict(default=False, type='bool'),
nat_traversal=dict(default='on', choices=['on', 'force']),
enable_dpd=dict(default=True, type='bool'),
dpd_delay=dict(default=10, type='int'),
dpd_maxfail=dict(default=5, type='int'),
apply=dict(default=True, type='bool'),
# Dropped in 2.5.2
responderonly=dict(required=False, type='bool'),
)
IPSEC_REQUIRED_IF = [
["state", "present", ["remote_gateway", "interface", "iketype", "authentication_method"]],
["enable_dpd", True, ["dpd_delay", "dpd_maxfail"]],
["iketype", "auto", ["mode"]],
["iketype", "ikev1", ["mode"]],
["authentication_method", "pre_shared_key", ["preshared_key"]],
["authentication_method", "rsasig", ["certificate", "certificate_authority"]],
["myid_type", "address", ["myid_data"]],
["myid_type", "fqdn", ["myid_data"]],
["myid_type", "user_fqdn", ["myid_data"]],
["myid_type", "asn1dn", ["myid_data"]],
["myid_type", "keyid tag", ["myid_data"]],
["myid_type", "dyn_dns", ["myid_data"]],
["peerid_type", "address", ["peerid_data"]],
["peerid_type", "fqdn", ["peerid_data"]],
["peerid_type", "user_fqdn", ["peerid_data"]],
["peerid_type", "asn1dn", ["peerid_data"]],
["peerid_type", "keyid tag", ["peerid_data"]],
]
# Booleans that map to different values
IPSEC_BOOL_VALUES = dict(
gw_duplicates=(None, ''),
)
IPSEC_MAP_PARAM = [
('preshared_key', 'pre-shared-key'),
('remote_gateway', 'remote-gateway'),
]
IPSEC_CREATE_DEFAULT = dict(
rand_time=None,
reauth_time=None,
rekey_time=None,
)
def p2o_ipsec_interface(self, name, params, obj):
# Valid interfaces are physical, virtual IPs, and gateway groups
# TODO - handle gateway groups
if params[name].lower().startswith('vip:'):
obj[name] = self.pfsense.get_virtual_ip_interface(params[name][4:])
else:
obj[name] = self.pfsense.parse_interface(params[name], with_virtual=False)
IPSEC_ARG_ROUTE = dict(
interface=dict(parse=p2o_ipsec_interface,),
)
class PFSenseIpsecModule(PFSenseModuleBase):
""" module managing pfsense ipsec tunnels phase 1 options """
@staticmethod
def get_argument_spec():
""" return argument spec """
return IPSEC_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseIpsecModule, self).__init__(module, pfsense, arg_route=IPSEC_ARG_ROUTE, bool_values=IPSEC_BOOL_VALUES, map_param=IPSEC_MAP_PARAM,
create_default=IPSEC_CREATE_DEFAULT)
# Override for use with aggregate
self.argument_spec = IPSEC_ARGUMENT_SPEC
self.name = "pfsense_ipsec"
self.apply = True
self.root_elt = self.pfsense.ipsec
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
ipsec_elt = self.pfsense.new_element('phase1')
self.obj['ikeid'] = str(self._find_free_ikeid())
return ipsec_elt
def _find_free_ikeid(self):
""" return first unused ikeid """
ikeid = 1
while True:
found = False
for ipsec_elt in self.root_elt:
ikeid_elt = ipsec_elt.find('ikeid')
if ikeid_elt is not None and ikeid_elt.text == str(ikeid):
found = True
break
if not found:
return ikeid
ikeid = ikeid + 1
def _find_target(self):
""" find the XML target_elt """
if self.params.get('ikeid') is not None:
return self.pfsense.find_ipsec_phase1(self.params['ikeid'], 'ikeid')
return self.pfsense.find_ipsec_phase1(self.obj['descr'])
def _get_params_to_remove(self):
""" returns the list of params to remove if they are not set """
params = ['disabled', 'rekey_enable', 'reauth_enable', 'splitconn', 'nattport', 'gw_duplicates']
if self.params.get('disable_rekey'):
params.append('margintime')
if not self.params['enable_dpd']:
params.append('dpd_delay')
params.append('dpd_maxfail')
return params
def _pre_remove_target_elt(self):
""" processing before removing elt """
self._remove_phases2()
def _remove_phases2(self):
""" remove phase2 elts from xml """
ikeid_elt = self.target_elt.find('ikeid')
if ikeid_elt is None:
return
ikeid = ikeid_elt.text
phase2_elts = self.root_elt.findall('phase2')
for phase2_elt in phase2_elts:
ikeid_elt = phase2_elt.find('ikeid')
if ikeid_elt is None:
continue
if ikeid == ikeid_elt.text:
self.root_elt.remove(phase2_elt)
##############################
# params processing
#
def _params_to_obj(self):
""" return an ipsec dict from module params """
ipsec = super(PFSenseIpsecModule, self)._params_to_obj()
params = self.params
self.apply = params['apply']
ipsec.pop('apply', None)
if params['state'] == 'present':
if params['authentication_method'] == 'rsasig':
ca_elt = self.pfsense.find_ca_elt(params['certificate_authority'])
if ca_elt is None:
self.module.fail_json(msg='%s is not a valid certificate authority' % (params['certificate_authority']))
ipsec['caref'] = ca_elt.find('refid').text
cert = self.pfsense.find_cert_elt(params['certificate'])
if cert is None:
self.module.fail_json(msg='%s is not a valid certificate' % (params['certificate']))
ipsec['certref'] = cert.find('refid').text
ipsec['pre-shared-key'] = ''
else:
ipsec['caref'] = ''
ipsec['certref'] = ''
if params.get('disable_rekey'):
ipsec['rekey_enable'] = ''
if params.get('enable_dpd'):
ipsec['dpd_delay'] = str(params['dpd_delay'])
ipsec['dpd_maxfail'] = str(params['dpd_maxfail'])
del ipsec['enable_dpd']
if params.get('disable_reauth'):
ipsec['reauth_enable'] = ''
return ipsec
def _deprecated_params(self):
return [
['disable_rekey', self.pfsense.is_at_least_2_5_0],
['margintime', self.pfsense.is_at_least_2_5_0],
['responderonly', self.pfsense.is_at_least_2_5_2],
]
def _onward_params(self):
return [
['gw_duplicates', self.pfsense.is_at_least_2_5_0],
['nattport', self.pfsense.is_at_least_2_5_0],
['rekey_time', self.pfsense.is_at_least_2_5_0],
['reauth_time', self.pfsense.is_at_least_2_5_0],
['rand_time', self.pfsense.is_at_least_2_5_0],
# TODO - Cannot add because it has a default value
# ['startaction', self.pfsense.is_at_least_2_5_2],
# ['closeaction', self.pfsense.is_at_least_2_5_2],
]
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params['state'] == 'absent':
return
if params.get('lifetime') is not None:
if (params.get('rekey_time') is not None and params.get('rekey_time') >= params.get('lifetime') or
params.get('reauth_time') is not None and params.get('reauth_time') >= params.get('lifetime')):
self.module.fail_json(msg='Life Time must be larger than Rekey Time and Reauth Time.')
for ipsec_elt in self.root_elt:
if ipsec_elt.tag != 'phase1':
continue
# don't check on ourself
name = ipsec_elt.find('descr')
if name is None:
name = ''
else:
name = name.text
if name == params['descr']:
continue
# Valid interfaces are physical, virtual IPs, and gateway groups
# TODO - handle gateway groups
if params['interface'].lower().startswith('vip:'):
if self.pfsense.get_virtual_ip_interface(params['interface'][4:]) is None:
self.module.fail_json(msg='Cannot find virtual IP "{0}".'.format(params['interface'][4:]))
# two ikev2 can share the same gateway
iketype_elt = ipsec_elt.find('iketype')
if iketype_elt is None:
continue
if iketype_elt.text == 'ikev2' and iketype_elt.text == params['iketype']:
continue
# others can't share the same gateway
rgw_elt = ipsec_elt.find('remote-gateway')
if rgw_elt is None:
continue
if rgw_elt.text == params['remote_gateway']:
self.module.fail_json(msg='The remote gateway "{0}" is already used by phase1 "{1}".'.format(params['remote_gateway'], name))
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.apply_ipsec_changes()
##############################
# Logging
#
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.diff['after'], 'iketype')
if self.diff['after']['iketype'] != 'ikev2':
values += self.format_cli_field(self.diff['after'], 'mode')
values += self.format_cli_field(self.diff['after'], 'protocol')
values += self.format_cli_field(self.params, 'interface')
values += self.format_cli_field(self.diff['after'], 'remote-gateway', fname='remote_gateway')
values += self.format_cli_field(self.diff['after'], 'nattport')
values += self.format_cli_field(self.diff['after'], 'authentication_method')
if self.diff['after']['authentication_method'] == 'rsasig':
values += self.format_cli_field(self.params, 'certificate')
values += self.format_cli_field(self.params, 'certificate_authority')
else:
values += self.format_cli_field(self.diff['after'], 'pre-shared-key', fname='preshared_key')
id_types = ['address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'dyn_dns']
values += self.format_cli_field(self.diff['after'], 'myid_type')
if self.diff['after']['myid_type'] in id_types:
values += self.format_cli_field(self.diff['after'], 'myid_data')
values += self.format_cli_field(self.diff['after'], 'peerid_type')
if self.diff['after']['peerid_type'] in id_types:
values += self.format_cli_field(self.diff['after'], 'peerid_data')
values += self.format_cli_field(self.diff['after'], 'lifetime')
values += self.format_cli_field(self.diff['after'], 'rekey_time')
values += self.format_cli_field(self.diff['after'], 'reauth_time')
values += self.format_cli_field(self.diff['after'], 'rand_time')
if self.diff['after']['iketype'] == 'ikev2':
values += self.format_cli_field(self.diff['after'], 'reauth_enable', fname='disable_reauth', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.diff['after'], 'mobike')
values += self.format_cli_field(self.diff['after'], 'splitconn', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.diff['after'], 'gw_duplicates', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'startaction')
values += self.format_cli_field(self.params, 'closeaction')
values += self.format_cli_field(self.diff['after'], 'nat_traversal')
values += self.format_cli_field(self.params, 'enable_dpd', fvalue=self.fvalue_bool)
if self.params['enable_dpd']:
values += self.format_cli_field(self.diff['after'], 'dpd_delay')
values += self.format_cli_field(self.diff['after'], 'dpd_maxfail')
else:
values += self.format_updated_cli_field(self.diff['after'], before, 'disabled', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.diff['after'], before, 'iketype', add_comma=(values))
if self.diff['after']['iketype'] != 'ikev2':
values += self.format_updated_cli_field(self.diff['after'], before, 'mode', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'protocol', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'interface', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'remote-gateway', add_comma=(values), fname='remote_gateway')
values += self.format_updated_cli_field(self.diff['after'], before, 'nattport', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'authentication_method', add_comma=(values))
if self.diff['after']['authentication_method'] == 'rsasig':
values += self.format_updated_cli_field(self.params, before, 'certificate', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'certificate_authority', add_comma=(values))
else:
values += self.format_updated_cli_field(self.diff['after'], before, 'pre-shared-key', add_comma=(values), fname='preshared_key')
values += self.format_updated_cli_field(self.diff['after'], before, 'myid_type', add_comma=(values))
id_types = ['address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'dyn_dns']
if self.diff['after']['myid_type'] in id_types:
values += self.format_updated_cli_field(self.diff['after'], before, 'myid_data', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'peerid_type', add_comma=(values))
if self.diff['after']['peerid_type'] in id_types:
values += self.format_updated_cli_field(self.diff['after'], before, 'peerid_data', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'lifetime', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'rekey_time', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'reauth_time', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'rand_time', add_comma=(values))
if self.diff['after']['iketype'] == 'ikev2':
values += self.format_updated_cli_field(self.diff['after'], before, 'reauth_enable', add_comma=(values), fname='disable_reauth',
fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.diff['after'], before, 'mobike', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'splitconn', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.diff['after'], before, 'gw_duplicates', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.diff['after'], before, 'startaction', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'closeaction', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'nat_traversal', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'enable_dpd', add_comma=(values), fvalue=self.fvalue_bool)
if self.params['enable_dpd']:
values += self.format_updated_cli_field(self.diff['after'], before, 'dpd_delay', add_comma=(values))
values += self.format_updated_cli_field(self.diff['after'], before, 'dpd_maxfail', add_comma=(values))
return values
def _get_ref_names(self, before):
""" get cert and ca names """
if before['caref'] is not None and before['caref'] != '':
elt = self.pfsense.find_ca_elt(before['caref'], 'refid')
if elt is not None:
before['certificate_authority'] = elt.find('descr').text
if before['certref'] is not None and before['certref'] != '':
elt = self.pfsense.find_cert_elt(before['certref'], 'refid')
if elt is not None:
before['certificate'] = elt.find('descr').text
================================================
FILE: plugins/module_utils/ipsec_p2.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from copy import deepcopy
IPSEC_P2_ARGUMENT_SPEC = dict(
apply=dict(default=True, type='bool'),
state=dict(default='present', choices=['present', 'absent']),
descr=dict(required=True, type='str'),
p1_descr=dict(required=True, type='str'),
disabled=dict(default=False, type='bool'),
mode=dict(choices=['tunnel', 'tunnel6', 'transport', 'vti'], type='str'),
protocol=dict(default='esp', choices=['esp', 'ah'], type='str'),
# addresses
local=dict(required=False, type='str'),
nat=dict(required=False, type='str'),
remote=dict(required=False, type='str'),
# encryptions
aes=dict(required=False, type='bool'),
aes128gcm=dict(required=False, type='bool'),
aes192gcm=dict(required=False, type='bool'),
aes256gcm=dict(required=False, type='bool'),
blowfish=dict(required=False, type='bool'),
des=dict(required=False, type='bool'),
cast128=dict(required=False, type='bool'),
aes_len=dict(required=False, choices=['auto', '128', '192', '256'], type='str'),
aes128gcm_len=dict(required=False, choices=['auto', '64', '96', '128'], type='str'),
aes192gcm_len=dict(required=False, choices=['auto', '64', '96', '128'], type='str'),
aes256gcm_len=dict(required=False, choices=['auto', '64', '96', '128'], type='str'),
blowfish_len=dict(required=False, choices=['auto', '128', '192', '256'], type='str'),
# hashes
sha1=dict(required=False, type='bool'),
sha256=dict(required=False, type='bool'),
sha384=dict(required=False, type='bool'),
sha512=dict(required=False, type='bool'),
aesxcbc=dict(required=False, type='bool'),
# misc
pfsgroup=dict(
default='14',
choices=['0', '1', '2', '5', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '28', '29', '30', '31', '32'],
type='str'
),
lifetime=dict(default=3600, type='int'),
pinghost=dict(required=False, type='str')
)
IPSEC_P2_REQUIRED_IF = [
["state", "present", ["mode"]],
["mode", "tunnel", ["local", "remote"]],
["mode", "tunnel6", ["local", "remote"]],
["mode", "vti", ["local", "remote"]],
# encryptions
["aes", True, ["aes_len"]],
["aes128gcm", True, ["aes128gcm_len"]],
["aes192gcm", True, ["aes192gcm_len"]],
["aes256gcm", True, ["aes256gcm_len"]],
["blowfish", True, ["blowfish_len"]],
]
class PFSenseIpsecP2Module(PFSenseModuleBase):
""" module managing pfsense ipsec phase 2 options and proposals """
@staticmethod
def get_argument_spec():
""" return argument spec """
return IPSEC_P2_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseIpsecP2Module, self).__init__(module, pfsense)
self.name = "pfsense_ipsec_p2"
self.apply = True
self.obj = dict()
if pfsense is None:
pfsense = PFSenseModule(module)
self.module = module
self.pfsense = pfsense
self.root_elt = self.pfsense.ipsec
self._phase1 = None
self.before_elt = None
##############################
# params processing
#
def _check_for_duplicate_phase2(self, phase2):
""" check for another phase2 with same remote and local """
def strip_phase(phase):
_phase2 = {}
if phase.get('localid') is not None:
_phase2['localid'] = phase['localid']
if phase.get('remoteid') is not None:
_phase2['remoteid'] = phase['remoteid']
return _phase2
_phase2 = strip_phase(phase2)
ikeid = self._phase1.find('ikeid').text
for phase2_elt in self.root_elt:
if phase2_elt.tag != 'phase2':
continue
if phase2_elt.find('ikeid').text != ikeid:
continue
if phase2_elt.find('descr').text == phase2['descr']:
continue
other_phase2 = self.pfsense.element_to_dict(phase2_elt)
if _phase2 == strip_phase(other_phase2):
self.module.fail_json(msg='Phase2 with this Local/Remote networks combination is already defined for this Phase1.')
def _id_to_phase2(self, name, phase2, address, param_name):
""" setup ipsec phase2 with address """
def set_ip_address():
phase2[name]['type'] = 'address'
phase2[name]['address'] = address
def set_ip_network():
phase2[name]['type'] = 'network'
(phase2[name]['address'], phase2[name]['netbits']) = self.pfsense.parse_ip_network(address, False)
phase2[name]['netbits'] = str(phase2[name]['netbits'])
phase2[name] = dict()
interface = self.pfsense.parse_interface(address, fail=False, with_virtual=False)
if interface is not None:
if phase2['mode'] == 'vti':
msg = 'VTI requires a valid local network or IP address for its endpoint address.'
self.module.fail_json(msg=msg)
phase2[name]['type'] = interface
elif self.pfsense.is_ipv4_address(address):
if self.params['mode'] == 'tunnel6':
self.module.fail_json(msg='A valid IPv6 address or network must be specified in {0} with tunnel6.'.format(param_name))
set_ip_address()
elif self.pfsense.is_ipv6_address(address):
if self.params['mode'] == 'tunnel':
self.module.fail_json(msg='A valid IPv4 address or network must be specified in {0} with tunnel.'.format(param_name))
set_ip_address()
elif self.pfsense.is_ipv4_network(address, False):
if self.params['mode'] == 'tunnel6':
self.module.fail_json(msg='A valid IPv6 address or network must be specified in {0} with tunnel6.'.format(param_name))
set_ip_network()
elif self.pfsense.is_ipv6_network(address, False):
if self.params['mode'] == 'tunnel':
self.module.fail_json(msg='A valid IPv4 address or network must be specified in {0} with tunnel.'.format(param_name))
set_ip_network()
else:
self.module.fail_json(msg='A valid IP address, network or interface must be specified in {0}.'.format(param_name))
def _params_to_obj(self):
""" return an phase2 dict from module params """
params = self.params
obj = dict()
obj['descr'] = params['descr']
self.apply = params['apply']
if params['state'] == 'present':
obj['mode'] = params['mode']
if obj['mode'] != 'transport':
if obj['mode'] == 'vti' and not self.pfsense.is_ipv4_address(params['remote']):
msg = 'VTI requires a valid remote IP address for its endpoint address.'
self.module.fail_json(msg=msg)
self._id_to_phase2('localid', obj, params['local'], 'local')
self._id_to_phase2('remoteid', obj, params['remote'], 'remote')
if obj['mode'] != 'vti' and params.get('nat') is not None:
self._id_to_phase2('natlocalid', obj, params['nat'], 'nat')
if params.get('disabled'):
obj['disabled'] = ''
obj['protocol'] = params['protocol']
obj['pfsgroup'] = params['pfsgroup']
if params.get('lifetime') is not None and params['lifetime'] > 0:
obj['lifetime'] = str(params['lifetime'])
else:
obj['lifetime'] = ''
if obj.get('pinghost'):
obj['pinghost'] = params['pinghost']
else:
obj['pinghost'] = ''
self._check_for_duplicate_phase2(obj)
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
def has_one_of(bools):
for name in bools:
if params.get(name):
return True
return False
params = self.params
# called from ipsec_aggregate
if params.get('ikeid') is not None:
self._phase1 = self.pfsense.find_ipsec_phase1(params['ikeid'], 'ikeid')
if self._phase1 is None:
self.module.fail_json(msg='No ipsec tunnel with ikeid {0}'.format(params['ikeid']))
else:
self._phase1 = self.pfsense.find_ipsec_phase1(params['p1_descr'])
if self._phase1 is None:
self.module.fail_json(msg='No ipsec tunnel named {0}'.format(params['p1_descr']))
if params['state'] == 'present':
encs = ['aes', 'aes128gcm', 'aes192gcm', 'aes256gcm', 'blowfish', 'des', 'cast128']
if params['protocol'] == 'esp' and not has_one_of(encs):
self.module.fail_json(msg='At least one encryption algorithm must be selected.')
if self.pfsense.is_at_least_2_5_0():
need_one_hash = has_one_of(['aes', 'blowfish', 'des', 'cast128'])
else:
need_one_hash = True
if need_one_hash and not has_one_of(['sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc']):
self.module.fail_json(msg='At least one hashing algorithm needs to be selected.')
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self._sync_encryptions(self.target_elt)
self._sync_hashes(self.target_elt)
self.root_elt.append(self.target_elt)
def _copy_and_update_target(self):
""" update the XML target_elt """
self.before_elt = deepcopy(self.target_elt)
before = self.pfsense.element_to_dict(self.target_elt)
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
if self._sync_encryptions(self.target_elt):
changed = True
if self._sync_hashes(self.target_elt):
changed = True
if self._remove_deleted_ipsec_params():
changed = True
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
target_elt = self.pfsense.new_element('phase2')
self.obj['ikeid'] = self._phase1.find('ikeid').text
self.obj['uniqid'] = self.pfsense.uniqid()
self.obj['reqid'] = str(self._find_free_reqid())
return target_elt
def _find_free_reqid(self):
""" return first unused reqid """
reqid = 1
while True:
found = False
for phase2_elt in self.root_elt:
if phase2_elt.tag != 'phase2':
continue
reqid_elt = phase2_elt.find('reqid')
if reqid_elt is not None and reqid_elt.text == str(reqid):
found = True
break
if not found:
return reqid
reqid = reqid + 1
def _find_target(self):
""" return ipsec phase2 elt if found """
ikeid = self._phase1.find('ikeid').text
for phase2_elt in self.root_elt:
if phase2_elt.tag != 'phase2':
continue
if phase2_elt.find('ikeid').text != ikeid:
continue
descr_elt = phase2_elt.find('descr')
if descr_elt is not None and descr_elt.text == self.obj['descr']:
return phase2_elt
return None
def _remove_deleted_ipsec_params(self):
""" Remove from phase2 a few deleted params """
changed = False
params = ['disabled']
for param in params:
if self.pfsense.remove_deleted_param_from_elt(self.target_elt, param, self.obj):
changed = True
for param in ['localid', 'remoteid', 'natlocalid']:
if self._remove_extra_deleted_ipsec_params(param):
changed = True
return changed
def _remove_extra_deleted_ipsec_params(self, name):
""" Remove from phase2 a few extra deleted params """
changed = False
params = ['type', 'address', 'netbits']
sub_elt = self.target_elt.find(name)
if sub_elt is not None:
for param in params:
if name in self.obj:
if self.pfsense.remove_deleted_param_from_elt(sub_elt, param, self.obj[name]):
changed = True
else:
if self.pfsense.remove_deleted_param_from_elt(sub_elt, param, dict()):
changed = True
if len(sub_elt) == 0:
self.target_elt.remove(sub_elt)
return changed
def _sync_encryptions(self, phase2_elt):
""" sync encryptions params """
def get_encryption(encryptions_elt, name):
for encryption_elt in encryptions_elt:
name_elt = encryption_elt.find('name')
if name_elt is not None and name_elt.text == name:
return encryption_elt
return None
def sync_encryption(encryptions_elt, name, param_name):
encryption_elt = get_encryption(encryptions_elt, name)
if self.params.get(param_name):
encryption = dict()
encryption['name'] = name
if self.params.get(param_name + '_len') is not None:
encryption['keylen'] = self.params[param_name + '_len']
if encryption_elt is None:
encryption_elt = self.pfsense.new_element('encryption-algorithm-option')
self.pfsense.copy_dict_to_element(encryption, encryption_elt)
phase2_elt.append(encryption_elt)
return True
else:
old_encryption = self.pfsense.element_to_dict(encryption_elt)
if old_encryption != encryption:
self.pfsense.copy_dict_to_element(encryption, encryption_elt)
return True
else:
if encryption_elt is not None:
phase2_elt.remove(encryption_elt)
return True
return False
changed = False
encryptions_elt = phase2_elt.findall('encryption-algorithm-option')
if sync_encryption(encryptions_elt, 'aes', 'aes'):
changed = True
if sync_encryption(encryptions_elt, 'aes128gcm', 'aes128gcm'):
changed = True
if sync_encryption(encryptions_elt, 'aes192gcm', 'aes192gcm'):
changed = True
if sync_encryption(encryptions_elt, 'aes256gcm', 'aes256gcm'):
changed = True
if sync_encryption(encryptions_elt, 'blowfish', 'blowfish'):
changed = True
if sync_encryption(encryptions_elt, '3des', 'des'):
changed = True
if sync_encryption(encryptions_elt, 'cast128', 'cast128'):
changed = True
return changed
def _sync_hashes(self, phase2_elt):
""" sync hashes params """
def get_hash(hashes_elt, name):
for hash_elt in hashes_elt:
if hash_elt.text == name:
return hash_elt
return None
def sync_hash(hashes_elt, name, param_name):
if self.params.get(param_name) is True:
if get_hash(hashes_elt, name) is None:
hash_elt = self.pfsense.new_element('hash-algorithm-option')
hash_elt.text = name
phase2_elt.append(hash_elt)
return True
else:
hash_elt = get_hash(hashes_elt, name)
if hash_elt is not None:
phase2_elt.remove(hash_elt)
return True
return False
changed = False
hashes_elt = phase2_elt.findall('hash-algorithm-option')
if sync_hash(hashes_elt, 'hmac_sha1', 'sha1'):
changed = True
if sync_hash(hashes_elt, 'hmac_sha256', 'sha256'):
changed = True
if sync_hash(hashes_elt, 'hmac_sha384', 'sha384'):
changed = True
if sync_hash(hashes_elt, 'hmac_sha512', 'sha512'):
changed = True
if sync_hash(hashes_elt, 'aesxcbc', 'aesxcbc'):
changed = True
return changed
##############################
# run
#
def _update(self):
return self.pfsense.apply_ipsec_changes()
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}' on '{1}'".format(self.obj['descr'], self.params['p1_descr'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
def log_enc(name):
log = ''
log += self.format_cli_field(self.params, name, fvalue=self.fvalue_bool)
if self.params.get(name) and self.params.get(name + '_len') is not None:
log += self.format_cli_field(self.params, name + '_len')
return log
values = ''
if before is None:
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.obj, 'mode')
values += self.format_cli_field(self.params, 'local')
values += self.format_cli_field(self.params, 'remote')
values += self.format_cli_field(self.params, 'nat')
values += log_enc('aes')
values += log_enc('aes128gcm')
values += log_enc('aes192gcm')
values += log_enc('aes256gcm')
values += log_enc('blowfish')
values += log_enc('des')
values += log_enc('cast128')
values += self.format_cli_field(self.params, 'sha1', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'sha256', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'sha384', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'sha512', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'aesxcbc', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'pfsgroup')
values += self.format_cli_field(self.params, 'lifetime')
values += self.format_cli_field(self.params, 'pinghost')
else:
self._prepare_log_address(before, 'local', 'localid')
self._prepare_log_address(before, 'nat', 'natlocalid')
self._prepare_log_address(before, 'remote', 'remoteid')
self._prepare_log_encryptions(before, self.before_elt)
self._prepare_log_hashes(before, self.before_elt)
values += self.format_updated_cli_field(self.obj, before, 'disabled', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'mode', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'local', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'remote', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'nat', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'aes', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'aes_len', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'aes128gcm', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'aes128gcm_len', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'aes192gcm', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'aes192gcm_len', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'aes256gcm', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'aes256gcm_len', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'blowfish', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'blowfish_len', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'des', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'cast128', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'sha1', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'sha256', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'sha384', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'sha512', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.params, before, 'aesxcbc', add_comma=(values), fvalue=self.fvalue_bool)
values += self.format_updated_cli_field(self.obj, before, 'pfsgroup', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'lifetime', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'pinghost', add_comma=(values))
return values
def _prepare_log_address(self, before, param, name):
""" reparse some params for logging """
if before.get(name) is None or not isinstance(before[name], dict) or before[name].get('type') is None:
before[param] = None
return
if before[name]['type'] == 'address':
before[param] = before[name]['address']
elif before[name]['type'] == 'network':
before[param] = before[name]['address'] + '/' + str(before[name]['netbits'])
else:
before[param] = self.pfsense.get_interface_display_name(before[name]['type'])
@staticmethod
def _prepare_log_encryptions(before, before_elt):
""" reparse some params for logging """
encryptions_elt = before_elt.findall('encryption-algorithm-option')
for encryption_elt in encryptions_elt:
name = encryption_elt.find('name').text
len_elt = encryption_elt.find('keylen')
if name == '3des':
name = 'des'
before[name] = True
if len_elt is not None:
before[name + '_len'] = len_elt.text
encs = ['aes', 'aes128gcm', 'aes192gcm', 'aes256gcm', 'blowfish', 'des', 'cast128']
for enc in encs:
if enc not in before.keys():
before[enc] = False
if enc + '_len' not in before.keys():
before[enc + '_len'] = None
@staticmethod
def _prepare_log_hashes(before, before_elt):
""" reparse some params for logging """
hashes_elt = before_elt.findall('hash-algorithm-option')
for hash_elt in hashes_elt:
name = hash_elt.text.replace("hmac_", "")
before[name] = True
================================================
FILE: plugins/module_utils/ipsec_proposal.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from copy import deepcopy
IPSEC_PROPOSAL_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
descr=dict(required=False, type='str'),
encryption=dict(required=True, choices=['aes', 'aes128gcm', 'aes192gcm', 'aes256gcm', 'blowfish', '3des', 'cast128'], type='str'),
key_length=dict(required=False, choices=[64, 96, 128, 192, 256], type='int'),
hash=dict(required=True, choices=['md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc'], type='str'),
prf=dict(required=False, choices=['md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc'], type='str'),
dhgroup=dict(required=True, choices=[1, 2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 28, 29, 30, 31, 32], type='int'),
apply=dict(default=True, type='bool'),
)
IPSEC_PROPOSAL_REQUIRED_IF = [
["encryption", "aes", ["key_length"]],
["encryption", "aes128-gcm", ["key_length"]],
["encryption", "aes192-gcm", ["key_length"]],
["encryption", "aes256-gcm", ["key_length"]],
["encryption", "blowfish", ["key_length"]],
]
class PFSenseIpsecProposalModule(PFSenseModuleBase):
""" module managing pfsense ipsec phase 1 proposals """
@staticmethod
def get_argument_spec():
""" return argument spec """
return IPSEC_PROPOSAL_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseIpsecProposalModule, self).__init__(module, pfsense)
self.name = "pfsense_ipsec_proposal"
self.root_elt = None
self.obj = dict()
self.apply = True
self.ipsec = self.pfsense.ipsec
self._phase1 = None
##############################
# params processing
#
def _onward_params(self):
return [
['prf', self.pfsense.is_at_least_2_5_0],
]
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['encryption-algorithm'] = dict()
obj['encryption-algorithm']['name'] = params['encryption']
if params.get('key_length') is not None:
obj['encryption-algorithm']['keylen'] = str(params['key_length'])
else:
obj['encryption-algorithm']['keylen'] = ''
obj['hash-algorithm'] = params['hash']
obj['dhgroup'] = str(params['dhgroup'])
if self.pfsense.is_at_least_2_5_0():
if params.get('prf') is not None:
obj['prf-algorithm'] = params['prf']
else:
obj['prf-algorithm'] = 'sha256'
self.apply = params['apply']
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
key_length = dict()
key_length['aes'] = ['128', '192', '256']
key_length['aes192gcm'] = ['64', '96', '128']
key_length['aes128gcm'] = ['64', '96', '128']
key_length['aes256gcm'] = ['64', '96', '128']
key_length['blowfish'] = ['128', '192', '256']
if params['encryption'] in key_length.keys() and str(params['key_length']) not in key_length[params['encryption']]:
msg = 'key_length for encryption {0} must be one of: {1}.'.format(params['encryption'], ', '.join(key_length[params['encryption']]))
self.module.fail_json(msg=msg)
# called from ipsec_aggregate
if params.get('ikeid') is not None:
self._phase1 = self.pfsense.find_ipsec_phase1(params['ikeid'], 'ikeid')
if self._phase1 is None:
self.module.fail_json(msg='No ipsec tunnel with ikeid {0}'.format(params['ikeid']))
else:
self._phase1 = self.pfsense.find_ipsec_phase1(params['descr'])
if self._phase1 is None:
self.module.fail_json(msg='No ipsec tunnel named {0}'.format(params['descr']))
self.root_elt = self._phase1.find('encryption')
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('encryption')
self._phase1.append(self.root_elt)
if params['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
iketype_elt = self._phase1.find('iketype')
if iketype_elt is not None and iketype_elt.text != 'ikev2':
self.module.fail_json(msg='Encryption Algorithm AES-GCM can only be used with IKEv2')
##############################
# XML processing
#
@staticmethod
def _copy_and_update_target():
""" update the XML target_elt """
return (None, False)
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('item')
def _find_target(self):
""" find the XML target_elt """
# 2.5.0: when deleting, if prf is not specified we're taking the first matching proposal without taking prf into account
if self.params['state'] == 'absent' and self.params.get('prf') is None and self.pfsense.is_at_least_2_5_0():
obj = deepcopy(self.obj)
obj.pop('prf-algorithm', None)
else:
obj = self.obj
items_elt = self.root_elt.findall('item')
for item in items_elt:
existing = self.pfsense.element_to_dict(item)
if self.params['state'] == 'absent' and self.params.get('prf') is None and self.pfsense.is_at_least_2_5_0():
existing.pop('prf-algorithm', None)
if existing == obj:
return item
return None
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.apply_ipsec_changes()
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.params['descr'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
values += self.format_cli_field(self.params, 'encryption')
values += self.format_cli_field(self.params, 'key_length')
values += self.format_cli_field(self.obj, 'hash-algorithm', fname='hash')
values += self.format_cli_field(self.obj, 'dhgroup')
if self.pfsense.is_at_least_2_5_0():
values += self.format_cli_field(self.obj, 'prf-algorithm', fname='prf')
return values
def _log_fields_delete(self):
""" generate pseudo-CLI command fields parameters to delete an obj """
return self._log_fields()
================================================
FILE: plugins/module_utils/module_base.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# Copyright: (c) 2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from ansible_collections.pfsensible.core.plugins.module_utils.arg_route import p2o_interface
BASE_ARG_ROUTE = dict(
interface=dict(parse=p2o_interface,),
)
# Merge two nested dictionaries, combining instead of overwriting elements
def merge_dicts(a: dict, b: dict, path=None):
if path is None:
path = []
for key in b:
if key in a:
if isinstance(a[key], dict) and isinstance(b[key], dict):
merge_dicts(a[key], b[key], path + [str(key)])
else:
a[key] = b[key]
else:
a[key] = b[key]
return a
# Move a key in dict to a new one, allowing the use '/' to specify nested dict location
def move_dict_key(obj, src, dst):
item = None
for n in reversed(dst.split('/')):
if item is None:
item = dict()
item[n] = obj[src]
else:
parent = dict()
parent[n] = item
item = parent
merge_dicts(obj, item)
del obj[src]
class PFSenseModuleBase(object):
""" class providing base services for pfSense modules """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
raise NotImplementedError()
##############################
# init
#
def __init__(self, module, pfsense=None, package=None, name=None, root=None, root_is_exclusive=True, create_root=False, node=None, key='descr',
update_php=None, arg_route=None, map_param=None, map_param_if=None, param_force=None, bool_style=None, bool_values=None, have_refid=False,
create_default=None):
self.module = module # ansible module
self.argument_spec = module.argument_spec # Allow for being overriden for use with aggregate
# pfSense helper module
if pfsense is None:
pfsense = PFSenseModule(module)
self.pfsense = pfsense
if name is not None: # ansible module name
self.name = name
elif node is not None:
self.name = 'pfsense_' + node
else:
self.name = None
self.apply = True # apply configuration at the end
# xml parent of target_elt, node named by root
# TODO - handle paths with creation - e.g.
if root is not None:
if root == 'pfsense':
self.root_elt = self.pfsense.root
self.root_is_exclusive = False
else:
if package is not None:
self.package_elt = self.pfsense.find_elt('package', package, search_field='name', root_elt=self.pfsense.root.find('installedpackages'))
if self.package_elt is None:
self.module.fail_json(
msg=f'Unable to find package {package} in installed packages. Are you sure that it is installed?')
self.root_elt = self.pfsense.get_element(root, root_elt=self.pfsense.root.find('installedpackages'), create_node=create_root)
if self.root_elt is None:
self.module.fail_json(
msg=f'Unable to find configuration for the package {package}. Are you sure that it is installed?')
else:
root_elt = self.pfsense.root
for this in root.split('/'):
root_elt = self.pfsense.get_element(this, root_elt=root_elt, create_node=create_root)
self.root_elt = root_elt
if root in ['system']:
self.root_is_exclusive = False
else:
self.root_is_exclusive = root_is_exclusive
else:
self.root_elt = None
self.root_is_exclusive = root_is_exclusive
self.root = root
# List of elements named node
if node is not None:
self.elements = self.root_elt.findall(node)
else:
self.elements = None
self.node = node
self.key = key # item that identifies a target element
self.obj = dict() # dict holding target pfsense parameters
self.package = package
# routing for argument handling
self.arg_route = BASE_ARG_ROUTE.copy()
if arg_route is not None:
self.arg_route.update(arg_route)
# rules for mapping parameters
if map_param is not None:
self.map_param = map_param
else:
self.map_param = list()
# conditional rules for mapping parameters
if map_param_if is not None:
self.map_param_if = map_param_if
else:
self.map_param_if = list()
if param_force is not None:
self.param_force = param_force # parameters that are forced to be present
else:
self.param_force = list()
self.bool_style = bool_style # default boolean value style for arguments
if bool_values is not None:
self.bool_values = bool_values # boolean values for specific arguments
else:
self.bool_values = dict()
self.create_default = create_default # default values for a created target
self.have_refid = have_refid # if the element has a refid item
self.target_elt = None # xml object holding target pfsense parameters
self.update_php = update_php # php code to update configuration
self.change_descr = ''
self.result = {}
self.result['changed'] = False
self.result['commands'] = []
self.diff = {'after': {}, 'before': {}}
self.result['diff'] = self.diff
##############################
# params processing
#
def _get_ansible_param(self, obj, name, fname=None, force=False, exclude=None, force_value='', params=None):
""" get parameter from params and set it into obj """
if fname is None:
fname = name
if params is None:
params = self.params
if params.get(name) is not None:
if not (exclude is not None and exclude == params[name]):
if isinstance(self.params[name], int):
obj[fname] = str(self.params[name])
else:
obj[fname] = self.params[name]
elif force:
obj[fname] = force_value
def _get_ansible_param_bool(self, obj, name, fname=None, force=False, value='yes', value_false=None, params=None):
""" get bool parameter from params and set it into obj """
if fname is None:
fname = name
if params is None:
params = self.params
if params.get(name) is not None:
if params.get(name):
obj[fname] = value
elif value_false is not None:
obj[fname] = value_false
elif force:
obj[fname] = None
elif value_false is None and fname in obj:
del obj[fname]
elif force:
obj[fname] = value_false
def _params_to_obj(self, obj=None):
""" return a dict from module params that sets self.obj """
if obj is None:
obj = dict()
# Not all modules have 'state', treat them like they did
if self.params.get('state', 'present') == 'present':
# Skip 'state', but otherwise process all parameters. Ansible sets unspecified parameters to None.
for param in [p for p in self.params if p != 'state']:
force = False
if param in self.param_force:
force = True
# If we have defined a parser for this arg, use it
if param in self.arg_route and 'parse' in self.arg_route[param] and self.params.get(param) is not None:
self.arg_route[param]['parse'](self, param, self.params, obj)
elif self.argument_spec[param].get('type') == 'bool':
if param in self.bool_values:
self._get_ansible_param_bool(obj, param, value=self.bool_values[param][1], value_false=self.bool_values[param][0], force=force)
elif self.bool_style == 'absent/present':
self._get_ansible_param_bool(obj, param, value='', force=force)
elif self.bool_style == 'on':
self._get_ansible_param_bool(obj, param, value='on', force=force)
else:
self._get_ansible_param_bool(obj, param, force=force)
else:
self._get_ansible_param(obj, param, force=force)
# Handle renaming of parameters
for p, o in self.map_param:
if self.params.get(p) is not None:
move_dict_key(obj, p, o)
# Handle conditional renaming of parameters
for map_param, map_value, map_tuple in self.map_param_if:
if self.params.get(map_param) == map_value and map_tuple[0] in obj:
# Do not overwrite destination if it exists
if map_tuple[1] not in obj:
move_dict_key(obj, map_tuple[0], map_tuple[1])
else:
del obj[map_tuple[0]]
else:
# Just use the key to remove items
obj[self.key] = self.params[self.key]
return obj
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# Not all modules have 'state', treat them like they did
if self.params.get('state', 'present') == 'present':
# Ansible sets unspecied parameters to None, skip them
for param in [p for p in self.params if self.params[p] is not None]:
if param in self.arg_route and 'validate' in self.arg_route[param]:
try:
self.arg_route[param]['validate'](self, params[param])
except ValueError as e:
self.module.fail_json(msg=str(e))
def _deprecated_params(self):
""" return deprecated params """
return None
def _onward_params(self):
""" return onwards params """
return None
def _check_deprecated_params(self):
""" check if input parameters are deprecated """
deprecated_params = self._deprecated_params()
if deprecated_params is None:
return
for deprecated in deprecated_params:
if self.params.get(deprecated[0]) is not None and deprecated[1]():
self.module.fail_json(msg='{0} is deprecated on pfSense {1}.'.format(deprecated[0], self.pfsense.get_version()))
def _check_onward_params(self):
""" check if input parameters are too recents """
onwards_params = self._onward_params()
if onwards_params is None:
return
for onward in onwards_params:
if self.params.get(onward[0]) is not None and not onward[1]():
self.module.fail_json(msg='{0} is not supported on pfSense {1}.'.format(onward[0], self.pfsense.get_version()))
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.obj
if self.root_is_exclusive:
self.root_elt.append(self.target_elt)
else:
self.root_elt.insert(self._find_last_element_index(), self.target_elt)
# Reset elements list
self.elements = self.root_elt.findall(self.node)
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
if self._remove_deleted_params():
changed = True
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
if self.node is not None:
elt = self.pfsense.new_element(self.node)
if self.have_refid:
# Store in obj so that we can refer to it later if needed
self.obj['refid'] = self.pfsense.uniqid()
elt.append(self.pfsense.new_element('refid', text=self.obj['refid']))
if self.create_default is not None:
self.pfsense.copy_dict_to_element(self.create_default, elt)
return elt
else:
raise NotImplementedError()
def _find_this_element_index(self):
return self.elements.index(self.target_elt)
def _find_last_element_index(self):
if len(self.elements):
return list(self.root_elt).index(self.elements[len(self.elements) - 1])
else:
return len(list(self.root_elt))
def _find_target(self):
""" find the XML target_elt """
if self.node is not None:
result = self.root_elt.findall("{node}[{key}='{value}']".format(node=self.node, key=self.key, value=self.obj[self.key]))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple {node}s for {key} {value}.'.format(node=self.node, key=self.key, value=self.obj[self.key]))
else:
return None
else:
raise NotImplementedError()
def _get_params_to_remove(self):
""" returns the list of params to remove if they are set to false """
to_remove = []
# We need to remove any booleans set to false that are "None" when unset
for param in [n for n in self.argument_spec.keys() if self.argument_spec[n].get('type') == 'bool']:
if self.params.get(param, None) is False:
if param in self.bool_values and self.bool_values[param][0] is None:
to_remove.append(param)
elif self.bool_style == 'absent/present':
to_remove.append(param)
return to_remove
def _remove_deleted_params(self):
""" Remove from target_elt a few deleted params """
changed = False
params = self._get_params_to_remove()
for param in params:
if self.pfsense.remove_deleted_param_from_elt(self.target_elt, param, self.obj):
changed = True
return changed
def _remove_target_elt(self):
""" delete target_elt from xml """
self.root_elt.remove(self.target_elt)
self.result['changed'] = True
##############################
# run
#
def _add(self):
""" add or update obj """
if self.target_elt is None:
self.target_elt = self._create_target()
self._copy_and_add_target()
changed = True
self.change_descr = 'ansible {0} added {1}'.format(self._get_module_name(), self._get_obj_name())
self._log_create()
else:
(before, changed) = self._copy_and_update_target()
if changed:
self.change_descr = 'ansible {0} updated {1}'.format(self._get_module_name(), self._get_obj_name())
self._log_update(before)
if changed:
self.result['changed'] = changed
def commit_changes(self):
""" apply changes and exit module """
self.result['stdout'] = ''
self.result['stderr'] = ''
if self.result['changed'] and not self.module.check_mode:
if self.apply:
(dummy, self.result['stdout'], self.result['stderr']) = self._pre_update()
self.pfsense.write_config(descr=self.change_descr)
if self.apply:
(dummy, stdout, stderr) = self._update()
self.result['stdout'] += stdout
self.result['stderr'] += stderr
self.module.exit_json(**self.result)
def _post_remove_target_elt(self):
""" processing after removing elt """
pass
def _pre_remove_target_elt(self):
""" processing before removing elt """
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
def _remove(self):
""" delete obj """
if self.target_elt is not None:
self._pre_remove_target_elt()
self._log_delete()
self._remove_target_elt()
self._post_remove_target_elt()
self.change_descr = 'ansible {0} removed {1}'.format(self._get_module_name(), self._get_obj_name())
@staticmethod
def _pre_update():
""" tasks to run before making config changes """
return ('', '', '')
def _update(self):
""" make the target pfsense reload """
if self.update_php is not None:
return self.pfsense.phpshell(self.update_php)
else:
return ('', '', '')
def _run_post(self):
""" used to do some post-processing like adding results or decoding diff entries """
pass
# We take params here for use with pfsense_aggregate and the test framework
def run(self, params):
""" process input params to add/update/delete """
self.params = params
self.target_elt = None
self._check_deprecated_params()
self._check_onward_params()
self._validate_params()
self.obj = self._params_to_obj()
if self.target_elt is None:
self.target_elt = self._find_target()
if params.get('state', None) == 'absent':
self._remove()
else:
self._add()
self._run_post()
##############################
# Logging
#
def _log_create(self):
""" generate pseudo-CLI command to create an obj """
log = "create {0} {1}".format(self._get_module_name(True), self._get_obj_name())
log += self._log_fields()
self.result['commands'].append(log)
def _log_delete(self):
""" generate pseudo-CLI command to delete an obj """
log = "delete {0} {1}".format(self._get_module_name(True), self._get_obj_name())
log += self._log_fields_delete()
self.result['commands'].append(log)
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
for param in [n for n in self.argument_spec.keys() if n != 'state' and n != self.key]:
values += self.format_cli_field(self.obj, param)
else:
for param in [n for n in self.argument_spec.keys() if n != 'state' and n != self.key]:
if self.argument_spec[param].get('type') == 'bool':
values += self.format_updated_cli_field(self.diff['after'], before, param, fvalue=self.fvalue_bool, add_comma=(values))
else:
values += self.format_updated_cli_field(self.diff['after'], before, param, add_comma=(values))
return values
@staticmethod
def _log_fields_delete():
""" generate pseudo-CLI command fields parameters to delete an obj """
return ""
def _log_update(self, before):
""" generate pseudo-CLI command to update an obj """
log = "update {0} {1}".format(self._get_module_name(True), self._get_obj_name())
values = self._log_fields(before)
self.result['commands'].append(log + ' set ' + values)
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj[self.key])
def _get_module_name(self, strip=False):
""" return ansible module's name """
if strip:
return self.name.replace("pfsense_", "")
return self.name
def format_cli_field(self, after, field, log_none=False, add_comma=True, fvalue=None, default=None, fname=None, none_value=None, force=False):
""" format field for pseudo-CLI command """
if fvalue is None:
fvalue = self.fvalue_idem
if fname is None:
fname = field
if none_value is None:
none_value = 'none'
res = ''
if field in after:
if log_none and after[field] is None:
res = "{0}={1}".format(fname, fvalue(none_value))
if after[field] is not None:
if default is None or after[field] != default:
if isinstance(after[field], str) and fvalue != self.fvalue_bool:
res = "{0}='{1}'".format(fname, fvalue(after[field].replace("'", "\\'")))
else:
res = "{0}={1}".format(fname, fvalue(after[field]))
elif log_none or force:
res = "{0}={1}".format(fname, fvalue(none_value))
if add_comma and res:
return ', ' + res
return res
def format_updated_cli_field(self, after, before, field, log_none=True, add_comma=True, fvalue=None, default=None, fname=None, none_value=None):
""" format field for pseudo-CLI update command """
log = False
if none_value is None:
none_value = 'none'
if field in after and field in before:
if fvalue is None and after[field] != before[field]:
log = True
elif fvalue is not None and fvalue(after[field]) != fvalue(before[field]):
log = True
elif fvalue is None:
if field in after and field not in before or field not in after and field in before:
log = True
elif field in after and field not in before and fvalue(after[field]) != fvalue(none_value):
log = True
elif field not in after and field in before and fvalue(before[field]) != fvalue(none_value):
log = True
if log:
return self.format_cli_field(
after, field, log_none=log_none, add_comma=add_comma, fvalue=fvalue, default=default, fname=fname, none_value=none_value, force=True
)
return ''
@staticmethod
def fvalue_idem(value):
""" dummy value formatting function """
return value
@staticmethod
def fvalue_bool(value):
""" boolean value formatting function """
if value is None or value is False or value == 'none':
return 'False'
return 'True'
================================================
FILE: plugins/module_utils/module_config_base.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# Copyright: (c) 2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase, merge_dicts
class PFSenseModuleConfigBase(PFSenseModuleBase):
""" class for implementing pfSense modules that manage a set of configuration settings """
##############################
# init
#
def __init__(self, module, pfsense=None, package=None, name=None, root=None, root_is_exclusive=True, create_root=False, node=None, key='descr',
update_php=None, arg_route=None, map_param=None, map_param_if=None, param_force=None, bool_style=None, bool_values=None, have_refid=False,
create_default=None):
super(PFSenseModuleConfigBase, self).__init__(module, pfsense=pfsense, package=package, name=name, root=root, node=node, root_is_exclusive=True,
create_root=create_root, update_php=update_php, arg_route=arg_route, map_param=map_param,
map_param_if=map_param_if, param_force=param_force, bool_style=bool_style, bool_values=bool_values,
create_default=create_default)
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
# We need to pre-populate our object with the current config (if it exists) so that we only modify the options we specified
config_elt = self.pfsense.get_element(self.node, root_elt=self.root_elt)
if config_elt is None:
obj = {}
else:
obj = self.pfsense.element_to_dict(config_elt)
merge_dicts(obj, super(PFSenseModuleConfigBase, self)._params_to_obj(obj=obj))
return obj
##############################
# XML processing
#
def _find_target(self):
""" find the XML target_elt """
return self.pfsense.get_element(self.node, root_elt=self.root_elt, create_node=True)
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return re.sub(r'pfsense_', '', self.name)
================================================
FILE: plugins/module_utils/nat_outbound.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from string import hexdigits
from hashlib import md5
import re
import sys
NAT_OUTBOUND_ARGUMENT_SPEC = dict(
descr=dict(required=True, type='str'),
state=dict(default='present', choices=['present', 'absent']),
disabled=dict(default=False, required=False, type='bool'),
nonat=dict(default=False, required=False, type='bool'),
interface=dict(required=False, type='str'),
ipprotocol=dict(required=False, default='inet46', choices=['inet', 'inet46', 'inet6']),
protocol=dict(default='any', required=False, choices=["any", "tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "carp", "pfsync"]),
source=dict(required=False, type='str'),
destination=dict(required=False, type='str'),
invert=dict(default=False, required=False, type='bool'),
address=dict(required=False, type='str'),
poolopts=dict(
default='', required=False, choices=["", "round-robin", "round-robin sticky-address", "random", "random sticky-address", "source-hash", "bitmask"]
),
source_hash_key=dict(default='', type='str', no_log=True),
staticnatport=dict(default=False, required=False, type='bool'),
nosync=dict(default=False, required=False, type='bool'),
after=dict(required=False, type='str'),
before=dict(required=False, type='str'),
)
NAT_OUTBOUND_MUTUALLY_EXCLUSIVE = [
('after', 'before'),
]
NAT_OUTBOUND_REQUIRED_IF = [
["state", "present", ["interface", "source", "destination"]]
]
# Booleans that map to different values
NAT_OUTBOUND_BOOL_VALUES = dict(
disabled=(None, ''),
staticnatport=(None, ''),
nonat=(None, ''),
nosync=(None, ''),
)
def p2o_after(self, name, params, obj):
self.after = params[name]
def p2o_before(self, name, params, obj):
self.before = params[name]
def p2o_ipprotocol(self, name, params, obj):
# IPv4+6 is marked by the absense of an ipprotocol element
if params[name] != 'inet46':
obj[name] = params[name]
def p2o_protocol(self, name, params, obj):
# 'any' is marked by the absense of a protocol element
if params[name] != 'any':
obj[name] = params[name]
NAT_OUTBOUND_ARG_ROUTE = dict(
after=dict(parse=p2o_after),
before=dict(parse=p2o_before),
ipprotocol=dict(parse=p2o_ipprotocol),
protocol=dict(parse=p2o_protocol),
)
class PFSenseNatOutboundModule(PFSenseModuleBase):
""" module managing pfsense NAT rules """
@staticmethod
def get_argument_spec():
""" return argument spec """
return NAT_OUTBOUND_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseNatOutboundModule, self).__init__(module, pfsense, root='nat/outbound', create_root=True, arg_route=NAT_OUTBOUND_ARG_ROUTE,
bool_values=NAT_OUTBOUND_BOOL_VALUES)
self.name = "pfsense_nat_outbound"
# Override for use with aggregate
self.argument_spec = NAT_OUTBOUND_ARGUMENT_SPEC
self.after = None
self.before = None
self.position_changed = False
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
obj = super(PFSenseNatOutboundModule, self)._params_to_obj()
params = self.params
if params['state'] == 'present':
self._parse_address(obj, 'source', 'sourceport', True, 'network')
self._parse_address(obj, 'destination', 'dstport', False, 'network')
if params['invert']:
obj['destination']['not'] = None
self._parse_translated_address(obj)
if obj['source_hash_key'] != '' and not obj['source_hash_key'].startswith('0x'):
if sys.version_info[0] >= 3:
obj['source_hash_key'] = '0x' + md5(obj['source_hash_key'].encode('utf-8')).hexdigest()
else:
obj['source_hash_key'] = '0x' + md5(obj['source_hash_key']).hexdigest()
return obj
def _parse_address(self, obj, field, field_port, allow_self, target):
""" validate param address field and returns it as a dict """
if self.params.get(field) is None or self.params[field] == '':
return
param = self.params[field]
addr = param.split(':')
if len(addr) > 3:
self.module.fail_json(msg='Cannot parse address %s' % (param))
address = addr[0]
ret = dict()
if address == 'NET':
interface = addr[1] if len(addr) > 1 else None
ports = addr[2] if len(addr) > 2 else None
if interface is None or interface == '':
self.module.fail_json(msg='Cannot parse address %s' % (param))
ret['network'] = self.pfsense.parse_interface(interface)
else:
ports = addr[1] if len(addr) > 1 else None
if address == 'any':
if field == 'source':
ret[target] = 'any'
else:
ret['any'] = ''
# rule with this firewall
elif allow_self and address == '(self)':
ret[target] = '(self)'
elif self.params['ipprotocol'] != 'inet6' and self.pfsense.is_ipv4_address(address):
ret[target] = address + '/32'
self.module.warn('Specifying an address without a CIDR prefix is depracated. Please add /32 if you want a single host address')
elif self.params['ipprotocol'] != 'inet4' and self.pfsense.is_ipv6_address(address):
ret[target] = address + '/128'
self.module.warn('Specifying an address without a CIDR prefix is depracated. Please add /128 if you want a single host address')
elif self.params['ipprotocol'] != 'inet6' and self.pfsense.is_ipv4_network(address, False):
(addr, bits) = self.pfsense.parse_ip_network(address, False, False)
ret[target] = addr + '/' + str(bits)
elif self.params['ipprotocol'] != 'inet4' and self.pfsense.is_ipv6_network(address, False):
(addr, bits) = self.pfsense.parse_ip_network(address, False, False)
ret[target] = addr + '/' + str(bits)
elif self.pfsense.find_alias(address, 'host') is not None or self.pfsense.find_alias(address, 'network') is not None:
ret[target] = address
else:
self.module.fail_json(msg='Cannot parse address %s, not %s network or alias' % (address, self.params['ipprotocol']))
if ports is not None:
self._parse_ports(obj, ports, field_port, param)
obj[field] = ret
def _parse_ports(self, obj, ports, field_port, param):
""" validate param address field and returns it as a dict """
if ports is not None:
ports = ports.split('-')
if len(ports) > 2 or ports[0] is None or ports[0] == '' or len(ports) == 2 and (ports[1] is None or ports[1] == ''):
self.module.fail_json(msg='Cannot parse address %s' % (param))
if not self.pfsense.is_port_or_alias(ports[0]):
self.module.fail_json(msg='Cannot parse port %s, not port number or alias' % (ports[0]))
obj[field_port] = ports[0]
if len(ports) > 1:
if not self.pfsense.is_port_or_alias(ports[1]):
self.module.fail_json(msg='Cannot parse port %s, not port number or alias' % (ports[1]))
obj[field_port] += ':' + ports[1]
def _parse_translated_address(self, obj):
""" validate param address field and returns it as a dict """
obj['target'] = ''
obj['target_subnet'] = ''
if self.params.get('address') is None or self.params['address'] == '':
return
param = self.params['address']
addr = param.split(':')
if len(addr) > 2:
self.module.fail_json(msg='Cannot parse address %s' % (param))
address = addr[0]
ports = addr[1] if len(addr) > 1 else None
if address is not None and address != '':
if self.pfsense.is_virtual_ip(address):
obj['target'] = address
obj['target_subnet'] = None
elif self.pfsense.find_alias(address, 'host') is not None or self.pfsense.find_alias(address, 'network') is not None:
obj['target'] = address
if obj['poolopts'] != '' and not obj['poolopts'].startswith('round-robin'):
self.module.fail_json(msg='Only Round Robin pool options may be chosen when selecting an alias.')
obj['target_subnet'] = '32'
elif self.pfsense.is_ipv4_address(address):
obj['target'] = address
obj['target_subnet'] = '32'
else:
(addr, part) = self.pfsense.parse_ip_network(address, False, False)
if addr is None:
self.module.fail_json(msg='Cannot parse address %s, not IP or alias' % (address))
obj['target'] = addr
obj['target_subnet'] = str(part)
del obj['address']
self._parse_ports(obj, ports, 'natport', param)
def _validate_params(self):
""" do some extra checks on input parameters """
if self.params.get('after'):
if self.params['after'] == self.params['descr']:
self.module.fail_json(msg='Cannot specify the current rule in after')
elif self.params.get('before'):
if self.params['before'] == self.params['descr']:
self.module.fail_json(msg='Cannot specify the current rule in before')
if self.params.get('source_hash_key') is not None and self.params['source_hash_key'].startswith('0x'):
hash = self.params['source_hash_key'][2:]
if len(hash) != 32 or not all(c in hexdigits for c in hash):
self.module.fail_json(msg='Incorrect format for source-hash key, \"0x\" must be followed by exactly 32 hexadecimal characters.')
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.obj
self._insert(self.target_elt)
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
# Remove empty/None optional fields from obj when the XML doesn't have them to avoid false change detection
# (copy_dict_to_element would create empty elements and report changed=True even though the semantic value is unchanged)
for field in ['poolopts', 'source_hash_key', 'target', 'target_subnet', 'address']:
if field in self.obj and (self.obj[field] == '' or self.obj[field] is None) and field not in before:
del self.obj[field]
# pfSense omits for single-host IPs (implicit /32).
# Don't create it if the target itself hasn't changed.
if ('target_subnet' in self.obj and 'target_subnet' not in before
and 'target' in self.obj and before.get('target', '') == self.obj['target']):
del self.obj['target_subnet']
# pfSense XML may use either 'network' or 'address' as the key inside /.
# Adjust to match the existing XML to avoid a phantom sub-element swap.
for field in ('source', 'destination'):
if field in self.obj and isinstance(self.obj[field], dict) and 'network' in self.obj[field]:
field_elt = self.target_elt.find(field)
if field_elt is not None and field_elt.find('network') is None and field_elt.find('address') is not None:
self.obj[field]['address'] = self.obj[field].pop('network')
# pfSense may store a special reference (e.g. 'other-subnet') in instead of the raw IP.
# When the existing XML target is not a plain IP and the semantic value hasn't changed, preserve it.
if 'target' in self.obj:
xml_target_elt = self.target_elt.find('target')
if xml_target_elt is not None and xml_target_elt.text:
xml_target = xml_target_elt.text
if (xml_target != self.obj['target']
and not self.pfsense.is_ipv4_address(xml_target)
and not self.pfsense.is_ipv4_network(xml_target, False)):
self.obj['target'] = xml_target
if 'target_subnet' in self.obj and self.target_elt.find('target_subnet') is None:
del self.obj['target_subnet']
# For presence-based booleans (true_val=''), pfSense may store the element text as 'yes' or '' - both mean "present/true".
# Match the existing XML text so copy_dict_to_element doesn't see a difference.
for param, (false_val, true_val) in NAT_OUTBOUND_BOOL_VALUES.items():
if true_val == '' and param in self.obj and self.obj[param] == '':
param_elt = self.target_elt.find(param)
if param_elt is not None and param_elt.text is not None and param_elt.text != '':
self.obj[param] = param_elt.text
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
if self._remove_deleted_params():
changed = True
if self._update_rule_position(self.target_elt):
changed = True
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
target_elt = self.pfsense.new_element('rule')
return target_elt
def _find_first_rule_idx(self):
""" find the XML first rule idx """
for idx, rule_elt in enumerate(self.root_elt):
if rule_elt.tag != 'rule':
continue
return idx
return len(self.root_elt)
def _find_rule_by_descr(self, descr):
""" find the XML target_elt """
for idx, rule_elt in enumerate(self.root_elt):
if rule_elt.tag != 'rule':
continue
if rule_elt.find('descr').text == descr:
return (rule_elt, idx)
return (None, None)
def _find_target(self):
""" find the XML target_elt """
for rule_elt in self.root_elt:
if rule_elt.tag != 'rule':
continue
if rule_elt.find('descr').text == self.obj['descr']:
return rule_elt
return None
def _get_expected_rule_position(self):
""" get expected rule position in interface/floating """
if self.before == 'bottom':
return len(self.root_elt)
elif self.after == 'top':
return self._find_first_rule_idx()
elif self.after is not None:
return self._get_rule_position(self.after) + 1
elif self.before is not None:
position = self._get_rule_position(self.before) - 1
if position < 0:
return self._find_first_rule_idx()
return position
else:
position = self._get_rule_position(self.after, fail=False)
if position is not None:
return position
return len(self.root_elt)
return -1
def _get_expected_rule_xml_index(self):
""" get expected rule index in xml """
if self.before == 'bottom':
return len(self.root_elt)
elif self.after == 'top':
return self._find_first_rule_idx()
elif self.after is not None:
found, i = self._find_rule_by_descr(self.after)
if found is not None:
return i + 1
else:
self.module.fail_json(msg='Failed to insert after rule=%s' % (self.after))
elif self.before is not None:
found, i = self._find_rule_by_descr(self.before)
if found is not None:
return i
else:
self.module.fail_json(msg='Failed to insert before rule=%s' % (self.before))
else:
found, i = self._find_rule_by_descr(self.obj['descr'])
if found is not None:
return i
return len(self.root_elt)
return -1
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['disabled', 'nonat', 'invert', 'staticnatport', 'nosync', 'dstport', 'natport', 'ipprotocol', 'protocol']
def _get_rule_position(self, descr=None, fail=True):
""" get rule position in interface/floating """
if descr is None:
descr = self.obj['descr']
(res, idx) = self._find_rule_by_descr(descr)
if fail and res is None:
self.module.fail_json(msg='Failed to find rule=%s' % (descr))
return idx
def _insert(self, rule_elt):
""" insert rule into xml """
rule_xml_idx = self._get_expected_rule_xml_index()
self.root_elt.insert(rule_xml_idx, rule_elt)
def _update_rule_position(self, rule_elt):
""" move rule in xml if required """
current_position = self._get_rule_position()
expected_position = self._get_expected_rule_position()
if current_position == expected_position:
self.position_changed = False
return False
self.root_elt.remove(rule_elt)
self._insert(rule_elt)
self.position_changed = True
return True
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('natconf'); clear_subsystem_dirty('filter'); }''')
##############################
# Logging
#
@staticmethod
def fvalue_protocol(value):
""" boolean value formatting function """
if value is None or value == 'none':
return 'any'
return value
@staticmethod
def fvalue_ipprotocol(value):
""" boolean value formatting function """
if value is None or value == 'none':
return 'inet46'
return value
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
fafter = self._obj_to_log_fields(self.obj)
if before is None:
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'nonat', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'interface')
values += self.format_cli_field(self.params, 'ipprotocol', fvalue=self.fvalue_ipprotocol, default='inet46')
values += self.format_cli_field(self.params, 'protocol', fvalue=self.fvalue_protocol, default='any')
values += self.format_cli_field(self.params, 'source')
values += self.format_cli_field(self.params, 'destination')
values += self.format_cli_field(self.params, 'invert', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(fafter, 'address', default='')
values += self.format_cli_field(self.params, 'poolopts', default='')
values += self.format_cli_field(self.obj, 'source_hash_key', default='')
values += self.format_cli_field(self.params, 'staticnatport', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'nosync', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'after')
values += self.format_cli_field(self.params, 'before')
else:
fbefore = self._obj_to_log_fields(before)
fafter['before'] = self.before
fafter['after'] = self.after
values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'nonat', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'interface', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ipprotocol', fvalue=self.fvalue_ipprotocol, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'protocol', fvalue=self.fvalue_protocol, add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'source', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'destination', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'invert', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'address', default='', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'poolopts', default='', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'source_hash_key', default='', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'staticnatport', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'nosync', fvalue=self.fvalue_bool, default=False, add_comma=(values))
if self.position_changed:
values += self.format_updated_cli_field(fafter, {}, 'after', log_none=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, {}, 'before', log_none=False, add_comma=(values))
return values
def _obj_address_to_log_field(self, rule, addr, target, port):
""" return formated address from dict """
field = ''
if addr in rule:
# pfSense XML may use 'address' instead of 'network' as the key
actual_target = target
if target not in rule[addr] and target == 'network' and 'address' in rule[addr]:
actual_target = 'address'
if actual_target in rule[addr]:
if self.pfsense.interfaces.find(rule[addr][actual_target]):
field = 'NET:'
field += rule[addr][actual_target]
elif addr == 'destination' and 'any' in rule[addr]:
field = 'any'
if port in rule and rule[port] is not None and rule[port] != '':
field += ':'
field += rule[port].replace(':', '-')
return field
def _obj_to_log_fields(self, rule):
""" return formated source and destination from dict """
res = {}
res['source'] = self._obj_address_to_log_field(rule, 'source', 'network', 'sourceport')
res['destination'] = self._obj_address_to_log_field(rule, 'destination', 'network', 'dstport')
res['interface'] = self.pfsense.get_interface_display_name(rule['interface'])
if rule.get('target', '') != '':
if re.match(r'[a-zA-Z]', rule['target']) or rule.get('target_subnet') is None:
res['address'] = rule['target']
else:
res['address'] = rule['target'] + '/' + rule['target_subnet']
if rule.get('natport', '') != '':
res['address'] += ':'
res['address'] += rule['natport'].replace(':', '-')
return res
================================================
FILE: plugins/module_utils/nat_port_forward.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# Copyright: (c) 2023, Orion Poplwski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule
NAT_PORT_FORWARD_ARGUMENT_SPEC = dict(
descr=dict(required=True, type='str'),
state=dict(default='present', choices=['present', 'absent']),
disabled=dict(default=False, required=False, type='bool'),
nordr=dict(default=False, required=False, type='bool'),
interface=dict(required=False, type='str'),
ipprotocol=dict(default='inet', choices=['inet', 'inet6']),
protocol=dict(default='tcp', required=False, choices=["tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "pim", "ospf"]),
source=dict(required=False, type='str'),
destination=dict(required=False, type='str'),
target=dict(required=False, type='str'),
natreflection=dict(default='system-default', choices=["system-default", "enable", "purenat", "disable"]),
associated_rule=dict(default='associated', required=False, choices=["associated", "unassociated", "pass", "none"]),
nosync=dict(default=False, required=False, type='bool'),
after=dict(required=False, type='str'),
before=dict(required=False, type='str'),
)
NAT_PORT_FORWARD_REQUIRED_IF = [
["state", "present", ["interface", "source", "destination", "target"]]
]
class PFSenseNatPortForwardModule(PFSenseModuleBase):
""" module managing pfsense NAT rules """
@staticmethod
def get_argument_spec():
""" return argument spec """
return NAT_PORT_FORWARD_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseNatPortForwardModule, self).__init__(module, pfsense)
self.name = "pfsense_nat_port_forward"
# Override for use with aggregate
self.argument_spec = NAT_PORT_FORWARD_ARGUMENT_SPEC
self.obj = dict()
self.after = None
self.before = None
self.position_changed = False
self.root_elt = self.pfsense.get_element('nat')
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('nat')
self.pfsense.root.append(self.root_elt)
self.pfsense_rule_module = None
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
obj = dict()
self.obj = obj
obj['descr'] = self.params['descr']
if self.params['state'] == 'present':
obj['interface'] = self.pfsense.parse_interface(self.params['interface'])
if self.pfsense.is_at_least_2_5_0():
self._get_ansible_param(obj, 'ipprotocol')
self._get_ansible_param(obj, 'protocol')
self._get_ansible_param(obj, 'poolopts')
self._get_ansible_param(obj, 'source_hash_key')
self._get_ansible_param(obj, 'natport')
self._get_ansible_param(obj, 'natreflection')
if obj['natreflection'] == 'system-default':
del obj['natreflection']
if self.params['associated_rule'] == 'pass':
obj['associated-rule-id'] = 'pass'
elif self.params['associated_rule'] == 'unassociated' and self._find_target() is not None:
self.module.fail_json(msg='You cannot set an unassociated filter rule if the NAT rule already exists.')
else:
obj['associated-rule-id'] = ''
self._get_ansible_param_bool(obj, 'disabled')
self._get_ansible_param_bool(obj, 'nordr')
self._get_ansible_param_bool(obj, 'nosync')
if 'after' in self.params and self.params['after'] is not None:
self.after = self.params['after']
if 'before' in self.params and self.params['before'] is not None:
self.before = self.params['before']
obj['source'] = self.pfsense.parse_address(self.params['source'], allow_self=False)
obj['destination'] = self.pfsense.parse_address(self.params['destination'])
self._parse_target_address(obj)
return obj
def _parse_target_address(self, obj):
""" validate param address field and returns it as a dict """
if self.params.get('target') is None or self.params['target'] == '':
self.module.fail_json(msg='The field Redirect target IP is required.')
param = self.params['target']
addr = param.split(':')
if len(addr) > 2:
self.module.fail_json(msg='Cannot parse address %s' % (param))
address = addr[0]
ports = addr[1] if len(addr) > 1 else None
if self.pfsense.find_alias(address, 'host') is not None or self.pfsense.is_ipv4_address(address):
obj['target'] = address
else:
self.module.fail_json(msg='"%s" is not a valid redirect target IP address or host alias.' % (param))
if ports is None:
if self.params['protocol'] in ["tcp", "udp", "tcp/udp"]:
self.module.fail_json(msg='Must specify a target port with protocol "{0}".'.format(self.params['protocol']))
else:
# pfSense seems to always add an empty local-port element
obj['local-port'] = ''
if ports is not None:
if self.params['protocol'] not in ["tcp", "udp", "tcp/udp"]:
self.module.fail_json(msg='Cannot specify a target port with protocol "{0}".'.format(self.params['protocol']))
elif self.pfsense.is_port_or_alias(ports):
obj['local-port'] = ports
else:
self.module.fail_json(msg='"{0}" is not a valid redirect target port. It must be a port alias or integer between 1 and 65535.'.format(ports))
def _validate_params(self):
""" do some extra checks on input parameters """
if self.params.get('after') and self.params.get('before'):
self.module.fail_json(msg='Cannot specify both after and before')
elif self.params.get('after'):
if self.params['after'] == self.params['descr']:
self.module.fail_json(msg='Cannot specify the current rule in after')
elif self.params.get('before'):
if self.params['before'] == self.params['descr']:
self.module.fail_json(msg='Cannot specify the current rule in before')
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
self._set_associated_rule()
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
self._insert(self.target_elt)
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
changed = self._set_associated_rule(before)
if self.pfsense.copy_dict_to_element(self.obj, self.target_elt):
changed = True
if self._remove_deleted_params():
changed = True
if self._update_rule_position(self.target_elt):
changed = True
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
return (before, changed)
def _create_associated_rule(self):
if self.pfsense_rule_module is None:
self.pfsense_rule_module = PFSenseRuleModule(self.module, self.pfsense)
params = dict()
params['name'] = 'NAT ' + self.params['descr']
params['state'] = 'present'
params['action'] = 'pass'
if self.pfsense.is_at_least_2_5_0():
params['ipprotocol'] = 'inet'
params['statetype'] = 'keep state'
params['interface'] = self.params['interface']
params['source'] = self.params['source']
params['destination'] = self.params['target']
params['disabled'] = self.params['disabled']
params['protocol'] = self.params['protocol']
if self.params['associated_rule'] == 'associated':
params['associated-rule-id'] = self.pfsense.uniqid('nat_', True)
self.obj['associated-rule-id'] = params['associated-rule-id']
self.result['commands'] = list()
self.pfsense_rule_module.run(params)
self.result['commands'] += self.pfsense_rule_module.result['commands']
def _create_target(self):
""" create the XML target_elt """
target_elt = self.pfsense.new_element('rule')
return target_elt
def _delete_associated_rule(self, ruleid, interface=None):
if ruleid is None or ruleid == '' or ruleid == 'pass':
return
if interface is None:
interface = self.params['interface']
self.pfsense_rule_module = PFSenseRuleModule(self.module, self.pfsense)
params = dict()
if self.params['descr'] is None:
params['name'] = 'NAT '
else:
params['name'] = 'NAT ' + self.params['descr']
params['interface'] = interface
params['state'] = 'absent'
params['associated-rule-id'] = ruleid
self.pfsense_rule_module.run(params)
self.result['commands'] += self.pfsense_rule_module.result['commands']
def _find_rule_by_descr(self, descr):
""" find the XML target_elt """
for idx, rule_elt in enumerate(self.root_elt):
if rule_elt.tag != 'rule':
continue
if rule_elt.find('descr').text == descr:
return (rule_elt, idx)
return (None, None)
def _find_target(self):
""" find the XML target_elt """
for rule_elt in self.root_elt:
if rule_elt.tag != 'rule':
continue
if rule_elt.find('descr').text == self.obj['descr']:
return rule_elt
return None
def _get_expected_rule_position(self):
""" get expected rule position in interface/floating """
if self.before == 'bottom':
return len(self.root_elt)
elif self.after == 'top':
return 0
elif self.after is not None:
return self._get_rule_position(self.after) + 1
elif self.before is not None:
position = self._get_rule_position(self.before) - 1
if position < 0:
return 0
return position
else:
position = self._get_rule_position(self.after, fail=False)
if position is not None:
return position
return len(self.root_elt)
return -1
def _get_expected_rule_xml_index(self):
""" get expected rule index in xml """
if self.before == 'bottom':
return len(self.root_elt)
elif self.after == 'top':
return 0
elif self.after is not None:
found, i = self._find_rule_by_descr(self.after)
if found is not None:
return i + 1
else:
self.module.fail_json(msg='Failed to insert after rule=%s' % (self.after))
elif self.before is not None:
found, i = self._find_rule_by_descr(self.before)
if found is not None:
return i
else:
self.module.fail_json(msg='Failed to insert before rule=%s' % (self.before))
else:
found, i = self._find_rule_by_descr(self.obj['descr'])
if found is not None:
return i
return len(self.root_elt)
return -1
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['disabled', 'nordr', 'nosync', 'natreflection']
def _get_rule_position(self, descr=None, fail=True):
""" get rule position in interface/floating """
if descr is None:
descr = self.obj['descr']
(res, idx) = self._find_rule_by_descr(descr)
if fail and res is None:
self.module.fail_json(msg='Failed to find rule=%s' % (descr))
return idx
def _insert(self, rule_elt):
""" insert rule into xml """
rule_xml_idx = self._get_expected_rule_xml_index()
self.root_elt.insert(rule_xml_idx, rule_elt)
def _pre_remove_target_elt(self):
""" processing before removing elt """
ruleid_elt = self.target_elt.find('associated-rule-id')
if ruleid_elt is not None:
self._delete_associated_rule(ruleid_elt.text)
def _set_associated_rule(self, before=None):
""" manage changes to the associated rule """
if before is None:
if self.params['associated_rule'] == 'associated' or self.params['associated_rule'] == 'unassociated':
self._create_associated_rule()
else:
if self.params['associated_rule'] == 'associated':
if before['associated-rule-id'].startswith('nat_'):
if self.obj['interface'] != before['interface']:
self._delete_associated_rule(before['associated-rule-id'], before['interface'])
else:
self.obj['associated-rule-id'] = before['associated-rule-id']
return
self._create_associated_rule()
elif before['associated-rule-id'].startswith('nat_'):
self._delete_associated_rule(before['associated-rule-id'])
def _update_rule_position(self, rule_elt):
""" move rule in xml if required """
current_position = self._get_rule_position()
expected_position = self._get_expected_rule_position()
if current_position == expected_position:
self.position_changed = False
return False
self.diff['before']['position'] = current_position
self.diff['after']['position'] = expected_position
self.root_elt.remove(rule_elt)
self._insert(rule_elt)
self.position_changed = True
return True
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('natconf'); clear_subsystem_dirty('filter'); }''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['descr'])
@staticmethod
def fassociate(value):
""" associated-rule-id value formatting function """
if value is None or value == '':
return 'none'
if value == 'pass':
return 'pass'
return 'associated'
@staticmethod
def fnatreflection(value):
""" natreflection value formatting function """
if value is None or value == 'none':
return "'system-default'"
return value
@staticmethod
def fprotocol(value):
""" protocol value formatting function """
if value is None or value == 'none':
return 'any'
return value
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
fafter = self._obj_to_log_fields(self.obj)
if before is None:
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'nordr', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'interface')
if self.pfsense.is_at_least_2_5_0():
values += self.format_cli_field(self.params, 'ipprotocol', default='inet')
values += self.format_cli_field(self.params, 'protocol', default='tcp')
values += self.format_cli_field(self.params, 'source')
values += self.format_cli_field(self.params, 'destination')
values += self.format_cli_field(self.params, 'target')
values += self.format_cli_field(self.params, 'natreflection', default='system-default')
values += self.format_cli_field(self.params, 'associated_rule', default='associated')
values += self.format_cli_field(self.params, 'nosync', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'after')
values += self.format_cli_field(self.params, 'before')
else:
fbefore = self._obj_to_log_fields(before)
fafter['before'] = self.before
fafter['after'] = self.after
values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'nordr', fvalue=self.fvalue_bool, default=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'interface', add_comma=(values))
if self.pfsense.is_at_least_2_5_0():
values += self.format_updated_cli_field(self.obj, before, 'ipprotocol', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'protocol', fvalue=self.fprotocol, add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'source', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'destination', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'target', add_comma=(values))
values += self.format_updated_cli_field(
self.obj, before, 'natreflection', fvalue=self.fnatreflection, default='system-default', add_comma=(values)
)
values += self.format_updated_cli_field(self.obj, before, 'associated-rule-id', fvalue=self.fassociate, fname='associated_rule', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'nosync', fvalue=self.fvalue_bool, default=False, add_comma=(values))
if self.position_changed:
values += self.format_updated_cli_field(fafter, {}, 'after', log_none=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, {}, 'before', log_none=False, add_comma=(values))
return values
@staticmethod
def _obj_address_to_log_field(rule, addr):
""" return formated address from dict """
field = ''
if isinstance(rule[addr], dict):
if 'any' in rule[addr]:
field = 'any'
if 'address' in rule[addr]:
field = rule[addr]['address']
if 'port' in rule[addr]:
if field:
field += ':'
field += rule[addr]['port']
else:
field = rule[addr]
return field
def _obj_to_log_fields(self, rule):
""" return formated source and destination from dict """
res = {}
res['source'] = self._obj_address_to_log_field(rule, 'source')
res['destination'] = self._obj_address_to_log_field(rule, 'destination')
res['target'] = rule['target']
if 'local-port' in rule:
res['target'] += ':' + rule['local-port']
res['interface'] = self.pfsense.get_interface_display_name(rule['interface'])
return res
================================================
FILE: plugins/module_utils/openvpn_client.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2020-2021, Orion Poplawski
# Copyright: (c) 2020, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
OPENVPN_CLIENT_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
mode=dict(default='p2p_tls', required=False, choices=['p2p_tls', 'p2p_shared_key']),
authmode=dict(default=list(), required=False, type='list', elements='str'),
state=dict(default='present', choices=['present', 'absent']),
custom_options=dict(default=None, required=False, type='str'),
disable=dict(default=False, required=False, type='bool'),
interface=dict(default='wan', required=False, type='str'),
server_addr=dict(required=True, type='str'),
server_port=dict(default=1194, required=False, type='int'),
protocol=dict(default='UDP4', required=False, choices=['UDP4', 'TCP4']),
dev_mode=dict(default='tun', required=False, choices=['tun', 'tap']),
tls=dict(required=False, type='str'),
tls_type=dict(default='auth', required=False, choices=['auth', 'crypt']),
ca=dict(required=False, type='str'),
crl=dict(required=False, type='str'),
cert=dict(required=False, type='str'),
cert_depth=dict(default=1, required=False, type='int'),
strictusercn=dict(default=False, required=False, type='bool'),
shared_key=dict(required=False, type='str', no_log=True),
dh_length=dict(default=2048, required=False, type='int'),
ecdh_curve=dict(default='none', required=False, choices=['none', 'prime256v1', 'secp384r1', 'secp521r1']),
ncp_enable=dict(default=False, required=False, type='bool'),
# ncp_ciphers=dict(default=list('AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'), required=False,
# choices=['AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], type='list', elements='str'),
data_ciphers=dict(default=None, required=False, choices=['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], type='list', elements='str'),
data_ciphers_fallback=dict(default='AES-256-CBC', required=False, choices=['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305']),
digest=dict(default='SHA256', required=False, type='str'),
tunnel_network=dict(default='', required=False, type='str'),
tunnel_networkv6=dict(default='', required=False, type='str'),
remote_network=dict(default='', required=False, type='str'),
remote_networkv6=dict(default='', required=False, type='str'),
gwredir=dict(default=False, required=False, type='bool'),
gwredir6=dict(default=False, required=False, type='bool'),
maxclients=dict(default=None, required=False, type='int'),
compression=dict(default='adaptive', required=False, choices=['adaptive', '']),
compression_push=dict(default=False, required=False, type='bool'),
passtos=dict(default=False, required=False, type='bool'),
client2client=dict(default=False, required=False, type='bool'),
dynamic_ip=dict(default=False, required=False, type='bool'),
topology=dict(default='subnet', required=False, choices=['net30', 'subnet']),
dns_domain=dict(default='', required=False, type='str'),
dns_client1=dict(default='', required=False, type='str'),
dns_client2=dict(default='', required=False, type='str'),
dns_client3=dict(default='', required=False, type='str'),
dns_client4=dict(default='', required=False, type='str'),
push_register_dns=dict(default=False, required=False, type='bool'),
create_gw=dict(default='both', required=False, choices=['both', 'v4only', 'v6only']),
verbosity_level=dict(default=3, required=False, type='int'),
)
OPENVPN_CLIENT_REQUIRED_IF = [
['mode', 'p2p_tls', ['ca']],
['mode', 'p2p_shared_key', ['shared_key']],
]
OPENVPN_CLIENT_PHP_COMMAND_PREFIX = """
require_once('openvpn.inc');
$ovpn = config_get_path('openvpn/openvpn-client', [])[{idx}];
"""
OPENVPN_CLIENT_PHP_COMMAND_SET = OPENVPN_CLIENT_PHP_COMMAND_PREFIX + """
openvpn_resync('client',$ovpn);
"""
OPENVPN_CLIENT_PHP_COMMAND_DEL = OPENVPN_CLIENT_PHP_COMMAND_PREFIX + """
openvpn_delete($ovpn);
unset($ovpn);
openvpn_resync('client',$ovpn);
"""
class PFSenseOpenVPNClientModule(PFSenseModuleBase):
""" module managing pfSense OpenVPN configuration """
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseOpenVPNClientModule, self).__init__(module, pfsense)
self.name = "pfsense_openvpn"
self.root_elt = self.pfsense.get_element('openvpn', create_node=True)
self.obj = dict()
cmd = ('require_once("openvpn.inc");;'
'$digestlist = openvpn_get_digestlist();'
'echo json_encode($digestlist);')
self.digestlist = self.pfsense.php(cmd)
##############################
# params processing
#
def _get_digest_name(self, digest: str):
for dname, ddescr in self.digestlist.items():
if digest == dname or digest == ddescr:
return dname
self.module.fail_json(msg=f"Invalid digest '{digest}'")
def _params_to_obj(self):
""" return dict from module params """
obj = dict()
obj['description'] = self.params['name']
if self.params['state'] == 'present':
obj['custom_options'] = self.params['custom_options']
self._get_ansible_param_bool(obj, 'disable')
self._get_ansible_param_bool(obj, 'strictusercn')
obj['mode'] = self.params['mode']
obj['dev_mode'] = self.params['dev_mode']
obj['interface'] = self.params['interface']
obj['protocol'] = self.params['protocol']
obj['server_addr'] = self.params['server_addr']
obj['server_port'] = str(self.params['server_port'])
self._get_ansible_param(obj, 'maxclients')
obj['verbosity_level'] = str(self.params['verbosity_level'])
obj['data_ciphers_fallback'] = self.params['data_ciphers_fallback']
obj['data_ciphers'] = ",".join(self.params['data_ciphers'])
self._get_ansible_param_bool(obj, 'ncp_enable', 'enabled')
self._get_ansible_param_bool(obj, 'gwredir')
self._get_ansible_param_bool(obj, 'gwredirr6')
self._get_ansible_param_bool(obj, 'compression_push')
self._get_ansible_param_bool(obj, 'passtos')
self._get_ansible_param_bool(obj, 'client2client')
self._get_ansible_param_bool(obj, 'dynamic_ip')
self._get_ansible_param_bool(obj, 'push_register_dns')
obj['digest'] = self._get_digest_name(self.params['digest'])
obj['tunnel_network'] = self.params['tunnel_network']
obj['tunnel_networkv6'] = self.params['tunnel_networkv6']
obj['remote_network'] = self.params['remote_network']
obj['remote_networkv6'] = self.params['remote_networkv6']
obj['compression'] = self.params['compression']
obj['topology'] = self.params['topology']
obj['create_gw'] = self.params['create_gw']
if 'user' in self.params['mode']:
obj['authmode'] = ",".join(self.params['authmode'])
if 'tls' in self.params['mode']:
# Find the caref id for the named CA
if self.params is not None:
ca_elt = self.pfsense.find_ca_elt(self.params['ca'])
if ca_elt is None:
self.module.fail_json(msg='%s is not a valid certificate authority' % (self.params['ca']))
obj['caref'] = ca_elt.find('refid').text
# Find the crlref id for the named CRL if any
if self.params['crl'] is not None:
crl_elt = self.pfsense.find_crl_elt(self.params['crl'])
if crl_elt is None:
self.module.fail_json(msg='%s is not a valid certificate revocation list' % (self.params['crl']))
obj['crlref'] = crl_elt.find('refid').text
else:
obj['crlref'] = ''
# Find the certref id for the named certificate if any
if self.params['cert'] is not None:
cert_elt = self.pfsense.find_cert_elt(self.params['cert'])
if cert_elt is None:
self.module.fail_json(msg='%s is not a valid certificate' % (self.params['cert']))
obj['certref'] = cert_elt.find('refid').text
if self.params['tls'] is not None:
obj['tls'] = self.params['tls']
obj['tls_type'] = self.params['tls_type']
if self.params['mode'] == 'p2p_shared_key':
obj['shared_key'] = self.params['shared_key']
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
self.pfsense.validate_string(params['name'], 'openvpn')
if params['state'] == 'absent':
return True
# tls is not valid for p2p_shared_key
if params['mode'] == 'p2p_shared_key' and params['tls'] is not None:
self.module.fail_json(msg='tls parameter is not valied with p2p_shared_key mode.')
# check tunnel_networks - can be network alias or non-strict IP CIDR network
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network'), 'ipv4')
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network6'), 'ipv6')
# Check auth clients
if len(params['authmode']) > 0:
system = self.pfsense.get_element('system')
for authsrv in params['authmode']:
if len(system.findall("authclient[name='{0}']".format(authsrv))) == 0:
self.module.fail_json(msg='Cannot find authentication client {0}.'.format(authsrv))
# validate key
for param in ['shared_key', 'tls']:
if params[param] is not None:
key = params[param]
if key == 'generate':
# generate during params_to_obj
pass
elif re.search('^-----BEGIN OpenVPN Static key V1-----.*-----END OpenVPN Static key V1-----$', key, flags=re.MULTILINE | re.DOTALL):
params[param] = base64.b64encode(key.encode()).decode()
else:
key_decoded = base64.b64decode(key.encode()).decode()
if not re.search('^-----BEGIN OpenVPN Static key V1-----.*-----END OpenVPN Static key V1-----$',
key_decoded, flags=re.MULTILINE | re.DOTALL):
self.module.fail_json(msg='Could not recognize {0} key format: {1}'.format(param, key_decoded))
def _nextvpnid(self):
""" find next available vpnid """
vpnid = 1
while len(self.root_elt.findall("*[vpnid='{0}']".format(vpnid))) != 0:
vpnid += 1
return str(vpnid)
##############################
# XML processing
#
def _find_openvpn_client(self, value, field='description'):
""" return openvpn-client element """
i = 0
for elt in self.root_elt.findall('openvpn-client'):
field_elt = elt.find(field)
if field_elt is not None and field_elt.text == value:
return (elt, i)
i += 1
return (None, -1)
def _find_last_openvpn_idx(self):
i = 0
for elt in self.root_elt.findall('openvpn-client'):
i += 1
return i
def _copy_and_update_target(self):
""" update the XML target_elt """
(before, changed) = super(PFSenseOpenVPNClientModule, self)._copy_and_update_target()
if not changed:
self.diff['after'] = self.obj
self.result['vpnid'] = int(before['vpnid'])
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
target_elt = self.pfsense.new_element('openvpn-client')
self.obj['vpnid'] = self._nextvpnid()
self.result['vpnid'] = int(self.obj['vpnid'])
self.diff['before'] = ''
self.diff['after'] = self.obj
self.result['changed'] = True
self.idx = self._find_last_openvpn_idx()
self.result['idx'] = self.idx
return target_elt
def _find_target(self):
""" find the XML target_elt """
(target_elt, self.idx) = self._find_openvpn_client(self.obj['description'])
for param in ['shared_key', 'tls']:
current_elt = self.pfsense.get_element(param, target_elt)
if self.params[param] == 'generate':
if current_elt is None:
(dummy, key, stderr) = self.module.run_command('/usr/local/sbin/openvpn --genkey secret /dev/stdout')
if stderr != "":
self.module.fail_json(msg='generate for "{0}" secret key: {1}'.format(param, stderr))
self.obj[param] = base64.b64encode(key.encode()).decode()
self.result[param] = self.obj[param]
else:
self.obj[param] = current_elt.text
return target_elt
def _remove_target_elt(self):
""" delete target_elt from xml """
super(PFSenseOpenVPNClientModule, self)._remove_target_elt()
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
##############################
# run
#
def _remove(self):
""" delete obj """
self.diff['after'] = ''
self.diff['before'] = ''
super(PFSenseOpenVPNClientModule, self)._remove()
return self.pfsense.phpshell(OPENVPN_CLIENT_PHP_COMMAND_DEL.format(idx=self.idx))
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell(OPENVPN_CLIENT_PHP_COMMAND_SET.format(idx=self.idx))
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'" + self.obj['description'] + "'"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'description')
else:
values += self.format_updated_cli_field(self.obj, before, 'description', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/openvpn_override.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2020-2022, Orion Poplawski
# Copyright: (c) 2020, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
OPENVPN_OVERRIDE_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
state=dict(default='present', choices=['present', 'absent']),
server_list=dict(default=None, type='list', elements='str'),
disable=dict(default=False, required=False, type='bool'),
descr=dict(default=None, required=False, type='str'),
block=dict(default=False, required=False, type='bool'),
tunnel_network=dict(default=None, required=False, type='str'),
tunnel_networkv6=dict(default=None, required=False, type='str'),
local_network=dict(default=None, required=False, type='str'),
local_networkv6=dict(default=None, required=False, type='str'),
remote_network=dict(default=None, required=False, type='str'),
remote_networkv6=dict(default=None, required=False, type='str'),
gwredir=dict(default=False, required=False, type='bool'),
push_reset=dict(default=False, required=False, type='bool'),
netbios_enable=dict(default=False, required=False, type='bool'),
netbios_ntype=dict(required=False, choices=['none', 'b-node', 'p-node', 'm-node', 'h-node']),
netbios_scope=dict(required=False, type='str'),
wins_server_enable=dict(default=False, required=False, type='bool'),
custom_options=dict(default=None, required=False, type='str'),
)
OPENVPN_OVERRIDE_REQUIRED_IF = [
]
OPENVPN_OVERRIDE_PHP_COMMAND_PREFIX = """
require_once('openvpn.inc');
$csc = config_get_path('openvpn/openvpn-csc')[{idx}];
"""
OPENVPN_OVERRIDE_PHP_COMMAND_SET = OPENVPN_OVERRIDE_PHP_COMMAND_PREFIX + """
openvpn_resync_csc($csc);
"""
OPENVPN_OVERRIDE_PHP_COMMAND_DEL = OPENVPN_OVERRIDE_PHP_COMMAND_PREFIX + """
openvpn_delete_csc($csc);
unset($csc);
"""
class PFSenseOpenVPNOverrideModule(PFSenseModuleBase):
""" module managing pfSense OpenVPN Client Specific Overrides """
@staticmethod
def get_argument_spec():
""" return argument spec """
return OPENVPN_OVERRIDE_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseOpenVPNOverrideModule, self).__init__(module, pfsense)
self.name = "pfsense_openvpn_override"
self.root_elt = self.pfsense.get_element('openvpn')
self.openvpn_csc_elt = self.root_elt.findall('openvpn-csc')
self.obj = dict()
##############################
# params processing
#
def _params_to_obj(self):
""" return dict from module params """
obj = dict()
obj['common_name'] = self.params['name']
if self.params['state'] == 'present':
# Find the ids for server names
server_list = list()
if self.params['server_list'] is not None:
for server in self.params['server_list']:
vpnid = ''
if isinstance(server, int) or (isinstance(server, str) and server.isdigit()):
openvpn_server_elt = self.pfsense.find_elt('openvpn-server', str(server), 'vpnid', root_elt=self.root_elt)
else:
openvpn_server_elt = self.pfsense.find_elt('openvpn-server', server, 'description', root_elt=self.root_elt)
if openvpn_server_elt is None:
self.module.fail_json(msg="Could not find openvpn server '%s'" % (server))
vpnid = openvpn_server_elt.find('vpnid').text
server_list.append(vpnid)
obj['server_list'] = ','.join(server_list)
self.result['vpnids'] = server_list
obj['custom_options'] = self.params['custom_options']
obj['description'] = self.params['descr']
self._get_ansible_param_bool(obj, 'disable')
self._get_ansible_param_bool(obj, 'block', force=True, value='yes')
self._get_ansible_param_bool(obj, 'gwredir', force=True, value='yes')
if self.pfsense.config_version >= 23.4:
self._get_ansible_param_bool(obj, 'push_reset')
else:
self._get_ansible_param_bool(obj, 'push_reset', force=True, value='yes')
obj['tunnel_network'] = self.params['tunnel_network']
obj['tunnel_networkv6'] = self.params['tunnel_networkv6']
obj['local_network'] = self.params['local_network']
obj['local_networkv6'] = self.params['local_networkv6']
obj['remote_network'] = self.params['remote_network']
obj['remote_networkv6'] = self.params['remote_networkv6']
self._get_ansible_param_bool(obj, 'netbios_enable')
if self.params['netbios_enable']:
obj['netbios_ntype'] = self.params['netbios_ntype']
obj['netbios_scope'] = str(self.params['netbios_scope'])
self._get_ansible_param(obj, 'netbios_scope')
self._get_ansible_param_bool(obj, 'wins_server_enable')
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
self.pfsense.validate_string(params['name'], 'openvpn_override')
if params['state'] == 'absent':
return True
# check tunnel_networks - can be network alias or non-strict IP CIDR network
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network'), 'ipv4')
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network6'), 'ipv6')
if params.get('local_network') and not self.pfsense.is_ipv4_network(params['local_network']):
self.module.fail_json(msg='A valid IPv4 network must be specified for local_network.')
if params.get('local_network6') and not self.pfsense.is_ipv6_network(params['local_networkv6']):
self.module.fail_json(msg='A valid IPv6 network must be specified for local_network6.')
if params.get('remote_network') and not self.pfsense.is_ipv4_network(params['remote_network']):
self.module.fail_json(msg='A valid IPv4 network must be specified for remote_network.')
if params.get('remote_network6') and not self.pfsense.is_ipv6_network(params['remote_networkv6']):
self.module.fail_json(msg='A valid IPv6 network must be specified for remote_network6.')
##############################
# XML processing
#
def _find_openvpn_csc(self, value, field='common_name'):
""" return openvpn-csc element """
i = 0
for csc_elt in self.openvpn_csc_elt:
field_elt = csc_elt.find(field)
if field_elt is not None and field_elt.text == value:
return (csc_elt, i)
i += 1
return (None, -1)
def _find_last_openvpn_idx(self):
i = 0
for elt in self.openvpn_csc_elt:
i += 1
return i
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
if self._remove_deleted_params():
changed = True
self.diff['before'] = before
if changed:
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
self.result['changed'] = True
else:
self.diff['after'] = self.obj
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
target_elt = self.pfsense.new_element('openvpn-csc')
self.diff['before'] = ''
self.diff['after'] = self.obj
self.result['changed'] = True
self.idx = self._find_last_openvpn_idx()
return target_elt
def _find_target(self):
""" find the XML target_elt """
(target_elt, self.idx) = self._find_openvpn_csc(self.obj['common_name'])
return target_elt
def _get_params_to_remove(self):
""" returns the list of params to remove if they are not set """
params_to_remove = []
if self.pfsense.config_version >= 23.4:
params_to_remove.append('push_reset')
return params_to_remove
def _remove_target_elt(self):
""" delete target_elt from xml """
super(PFSenseOpenVPNOverrideModule, self)._remove_target_elt()
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
##############################
# run
#
def _remove(self):
""" delete obj """
self.diff['after'] = ''
self.diff['before'] = ''
super(PFSenseOpenVPNOverrideModule, self)._remove()
return self.pfsense.phpshell(OPENVPN_OVERRIDE_PHP_COMMAND_DEL.format(idx=self.idx))
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell(OPENVPN_OVERRIDE_PHP_COMMAND_SET.format(idx=self.idx))
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'" + self.obj['common_name'] + "'"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'common_name')
values += self.format_cli_field(self.obj, 'descr')
else:
values += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/openvpn_server.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2020-2022, Orion Poplawski
# Copyright: (c) 2020, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
OPENVPN_SERVER_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
mode=dict(type='str', choices=['p2p_tls', 'p2p_shared_key', 'server_tls', 'server_tls_user', 'server_user']),
dco=dict(default=False, required=False, type='bool'),
authmode=dict(default=list(), required=False, type='list', elements='str'),
state=dict(default='present', choices=['present', 'absent']),
custom_options=dict(default=None, required=False, type='str'),
disable=dict(default=False, required=False, type='bool'),
interface=dict(default='wan', required=False, type='str'),
local_port=dict(default=1194, required=False, type='int'),
protocol=dict(default='UDP4', required=False, choices=['UDP4', 'TCP4']),
dev_mode=dict(default='tun', required=False, choices=['tun', 'tap']),
tls=dict(required=False, type='str'),
tls_type=dict(default='auth', required=False, choices=['auth', 'crypt']),
ca=dict(required=False, type='str'),
crl=dict(required=False, type='str'),
cert=dict(required=False, type='str'),
cert_depth=dict(default=1, required=False, type='int'),
strictusercn=dict(default=False, required=False, type='bool'),
remote_cert_tls=dict(default=False, required=False, type='bool'),
shared_key=dict(required=False, type='str', no_log=True),
dh_length=dict(default=2048, required=False, type='int'),
ecdh_curve=dict(default='none', required=False, choices=['none', 'prime256v1', 'secp384r1', 'secp521r1']),
data_ciphers=dict(default=['AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], required=False,
choices=['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], type='list', elements='str'),
data_ciphers_fallback=dict(default='AES-256-CBC', required=False, choices=['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305']),
digest=dict(default='SHA256', required=False, type='str'),
tunnel_network=dict(default='', required=False, type='str'),
tunnel_networkv6=dict(default='', required=False, type='str'),
local_network=dict(default='', required=False, type='str'),
local_networkv6=dict(default='', required=False, type='str'),
remote_network=dict(default='', required=False, type='str'),
remote_networkv6=dict(default='', required=False, type='str'),
gwredir=dict(default=False, required=False, type='bool'),
gwredir6=dict(default=False, required=False, type='bool'),
maxclients=dict(default=None, required=False, type='int'),
allow_compression=dict(default='no', required=False, choices=['no', 'asym', 'yes']),
compression=dict(default='', required=False, choices=['', 'none', 'stub', 'stub-v2', 'lz4', 'lz4-v2', 'lzo', 'noadapt', 'adaptive', 'yes', 'no']),
compression_push=dict(default=False, required=False, type='bool'),
passtos=dict(default=False, required=False, type='bool'),
client2client=dict(default=False, required=False, type='bool'),
dynamic_ip=dict(default=False, required=False, type='bool'),
topology=dict(default='subnet', required=False, choices=['net30', 'subnet']),
inactive_seconds=dict(default=0, required=False, type='int'),
keepalive_interval=dict(default=10, required=False, type='int'),
keepalive_timeout=dict(default=60, required=False, type='int'),
exit_notify=dict(default='none', required=False, choices=['none', '1', '2']),
dns_domain=dict(default='', required=False, type='str'),
dns_server1=dict(default='', required=False, type='str'),
dns_server2=dict(default='', required=False, type='str'),
dns_server3=dict(default='', required=False, type='str'),
dns_server4=dict(default='', required=False, type='str'),
push_register_dns=dict(default=False, required=False, type='bool'),
username_as_common_name=dict(default=False, required=False, type='bool'),
create_gw=dict(default='both', required=False, type='str', choices=['both', 'v4only', 'v6only']),
verbosity_level=dict(default=1, required=False, type='int'),
)
OPENVPN_SERVER_REQUIRED_IF = [
['state', 'present', ['mode']],
['mode', 'p2p_tls', ['ca']],
['mode', 'server_tls', ['ca']],
['mode', 'server_tls_user', ['ca']],
['mode', 'p2p_shared_key', ['shared_key']],
]
OPENVPN_SERVER_PHP_COMMAND_PREFIX = """
require_once('openvpn.inc');
alias_make_table();
$ovpn = config_get_path('openvpn/openvpn-server')[{idx}];
"""
OPENVPN_SERVER_PHP_COMMAND_SET = OPENVPN_SERVER_PHP_COMMAND_PREFIX + """
openvpn_resync('server',$ovpn);
openvpn_resync_csc_all();
"""
OPENVPN_SERVER_PHP_COMMAND_DEL = OPENVPN_SERVER_PHP_COMMAND_PREFIX + """
openvpn_delete('server',$ovpn);
"""
# Define the line endings in bytes for binary mode
UNIX_LINE_ENDING = b'\n'
WINDOWS_LINE_ENDING = b'\r\n'
class PFSenseOpenVPNServerModule(PFSenseModuleBase):
""" module managing pfSense OpenVPN configuration """
@staticmethod
def get_argument_spec():
""" return argument spec """
return OPENVPN_SERVER_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseOpenVPNServerModule, self).__init__(module, pfsense)
self.name = "pfsense_openvpn_server"
self.root_elt = self.pfsense.get_element('openvpn', create_node=True)
self.obj = dict()
cmd = ('require_once("openvpn.inc");;'
'$digestlist = openvpn_get_digestlist();'
'echo json_encode($digestlist);')
self.digestlist = self.pfsense.php(cmd)
##############################
# params processing
#
def _get_digest_name(self, digest: str):
for dname, ddescr in self.digestlist.items():
if digest == dname or digest == ddescr:
return dname
self.module.fail_json(msg=f"Invalid digest '{digest}'")
def _params_to_obj(self):
""" return dict from module params """
obj = dict()
obj['description'] = self.params['name']
if self.params['state'] == 'present':
obj['custom_options'] = self.params['custom_options']
self._get_ansible_param_bool(obj, 'disable')
self._get_ansible_param_bool(obj, 'strictusercn')
self._get_ansible_param_bool(obj, 'remote_cert_tls')
obj['mode'] = self.params['mode']
obj['dev_mode'] = self.params['dev_mode']
obj['interface'] = self.params['interface']
obj['protocol'] = self.params['protocol']
obj['local_port'] = str(self.params['local_port'])
self._get_ansible_param(obj, 'maxclients')
obj['verbosity_level'] = str(self.params['verbosity_level'])
obj['data_ciphers_fallback'] = self.params['data_ciphers_fallback']
obj['data_ciphers'] = ",".join(self.params['data_ciphers'])
self._get_ansible_param_bool(obj, 'gwredir', force=True, value='yes')
self._get_ansible_param_bool(obj, 'gwredir6', force=True, value='yes')
self._get_ansible_param_bool(obj, 'compression_push', force=True, value='yes', value_false='')
self._get_ansible_param_bool(obj, 'passtos', force=True, value='yes', value_false='')
self._get_ansible_param_bool(obj, 'client2client', force=True, value='yes', value_false='')
self._get_ansible_param_bool(obj, 'dynamic_ip', force=True, value='yes', value_false='')
self._get_ansible_param_bool(obj, 'push_register_dns')
self._get_ansible_param_bool(obj, 'username_as_common_name', force=True, value='enabled', value_false='disabled')
obj['digest'] = self._get_digest_name(self.params['digest'])
obj['tunnel_network'] = self.params['tunnel_network']
obj['tunnel_networkv6'] = self.params['tunnel_networkv6']
obj['local_network'] = self.params['local_network']
obj['local_networkv6'] = self.params['local_networkv6']
obj['remote_network'] = self.params['remote_network']
obj['remote_networkv6'] = self.params['remote_networkv6']
obj['allow_compression'] = self.params['allow_compression']
obj['compression'] = self.params['compression']
obj['topology'] = self.params['topology']
self._get_ansible_param(obj, 'inactive_seconds')
self._get_ansible_param(obj, 'keepalive_interval')
self._get_ansible_param(obj, 'keepalive_timeout')
obj['exit_notify'] = self.params['exit_notify']
obj['create_gw'] = self.params['create_gw']
if 'user' in self.params['mode']:
obj['authmode'] = ",".join(self.params['authmode'])
if 'tls' in self.params['mode']:
# Find the caref id for the named CA
if self.params is not None:
ca_elt = self.pfsense.find_ca_elt(self.params['ca'])
if ca_elt is None:
self.module.fail_json(msg='{0} is not a valid certificate authority'.format(self.params['ca']))
obj['caref'] = ca_elt.find('refid').text
# Find the crlref id for the named CRL if any
if self.params['crl'] is not None:
crl_elt = self.pfsense.find_crl_elt(self.params['crl'])
if crl_elt is None:
self.module.fail_json(msg='{0} is not a valid certificate revocation list'.format(self.params['crl']))
obj['crlref'] = crl_elt.find('refid').text
else:
obj['crlref'] = ''
# Find the certref id for the named certificate if any
if self.params['cert'] is not None:
cert_elt = self.pfsense.find_cert_elt(self.params['cert'])
if cert_elt is None:
self.module.fail_json(msg='{0} is not a valid certificate'.format(self.params['cert']))
obj['certref'] = cert_elt.find('refid').text
obj['cert_depth'] = str(self.params['cert_depth'])
obj['dh_length'] = str(self.params['dh_length'])
obj['ecdh_curve'] = self.params['ecdh_curve']
self._get_ansible_param(obj, 'tls')
if self.params['tls'] is not None:
obj['tls'] = self.params['tls']
obj['tls_type'] = self.params['tls_type']
if 'server' in self.params['mode']:
obj['dns_domain'] = self.params['dns_domain']
obj['dns_server1'] = self.params['dns_server1']
obj['dns_server2'] = self.params['dns_server2']
obj['dns_server3'] = self.params['dns_server3']
obj['dns_server4'] = self.params['dns_server4']
if self.params['mode'] == 'p2p_shared_key':
obj['shared_key'] = self.params['shared_key']
if self.params['dco']:
if not self.pfsense.is_ce_version():
self._get_ansible_param_bool(obj, 'dco', force=True, value='enabled', value_false='disabled')
# these are requirements for DCO
obj['allow_compression'] = 'no'
obj['data_ciphers_fallback'] = 'AES-256-GCM'
obj.pop('compression')
obj.pop('compression_push')
else:
self.module.warn("DCO option specified but not supported on CE versions, ignoring...")
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check name
self.pfsense.validate_string(params['name'], 'openvpn')
if params['state'] == 'absent':
return True
# tls is not valid for p2p_shared_key
if params['mode'] == 'p2p_shared_key' and params['tls'] is not None:
self.module.fail_json(msg='tls parameter is not valied with p2p_shared_key mode.')
# check tunnel_networks - can be network alias or non-strict IP CIDR network
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network'), 'ipv4')
self.pfsense.validate_openvpn_tunnel_network(params.get('tunnel_network6'), 'ipv6')
# Check auth servers
if len(params['authmode']) > 0:
system = self.pfsense.get_element('system')
for authsrv in params['authmode']:
if authsrv != 'Local Database' and len(system.findall("authserver[name='{0}']".format(authsrv))) == 0:
self.module.fail_json(msg='Cannot find authentication server {0}.'.format(authsrv))
# validate key
for param in ['shared_key', 'tls']:
if params[param] is not None:
key = params[param]
if key == 'generate':
# generate during _find_target (after _params_to_obj) - for just generate if not exists
pass
elif re.search('^-----BEGIN OpenVPN Static key V1-----.*-----END OpenVPN Static key V1-----$', key, flags=re.MULTILINE | re.DOTALL):
key = key.encode().replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING) # Normalize existing CRLF to LF
key = key.replace(UNIX_LINE_ENDING, WINDOWS_LINE_ENDING) # Convert all LF to CRLF
params[param] = base64.b64encode(key).decode()
else:
key_decoded = base64.b64decode(key.encode()).decode()
if not re.search('^-----BEGIN OpenVPN Static key V1-----.*-----END OpenVPN Static key V1-----$',
key_decoded, flags=re.MULTILINE | re.DOTALL):
self.module.fail_json(msg='Could not recognize {0} key format: {1}'.format(param, key_decoded))
def _openvpn_port_used(self, protocol, interface, port, vpnid=0):
for elt in self.root_elt.findall('*[local_port]'):
if (elt.find('disable')):
continue
this_vpnid = int(elt.find('vpnid').text)
if (this_vpnid == int(vpnid)):
continue
this_interface = elt.find('interface').text
this_protocol = elt.find('protocol').text
# (TCP|UDP)(4|6) does not conflict unless interface is any
if ((this_interface != "any" and interface != "any") and (len(protocol) == 4) and
(len(this_protocol) == 4) and (this_protocol[0:3] == protocol[0:3]) and (this_protocol[3] != protocol[3])):
continue
this_port_text = elt.find('local_port').text
if this_port_text is None:
continue
this_port = int(this_port_text)
if (this_port == port and (this_protocol[0:3] == protocol[0:3]) and
(this_interface == interface or this_interface == "any" or interface == "any")):
self.module.fail_json(msg='The specified local_port ({0}) is in use by vpn ID {1}'.format(port, this_vpnid))
def _nextvpnid(self):
""" find next available vpnid """
vpnid = 1
while len(self.root_elt.findall("*[vpnid='{0}']".format(vpnid))) != 0:
vpnid += 1
return str(vpnid)
##############################
# XML processing
#
def _find_openvpn_server(self, value, field='description'):
""" return openvpn-server element """
i = 0
for elt in self.root_elt.findall('openvpn-server'):
field_elt = elt.find(field)
if field_elt is not None and field_elt.text == value:
return (elt, i)
i += 1
return (None, -1)
def _find_last_openvpn_idx(self):
i = 0
for elt in self.root_elt.findall('openvpn-server'):
i += 1
return i
def _get_params_to_remove(self):
""" returns the list of params to remove if they are not set """
params_to_remove = []
for param in ['disable', 'push_register_dns', 'remote_cert_tls']:
if not self.params[param]:
params_to_remove.append(param)
return params_to_remove
def _copy_and_update_target(self):
""" update the XML target_elt """
(before, changed) = super(PFSenseOpenVPNServerModule, self)._copy_and_update_target()
# Check if local port is used
self._openvpn_port_used(self.params['protocol'], self.params['interface'], self.params['local_port'], before['vpnid'])
if not changed:
self.diff['after'] = self.obj
self.result['vpnid'] = int(before['vpnid'])
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
# Check if local port is used
self._openvpn_port_used(self.params['protocol'], self.params['interface'], self.params['local_port'])
target_elt = self.pfsense.new_element('openvpn-server')
self.obj['vpnid'] = self._nextvpnid()
self.result['vpnid'] = int(self.obj['vpnid'])
self.diff['before'] = ''
self.diff['after'] = self.obj
self.result['changed'] = True
self.idx = self._find_last_openvpn_idx()
return target_elt
def _find_target(self):
""" find the XML target_elt """
(target_elt, self.idx) = self._find_openvpn_server(self.obj['description'])
for param in ['shared_key', 'tls']:
current_elt = self.pfsense.get_element(param, target_elt)
if self.params[param] == 'generate':
if current_elt is None:
(dummy, key, stderr) = self.module.run_command('/usr/local/sbin/openvpn --genkey secret /dev/stdout')
if stderr != "":
self.module.fail_json(msg='generate for "{0}" secret key: {1}'.format(param, stderr))
self.obj[param] = base64.b64encode(key.encode()).decode()
self.result[param] = self.obj[param]
else:
self.obj[param] = current_elt.text
return target_elt
##############################
# run
#
def _pre_remove_target_elt(self):
""" processing before removing elt """
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
if len(self.pfsense.interfaces.findall("*[if='ovpns{0}']".format(self.diff['before']['vpnid']))) > 0:
self.module.fail_json(msg='Cannot delete the OpenVPN instance while the interface ovpns{0} is assigned. Remove the interface assignment first.'
.format(self.diff['before']['vpnid']))
self.result['vpnid'] = int(self.diff['before']['vpnid'])
self.command_output = self.pfsense.phpshell(OPENVPN_SERVER_PHP_COMMAND_DEL.format(idx=self.idx))
def _update(self):
""" make the target pfsense reload """
if self.params['state'] == 'present':
return self.pfsense.phpshell(OPENVPN_SERVER_PHP_COMMAND_SET.format(idx=self.idx))
else:
return self.command_output
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'" + self.obj['description'] + "'"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'description')
else:
values += self.format_updated_cli_field(self.obj, before, 'description', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/pfsense.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import sys
if sys.version_info >= (3, 4):
import html
try:
from ipaddress import ip_address, ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network
except ImportError:
from ansible_collections.community.general.plugins.module_utils.compat.ipaddress import (
ip_address, IPv4Address, IPv6Address,
ip_network, IPv4Network, IPv6Network
)
import json
import shutil
import os
import pwd
import random
import re
import socket
import time
import xml.etree.ElementTree as ET
from tempfile import mkstemp
# Return an element in node, but return an empty element instead of None if not found
def xml_find(node, elt):
res = node.find(elt)
if res is None:
res = ET.Element('')
res.text = ''
return res
class PFSenseModule(object):
""" class managing pfsense base configuration """
def __init__(self, module, config='/cf/conf/config.xml'):
self.module = module
self.config = config
self.tree = ET.parse(config)
self.root = self.tree.getroot()
self.config_version = float(self.get_element('version').text)
self.aliases = self.get_element('aliases', create_node=True)
self.interfaces = self.get_element('interfaces')
self.ifgroups = self.get_element('ifgroups')
self.rules = self.get_element('filter')
self.shapers = self.get_element('shaper')
self.dnshapers = self.get_element('dnshaper')
self.vlans = self.get_element('vlans')
self.gateways = self.get_element('gateways')
self.ipsec = self.get_element('ipsec')
self.openvpn = self.get_element('openvpn')
self.virtualip = self.get_element('virtualip')
self.debug = open('/tmp/pfsense.debug', 'w')
if sys.version_info >= (3, 4):
self._scrub()
self.pfsense_version = None
# Work around pfSense CDATA xml formatting issue
# https://github.com/opoplawski/ansible-pfsense/issues/61
def _scrub(self):
for elt in self.root.iter():
if elt.text is not None:
elt.text = html.unescape(elt.text)
def get_interface_by_display_name(self, name):
""" return interface_id by name """
for interface in self.interfaces:
descr_elt = interface.find('descr')
if descr_elt is not None and descr_elt.text.strip().lower() == name.lower():
return interface.tag
return None
def get_interface_by_port(self, name):
""" return interface_id by port (os name) """
for interface in self.interfaces:
if interface.find('if').text.strip() == name:
return interface.tag
return None
def get_interface_display_name(self, interface_id, return_none=False):
""" return interface display name if found, otherwhise return the interface_id """
if interface_id == 'enc0':
if return_none and not self.is_ipsec_enabled():
return None
return 'IPsec'
if interface_id == 'openvpn':
if return_none and not self.is_openvpn_enabled():
return None
return 'OpenVPN'
for interface in self.interfaces:
if interface.tag == interface_id:
descr_elt = interface.find('descr')
if descr_elt is not None:
return descr_elt.text.strip()
break
if return_none:
return None
return interface_id
def get_interface_elt(self, interface_id):
""" return interface """
for interface in self.interfaces:
if interface.tag == interface_id:
return interface
return None
def get_interface_port(self, interface_id):
""" return interface port """
for interface in self.interfaces:
if interface.tag == interface_id:
return interface.find('if').text.strip()
return None
def get_interface_port_by_display_name(self, name):
""" return interface port """
for interface in self.interfaces:
descr_elt = interface.find('descr')
if descr_elt is not None and descr_elt.text.strip().lower() == name.lower():
return interface.find('if').text.strip()
return None
def get_interfaces_networks(self):
""" return interface local networks """
ret = []
for interface in self.interfaces:
if interface.find('enable') is None:
continue
ipaddr_elt = interface.find('ipaddr')
subnet_elt = interface.find('subnet')
if ipaddr_elt is not None and subnet_elt is not None and ipaddr_elt.text is not None and subnet_elt.text is not None:
ret.append('{0}/{1}'.format(ipaddr_elt.text, subnet_elt.text))
ipaddr_elt = interface.find('ipaddrv6')
subnet_elt = interface.find('subnetv6')
if ipaddr_elt is not None and subnet_elt is not None and ipaddr_elt.text is not None and subnet_elt.text is not None:
ret.append('{0}/{1}'.format(ipaddr_elt.text, subnet_elt.text))
# TODO: add vip networks
return ret
def is_interface_port(self, interface_port):
""" determines if arg is a pfsense interface port or not """
for interface in self.interfaces:
interface_elt = interface.tag.strip()
if interface_elt == interface_port:
return True
return False
def is_interface_display_name(self, name):
""" determines if arg is an interface name or not """
for interface in self.interfaces:
descr_elt = interface.find('descr')
if descr_elt is not None:
if descr_elt.text.strip().lower() == name.lower():
return True
return False
def is_interface_group(self, name):
""" determines if arg is an interface group name or not """
if self.ifgroups is not None:
for interface in self.ifgroups:
ifname_elt = interface.find('ifname')
if ifname_elt is not None:
# ifgroup names appear to be case sensitive
if ifname_elt.text.strip() == name:
return True
return False
def parse_interface(self, interface, fail=True, with_virtual=True, with_gwgroup=False):
""" validate param interface field """
if with_virtual and (interface == 'enc0' or interface.lower() == 'ipsec') and self.is_ipsec_enabled():
return 'enc0'
if with_virtual and (interface == 'openvpn' or interface.lower() == 'openvpn') and self.is_openvpn_enabled():
return 'openvpn'
if with_gwgroup and self.is_gateway_group(interface):
return interface
if self.is_interface_display_name(interface):
return self.get_interface_by_display_name(interface)
elif self.is_interface_port(interface):
return interface
elif self.is_interface_group(interface):
return interface
if fail:
self.module.fail_json(msg='%s is not a valid interface' % (interface))
return None
@staticmethod
def is_ipv4_address(address):
""" test if address is a valid ipv4 address """
try:
addr = ip_address(u'{0}'.format(address))
return isinstance(addr, IPv4Address)
except ValueError:
pass
return False
@staticmethod
def is_ipv6_address(address):
""" test if address is a valid ipv6 address """
try:
addr = ip_address(u'{0}'.format(address))
return isinstance(addr, IPv6Address)
except ValueError:
pass
return False
@staticmethod
def is_ipv4_network(address, strict=True):
""" test if address is a valid ipv4 network """
try:
addr = ip_network(u'{0}'.format(address), strict=strict)
return isinstance(addr, IPv4Network)
except ValueError:
pass
return False
@staticmethod
def is_ipv6_network(address, strict=True):
""" test if address is a valid ipv6 network """
try:
addr = ip_network(u'{0}'.format(address), strict=strict)
return isinstance(addr, IPv6Network)
except ValueError:
pass
return False
def is_ip_network(self, address, strict=True):
""" test if address is a valid ip network """
return self.is_ipv4_network(address, strict) or self.is_ipv6_network(address, strict)
def is_within_local_networks(self, address):
""" test if address is contained in our local networks """
networks = self.get_interfaces_networks()
try:
addr = ip_address(u'{0}'.format(address))
except ValueError:
return False
for network in networks:
try:
net = ip_network(u'{0}'.format(network), strict=False)
if addr in net:
return True
except ValueError:
# ignore invalid networks, keep trying
pass
return False
@staticmethod
def parse_ip_network(address, strict=True, returns_ip=True):
""" return cidr parts of address """
try:
addr = ip_network(u'{0}'.format(address), strict=strict)
if strict or not returns_ip:
return (str(addr.network_address), addr.prefixlen)
else:
# we parse the address with ipaddr just for type checking
# but we use a regex to return the result as it dont kept the address bits
group = re.match(r'(.*)/(.*)', address)
if group:
return (group.group(1), group.group(2))
except ValueError:
return None
return None
def parse_address(self, param, allow_self=True):
""" validate param address field and returns it as a dict """
if self.is_ipv6_address(param) or self.is_ipv6_network(param):
addr = [param]
else:
addr = param.split(':', maxsplit=3)
if len(addr) > 3:
self.module.fail_json(msg='Cannot parse address %s' % (param))
address = addr[0]
ret = dict()
# Check if the first character is "!"
if address[0] == '!':
# Invert the rule
ret['not'] = None
address = address[1:]
if address == 'NET' or address == 'IP':
interface = addr[1] if len(addr) > 1 else None
ports = addr[2] if len(addr) > 2 else None
if interface is None or interface == '':
self.module.fail_json(msg='Cannot parse address %s' % (param))
ret['network'] = self.parse_interface(interface)
if address == 'IP':
ret['network'] += 'ip'
else:
ports = addr[1] if len(addr) > 1 else None
if address == 'any':
ret['any'] = None
# rule with this firewall
elif allow_self and address == '(self)':
ret['network'] = '(self)'
# rule with interface name (LAN, WAN...)
elif self.is_interface_display_name(address):
ret['network'] = self.get_interface_by_display_name(address)
else:
if not self.is_ip_or_alias(address):
self.module.fail_json(msg='Cannot parse address %s, not IP or alias' % (address))
ret['address'] = address
if ports is not None:
self.parse_port(ports, ret)
msg = "the :ports syntax at end of addresses is deprecated and support will be removed soon. Please use source_port and destination_port options."
self.module.warn(msg)
return ret
def parse_port(self, src_ports, ret):
""" validate and parse port address field and set it in ret """
ports = src_ports.split('-')
if len(ports) > 2 or ports[0] is None or ports[0] == '' or len(ports) == 2 and (ports[1] is None or ports[1] == ''):
self.module.fail_json(msg='Cannot parse port %s' % (src_ports))
if not self.is_port_or_alias(ports[0]):
self.module.fail_json(msg='Cannot parse port %s, not port number or alias' % (ports[0]))
ret['port'] = ports[0]
if len(ports) > 1:
if not self.is_port_or_alias(ports[1]):
self.module.fail_json(msg='Cannot parse port %s, not port number or alias' % (ports[1]))
ret['port'] += '-' + ports[1]
def check_name(self, name, objtype):
""" check name validity """
msg = None
if len(name) >= 32 or len(re.findall(r'(^_*$|^\d*$|[^a-zA-Z0-9_])', name)) > 0:
msg = f"The {objtype} name '{name}' must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, "
msg += "and may only contain the following characters: a-z, A-Z, 0-9, _"
elif name in ["port", "pass"]:
msg = f"The {objtype} name must not be either of the reserved words 'port' or 'pass'"
else:
try:
socket.getprotobyname(name)
msg = f"The {objtype} name must not be an IP protocol name such as TCP, UDP, ICMP etc."
except socket.error:
# If the protocol name lookup fails, the name is not a reserved protocol and is therefore allowed.
pass
try:
socket.getservbyname(name)
msg = f"The {objtype} name must not be a well-known or registered TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc."
except socket.error:
# If the service name lookup fails, the name is not a reserved TCP/UDP service and is therefore allowed.
pass
if msg is not None:
self.module.fail_json(msg=msg)
def check_ip_address(self, address, ipprotocol, objtype, allow_networks=False, fail_ifnotip=False):
""" check address according to ipprotocol """
if address is None:
return
if allow_networks:
ipv4 = self.is_ipv4_network(address, False)
ipv6 = self.is_ipv6_network(address, False)
else:
ipv4 = self.is_ipv4_address(address)
ipv6 = self.is_ipv6_address(address)
if ipprotocol == 'inet':
if ipv6 or not ipv4 and fail_ifnotip:
self.module.fail_json(msg='{0} must use an IPv4 address'.format(objtype))
elif ipprotocol == 'inet6':
if ipv4 or not ipv6 and fail_ifnotip:
self.module.fail_json(msg='{0} must use an IPv6 address'.format(objtype))
elif ipprotocol == 'inet46':
if ipv4 or ipv6:
self.module.fail_json(msg='IPv4 and IPv6 addresses can not be used in objects that apply to both IPv4 and IPv6 (except within an alias).')
def validate_openvpn_tunnel_network(self, network, ipproto):
""" check openvpn tunnel network validity - based on pfSense's openvpn_validate_tunnel_network() """
if network is not None and network != '':
alias_elt = self.find_alias(network, aliastype='network')
if alias_elt is not None:
networks = alias_elt.find('address').text.split()
if len(networks) > 1:
self.module.fail_json("The alias {0} contains more than one network".format(network))
network = networks[0]
if not self.is_ipv4_network(network, strict=False) and ipproto == 'ipv4':
self.module.fail_json("{0} is not a valid IPv4 network".format(network))
if not self.is_ipv6_network(network, strict=False) and ipproto == 'ipv6':
self.module.fail_json("{0} is not a valid IPv6 network".format(network))
return True
return True
def validate_string(self, name, objtype):
""" check string validity - similar to pfSense's do_input_validate() """
if len(re.findall(r'[\000-\010\013\014\016-\037]', name)) > 0:
self.module.fail_json("The {0} name contains invalid characters.".format(objtype))
@staticmethod
def addr_normalize(addr):
""" return address element formatted like module argument """
address = ''
ports = ''
if 'address' in addr:
address = addr['address']
if 'any' in addr:
address = 'any'
if 'network' in addr:
address = 'NET:%s' % addr['network']
if address == '':
raise ValueError('UNKNOWN addr %s' % addr)
if 'port' in addr:
ports = addr['port']
if 'not' in addr:
address = '!' + address
return address, ports
@staticmethod
def new_element(tag, text='\n\t\t\t'):
""" Create and return new XML configuration element """
elt = ET.Element(tag)
# Attempt to preserve some of the formatting of pfSense's config.xml
elt.text = text
elt.tail = '\n\t\t'
return elt
def get_element(self, node, root_elt=None, create_node=False):
""" return configuration element """
if root_elt is None:
root_elt = self.root
top_elt = root_elt
for item in node.split('/'):
elt = top_elt.find(item)
if elt is None and create_node:
elt = self.new_element(item)
top_elt.append(elt)
top_elt = elt
return elt
def get_elements(self, node, root_elt=None):
""" return all configuration elements """
if root_elt is None:
root_elt = self.root
return root_elt.findall(node)
def get_index(self, elt, root_elt=None):
""" Get elt index """
if root_elt is None:
root_elt = self.root
return list(root_elt).index(elt)
def find_elt(self, node, search_text, search_field='descr', root_elt=None, multiple_ok=False):
""" return object elt if found """
search_xpath = "{0}[{1}='{2}']".format(node, search_field, search_text)
return self.find_elt_xpath(search_xpath, root_elt, multiple_ok)
def find_elt_xpath(self, search_xpath, root_elt=None, multiple_ok=False):
""" return object elt if found """
if root_elt is None:
root_elt = self.root
result = root_elt.findall(search_xpath)
# Always return an iterable if multiple_ok
if multiple_ok:
return result
else:
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple elements for name {0}.'.format(self.obj['name']))
return None
@staticmethod
def remove_deleted_param_from_elt(elt, param, params):
""" Remove from a deleted param from an xml elt """
changed = False
if param not in params:
param_elt = elt.find(param)
if param_elt is not None:
changed = True
elt.remove(param_elt)
return changed
def is_ipsec_enabled(self):
""" return True if ipsec is enabled """
if self.ipsec is None:
return False
for elt in self.ipsec:
if elt.tag == 'phase1' and elt.find('disabled') is None:
return True
return False
def is_openvpn_enabled(self):
""" return True if openvpn is enabled """
if self.openvpn is None:
return False
for elt in self.openvpn:
if elt.tag == 'openvpn-server' or elt.tag == 'openvpn-client':
return True
return False
def find_ipsec_phase1(self, field_value, field='descr'):
""" return ipsec phase1 elt if found """
for ipsec_elt in self.ipsec:
if ipsec_elt.tag != 'phase1':
continue
field_elt = ipsec_elt.find(field)
if field_elt is not None and field_elt.text == field_value:
return ipsec_elt
return None
@staticmethod
def rule_match_interface(rule_elt, interface, floating):
""" check if a rule elt match the targeted interface
floating rules must match the floating mode instead of the interface name
"""
interface_elt = rule_elt.find('interface')
floating_elt = rule_elt.find('floating')
if floating_elt is not None:
return floating
elif floating:
return False
return interface_elt is not None and interface_elt.text.lower() == interface.lower()
def get_interface_rules_count(self, interface, floating):
""" get rules count in interface/floating """
count = 0
for rule_elt in self.rules:
if not self.rule_match_interface(rule_elt, interface, floating):
continue
count += 1
return count
def get_rule_position(self, descr, interface, floating, first=True):
""" get rule position in interface/floating """
i = 0
found = None
for rule_elt in self.rules:
if not self.rule_match_interface(rule_elt, interface, floating):
continue
descr_elt = rule_elt.find('descr')
if descr_elt is not None and descr_elt.text == descr:
if first:
return i
else:
found = i
i += 1
return found
def copy_dict_to_element(self, src, top_elt, sub=0, prev_elt=None):
""" Copy/update top_elt from src """
changed = False
for (key, value) in src.items():
this_elt = top_elt.find(key)
self.debug.write('changed=%s key=%s value=%s this_elt=%s, sub=%d\n' % (changed, key, value, this_elt, sub))
if this_elt is None:
if isinstance(value, dict):
changed = True
self.debug.write('calling copy_dict_to_element()\n')
# Create a new element
new_elt = ET.Element(key)
new_elt.text = '\n%s' % ('\t' * (sub + 4))
new_elt.tail = '\n%s' % ('\t' * (sub + 2))
if prev_elt is not None:
prev_elt.tail = '\n%s' % ('\t' * (sub + 2))
prev_elt = new_elt
self.copy_dict_to_element(value, new_elt, sub=sub + 1, prev_elt=prev_elt)
top_elt.append(new_elt)
elif isinstance(value, list):
if value:
changed = True
if prev_elt is not None:
prev_elt.tail = '\n%s' % ('\t' * (sub + 2))
for item in value:
new_elt = self.new_element(key)
prev_elt = new_elt
if isinstance(item, dict):
self.copy_dict_to_element(item, new_elt, sub=sub + 1, prev_elt=prev_elt)
else:
new_elt.text = item
top_elt.append(new_elt)
else:
changed = True
# Create a new element
new_elt = ET.Element(key)
new_elt.text = value
new_elt.tail = '\n%s' % ('\t' * (sub + 2))
if prev_elt is not None:
prev_elt.tail = '\n%s' % ('\t' * (sub + 2))
prev_elt = new_elt
top_elt.append(new_elt)
self.debug.write('changed=%s added key=%s value=%s tag=%s\n' % (changed, key, value, top_elt.tag))
else:
if isinstance(value, dict):
self.debug.write('calling copy_dict_to_element()\n')
if self.copy_dict_to_element(value, this_elt, sub=sub + 1, prev_elt=this_elt):
changed = True
elif isinstance(value, list):
all_sub_elts = top_elt.findall(key)
# remove extra elts
while len(all_sub_elts) > len(value):
top_elt.remove(all_sub_elts.pop())
changed = True
# add new elts
while len(all_sub_elts) < len(value):
new_elt = self.new_element(key)
top_elt.append(new_elt)
all_sub_elts.append(new_elt)
changed = True
prev_elt = new_elt
# set all elts
for idx, item in enumerate(value):
if isinstance(item, str):
if all_sub_elts[idx].text is None and item == '':
pass
elif all_sub_elts[idx].text != item:
all_sub_elts[idx].text = item
changed = True
elif self.copy_dict_to_element(item, all_sub_elts[idx], sub=sub + 1, prev_elt=prev_elt):
changed = True
elif this_elt.text is None and value == '':
pass
elif this_elt.text != value:
changed = True
self.debug.write('changed=%s this_elt.text=%s != value=%s\n' % (changed, repr(this_elt.text), repr(value)))
this_elt.text = value
prev_elt = this_elt
# Sub-elements must be completely described, so remove any missing elements
if sub:
for child_elt in list(top_elt):
if child_elt.tag not in src:
changed = True
self.debug.write('changed=%s removed tag=%s\n' % (changed, child_elt.tag))
top_elt.remove(child_elt)
if prev_elt is not None:
prev_elt.tail = '\n%s' % ('\t' * (sub + 1))
self.debug.flush()
return changed
@staticmethod
def array_to_php(src, php_name):
""" Generate PHP commands to initialiaze a variable with contents of an array """
array_values = "'" + "','".join(src) + "'"
cmd = f"${php_name} = array({array_values});\n"
return cmd
@staticmethod
def dict_to_php(src, php_name):
""" Generate PHP commands to initialiaze a variable with contents of a dict """
cmd = "${0} = array();\n".format(php_name)
for key, value in src.items():
if value is not None:
cmd += "${0}['{1}'] = '{2}';\n".format(php_name, key, value)
else:
cmd += "${0}['{1}'] = '';\n".format(php_name, key)
return cmd
@staticmethod
def element_to_dict(src_elt):
""" Create dict from XML src_elt """
res = {}
for elt in src_elt:
if len(elt) > 0:
value = PFSenseModule.element_to_dict(elt)
else:
value = elt.text if elt.text is not None else ''
if elt.tag in res:
if not isinstance(res[elt.tag], list):
res[elt.tag] = [res[elt.tag]]
res[elt.tag].append(value)
else:
res[elt.tag] = value
return res
def config_get_path(self, name, default=None):
""" get value of a specific configuration path """
elt = self.find_elt_xpath(name)
if elt is not None:
return elt.text
else:
return default
def get_refid(self, node, name):
""" get refid of name in specific nodes """
elt = self.find_elt(node, name)
if elt is not None:
return xml_find(elt, 'refid').text
else:
return None
def get_caref(self, name):
""" get CA refid for name """
# global is a special case
if name == 'global':
return 'global'
# Otherwise search the ca elements
return self.get_refid('ca', name)
def get_certref(self, name):
""" get Cert refid for name """
return self.get_refid('cert', name)
def get_crlref(self, name):
""" get CRL refid for name """
return self.get_refid('crl', name)
@staticmethod
def get_username():
""" get username logged """
username = pwd.getpwuid(os.getuid()).pw_name
if os.environ.get('SUDO_USER'):
username = os.environ.get('SUDO_USER')
# sudo masks this
sshclient = os.environ.get('SSH_CLIENT')
if sshclient:
username = username + '@' + sshclient
return username
def find_alias(self, name, aliastype=None):
""" return alias named name, having type aliastype if specified """
for alias in self.aliases:
if xml_find(alias, 'name').text == name and (aliastype is None or xml_find(alias, 'type').text == aliastype):
return alias
return None
def is_ip_or_alias(self, address):
""" return True if address is an ip or an alias """
# Is it an alias?
if (self.find_alias(address, 'host') is not None
or self.find_alias(address, 'network') is not None
or self.find_alias(address, 'urltable') is not None):
return True
# Is it an IP address or network?
if self.is_ipv4_address(address) or self.is_ipv4_network(address) or self.is_ipv6_address(address) or self.is_ipv6_network(address):
return True
# None of the above
return False
def is_gateway_group(self, gwgroup):
""" return True if gwgroup is a gateway group """
return self.find_elt_xpath(f"./gateways/gateway_group[name='{gwgroup}']") is not None
def is_port_or_alias(self, port):
""" return True if port is a valid port number or an alias """
if (self.find_alias(port, 'port') is not None
or self.find_alias(port, 'urltable_ports') is not None):
return True
try:
if int(port) > 0 and int(port) < 65536:
return True
except ValueError:
pass
return False
def is_virtual_ip(self, addr):
""" return True if addr is a virtual ip """
if self.virtualip is None:
return False
if self.find_elt('vip', addr, 'subnet', root_elt=self.virtualip) is None:
return False
return True
def get_virtual_ip_interface(self, vip):
""" return interface name for virtual IP name or network """
if self.virtualip is None:
return None
vip_elt = self.find_elt('vip', vip, 'descr', root_elt=self.virtualip)
if vip_elt is None:
vip_elt = self.find_elt('vip', vip, 'subnet', root_elt=self.virtualip)
if vip_elt is None:
return None
uniqid_elt = vip_elt.find('uniqid')
if uniqid_elt is None:
return None
return "_vip" + xml_find(vip_elt, 'uniqid').text
def find_queue(self, name, interface=None, enabled=False):
""" return QOS queue if found """
# iterate each interface
for shaper_elt in self.shapers:
if interface is not None:
interface_elt = shaper_elt.find('interface')
if interface_elt is None or interface_elt.text != interface:
continue
if enabled:
enabled_elt = shaper_elt.find('enabled')
if enabled_elt is None or enabled_elt.text != 'on':
continue
# iterate each queue
for queue_elt in shaper_elt.findall('.//queue'):
name_elt = queue_elt.find('name')
if name_elt is None or name_elt.text != name:
continue
if enabled:
enabled_elt = queue_elt.find('enabled')
if enabled_elt is None or enabled_elt.text != 'on':
continue
# found it
return queue_elt
return None
def find_limiter(self, name, enabled=False):
""" return QOS limiter if found """
# iterate each queue
for queue_elt in self.dnshapers:
if enabled:
enabled_elt = queue_elt.find('enabled')
if enabled_elt is None or enabled_elt.text != 'on':
continue
name_elt = queue_elt.find('name')
if name_elt is None or name_elt.text != name:
continue
return queue_elt
return None
def find_vlan(self, interface, tag):
""" return vlan elt if found """
if self.vlans is None:
self.vlans = self.get_element('vlans')
if self.vlans is not None:
for vlan in self.vlans:
if xml_find(vlan, 'if').text == interface and xml_find(vlan, 'tag').text == tag:
return vlan
return None
def _create_gw_elt(self, name, interface_id, protocol):
gw_elt = ET.Element('gateway_item')
gw_elt.append(self.new_element('interface', interface_id))
gw_elt.append(self.new_element('gateway', 'dynamic'))
gw_elt.append(self.new_element('name', name))
gw_elt.append(self.new_element('weight', '1'))
gw_elt.append(self.new_element('ipprotocol', protocol))
gw_elt.append(self.new_element('descr', 'Interface ' + name + ' Gateway'))
return gw_elt
def find_gateway_elt(self, name, interface=None, protocol=None, dhcp=False, vti=False):
""" return gateway elt if found """
for gw_elt in self.gateways:
if gw_elt.tag != 'gateway_item':
continue
if protocol is not None and xml_find(gw_elt, 'ipprotocol').text != protocol:
continue
if interface is not None and xml_find(gw_elt, 'interface').text != interface:
continue
if xml_find(gw_elt, 'name').text == name:
return gw_elt
for interface_elt in self.interfaces:
descr_elt = interface_elt.find('descr')
if descr_elt is None or descr_elt.text is None:
continue
if_elt = interface_elt.find('if')
if if_elt is None or if_elt.text is None:
continue
descr_text = descr_elt.text.strip().upper()
# todo: implement interface match with ipsec tunnels threw vtimaps
if vti and (protocol is None or protocol == 'inet') and if_elt.text.startswith('ipsec') and descr_text + '_VTIV4' == name:
return self._create_gw_elt(name, interface_elt.tag, 'inet')
if vti and (protocol is None or protocol == 'inet6') and if_elt.text.startswith('ipsec') and descr_text + '_VTIV6' == name:
return self._create_gw_elt(name, interface_elt.tag, 'inet6')
if dhcp:
ipaddr_elt = interface_elt.find('ipaddr')
if (protocol is None or protocol == 'inet') and ipaddr_elt is not None and ipaddr_elt.text == 'dhcp' and descr_text + "_DHCP" == name:
return self._create_gw_elt(name, interface_elt.tag, 'inet')
ipaddr_elt = interface_elt.find('ipaddrv6')
if (protocol is None or protocol == 'inet6') and ipaddr_elt is not None and ipaddr_elt.text == 'dhcp6' and descr_text + "_DHCP6" == name:
return self._create_gw_elt(name, interface_elt.tag, 'inet6')
return None
def find_gateway_group_elt(self, name, protocol='inet'):
""" return gateway_group elt if found """
for gw_grp_elt in self.gateways:
if gw_grp_elt.tag != 'gateway_group':
continue
if xml_find(gw_grp_elt, 'name').text != name:
continue
# check if protocol match
match_protocol = True
for gw_elt in gw_grp_elt:
if gw_elt.tag != 'item' or gw_elt.text is None:
continue
items = gw_elt.text.split('|')
if not items or self.find_gateway_elt(items[0], None, protocol) is None:
match_protocol = False
break
if not match_protocol:
continue
return gw_grp_elt
return None
def find_active_gateways(self):
""" returns list of active gateways """
(retcode, raw_output, error) = self.phpshell("playback gatewaystatus")
write = False
output = []
lines = raw_output.split("\n")
for line in lines:
if write and line != "" and "shell:" not in line:
output.append(line)
if "started" in line:
write = True
head = output[0].split()
data = []
for line in output[1:]:
c = 0
dline = {}
for item in line.split():
dline[head[c]] = item
c += 1
if dline is not {}:
data.append(dline)
return data
def find_ca_elt(self, ca, search_field='descr'):
""" return certificate authority elt if found """
return self.find_elt('ca', ca, search_field)
def find_cert_elt(self, cert, search_field='descr'):
""" return certificate elt if found """
return self.find_elt('cert', cert, search_field)
def find_crl_elt(self, crl, search_field='descr'):
""" return certificate revocation list elt if found """
return self.find_elt('crl', crl, search_field)
def find_schedule_elt(self, name):
""" return schedule elt if found """
return self.find_elt_xpath("./schedules/schedule[name='{0}']".format(name))
@staticmethod
def uniqid(prefix='', more_entropy=False):
""" return an identifier based on time """
if more_entropy:
return prefix + '{0:x}{1:05x}{2:.8F}'.format(int(time.time()), int(time.time() * 1000000) % 0x100000, random.random() * 10)
return prefix + '{0:x}{1:05x}'.format(int(time.time()), int(time.time() * 1000000) % 0x100000)
def phpshell(self, command, debug=True):
""" Run a command in the php developer shell """
phpshell = "global $config;\n"
if debug:
phpshell = "global $debug;\n$debug = 1;\n"
phpshell += command + "\nexec\nexit"
# Dummy argument suppresses displaying help message
return self.module.run_command('/usr/local/sbin/pfSsh.php dummy', data=phpshell)
def php(self, command):
""" Run a command in php and return the output """
cmd = '\n'
(dummy, stdout, stderr) = self.module.run_command('/usr/local/bin/php', data=cmd)
# If /var/run/booting is in place, various requires will emit a "."
(stdout, nsubs) = re.subn(r'^\.+', '', stdout)
if nsubs > 0:
self.module.warn('/var/run/booting appears to be present, confirm successful boot and remove if appropriate.')
# TODO: check stderr for errors
try:
result = json.loads(stdout)
except json.JSONDecodeError as e:
self.module.fail_json(msg=f"{e}", cmd=cmd, stdout=stdout, stderr=stderr)
return result
def write_config(self, descr='Updated by ansible pfsense module'):
""" Generate config file """
revision = self.get_element('revision')
xml_find(revision, 'time').text = '%d' % time.time()
revdescr = revision.find('description')
if revdescr is None:
revdescr = ET.Element('description')
revision.append(revdescr)
revdescr.text = descr
username = self.get_username()
xml_find(revision, 'username').text = username
(tmp_handle, tmp_name) = mkstemp()
os.close(tmp_handle)
if sys.version_info >= (3, 4):
self.tree.write(tmp_name, xml_declaration=True, method='xml', short_empty_elements=False)
else:
self.tree.write(tmp_name, xml_declaration=True, method='xml')
shutil.move(tmp_name, self.config)
os.chmod(self.config, 0o644)
try:
os.remove('/tmp/config.cache')
except OSError as exception:
if exception.errno == 2:
# suppress "No such file or directory error
pass
else:
raise
@staticmethod
def get_version():
""" get pfSense version """
# TODO: use subprocess when we'll drop support for python 2.7
os.system("pkg-static info | grep pfSense-base > /tmp/pfVersion")
vfile = open("/tmp/pfVersion", "r")
version = vfile.read().replace("pfSense-base-", "").split()[0]
vfile.close()
return version
@staticmethod
def is_ce_version(version=None):
""" return True if version is a CE version (for now, we only have 2.x patterns) """
if isinstance(version, list):
return version[0] == 2
if version is None:
version = PFSenseModule.get_version()
return len(version.split('.')[0]) == 1
def is_version(self, version, or_more=True):
""" check target pfSense version """
if self.pfsense_version is None:
pfsense_version = self.get_version()
self.pfsense_version = []
match = re.match(r'(\d+)\.(\d+)\.?(\d+)?', pfsense_version)
if match is None:
self.module.fail_json(msg="Unable to get version from pfSense (got '{0}')".format(pfsense_version))
for idx in range(0, match.lastindex):
self.pfsense_version.append(int(match.group(idx + 1)))
# we must compare a CE with a CE or pfSense+ with pfSense+
is_ce_in = self.is_ce_version(version)
is_ce = self.is_ce_version(self.pfsense_version)
if is_ce != is_ce_in:
return False
for idx, ver in enumerate(version):
if idx == len(self.pfsense_version):
return True
if self.pfsense_version[idx] > ver and or_more:
return True
if ver < self.pfsense_version[idx] and not or_more or ver > self.pfsense_version[idx]:
return False
return True
def is_at_least_2_5_2(self):
""" check target pfSense version """
return self.is_version([2, 5, 2]) or self.is_version([21, 5])
def is_at_least_2_5_0(self):
""" check target pfSense version """
return self.is_version([2, 5, 0]) or self.is_version([21, 2])
def apply_ipsec_changes(self):
""" execute pfSense code to appy ipsec changes """
if self.is_at_least_2_5_0():
return self.phpshell(
"require_once('vpn.inc');"
"$ipsec_dynamic_hosts = ipsec_configure();"
"ipsec_reload_package_hook();"
"$retval = 0;"
"$retval |= filter_configure();"
"if ($ipsec_dynamic_hosts >= 0 && is_subsystem_dirty('ipsec'))"
" clear_subsystem_dirty('ipsec');"
)
return self.phpshell(
"require_once('vpn.inc');"
"$ipsec_dynamic_hosts = vpn_ipsec_configure();"
"$retval = 0;"
"$retval |= filter_configure();"
"if ($ipsec_dynamic_hosts >= 0 && is_subsystem_dirty('ipsec'))"
" clear_subsystem_dirty('ipsec');"
)
================================================
FILE: plugins/module_utils/route.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
ROUTE_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
descr=dict(required=True, type='str'),
gateway=dict(required=False, type='str'),
network=dict(required=False, type='str'),
disabled=dict(default=False, type='bool'),
)
ROUTE_REQUIRED_IF = [
["state", "present", ["gateway", "network"]],
]
class PFSenseRouteModule(PFSenseModuleBase):
""" module managing pfsense routes """
@staticmethod
def get_argument_spec():
""" return argument spec """
return ROUTE_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseRouteModule, self).__init__(module, pfsense)
self.name = "pfsense_route"
self.root_elt = self.pfsense.get_element('staticroutes')
self.obj = dict()
self.route_cmd = list()
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('staticroutes')
self.pfsense.root.append(self.root_elt)
##############################
# params processing
#
def _expand_alias(self, networks):
""" return real addresses of alias """
ret = list()
while len(networks) > 0:
alias = networks.pop()
if self.pfsense.is_ipv4_network(alias, strict=False):
ret.append(alias)
else:
alias_elt = self.pfsense.find_alias(alias, aliastype='host')
if alias_elt is None:
alias_elt = self.pfsense.find_alias(alias, aliastype='network')
networks += alias_elt.find('address').text.split(' ')
return ret
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['descr'] = params['descr']
target_elt = self._find_target()
if params['state'] == 'present':
self._get_ansible_param(obj, 'gateway')
self._get_ansible_param(obj, 'descr')
self._get_ansible_param(obj, 'network')
self._get_ansible_param_bool(obj, 'disabled')
if target_elt is not None:
old_network = target_elt.find('network').text
if params['state'] == 'absent' or old_network != params['network']:
networks = self._expand_alias([old_network])
for network in networks:
if self.pfsense.is_ipv4_address(network):
network = network + '/32'
elif self.pfsense.is_ipv6_address(old_network):
network = network + '/128'
if self.pfsense.is_ipv4_network(network, False):
family = '-inet'
else:
family = '-inet6'
self.route_cmd.append('/sbin/route delete {0} {1}'.format(family, network))
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params['state'] == 'present':
gw_elt = self.pfsense.find_gateway_elt(params['gateway'], dhcp=True, vti=True)
if gw_elt is None:
self.module.fail_json(msg='The gateway {0} does not exist'.format(params['gateway']))
if (self.pfsense.is_ipv4_address(params['network']) and gw_elt.find('ipprotocol').text == 'inet6' or
self.pfsense.is_ipv6_address(params['network']) and gw_elt.find('ipprotocol').text == 'inet'):
msg = 'The gateway "{0}" is a different Address Family than network "{1}".'.format(gw_elt.find('gateway').text, params['network'])
self.module.fail_json(msg=msg)
if (not self.pfsense.is_ip_network(params['network'], False) and self.pfsense.find_alias(params['network'], aliastype='host') is None and
self.pfsense.find_alias(params['network'], aliastype='network') is None):
self.module.fail_json(msg='A valid IPv4 or IPv6 destination network or alias must be specified.')
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('route')
def _find_target(self):
""" find the XML target_elt """
for route_elt in self.root_elt:
if route_elt.find('descr').text == self.obj['descr']:
return route_elt
return None
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['disabled']
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
for cmd in self.route_cmd:
self.module.run_command(cmd)
return self.pfsense.phpshell('''
require_once("filter.inc");
$retval = 0;
if (file_exists("{$g['tmp_path']}/.system_routes.apply")) {
$toapplylist = unserialize(file_get_contents("{$g['tmp_path']}/.system_routes.apply"));
foreach ($toapplylist as $toapply)
mwexec("{$toapply}");
@unlink("{$g['tmp_path']}/.system_routes.apply");
}
$retval |= system_routing_configure();
$retval |= filter_configure();
/* reconfigure our gateway monitor */
setup_gateways_monitor();
if ($retval == 0) clear_subsystem_dirty('staticroutes');
''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['descr'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'network')
values += self.format_cli_field(self.obj, 'gateway')
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False)
else:
values += self.format_updated_cli_field(self.obj, before, 'network', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'gateway', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, default=False, add_comma=(values))
return values
================================================
FILE: plugins/module_utils/rule.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import time
import re
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
RULE_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
action=dict(default='pass', choices=['pass', 'block', 'match', 'reject']),
state=dict(default='present', choices=['present', 'absent']),
disabled=dict(default=False, required=False, type='bool'),
interface=dict(required=True, type='str'),
floating=dict(required=False, type='bool'),
direction=dict(required=False, choices=["any", "in", "out"]),
ipprotocol=dict(default='inet', choices=['inet', 'inet46', 'inet6']),
protocol=dict(default='any', choices=["any", "tcp", "udp", "tcp/udp", "icmp", "igmp", "ospf", "esp", "ah", "gre", "pim", "sctp", "pfsync", "carp"]),
source=dict(required=False, type='str'),
source_port=dict(required=False, type='str'),
destination=dict(required=False, type='str'),
destination_port=dict(required=False, type='str'),
log=dict(required=False, type='bool'),
after=dict(required=False, type='str'),
before=dict(required=False, type='str'),
tcpflags_any=dict(required=False, type='bool'),
statetype=dict(default='keep state', choices=['keep state', 'sloppy state', 'synproxy state', 'none']),
queue=dict(required=False, type='str'),
ackqueue=dict(required=False, type='str'),
in_queue=dict(required=False, type='str'),
out_queue=dict(required=False, type='str'),
queue_error=dict(default=True, type='bool'),
gateway=dict(default='default', type='str'),
tracker=dict(required=False, type='str'),
icmptype=dict(default='any', required=False, type='str'),
sched=dict(required=False, type='str'),
quick=dict(default=False, type='bool'),
)
RULE_REQUIRED_IF = [
["floating", True, ["direction"]],
["state", "present", ["source", "destination"]],
["protocol", "icmp", ["icmptype"]],
]
# These are rule elements that are (currently) unmanaged by this module
RULE_UNMANAGED_ELEMENTS = [
'created', 'id', 'max', 'max-src-conn', 'max-src-nodes', 'max-src-states', 'os',
'statetimeout', 'statetype', 'tag', 'tagged', 'updated'
]
class PFSenseRuleModule(PFSenseModuleBase):
""" module managing pfsense rules """
@staticmethod
def get_argument_spec():
""" return argument spec """
return RULE_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseRuleModule, self).__init__(module, pfsense)
self.name = "pfsense_rule"
# Override for use with aggregate
self.argument_spec = RULE_ARGUMENT_SPEC
self.root_elt = self.pfsense.get_element('filter')
self.obj = dict()
self.result['added'] = []
self.result['deleted'] = []
self.result['modified'] = []
self.obj = None
self._floating = None # are we on floating rule
self._floating_interfaces = None # rule's interfaces before change
self._after = None # insert/move after
self._before = None # insert/move before
self._position_changed = False
self.trackers = set()
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['descr'] = params['name']
if params.get('floating'):
obj['floating'] = 'yes'
obj['interface'] = self._parse_floating_interfaces(params['interface'])
else:
obj['interface'] = self.pfsense.parse_interface(params['interface'])
if params['state'] == 'present':
obj['type'] = params['action']
obj['ipprotocol'] = params['ipprotocol']
obj['statetype'] = params['statetype']
obj['source'] = self.pfsense.parse_address(params['source'])
if params.get('source_port'):
self.pfsense.parse_port(params['source_port'], obj['source'])
obj['destination'] = self.pfsense.parse_address(params['destination'])
if params.get('destination_port'):
self.pfsense.parse_port(params['destination_port'], obj['destination'])
if params['protocol'] not in ['tcp', 'udp', 'tcp/udp', 'any'] and ('port' in obj['source'] or 'port' in obj['destination']):
self.module.fail_json(msg="{0}: you can't use ports on protocols other than tcp, udp, tcp/udp or any".format(self._get_obj_name()))
for param in ['destination', 'source']:
if 'address' in obj[param]:
self.pfsense.check_ip_address(obj[param]['address'], obj['ipprotocol'], 'rule')
if 'network' in obj[param]:
self.pfsense.check_ip_address(obj[param]['network'], obj['ipprotocol'], 'rule', allow_networks=True)
self._get_ansible_param(obj, 'protocol', exclude='any')
if params['protocol'] == 'icmp':
self._get_ansible_param(obj, 'icmptype')
self._get_ansible_param(obj, 'direction')
self._get_ansible_param(obj, 'queue', fname='defaultqueue')
if params.get('ackqueue'):
self._get_ansible_param(obj, 'ackqueue')
self._get_ansible_param(obj, 'in_queue', fname='dnpipe')
self._get_ansible_param(obj, 'out_queue', fname='pdnpipe')
self._get_ansible_param(obj, 'associated-rule-id')
self._get_ansible_param(obj, 'tracker', exclude='')
self._get_ansible_param(obj, 'gateway', exclude='default')
self._get_ansible_param(obj, 'sched')
self._get_ansible_param_bool(obj, 'disabled', value='')
self._get_ansible_param_bool(obj, 'log', value='')
self._get_ansible_param_bool(obj, 'quick')
self._get_ansible_param_bool(obj, 'tcpflags_any', value='')
self._floating = 'floating' in self.obj and self.obj['floating'] == 'yes'
self._after = params.get('after')
self._before = params.get('before')
return obj
def _parse_floating_interfaces(self, interfaces):
""" validate param interface field when floating is true """
res = []
for interface in interfaces.split(','):
if interface == 'any':
res.append(interface)
else:
res.append(self.pfsense.parse_interface(interface))
self._floating_interfaces = interfaces
return ','.join(res)
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params.get('ackqueue') and params['queue'] is None:
self.module.fail_json(msg='A default queue must be selected when an acknowledge queue is also selected')
if params.get('ackqueue') and params['ackqueue'] == params['queue']:
self.module.fail_json(msg='Acknowledge queue and default queue cannot be the same')
# as in pfSense 2.4, the GUI accepts any queue defined on any interface without checking, we do the same
if params.get('ackqueue') and self.pfsense.find_queue(params['ackqueue'], enabled=True) is None and params['queue_error']:
self.module.fail_json(msg='Failed to find enabled ackqueue=%s' % params['ackqueue'])
if params.get('queue') is not None and self.pfsense.find_queue(params['queue'], enabled=True) is None and params['queue_error']:
self.module.fail_json(msg='Failed to find enabled queue=%s' % params['queue'])
if params.get('out_queue') is not None and params['in_queue'] is None:
self.module.fail_json(msg='A queue must be selected for the In direction before selecting one for Out too')
if params.get('out_queue') is not None and params['out_queue'] == params['in_queue']:
self.module.fail_json(msg='In and Out Queue cannot be the same')
if params.get('out_queue') is not None and self.pfsense.find_limiter(params['out_queue'], enabled=True) is None:
self.module.fail_json(msg='Failed to find enabled out_queue=%s' % params['out_queue'])
if params.get('in_queue') is not None and self.pfsense.find_limiter(params['in_queue'], enabled=True) is None:
self.module.fail_json(msg='Failed to find enabled in_queue=%s' % params['in_queue'])
if params.get('floating') and params.get('direction') == 'any' and (params['in_queue'] is not None or params['out_queue'] is not None):
self.module.fail_json(msg='Limiters can not be used in Floating rules without choosing a direction')
if params.get('after') and params.get('before'):
self.module.fail_json(msg='Cannot specify both after and before')
elif params.get('after'):
if params['after'] == params['name']:
self.module.fail_json(msg='Cannot specify the current rule in after')
elif params.get('before'):
if params['before'] == params['name']:
self.module.fail_json(msg='Cannot specify the current rule in before')
# gateway
if params.get('gateway') is not None and params['gateway'] != 'default':
if params['ipprotocol'] == 'inet46':
self.module.fail_json(msg='Gateway selection is not valid for "IPV4+IPV6" address family.')
elif (self.pfsense.find_gateway_group_elt(params['gateway'], params['ipprotocol']) is None
and self.pfsense.find_gateway_elt(params['gateway'], None, params['ipprotocol']) is None):
self.module.fail_json(msg='Gateway "%s" does not exist or does not match target rule ip protocol.' % params['gateway'])
if params.get('floating') and params.get('direction') == 'any':
self.module.fail_json(msg='Gateways can not be used in Floating rules without choosing a direction')
# tracker
if params.get('tracker') is not None and int(params['tracker']) < 0:
self.module.fail_json(msg='tracker {0} must be a positive integer'.format(params['tracker']))
# sched
if params.get('sched') is not None and self.pfsense.find_schedule_elt(params['sched']) is None:
self.module.fail_json(msg='Schedule {0} does not exist'.format(params['sched']))
# quick
if params.get('quick') and not params.get('floating'):
self.module.fail_json(msg='quick can only be used on floating rules')
# ICMP
if params.get('protocol') == 'icmp' and params.get('icmptype') is not None:
both_types = ['any', 'echorep', 'echoreq', 'paramprob', 'redir', 'routeradv', 'routersol', 'timex', 'unreach']
v4_types = ['althost', 'dataconv', 'inforep', 'inforeq', 'ipv6-here', 'ipv6-where', 'maskrep', 'maskreq', 'mobredir', 'mobregrep', 'mobregreq']
v4_types += ['photuris', 'skip', 'squench', 'timerep', 'timereq', 'trace']
v6_types = ['fqdnrep', 'fqdnreq', 'groupqry', 'grouprep', 'groupterm', 'listendone', 'listenrep', 'listqry', 'mtrace', 'mtraceresp', 'neighbradv']
v6_types += ['neighbrsol', 'niqry', 'nirep', 'routrrenum', 'toobig', 'wrurep', 'wrureq']
icmptypes = list(set(map(str.strip, params['icmptype'].split(','))))
icmptypes.sort()
if '' in icmptypes:
icmptypes.remove('')
if len(icmptypes) == 0:
self.module.fail_json(msg='You must specify at least one icmptype or any for all of them')
invalids = set(icmptypes) - set(v4_types) - set(v6_types) - set(both_types)
if len(invalids) > 0:
self.module.fail_json(msg='ICMP types {0} does not exist'.format(','.join(invalids)))
if params['ipprotocol'] == 'inet':
left = set(icmptypes) - set(v4_types) - set(both_types)
elif params['ipprotocol'] == 'inet6':
left = set(icmptypes) - set(v6_types) - set(both_types)
else: # inet46 only allow
left = set(icmptypes) - set(both_types)
if len(left) > 0:
self.module.fail_json(msg='ICMP types {0} are invalid with IP type {1}'.format(','.join(left), params['ipprotocol']))
params['icmptype'] = ','.join(icmptypes)
##############################
# XML processing
#
def _adjust_separators(self, start_idx, add=True, before=False):
""" update separators position """
separators_elt = self.root_elt.find('separator')
if separators_elt is None:
return
separators_elt = separators_elt.find(self.obj['interface'])
if separators_elt is None:
return
for separator_elt in separators_elt:
row_elt = separator_elt.find('row')
if row_elt is None or row_elt.text is None:
continue
if_elt = separator_elt.find('if')
if if_elt is None or if_elt.text != self.obj['interface']:
continue
match = re.match(r'fr(\d+)', row_elt.text)
if match:
idx = int(match.group(1))
if add:
if before:
if idx > start_idx:
row_elt.text = 'fr' + str(idx + 1)
else:
if idx >= start_idx:
row_elt.text = 'fr' + str(idx + 1)
elif idx > start_idx:
row_elt.text = 'fr' + str(idx - 1)
def _check_tracker(self):
""" check the tracking used is unique and change it if required """
if not self.trackers:
trackers = self.root_elt.findall('tracker')
for tracker in trackers:
self.trackers.add(tracker.text)
start = int(time.time())
while self.obj['tracker'] in self.trackers:
start = start + 1
self.obj['tracker'] = str(start)
# keep the tracker for future calls if module is used with aggregate
self.trackers.add(self.obj['tracker'])
def _copy_and_add_target(self):
""" create the XML target_elt """
timestamp = '%d' % int(time.time())
self.obj['id'] = ''
if 'tracker' not in self.obj:
self.obj['tracker'] = timestamp
self._check_tracker()
self.obj['created'] = self.obj['updated'] = dict()
self.obj['created']['time'] = self.obj['updated']['time'] = timestamp
self.obj['created']['username'] = self.obj['updated']['username'] = self.pfsense.get_username()
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self._rule_element_to_dict()
self._insert(self.target_elt)
self.result['added'].append(self.obj)
def _copy_and_update_target(self):
""" update the XML target_elt """
timestamp = '%d' % int(time.time())
before = self._rule_element_to_dict()
if 'tracker' not in self.obj:
self.obj['tracker'] = before['tracker']
if 'associated-rule-id' not in self.obj and 'associated-rule-id' in before and before['associated-rule-id'] != '':
self.module.fail_json(msg='Target filter rule is associated with a NAT rule.')
self.diff['before'] = before
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
if self._remove_deleted_params():
changed = True
if self._update_rule_position(self.target_elt):
changed = True
if changed:
updated_elt = self.target_elt.find('updated')
if updated_elt is None:
updated_elt = self.pfsense.new_element('updated')
updated_elt.append(self.pfsense.new_element('time', timestamp))
updated_elt.append(self.pfsense.new_element('username', self.pfsense.get_username()))
self.target_elt.append(updated_elt)
else:
updated_elt.find('time').text = timestamp
updated_elt.find('username').text = self.pfsense.get_username()
self.diff['after'].update(self._rule_element_to_dict())
self.result['modified'].append(self._rule_element_to_dict())
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('rule')
def _find_matching_rule(self):
""" return rule element and index that matches by description or action """
# Prioritize matching my name
if 'associated-rule-id' in self.obj:
found, i = self._find_rule(self.obj['associated-rule-id'], 'associated-rule-id')
if found is not None:
return (found, i)
found, i = self._find_rule(self.obj['descr'])
if found is not None:
return (found, i)
# Match action without name/descr
match_rule = self.obj.copy()
del match_rule['descr']
for rule_elt in self.root_elt:
this_rule = self.pfsense.element_to_dict(rule_elt)
this_rule.pop('descr', None)
# Remove unmanaged elements
for unwanted in RULE_UNMANAGED_ELEMENTS:
this_rule.pop(unwanted, None)
if this_rule == match_rule:
return (rule_elt, i)
i += 1
return (None, -1)
def _find_rule(self, value, field='descr'):
""" return rule element and index on interface/floating that matches criteria """
i = 0
for rule_elt in self.root_elt:
field_elt = rule_elt.find(field)
if self._match_interface(rule_elt) and field_elt is not None and field_elt.text == value:
return (rule_elt, i)
i += 1
return (None, -1)
def _find_target(self):
""" find the XML target_elt """
rule_elt, dummy = self._find_matching_rule()
if rule_elt is not None and self._floating:
ifs_elt = rule_elt.find('interface')
self._floating_interfaces = ','.join([self.pfsense.get_interface_display_name(interface) for interface in ifs_elt.text.split(',')])
return rule_elt
def _get_expected_rule_position(self):
""" get expected rule position in interface/floating """
if self._before == 'bottom':
return self.pfsense.get_interface_rules_count(self.obj['interface'], self._floating) - 1
elif self._after == 'top':
return 0
elif self._after is not None:
return self._get_rule_position(self._after, first=False) + 1
elif self._before is not None:
position = self._get_rule_position(self._before) - 1
if position < 0:
return 0
return position
else:
position = self._get_rule_position(self._after, fail=False)
if position is not None:
return position
return self.pfsense.get_interface_rules_count(self.obj['interface'], self._floating)
return -1
def _get_expected_rule_xml_index(self):
""" get expected rule index in xml """
if self._before == 'bottom':
return self._get_last_rule_xml_index() + 1
elif self._after == 'top':
return self._get_first_rule_xml_index()
elif self._after is not None:
found, i = self._find_rule(self._after)
if found is not None:
return i + 1
else:
self.module.fail_json(msg='Failed to insert after rule=%s interface=%s' % (self._after, self._interface_name()))
elif self._before is not None:
found, i = self._find_rule(self._before)
if found is not None:
return i
else:
self.module.fail_json(msg='Failed to insert before rule=%s interface=%s' % (self._before, self._interface_name()))
else:
found, i = self._find_rule(self.obj['descr'])
if found is not None:
return i
# For pass/match rules, insert before the first block/reject rule
# on the same interface to maintain correct allow-before-deny ordering.
if self.obj.get('type', 'pass') not in ('block', 'reject'):
idx = self._get_first_deny_rule_xml_index()
if idx is not None:
return idx
return self._get_last_rule_xml_index() + 1
return -1
def _get_first_rule_xml_index(self):
""" Find the first rule for the interface/floating and return its xml index """
i = 0
for rule_elt in self.root_elt:
if self._match_interface(rule_elt):
break
i += 1
return i
def _get_last_rule_xml_index(self):
""" Find the last rule for the interface/floating and return its xml index """
last_found = -1
i = 0
for rule_elt in self.root_elt:
if self._match_interface(rule_elt):
last_found = i
i += 1
return last_found
def _get_first_deny_rule_xml_index(self):
""" Find the first block/reject rule for the interface/floating and return its xml index.
Returns None if no block/reject rules exist for this interface. """
i = 0
for rule_elt in self.root_elt:
if self._match_interface(rule_elt):
type_elt = rule_elt.find('type')
if type_elt is not None and type_elt.text in ('block', 'reject'):
return i
i += 1
return None
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['log', 'protocol', 'disabled', 'defaultqueue', 'ackqueue', 'dnpipe', 'pdnpipe', 'gateway', 'icmptype', 'sched', 'quick', 'tcpflags_any']
def _get_rule_position(self, descr=None, fail=True, first=True):
""" get rule position in interface/floating """
if descr is None:
descr = self.obj['descr']
res = self.pfsense.get_rule_position(descr, self.obj['interface'], self._floating, first=first)
if fail and res is None:
self.module.fail_json(msg='Failed to find rule=%s interface=%s' % (descr, self._interface_name()))
return res
def _insert(self, rule_elt):
""" insert rule into xml """
rule_xml_idx = self._get_expected_rule_xml_index()
self.root_elt.insert(rule_xml_idx, rule_elt)
rule_position = self._get_rule_position()
self._adjust_separators(rule_position, before=(self._after is None and self._before is not None))
def _match_interface(self, rule_elt):
""" check if a rule elt match the targeted interface """
return self.pfsense.rule_match_interface(rule_elt, self.obj['interface'], self._floating)
def _update_rule_position(self, rule_elt):
""" move rule in xml if required """
current_position = self._get_rule_position()
expected_position = self._get_expected_rule_position()
if current_position == expected_position:
self._position_changed = False
return False
self.diff['before']['position'] = current_position
self.diff['after']['position'] = expected_position
self._adjust_separators(current_position, add=False)
self.root_elt.remove(rule_elt)
self._insert(rule_elt)
self._position_changed = True
return True
##############################
# run
#
def _pre_remove_target_elt(self):
""" processing before removing elt """
self._adjust_separators(self._get_rule_position(), add=False)
self.diff['before'] = self._rule_element_to_dict()
self.result['deleted'].append(self._rule_element_to_dict())
def _rule_element_to_dict(self):
""" convert rule_elt to dictionary like module arguments """
rule = self.pfsense.element_to_dict(self.target_elt)
# We use 'name' for 'descr'
rule['name'] = rule.pop('descr', 'UNKNOWN')
# We use 'action' for 'type'
rule['action'] = rule.pop('type', 'UNKNOWN')
# Convert addresses to argument format
for addr_item in ['source', 'destination']:
rule[addr_item], rule[addr_item + '_port'] = self.pfsense.addr_normalize(rule[addr_item])
return rule
def _update(self):
""" make the target pfsense reload rules """
return self.pfsense.phpshell('''require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('filter'); }''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}' on '{1}'".format(self.obj['descr'], self._interface_name())
def _interface_name(self):
""" return formated interface name for logging """
if self._floating:
if self._floating_interfaces is not None:
return 'floating(' + self._floating_interfaces + ')'
return 'floating(' + self.params['interface'] + ')'
return self.params['interface']
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'source')
values += self.format_cli_field(self.params, 'source_port')
values += self.format_cli_field(self.params, 'destination')
values += self.format_cli_field(self.params, 'destination_port')
values += self.format_cli_field(self.params, 'protocol', default='any')
values += self.format_cli_field(self.params, 'direction')
values += self.format_cli_field(self.params, 'ipprotocol', default='inet')
values += self.format_cli_field(self.params, 'icmptype', default='any')
values += self.format_cli_field(self.params, 'tcpflags_any', fvalue=self.fvalue_bool)
values += self.format_cli_field(self.params, 'statetype', default='keep state')
values += self.format_cli_field(self.params, 'action', default='pass')
values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'log', fvalue=self.fvalue_bool, default=False)
values += self.format_cli_field(self.params, 'after')
values += self.format_cli_field(self.params, 'before')
values += self.format_cli_field(self.params, 'queue')
values += self.format_cli_field(self.params, 'ackqueue')
values += self.format_cli_field(self.params, 'in_queue')
values += self.format_cli_field(self.params, 'out_queue')
values += self.format_cli_field(self.params, 'gateway', default='default')
values += self.format_cli_field(self.params, 'tracker')
values += self.format_cli_field(self.params, 'sched')
values += self.format_cli_field(self.params, 'quick', fvalue=self.fvalue_bool, default=False)
else:
fbefore = self._obj_to_log_fields(before)
fafter = self._obj_to_log_fields(self.obj)
fafter['before'] = self._before
fafter['after'] = self._after
values += self.format_updated_cli_field(fafter, fbefore, 'source', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'source_port', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'destination', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'destination_port', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'protocol', none_value="'any'", add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'icmptype', add_comma=(values))
values += self.format_updated_cli_field(fafter, fbefore, 'interface', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'floating', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'direction', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ipprotocol', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'tcpflags_any', fvalue=self.fvalue_bool, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'statetype', add_comma=(values))
values += self.format_updated_cli_field(self.params, before, 'action', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'log', fvalue=self.fvalue_bool, add_comma=(values))
if self._position_changed:
values += self.format_updated_cli_field(fafter, {}, 'after', log_none=False, add_comma=(values))
values += self.format_updated_cli_field(fafter, {}, 'before', log_none=False, add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'defaultqueue', fname='queue', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ackqueue', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'dnpipe', fname='in_queue', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'pdnpipe', fname='out_queue', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'gateway', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'tracker', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'sched', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'quick', fvalue=self.fvalue_bool, add_comma=(values))
return values
def _obj_address_to_log_field(self, rule, addr):
""" return formated address from dict """
field = ''
field_port = ''
if isinstance(rule[addr], dict):
if 'not' in rule[addr]:
field += '!'
if 'any' in rule[addr]:
field += 'any'
if 'address' in rule[addr]:
field += rule[addr]['address']
elif 'network' in rule[addr]:
interface = None
if rule[addr]['network'].endswith('ip'):
interface = self.pfsense.get_interface_display_name(rule[addr]['network'][:-2], return_none=True)
if interface is None:
field += 'NET:' + self.pfsense.get_interface_display_name(rule[addr]['network'])
else:
field += 'IP:' + interface
if 'port' in rule[addr]:
field_port += rule[addr]['port']
else:
if rule[addr].startswith('NET:'):
field = 'NET:' + self.pfsense.get_interface_display_name(rule[addr][4:])
elif rule[addr].startswith('IP:'):
field = 'IP:' + self.pfsense.get_interface_display_name(rule[addr][3:])
else:
field = rule[addr]
field_port = rule[addr + '_port']
return field, field_port
def _obj_to_log_fields(self, rule):
""" return formated source and destination from dict """
res = {}
res['source'], res['source_port'] = self._obj_address_to_log_field(rule, 'source')
res['destination'], res['destination_port'] = self._obj_address_to_log_field(rule, 'destination')
res['interface'] = ','.join([self.pfsense.get_interface_display_name(interface) for interface in rule['interface'].split(',')])
return res
================================================
FILE: plugins/module_utils/rule_separator.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
RULE_SEPARATOR_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
state=dict(default='present', choices=['present', 'absent']),
interface=dict(required=False, type='str'),
floating=dict(required=False, type='bool'),
color=dict(default='info', required=False, choices=['info', 'warning', 'danger', 'success']),
after=dict(default=None, required=False, type='str'),
before=dict(default=None, required=False, type='str'),
)
RULE_SEPARATOR_REQUIRED_ONE_OF = [['interface', 'floating']]
RULE_SEPARATOR_MUTUALLY_EXCLUSIVE = [['interface', 'floating']]
class PFSenseRuleSeparatorModule(PFSenseModuleBase):
""" module managing pfsense rule separators """
@staticmethod
def get_argument_spec():
""" return argument spec """
return RULE_SEPARATOR_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseRuleSeparatorModule, self).__init__(module, pfsense)
self.name = "pfsense_rule_separator"
# Override for use with aggregate
self.argument_spec = RULE_SEPARATOR_ARGUMENT_SPEC
self.root_elt = None
self.obj = dict()
self.separators = self.pfsense.rules.find('separator')
if self.separators is None:
self.separators = self.pfsense.new_element('separator')
self.pfsense.rules.append(self.separators)
self._interface_name = None
self._floating = None
self._after = None
self._before = None
##############################
# params processing
#
def _params_to_obj(self):
""" return an separator dict from module params """
params = self.params
self._floating = (params.get('floating'))
self._after = params.get('after')
self._before = params.get('before')
obj = dict()
self.obj = obj
obj['text'] = params['name']
if params.get('floating'):
self._interface_name = 'floating'
obj['if'] = 'floatingrules'
else:
self._interface_name = params['interface'].lower()
obj['if'] = self.pfsense.parse_interface(params['interface']).lower()
if params['state'] == 'present':
obj['color'] = 'bg-' + params['color']
obj['row'] = 'fr' + str(self._get_expected_separator_position())
self.root_elt = self.separators.find(obj['if'])
if self.root_elt is None:
self.root_elt = self.pfsense.new_element(obj['if'])
self.separators.append(self.root_elt)
return obj
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('sep')
def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.root_elt.append(self.target_elt)
self._recompute_separators_tag()
def _find_target(self):
""" find the XML target_elt """
if_elt = self.separators.find(self.obj['if'])
if if_elt is not None:
for separator_elt in if_elt:
if separator_elt.find('text').text == self.obj['text']:
return separator_elt
return None
def _get_expected_separator_position(self):
""" get expected separator position in interface/floating """
if self._before == 'bottom':
return self.pfsense.get_interface_rules_count(self.obj['if'], self._floating)
elif self._after == 'top':
return 0
elif self._after is not None:
return self._get_rule_position(self._after) + 1
elif self._before is not None:
return self._get_rule_position(self._before)
else:
position = self._get_separator_position()
if position is not None:
return position
return self.pfsense.get_interface_rules_count(self.obj['if'], self._floating)
return -1
def _get_rule_position(self, descr):
""" get rule position in interface/floating """
res = self.pfsense.get_rule_position(descr, self.obj['if'], self._floating)
if res is None:
self.module.fail_json(msg='Failed to find rule=%s interface=%s' % (descr, self._interface_name))
return res
def _get_separator_position(self):
""" get separator position in interface/floating """
separator_elt = self._find_target()
if separator_elt is not None:
return int(separator_elt.find('row').text.replace('fr', ''))
return None
def _post_remove_target_elt(self):
""" processing after removing elt """
self._recompute_separators_tag()
def _recompute_separators_tag(self):
""" recompute separators tag name """
if_elt = self.separators.find(self.obj['if'])
if if_elt is not None:
i = 0
for separator_elt in if_elt:
name = 'sep' + str(i)
if separator_elt.tag != name:
separator_elt.tag = name
i += 1
##############################
# run
#
def _update(self):
""" make the target pfsense reload separators """
return self.pfsense.phpshell('''require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('filter'); }''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}' on '{1}'".format(self.obj['text'], self._interface_name)
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'color')
values += self.format_cli_field(self.params, 'after')
values += self.format_cli_field(self.params, 'before')
else:
values += self.format_cli_field(self.params, 'color', add_comma=(values))
values += self.format_cli_field(self.params, 'after', add_comma=(values))
values += self.format_cli_field(self.params, 'before', add_comma=(values))
return values
================================================
FILE: plugins/module_utils/vlan.py
================================================
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
VLAN_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
interface=dict(required=True, type='str'),
vlan_id=dict(required=True, type='int'),
priority=dict(default=None, required=False, type='int'),
descr=dict(default='', type='str'),
)
class PFSenseVlanModule(PFSenseModuleBase):
""" module managing pfsense vlans """
@staticmethod
def get_argument_spec():
""" return argument spec """
return VLAN_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseVlanModule, self).__init__(module, pfsense)
self.name = "pfsense_vlan"
# Override for use with aggregate
self.argument_spec = VLAN_ARGUMENT_SPEC
self.root_elt = self.pfsense.get_element('vlans')
self.obj = dict()
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('vlans')
self.pfsense.root.append(self.root_elt)
self.setup_vlan_cmds = ""
# get physical interfaces on which vlans can be set
get_interface_cmd = (
'require_once("/etc/inc/interfaces.inc");'
'$portlist = get_interface_list();'
'$lagglist = get_lagg_interface_list();'
'$portlist = array_merge($portlist, $lagglist);'
'foreach ($lagglist as $laggif => $lagg) {'
" $laggmembers = explode(',', $lagg['members']);"
' foreach ($laggmembers as $lagm)'
' if (isset($portlist[$lagm]))'
' unset($portlist[$lagm]);'
'}')
if self.pfsense.is_at_least_2_5_0():
get_interface_cmd += (
'$list = array();'
'foreach ($portlist as $ifn => $ifinfo) {'
' $list[$ifn] = $ifn . " (" . $ifinfo["mac"] . ")";'
' $iface = convert_real_interface_to_friendly_interface_name($ifn);'
' if (isset($iface) && strlen($iface) > 0)'
' $list[$ifn] .= " - $iface";'
'}'
'echo json_encode($list);')
else:
get_interface_cmd += (
'$list = array();'
'foreach ($portlist as $ifn => $ifinfo)'
' if (is_jumbo_capable($ifn))'
' array_push($list, $ifn);'
'echo json_encode($list);')
self.interfaces = self.pfsense.php(get_interface_cmd)
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['tag'] = str(params['vlan_id'])
if params['interface'] not in self.interfaces:
obj['if'] = self.pfsense.get_interface_port_by_display_name(params['interface'])
if obj['if'] is None:
obj['if'] = self.pfsense.get_interface_port(params['interface'])
else:
obj['if'] = params['interface']
if params['state'] == 'present':
if params['priority'] is not None:
obj['pcp'] = str(params['priority'])
else:
obj['pcp'] = ''
obj['descr'] = params['descr']
obj['vlanif'] = '{0}.{1}'.format(obj['if'], obj['tag'])
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
# check interface
if params['interface'] not in self.interfaces:
# check with assign or friendly name
interface = self.pfsense.get_interface_port_by_display_name(params['interface'])
if interface is None:
interface = self.pfsense.get_interface_port(params['interface'])
if interface is None or interface not in self.interfaces:
self.module.fail_json(msg='Vlans can\'t be set on interface {0}'.format(params['interface']))
# check vlan
if params['vlan_id'] < 1 or params['vlan_id'] > 4094:
self.module.fail_json(msg='vlan_id must be between 1 and 4094 on interface {0}'.format(params['interface']))
# check priority
if params.get('priority') is not None and (params['priority'] < 0 or params['priority'] > 7):
self.module.fail_json(msg='priority must be between 0 and 7 on interface {0}'.format(params['interface']))
##############################
# XML processing
#
def _cmd_create(self):
""" return the php shell to create the vlan's interface """
cmd = "$vlan = array();\n"
cmd += "$vlan['if'] = '{0}';\n".format(self.obj['if'])
cmd += "$vlan['tag'] = '{0}';\n".format(self.obj['tag'])
cmd += "$vlan['pcp'] = '{0}';\n".format(self.obj['pcp'])
cmd += "$vlan['descr'] = '{0}';\n".format(self.obj['descr'])
cmd += "$vlan['vlanif'] = '{0}';\n".format(self.obj['vlanif'])
cmd += "$vlanif = interface_vlan_configure($vlan);\n"
cmd += "if ($vlanif == NULL || $vlanif != $vlan['vlanif']) {pfSense_interface_destroy('%s');} else {\n" % (self.obj['vlanif'])
# if vlan is assigned to an interface, configuration needs to be applied again
interface = self.pfsense.get_interface_by_port('{0}.{1}'.format(self.obj['if'], self.obj['tag']))
if interface is not None:
cmd += "interface_configure('{0}', true);\n".format(interface)
cmd += '}\n'
return cmd
def _copy_and_add_target(self):
""" create the XML target_elt """
super(PFSenseVlanModule, self)._copy_and_add_target()
self.setup_vlan_cmds += self._cmd_create()
def _copy_and_update_target(self):
""" update the XML target_elt """
old_vlanif = self.target_elt.find('vlanif').text
(before, changed) = super(PFSenseVlanModule, self)._copy_and_update_target()
if changed:
self.setup_vlan_cmds += "pfSense_interface_destroy('{0}');\n".format(old_vlanif)
self.setup_vlan_cmds += self._cmd_create()
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('vlan')
def _find_target(self):
""" find the XML target_elt """
return self.pfsense.find_vlan(self.obj['if'], self.obj['tag'])
def _pre_remove_target_elt(self):
""" processing before removing elt """
if self.pfsense.get_interface_by_port('{0}.{1}'.format(self.obj['if'], self.obj['tag'])) is not None:
self.module.fail_json(
msg='vlan {0} on {1} cannot be deleted because it is still being used as an interface'.format(self.obj['tag'], self.obj['if'])
)
self.setup_vlan_cmds += "pfSense_interface_destroy('{0}');\n".format(self.target_elt.find('vlanif').text)
##############################
# run
#
def get_update_cmds(self):
""" build and return php commands to setup interfaces """
cmd = 'require_once("filter.inc");\n'
if self.setup_vlan_cmds != "":
cmd += 'require_once("interfaces.inc");\n'
cmd += self.setup_vlan_cmds
cmd += "if (filter_configure() == 0) { clear_subsystem_dirty('filter'); }"
return cmd
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell(self.get_update_cmds())
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}.{1}'".format(self.obj['if'], self.obj['tag'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.obj, 'descr')
values += self.format_cli_field(self.obj, 'pcp', fname='priority')
else:
values += self.format_updated_cli_field(self.obj, before, 'pcp', add_comma=(values), fname='priority')
values += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(values))
return values
================================================
FILE: plugins/modules/pfsense_aggregate.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_aggregate
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage multiple pfSense firewall aliases, rules, and rule separators, plus interfaces and VLANs
description:
- Manage multiple pfSense firewall aliases, rules, and rule separators, plus interfaces and VLANs
notes:
- aggregated_* use the same options definitions than pfsense corresponding module
options:
aggregated_aliases:
description: Dict of aliases to apply on the target
required: False
type: list
elements: dict
suboptions:
name:
description: The name of the alias
required: true
type: str
state:
description: State in which to leave the alias
choices: [ "present", "absent" ]
default: present
type: str
type:
description: The type of the alias
choices: [ "host", "network", "port", "urltable", "urltable_ports" ]
type: str
address:
description: The address of the alias for `host`, `network` or `port` types. Use a space separator for multiple values
default: null
type: str
url:
description: The URL of the alias for `urltable` or `urltable_ports` types. Use a space separator for multiple values
default: null
type: str
descr:
description: The description of the alias
default: null
type: str
detail:
description: The descriptions of the items. Use || separator between items
default: null
type: str
updatefreq:
description: Update frequency in days for urltable
default: null
type: int
aggregated_interfaces:
description: Dict of interfaces to apply on the target
required: False
type: list
elements: dict
suboptions:
state:
description: State in which to leave the interface.
choices: [ "present", "absent" ]
default: present
type: str
descr:
description: Description (name) for the interface.
required: true
type: str
interface:
description: Network port to which assign the interface.
type: str
interface_descr:
description: Network port descr to which assign the interface.
type: str
enable:
description: Enable interface.
default: no
type: bool
ipv4_type:
description: IPv4 Configuration Type.
choices: [ "none", "static", "dhcp" ]
default: 'none'
type: str
ipv6_type:
description: IPv4 Configuration Type.
choices: [ "none", "static", "slaac" ]
default: 'none'
type: str
mac:
description: Used to modify ("spoof") the MAC address of this interface.
required: false
type: str
mtu:
description: Maximum transmission unit
required: false
type: int
mss:
description: MSS clamping for TCP connections.
required: false
type: int
speed_duplex:
description: Set speed and duplex mode for this interface.
required: false
default: autoselect
type: str
ipv4_address:
description: IPv4 Address.
required: false
type: str
ipv4_prefixlen:
description: IPv4 subnet prefix length.
required: false
default: 24
type: int
ipv4_gateway:
description: IPv4 gateway for this interface.
required: false
type: str
ipv6_address:
description: IPv6 Address.
required: false
type: str
ipv6_prefixlen:
description: IPv6 subnet prefix length.
required: false
default: 128
type: int
ipv6_gateway:
description: IPv6 gateway for this interface.
required: false
type: str
blockpriv:
description: Blocks traffic from IP addresses that are reserved for private networks.
required: false
type: bool
blockbogons:
description: Blocks traffic from reserved IP addresses (but not RFC 1918) or not yet assigned by IANA.
required: false
type: bool
slaacusev4iface:
description: IPv6 will use the IPv4 connectivity link (PPPoE). Only used when ipv6_type is slaac.
required: false
type: bool
version_added: 0.6.2
aggregated_nat_outbounds:
description: Dict of nat_outbound rules to apply on the target
required: False
type: list
elements: dict
suboptions:
descr:
description: The name of the nat rule
required: true
default: null
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
nonat:
description: This option will disable NAT for traffic matching this rule and stop processing Outbound NAT rules
default: false
type: bool
interface:
description: The interface for the rule
required: false
type: str
ipprotocol:
description: The Internet Protocol version this rule applies to.
default: inet46
choices: [ "inet", "inet46", "inet6" ]
type: str
protocol:
description: Which protocol this rule should match.
default: any
choices: [ "any", "tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "carp", "pfsync" ]
type: str
source:
description: The matching source address, in {any,(self),ALIAS,NETWORK}[:port] format.
required: false
default: null
type: str
destination:
description: The matching destination address, in {any,ALIAS,NETWORK}[:port] format.
required: false
default: null
type: str
invert:
description: Invert the sense of the destination match.
default: false
type: bool
address:
description: The translated to address, in {ALIAS,NETWORK}[:port] format. Leave address part empty to use interface address.
required: false
default: null
type: str
poolopts:
description: When an address pool is used, there are several options available that control how NAT translations happen on the pool.
default: ""
choices: [ "", "round-robin", "round-robin sticky-address", "random", "random sticky-address", "source-hash", "bitmask" ]
type: str
source_hash_key:
description: >
The key that is fed to the hashing algorithm in hex format, preceeded by "0x", or any string.
A non-hex string is hashed using md5 to a hexadecimal key. Defaults to a randomly generated value.
required: false
default: ''
type: str
staticnatport:
description: Do not randomize source port
default: false
type: bool
nosync:
description: >
Prevents the rule on Master from automatically syncing to other CARP members.
This does NOT prevent the rule from being overwritten on Slave.
default: false
type: bool
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
aggregated_nat_port_forwards:
description: Dict of nat_port_forward rules to apply on the target
required: False
type: list
elements: dict
suboptions:
descr:
description: The name of the nat rule
required: true
default: null
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
nordr:
description: Disable redirection for traffic matching this rule
default: false
type: bool
interface:
description: The interface for the rule
required: false
type: str
ipprotocol:
description: The IP protocol
default: inet
choices: [ "inet", "inet6" ]
type: str
protocol:
description: Which protocol this rule should match.
default: tcp
choices: [ "tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "pim", "ospf" ]
type: str
source:
description: The source address, in [!]{IP,HOST,ALIAS,any,IP:INTERFACE,NET:INTERFACE}[:port] format.
default: null
type: str
destination:
description: The destination address, in [!]{IP,HOST,ALIAS,any,IP:INTERFACE,NET:INTERFACE}[:port] format.
default: null
type: str
target:
description: The translated to address, in {ALIAS,IP}[:port] format.
required: false
default: null
type: str
natreflection:
description: Allows NAT reflection to be enabled or disabled on a per-port forward basis.
default: system-default
choices: [ "system-default", "enable", "purenat", "disable" ]
type: str
associated_rule:
description: >
Choose one of Add an associated filter rule gets updated when the port forward is updated,
or Add an unassociated filter rule, or pass which passes all traffic that matches the entry without having a firewall rule at all.
default: associated
choices: [ "associated", "unassociated", "pass", "none" ]
type: str
nosync:
description: >
Prevents the rule on Master from automatically syncing to other CARP members.
This does NOT prevent the rule from being overwritten on Slave.
default: false
type: bool
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
aggregated_rules:
description: Dict of rules to apply on the target
required: False
type: list
elements: dict
suboptions:
name:
description: The name the rule
required: true
default: null
type: str
action:
description: The action of the rule
default: pass
choices: [ 'pass', 'block', 'match', 'reject' ]
type: str
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
interface:
description: The interface for the rule
required: true
type: str
floating:
description: Is the rule floating
type: bool
direction:
description: Direction floating rule applies to
choices: [ "any", "in", "out" ]
type: str
ipprotocol:
description: The IP protocol
default: inet
choices: [ "inet", "inet46", "inet6" ]
type: str
protocol:
description: The protocol
default: any
choices: [ 'any', 'tcp', 'udp', 'tcp/udp', 'icmp', 'igmp', 'ospf', 'esp', 'ah', 'gre', 'pim', 'sctp', 'pfsync', 'carp' ]
type: str
source:
description: The source address, in [!]{IP,HOST,ALIAS,any,(self),IP:INTERFACE,NET:INTERFACE} format.
default: null
type: str
source_port:
description:
- Source port or port range specification.
- This can either be a alias or a port number.
- An inclusive range can also be specified, using the format C(first-last)..
default: null
type: str
destination:
description: The destination address, in [!]{IP,HOST,ALIAS,any,(self),IP:INTERFACE,NET:INTERFACE} format.
default: null
type: str
destination_port:
description:
- Destination port or port range specification.
- This can either be a alias or a port number.
- An inclusive range can also be specified, using the format C(first-last)..
default: null
type: str
log:
description: Log packets matched by rule
type: bool
after:
description: Rule to go after, or C(top)
type: str
before:
description: Rule to go before, or C(bottom)
type: str
tcpflags_any:
description: Allow TCP packets with any flags set.
type: bool
statetype:
description: State type
default: keep state
choices: ["keep state", "sloppy state", "synproxy state", "none"]
type: str
queue:
description: QOS default queue
type: str
ackqueue:
description: QOS acknowledge queue
type: str
in_queue:
description: Limiter queue for traffic coming into the chosen interface
type: str
out_queue:
description: Limiter queue for traffic leaving the chosen interface
type: str
queue_error:
description: Raise an error if a specified queue is missing
type: bool
default: True
gateway:
description: Leave as C(default) to use the system routing table or choose a gateway to utilize policy based routing.
type: str
default: default
tracker:
description: Rule tracking ID. Defaults to timestamp of rule creation.
type: str
icmptype:
description:
- One or more of these ICMP subtypes may be specified, separated by comma, or C(any) for all of them.
- The types must match ip protocol.
- althost, dataconv, echorep, echoreq, fqdnrep, fqdnreq, groupqry, grouprep, groupterm, inforep, inforeq, ipv6-here,
- ipv6-where, listendone, listenrep, listqry, maskrep, maskreq, mobredir, mobregrep, mobregreq, mtrace, mtraceresp,
- neighbradv, neighbrsol, niqry, nirep, paramprob, photuris, redir, routeradv, routersol, routrrenum, skip, squench,
- timerep, timereq, timex, toobig, trace, unreach, wrurep, wrureq
default: any
type: str
sched:
description: Schedule day/time when the rule must be active
required: False
type: str
quick:
description: Set this option to apply this action to traffic that matches this rule immediately
type: bool
default: False
aggregated_rule_separators:
description: Dict of rule separators to apply on the target
required: False
type: list
elements: dict
suboptions:
name:
description: The name of the separator
required: true
type: str
state:
description: State in which to leave the separator
choices: [ "present", "absent" ]
default: present
type: str
interface:
description: The interface for the separator
type: str
floating:
description: Is the rule on floating tab
type: bool
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
color:
description: The separator's color
default: info
choices: [ 'info', 'warning', 'danger', 'success' ]
type: str
aggregated_vlans:
description: Dict of VLANs to apply on the target
required: False
type: list
elements: dict
suboptions:
vlan_id:
description: The VLAN tag. Must be between 1 and 4094.
required: true
type: int
interface:
description: The interface on which to declare the VLAN. Friendly name (assignments) can be used.
required: true
type: str
priority:
description: 802.1Q VLAN Priority code point. Must be between 0 and 7.
required: false
type: int
descr:
description: The description of the VLAN
default: ''
type: str
state:
description: State in which to leave the VLAN
choices: [ "present", "absent" ]
default: present
type: str
order_rules:
description: rules will be generated following the playbook order
required: False
default: False
type: bool
purge_aliases:
description: delete all the aliases that are not defined into aggregated_aliases
required: False
default: False
type: bool
purge_interfaces:
description: delete all the interfaces that are not defined into aggregated_interfaces
required: False
default: False
type: bool
purge_nat_outbounds:
description: delete all the nat_outbound rules that are not defined into aggregated_nat_outbounds
required: False
default: False
type: bool
purge_nat_port_forwards:
description: delete all the nat_port_forward rules that are not defined into aggregated_nat_port_forwards
required: False
default: False
type: bool
purge_rules:
description: delete all the rules that are not defined into aggregated_rules
required: False
default: False
type: bool
purge_rule_separators:
description: delete all the rule separators that are not defined into aggregated_rule_separators
required: False
default: False
type: bool
purge_vlans:
description: delete all the VLANs that are not defined into aggregated_vlans
required: False
default: False
type: bool
interface_filter:
description: only apply rules and rules separators on those interfaces (separated by space)
required: False
type: str
ignored_aliases:
description: aliases that will be ignored (won't be auto deleted)
required: False
default: []
type: list
elements: str
ignored_rules:
description: rules that will be ignored (won't be auto deleted)
required: False
default: []
type: list
elements: str
"""
EXAMPLES = """
- name: "Setup two VLANs, three aliases, six rules, four separators, and delete everything else"
pfsense_aggregate:
purge_aliases: true
purge_rules: true
purge_rule_separators: true
purge_vlans: true
aggregated_aliases:
- { name: port_ssh, type: port, address: 22, state: present }
- { name: port_http, type: port, address: 80, state: present }
- { name: port_https, type: port, address: 443, state: present }
aggregated_rules:
- { name: "allow_all_ssh", source: any, destination: "any:port_ssh", protocol: tcp, interface: lan, state: present }
- { name: "allow_all_http", source: any, destination: "any:port_http", protocol: tcp, interface: lan, state: present }
- { name: "allow_all_https", source: any, destination: "any:port_https", protocol: tcp, interface: lan, state: present }
- { name: "allow_all_ssh", source: any, destination: "any:port_ssh", protocol: tcp, interface: wan, state: present }
- { name: "allow_all_http", source: any, destination: "any:port_http", protocol: tcp, interface: wan, state: present }
- { name: "allow_all_https", source: any, destination: "any:port_https", protocol: tcp, interface: wan, state: present }
aggregated_rule_separators:
- { name: "SSH", interface: lan, state: present, before: allow_all_ssh }
- { name: "HTTP", interface: lan, state: present, before: allow_all_http }
- { name: "SSH", interface: wan, state: present, before: allow_all_ssh }
- { name: "HTTP", interface: wan, state: present, before: allow_all_http }
aggregated_vlans:
- { descr: voice, vlan_id: 100, interface: mvneta0, state: present }
- { descr: video, vlan_id: 200, interface: mvneta0, state: present }
"""
RETURN = """
result_aliases:
description: the set of aliases commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create alias 'adservers', type='host', address='10.0.0.1 10.0.0.2'", "update alias 'one_host' set address='10.9.8.7'", "delete alias 'one_alias'"]
result_interfaces:
description: the set of interfaces commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create interface 'VOICE', port='mvneta1.100'", "create interface 'VIDEO', port='mvneta1.200'"]
result_nat_oubounds:
description: the set of nat outbounds commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create nat_outbound 'NAT outbound traffic', interface='wan', source='any', destination='any'", "delete nat_outbound 'NAT outbound traffic'"]
result_nat_port_forwards:
description: the set of nat port forwards commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create nat_port_forward 'ssh', interface='wan', source='any', destination='any:22', target='1.2.3.4:22', associated_rule='pass'"]
result_rules:
description: the set of rules commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample:
- "create rule 'allow_all_ssh', source='any', destination='any:port_ssh', protocol='tcp', interface='lan'"
- "update rule 'allow_all_http' set destination='any:port_http'"
- "delete rule 'allow_all'"
result_separators:
description: the set of separators commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create rule_separator 'SSH', interface='lan', color='info'", "update rule_separator 'SSH' set color='warning'", "delete rule_separator 'SSH'"]
result_vlans:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create vlan 'mvneta.100', descr='voice', priority='5'", "update vlan 'mvneta.100', set priority='6'", "delete vlan 'mvneta.100'"]
"""
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from ansible_collections.pfsensible.core.plugins.module_utils.alias import PFSenseAliasModule, ALIAS_ARGUMENT_SPEC, ALIAS_MUTUALLY_EXCLUSIVE, ALIAS_REQUIRED_IF
from ansible_collections.pfsensible.core.plugins.module_utils.interface import (
PFSenseInterfaceModule,
INTERFACE_ARGUMENT_SPEC,
INTERFACE_REQUIRED_IF,
INTERFACE_MUTUALLY_EXCLUSIVE,
)
from ansible_collections.pfsensible.core.plugins.module_utils.nat_outbound import PFSenseNatOutboundModule, NAT_OUTBOUND_ARGUMENT_SPEC, NAT_OUTBOUND_REQUIRED_IF
from ansible_collections.pfsensible.core.plugins.module_utils.nat_port_forward import (
PFSenseNatPortForwardModule,
NAT_PORT_FORWARD_ARGUMENT_SPEC,
NAT_PORT_FORWARD_REQUIRED_IF
)
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule, RULE_ARGUMENT_SPEC, RULE_REQUIRED_IF
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import (
PFSenseRuleSeparatorModule,
RULE_SEPARATOR_ARGUMENT_SPEC,
RULE_SEPARATOR_REQUIRED_ONE_OF,
RULE_SEPARATOR_MUTUALLY_EXCLUSIVE,
)
from ansible_collections.pfsensible.core.plugins.module_utils.vlan import PFSenseVlanModule, VLAN_ARGUMENT_SPEC
from ansible.module_utils.basic import AnsibleModule
class PFSenseModuleAggregate(object):
""" module managing pfsense aggregated aliases, rules, rule separators, interfaces and VLANs """
def __init__(self, module):
self.module = module
self.pfsense = PFSenseModule(module)
self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense)
self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense)
self.pfsense_nat_outbounds = PFSenseNatOutboundModule(module, self.pfsense)
self.pfsense_nat_port_forwards = PFSenseNatPortForwardModule(module, self.pfsense)
self.pfsense_rules = PFSenseRuleModule(module, self.pfsense)
self.pfsense_rule_separators = PFSenseRuleSeparatorModule(module, self.pfsense)
self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense)
self.defined_rules = dict()
def _update(self):
run = False
cmd = 'require_once("filter.inc");\n'
# TODO: manage one global list of commands as ordering can be important between modules
if self.pfsense_vlans.result['changed']:
run = True
cmd += self.pfsense_vlans.get_update_cmds()
if self.pfsense_interfaces.result['changed']:
run = True
cmd += self.pfsense_interfaces.get_update_cmds()
cmd += 'if (filter_configure() == 0) { \n'
if self.pfsense_aliases.result['changed']:
run = True
cmd += 'clear_subsystem_dirty(\'aliases\');\n'
if self.pfsense_nat_port_forwards.result['changed'] or self.pfsense_nat_outbounds.result['changed']:
run = True
cmd += 'clear_subsystem_dirty(\'natconf\');\n'
if (self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed'] or
self.pfsense_nat_port_forwards.result['changed'] or self.pfsense_nat_outbounds.result['changed']):
run = True
cmd += 'clear_subsystem_dirty(\'filter\');\n'
cmd += '}'
if run:
return self.pfsense.phpshell(cmd)
return ('', '', '')
def _parse_floating_interfaces(self, interfaces):
""" parse interfaces """
res = set()
for interface in interfaces.split(','):
parsed = self.pfsense.parse_interface(interface, fail=False)
if parsed is not None:
res.add(parsed)
return res
def want_rule(self, rule_elt, rules, name_field='name'):
""" return True if we want to keep rule_elt """
descr = rule_elt.find('descr')
interface = rule_elt.find('interface')
floating = rule_elt.find('floating') is not None
# probably not a rule
if descr is None or interface is None:
return True
if descr.text in self.module.params['ignored_rules']:
return True
key = '{0}_{1}'.format(interface.text, floating)
if key not in self.defined_rules:
defined_rules = set()
self.defined_rules[key] = defined_rules
else:
defined_rules = self.defined_rules[key]
# a rule can only exists once on an interface
if descr.text in defined_rules:
return False
for rule in rules:
if rule['state'] == 'absent':
continue
if rule[name_field] != descr.text:
continue
rule_floating = (rule.get('floating') is not None and
(isinstance(rule['floating'], bool) and
rule['floating'] or rule['floating'].lower() in ['yes', 'true']))
if floating != rule_floating:
continue
if floating:
defined_rules.add(descr.text)
return True
parsed = self.pfsense.parse_interface(rule['interface'], fail=False)
if parsed is not None and parsed == interface.text:
defined_rules.add(descr.text)
return True
return False
def want_rule_separator(self, separator_elt, rule_separators):
""" return True if we want to keep separator_elt """
name = separator_elt.find('text').text
interface = separator_elt.find('if').text
for separator in rule_separators:
if separator['state'] == 'absent':
continue
if separator['name'] != name:
continue
if separator.get('floating'):
if interface == 'floatingrules':
return True
else:
parsed = self.pfsense.parse_interface(separator['interface'], fail=False)
if parsed is not None and parsed == interface:
return True
return False
def want_alias(self, alias_elt, aliases):
""" return True if we want to keep alias_elt """
name = alias_elt.find('name')
alias_type = alias_elt.find('type')
# probably not an alias
if name is None or type is None:
return True
if name.text in self.module.params['ignored_aliases']:
return True
for alias in aliases:
if alias['state'] == 'absent':
continue
if alias['name'] == name.text and alias['type'] == alias_type.text:
return True
return False
@staticmethod
def want_interface(interface_elt, interfaces):
""" return True if we want to keep interface_elt """
descr_elt = interface_elt.find('descr')
if descr_elt is not None and descr_elt.text:
name = descr_elt.text
else:
name = interface_elt.tag
for interface in interfaces:
if interface['state'] == 'absent':
continue
if interface['descr'] == name:
return True
return False
@staticmethod
def want_vlan(vlan_elt, vlans):
""" return True if we want to keep vlan_elt """
tag = int(vlan_elt.find('tag').text)
interface = vlan_elt.find('if')
for vlan in vlans:
if vlan['state'] == 'absent':
continue
if vlan['vlan_id'] == tag and vlan['interface'] == interface.text:
return True
return False
@staticmethod
def is_filtered(interface_filter, params):
if interface_filter is None:
return False
if 'floating' in params:
if isinstance(params['floating'], str):
floating = params['floating'].lower()
else:
floating = 'true' if params['floating'] else 'false'
if floating != 'false' and floating != 'no':
return 'floating' not in interface_filter
return params['interface'].lower() not in interface_filter
def run_rules(self):
""" process input params to add/update/delete all rules """
want = self.module.params['aggregated_rules']
interface_filter = self.module.params['interface_filter'].lower().split(' ') if self.module.params.get('interface_filter') is not None else None
if want is None:
return
# delete every other rule if required
if self.module.params['purge_rules']:
todel = []
for rule_elt in self.pfsense_rules.root_elt:
if not self.want_rule(rule_elt, want):
params = {}
params['state'] = 'absent'
params['name'] = rule_elt.find('descr').text
if rule_elt.find('floating') is not None:
params['floating'] = True
interfaces = rule_elt.find('interface').text.split(',')
params['interface'] = list()
for interface in interfaces:
target = self.pfsense.get_interface_display_name(interface, return_none=True)
if target is not None:
params['interface'].append(target)
if not params['interface']:
continue
params['interface'] = ','.join(params['interface'])
else:
params['interface'] = self.pfsense.get_interface_display_name(rule_elt.find('interface').text, return_none=True)
if params['interface'] is None:
continue
todel.append(params)
for params in todel:
if self.is_filtered(interface_filter, params):
continue
self.pfsense_rules.run(params)
# generating order if required
if self.module.params.get('order_rules'):
last_rules = dict()
for params in want:
if params.get('before') is not None or params.get('after') is not None:
self.module.fail_json(msg="You can't use after or before parameters on rules when using order_rules (see {0})".format(params['name']))
if params.get('state') == 'absent':
continue
if params.get('floating'):
key = 'floating'
else:
key = params['interface']
# first rule on interface
if key not in last_rules:
params['after'] = 'top'
last_rules[key] = params['name']
continue
params['after'] = last_rules[key]
last_rules[key] = params['name']
# processing aggregated parameters
for params in want:
if self.is_filtered(interface_filter, params):
continue
# Skip rules whose interface doesn't exist on this firewall
if params.get('state', 'present') != 'absent' and params.get('interface') is not None:
if params.get('floating'):
# For floating rules, filter out invalid interfaces from the list
valid = [i for i in params['interface'].split(',')
if i == 'any' or self.pfsense.parse_interface(i, fail=False) is not None]
if not valid:
continue
params['interface'] = ','.join(valid)
else:
if self.pfsense.parse_interface(params['interface'], fail=False) is None:
continue
self.pfsense_rules.run(params)
def run_nat_outbounds_rules(self):
""" process input params to add/update/delete all nat_outbound rules """
want = self.module.params['aggregated_nat_outbounds']
interface_filter = self.module.params['interface_filter'].lower().split(' ') if self.module.params.get('interface_filter') is not None else None
if want is None:
return
# delete every other rule if required
if self.module.params['purge_nat_outbounds']:
todel = []
for rule_elt in self.pfsense_nat_outbounds.root_elt:
if not self.want_rule(rule_elt, want, name_field='descr'):
params = {}
params['state'] = 'absent'
params['descr'] = rule_elt.find('descr').text
params['interface'] = self.pfsense.get_interface_display_name(rule_elt.find('interface').text, return_none=True)
if params['interface'] is None:
continue
todel.append(params)
for params in todel:
if self.is_filtered(interface_filter, params):
continue
self.pfsense_nat_outbounds.run(params)
# processing aggregated parameters
for params in want:
if self.is_filtered(interface_filter, params):
continue
# Skip rules whose interface doesn't exist on this firewall
if params.get('state', 'present') != 'absent' and params.get('interface') is not None:
if self.pfsense.parse_interface(params['interface'], fail=False) is None:
continue
self.pfsense_nat_outbounds.run(params)
def run_nat_port_forwards_rules(self):
""" process input params to add/update/delete all nat_port_forwards_rule rules """
want = self.module.params['aggregated_nat_port_forwards']
interface_filter = self.module.params['interface_filter'].lower().split(' ') if self.module.params.get('interface_filter') is not None else None
if want is None:
return
# delete every other rule if required
if self.module.params['purge_nat_port_forwards']:
todel = []
for rule_elt in self.pfsense_nat_port_forwards.root_elt:
if not self.want_rule(rule_elt, want, name_field='descr'):
params = {}
params['state'] = 'absent'
params['descr'] = rule_elt.find('descr').text
params['interface'] = self.pfsense.get_interface_display_name(rule_elt.find('interface').text, return_none=True)
if params['interface'] is None:
continue
todel.append(params)
for params in todel:
if self.is_filtered(interface_filter, params):
continue
self.pfsense_nat_port_forwards.run(params)
# processing aggregated parameters
for params in want:
if self.is_filtered(interface_filter, params):
continue
# Skip rules whose interface doesn't exist on this firewall
if params.get('state', 'present') != 'absent' and params.get('interface') is not None:
if self.pfsense.parse_interface(params['interface'], fail=False) is None:
continue
self.pfsense_nat_port_forwards.run(params)
def run_aliases(self):
""" process input params to add/update/delete all aliases """
want = self.module.params['aggregated_aliases']
if want is None:
return
# processing aggregated parameter
for param in want:
self.pfsense_aliases.run(param)
# delete every other alias if required
if self.module.params['purge_aliases']:
todel = []
for alias_elt in self.pfsense_aliases.root_elt:
if not self.want_alias(alias_elt, want):
params = {}
params['state'] = 'absent'
params['name'] = alias_elt.find('name').text
todel.append(params)
for params in todel:
self.pfsense_aliases.run(params)
def run_interfaces(self):
""" process input params to add/update/delete all interfaces """
want = self.module.params['aggregated_interfaces']
if want is None:
return
# processing aggregated parameter
for param in want:
self.pfsense_interfaces.run(param)
# delete every other if required
if self.module.params['purge_interfaces']:
todel = []
for interface_elt in self.pfsense_interfaces.root_elt:
if not self.want_interface(interface_elt, want):
params = {}
params['state'] = 'absent'
descr_elt = interface_elt.find('descr')
if descr_elt is not None and descr_elt.text:
params['descr'] = descr_elt.text
todel.append(params)
for params in todel:
self.pfsense_interfaces.run(params)
def run_rule_separators(self):
""" process input params to add/update/delete all separators """
want = self.module.params['aggregated_rule_separators']
interface_filter = self.module.params['interface_filter'].lower().split(' ') if self.module.params.get('interface_filter') is not None else None
if want is None:
return
# processing aggregated parameter
for params in want:
if self.is_filtered(interface_filter, params):
continue
# Skip separators whose interface doesn't exist on this firewall
if params.get('state', 'present') != 'absent' and params.get('interface') is not None and not params.get('floating'):
if self.pfsense.parse_interface(params['interface'], fail=False) is None:
continue
self.pfsense_rule_separators.run(params)
# delete every other if required
if self.module.params['purge_rule_separators']:
todel = []
for interface_elt in self.pfsense_rule_separators.separators:
for separator_elt in interface_elt:
if not self.want_rule_separator(separator_elt, want):
params = {}
params['state'] = 'absent'
params['name'] = separator_elt.find('text').text
if interface_elt.tag == 'floatingrules':
params['floating'] = True
else:
params['interface'] = self.pfsense.get_interface_display_name(interface_elt.tag, return_none=True)
if params['interface'] is None:
continue
todel.append(params)
for params in todel:
if self.is_filtered(interface_filter, params):
continue
self.pfsense_rule_separators.run(params)
def run_vlans(self):
""" process input params to add/update/delete all VLANs """
want = self.module.params['aggregated_vlans']
if want is None:
return
# processing aggregated parameter
for param in want:
self.pfsense_vlans.run(param)
# delete every other if required
if self.module.params['purge_vlans']:
todel = []
for vlan_elt in self.pfsense_vlans.root_elt:
if not self.want_vlan(vlan_elt, want):
params = {}
params['state'] = 'absent'
params['interface'] = vlan_elt.find('if').text
params['vlan_id'] = int(vlan_elt.find('tag').text)
todel.append(params)
for params in todel:
self.pfsense_vlans.run(params)
def commit_changes(self):
""" apply changes and exit module """
stdout = ''
stderr = ''
changed = (
self.pfsense_aliases.result['changed'] or self.pfsense_interfaces.result['changed'] or self.pfsense_nat_outbounds.result['changed']
or self.pfsense_nat_port_forwards.result['changed'] or self.pfsense_rules.result['changed']
or self.pfsense_rule_separators.result['changed'] or self.pfsense_vlans.result['changed']
)
if changed and not self.module.check_mode:
self.pfsense.write_config(descr='aggregated change')
(dummy, stdout, stderr) = self._update()
result = {}
result['result_aliases'] = self.pfsense_aliases.result['commands']
result['result_interfaces'] = self.pfsense_interfaces.result['commands']
result['result_nat_outbounds'] = self.pfsense_nat_outbounds.result['commands']
result['result_nat_port_forwards'] = self.pfsense_nat_port_forwards.result['commands']
result['result_rules'] = self.pfsense_rules.result['commands']
result['result_rule_separators'] = self.pfsense_rule_separators.result['commands']
result['result_vlans'] = self.pfsense_vlans.result['commands']
result['changed'] = changed
result['stdout'] = stdout
result['stderr'] = stderr
self.module.exit_json(**result)
def main():
argument_spec = dict(
aggregated_aliases=dict(
type='list', elements='dict', options=ALIAS_ARGUMENT_SPEC, mutually_exclusive=ALIAS_MUTUALLY_EXCLUSIVE, required_if=ALIAS_REQUIRED_IF),
aggregated_interfaces=dict(
type='list', elements='dict',
options=INTERFACE_ARGUMENT_SPEC, required_if=INTERFACE_REQUIRED_IF, mutually_exclusive=INTERFACE_MUTUALLY_EXCLUSIVE),
aggregated_rules=dict(type='list', elements='dict', options=RULE_ARGUMENT_SPEC, required_if=RULE_REQUIRED_IF),
aggregated_nat_outbounds=dict(type='list', elements='dict', options=NAT_OUTBOUND_ARGUMENT_SPEC, required_if=NAT_OUTBOUND_REQUIRED_IF),
aggregated_nat_port_forwards=dict(type='list', elements='dict', options=NAT_PORT_FORWARD_ARGUMENT_SPEC, required_if=NAT_PORT_FORWARD_REQUIRED_IF),
aggregated_rule_separators=dict(
type='list', elements='dict',
options=RULE_SEPARATOR_ARGUMENT_SPEC, required_one_of=RULE_SEPARATOR_REQUIRED_ONE_OF, mutually_exclusive=RULE_SEPARATOR_MUTUALLY_EXCLUSIVE),
aggregated_vlans=dict(type='list', elements='dict', options=VLAN_ARGUMENT_SPEC),
order_rules=dict(default=False, type='bool'),
purge_aliases=dict(default=False, type='bool'),
purge_interfaces=dict(default=False, type='bool'),
purge_nat_outbounds=dict(default=False, type='bool'),
purge_nat_port_forwards=dict(default=False, type='bool'),
purge_rules=dict(default=False, type='bool'),
purge_rule_separators=dict(default=False, type='bool'),
purge_vlans=dict(default=False, type='bool'),
interface_filter=dict(required=False, type='str'),
ignored_aliases=dict(type='list', elements='str', default=[]),
ignored_rules=dict(type='list', elements='str', default=[]),
)
required_one_of = [[
'aggregated_aliases',
'aggregated_interfaces',
'aggregated_nat_outbounds',
'aggregated_nat_port_forwards',
'aggregated_rules',
'aggregated_rule_separators',
'aggregated_vlans'
]]
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
supports_check_mode=True)
pfmodule = PFSenseModuleAggregate(module)
pfmodule.run_vlans()
pfmodule.run_interfaces()
pfmodule.run_aliases()
pfmodule.run_nat_outbounds_rules()
pfmodule.run_nat_port_forwards_rules()
pfmodule.run_rules()
pfmodule.run_rule_separators()
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_alias.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_alias
version_added: 0.1.0
author: Orion Poplawski (@opoplawski), Frederic Bor (@f-bor)
short_description: Manage pfSense aliases
description:
- Manage pfSense aliases
notes:
options:
name:
description: The name of the alias
required: true
type: str
state:
description: State in which to leave the alias
choices: [ "present", "absent" ]
default: present
type: str
type:
description: The type of the alias
choices: [ "host", "network", "port", "urltable", "urltable_ports" ]
type: str
address:
description: The address of the alias for `host`, `network` or `port` types. Use a space separator for multiple values
default: null
type: str
url:
description: The URL of the alias for `urltable` or `urltable_ports` types. Use a space separator for multiple values
default: null
type: str
descr:
description: The description of the alias
default: null
type: str
detail:
description: The descriptions of the items. Use || separator between items
default: null
type: str
updatefreq:
description: Update frequency in days for urltable
default: null
type: int
"""
EXAMPLES = """
- name: Add adservers alias
pfsense_alias:
name: adservers
address: 10.0.0.1 10.0.0.2
state: present
- name: Remove adservers alias
pfsense_alias:
name: adservers
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create alias 'adservers', type='host', address='10.0.0.1 10.0.0.2'", "update alias 'one_host' set address='10.9.8.7'", "delete alias 'one_alias'"]
diff:
description: a pair of dicts, before and after, with alias settings before and after task run
returned: always
type: dict
sample: {}
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.alias import PFSenseAliasModule, ALIAS_ARGUMENT_SPEC, ALIAS_MUTUALLY_EXCLUSIVE, ALIAS_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=ALIAS_ARGUMENT_SPEC,
mutually_exclusive=ALIAS_MUTUALLY_EXCLUSIVE,
required_if=ALIAS_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseAliasModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_authserver_ldap.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018-2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_authserver_ldap
version_added: 0.1.0
short_description: Manage pfSense LDAP authentication servers
description:
>
Manage pfSense LDAP authentication servers
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the authentication server
required: true
type: str
state:
description: State in which to leave the authentication server
default: 'present'
choices: [ "present", "absent" ]
type: str
host:
description: The hostname or IP address of the authentication server
required: false
type: str
port:
description: Port to connect to
default: '389'
type: str
transport:
description: Transport to use
choices: [ "tcp", "starttls", "ssl" ]
type: str
ca:
description: Certificate Authority
default: global
type: str
protver:
description: LDAP protocol version
default: '3'
choices: [ "2", "3" ]
type: str
timeout:
description: Server timeout in seconds
default: '25'
type: str
scope:
description: Search scope
choices: [ 'one', 'subtree' ]
type: str
basedn:
description: Search base DN
type: str
authcn:
description: Authentication containers added to basedn
required: false
type: str
extended_enabled:
description: Enable extended query
default: False
type: bool
extended_query:
description: Extended query
default: ''
type: str
binddn:
description: Search bind DN
type: str
bindpw:
description: Search bind password
type: str
attr_user:
description: LDAP User naming attribute
default: cn
type: str
attr_group:
description: LDAP Group naming attribute
default: cn
type: str
attr_member:
description: LDAP Group member naming attribute
default: member
type: str
attr_groupobj:
description: LDAP Group objectClass naming attribute
default: posixGroup
type: str
ldap_rfc2307:
description: LDAP Server uses RFC 2307 style group membership (RFC 2307bis when False)
type: bool
ldap_rfc2307_userdn:
description: Use DN for username search (pfsense-CE >=2.5.0, pfsense-PLUS >=21.2)
type: bool
ldap_utf8:
description: UTF8 encode LDAP parameters before sending them to the server.
type: bool
ldap_nostrip_at:
description: Do not strip away parts of the username after the @ symbol
type: bool
ldap_pam_groupdn:
description: Shell Authentication Group DN (pfsense-CE >=2.5.0, pfsense-PLUS >=21.2)
type: str
ldap_allow_unauthenticated:
description: Allow unauthenticated bind (pfsense-CE >=2.5.0, pfsense-PLUS >=21.2). Defaults to true.
type: bool
"""
EXAMPLES = """
- name: Add adservers authentication server
pfsense_authserver_ldap:
name: AD
host: adserver.example.com
port: 636
transport: ssl
scope: subtree
authcn: cn=users
basedn: dc=example,dc=com
binddn: cn=bind,ou=Service Accounts,dc=example,dc=com
bindpw: "{{ vaulted_bindpw }}"
attr_user: samAccountName
attr_member: memberOf
attr_groupobj: group
state: present
- name: Remove LDAP authentication server
pfsense_authserver_ldap:
name: AD
state: absent
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
PFSENSE_AUTHSERVER_LDAP_SPEC = {
'name': {'required': True, 'type': 'str'},
'state': {
'default': 'present',
'choices': ['present', 'absent']
},
'host': {'type': 'str'},
'port': {'default': '389', 'type': 'str'},
'transport': {
'choices': ['tcp', 'starttls', 'ssl']
},
'ca': {'default': 'global', 'type': 'str'},
'protver': {
'default': '3',
'choices': ['2', '3']
},
'timeout': {'default': '25', 'type': 'str'},
'scope': {
'choices': ['one', 'subtree']
},
'basedn': {'required': False, 'type': 'str'},
'authcn': {'required': False, 'type': 'str'},
'extended_enabled': {'default': False, 'type': 'bool'},
'extended_query': {'default': '', 'type': 'str'},
'binddn': {'required': False, 'type': 'str'},
'bindpw': {'required': False, 'type': 'str'},
'attr_user': {'default': 'cn', 'type': 'str'},
'attr_group': {'default': 'cn', 'type': 'str'},
'attr_member': {'default': 'member', 'type': 'str'},
'attr_groupobj': {'default': 'posixGroup', 'type': 'str'},
'ldap_pam_groupdn': {'required': False, 'type': 'str'},
'ldap_utf8': {'required': False, 'type': 'bool'},
'ldap_nostrip_at': {'required': False, 'type': 'bool'},
'ldap_rfc2307': {'required': False, 'type': 'bool'},
'ldap_rfc2307_userdn': {'required': False, 'type': 'bool'},
'ldap_allow_unauthenticated': {'required': False, 'type': 'bool'},
}
AUTHSERVER_LDAP_CREATE_DEFAULT = dict(
ldap_allow_unauthenticated=None
)
AUTHSERVER_LDAP_PHP_COMMAND = """
require_once('auth.inc');
if (config_path_enabled('system/webgui', 'shellauth') &&
(config_get_path('system/webgui/authmode') == '{name}')) {{
set_pam_auth();
}}
"""
class PFSenseAuthserverLDAPModule(PFSenseModuleBase):
""" module managing pfsense LDAP authentication """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return PFSENSE_AUTHSERVER_LDAP_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseAuthserverLDAPModule, self).__init__(module, pfsense, name='pfsense_authserver_ldap', root='system', node='authserver', key='name',
bool_style='absent/present', have_refid=True, create_default=AUTHSERVER_LDAP_CREATE_DEFAULT)
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
if int(self.params['timeout']) < 1:
self.module.fail_json(msg='timeout {0} must be greater than 1'.format(self.params['timeout']))
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['name'] = params['name']
if params['state'] == 'present':
obj['type'] = 'ldap'
for option in ['host']:
if option in params and params[option] is not None:
obj[option] = params[option]
obj['ldap_port'] = params['port']
if self.pfsense.config_version >= 20.1:
urltype = dict({'tcp': 'Standard TCP', 'starttls': 'STARTTLS Encrypted', 'ssl': 'SSL/TLS Encrypted'})
else:
urltype = dict({'tcp': 'TCP - Standard', 'starttls': 'TCP - STARTTLS', 'ssl': 'SSL - Encrypted'})
obj['ldap_urltype'] = urltype[params['transport']]
obj['ldap_protver'] = params['protver']
obj['ldap_timeout'] = params['timeout']
obj['ldap_scope'] = params['scope']
obj['ldap_basedn'] = params['basedn']
obj['ldap_authcn'] = params['authcn']
if params['extended_enabled']:
obj['ldap_extended_enabled'] = 'yes'
else:
obj['ldap_extended_enabled'] = ''
obj['ldap_extended_query'] = params['extended_query']
if params['binddn']:
obj['ldap_binddn'] = params['binddn']
if params['bindpw']:
obj['ldap_bindpw'] = params['bindpw']
obj['ldap_attr_user'] = params['attr_user']
obj['ldap_attr_group'] = params['attr_group']
obj['ldap_attr_member'] = params['attr_member']
obj['ldap_attr_groupobj'] = params['attr_groupobj']
if params['ldap_utf8']:
obj['ldap_utf8'] = ''
if params['ldap_nostrip_at']:
obj['ldap_nostrip_at'] = ''
if params['ldap_rfc2307']:
obj['ldap_rfc2307'] = ''
if self.pfsense.is_at_least_2_5_0():
obj['ldap_pam_groupdn'] = params['ldap_pam_groupdn']
if params['ldap_rfc2307_userdn']:
obj['ldap_rfc2307_userdn'] = ''
if params['ldap_allow_unauthenticated']:
obj['ldap_allow_unauthenticated'] = ''
# Find the caref id for the named CA
obj['ldap_caref'] = self.pfsense.get_caref(params['ca'])
# CA is required for SSL/TLS
if self.pfsense.config_version >= 20.1:
if obj['ldap_caref'] is None and obj['ldap_urltype'] != 'Standard TCP':
self.module.fail_json(msg="Could not find CA '%s'" % (params['ca']))
else:
if obj['ldap_caref'] is None and obj['ldap_urltype'] != 'TCP - Standard':
self.module.fail_json(msg="Could not find CA '%s'" % (params['ca']))
return obj
##############################
# XML processing
#
def _find_target(self):
result = self.root_elt.findall("authserver[name='{0}'][type='ldap']".format(self.obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple ldap authentication servers for name {0}.'.format(self.obj['name']))
else:
return None
##############################
# run
#
def _update(self):
""" update system configuration if needed """
return self.pfsense.phpshell(AUTHSERVER_LDAP_PHP_COMMAND.format(name=self.obj['name']))
def main():
module = AnsibleModule(
argument_spec=PFSENSE_AUTHSERVER_LDAP_SPEC,
required_if=[
["state", "present", ["host", "port", "transport", "scope", "authcn"]],
],
supports_check_mode=True)
pfmodule = PFSenseAuthserverLDAPModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_authserver_radius.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018-2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_authserver_radius
version_added: 0.5.0
short_description: Manage pfSense RADIUS authentication servers
description:
>
Manage pfSense RADIUS authentication servers
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the authentication server
required: true
type: str
state:
description: State in which to leave the authentication server
default: 'present'
choices: [ "present", "absent" ]
type: str
host:
description: The hostname or IP address of the authentication server
required: false
type: str
auth_port:
description: RADIUS authentication port
default: 1812
type: int
acct_port:
description: RADIUS accounting port
default: 1813
type: int
protocol:
description: RADIUS protocol
default: MSCHAPv2
choices: [ "PAP", "CHAP_MD5", "MSCHAPv1", "MSCHAPv2" ]
type: str
secret:
description: RADIUS secret
type: str
timeout:
description: Server timeout in seconds
default: 5
type: int
nasip_attribute:
description: IP to use for the "NAS-IP-Address" attribute during RADIUS Acccess-Requests, must be an interface name
default: lan
type: str
"""
EXAMPLES = """
- name: Add adservers authentication server
pfsense_authserver_radius:
name: RADIUS
host: radius.example.com
secret: password
nasip_attribute: lan
state: present
- name: Remove RADIUS authentication server
pfsense_authserver_radius:
name: RADIUS
state: absent
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
AUTHSERVER_RADIUS_SPEC = {
'name': {'required': True, 'type': 'str'},
'state': {
'default': 'present',
'choices': ['present', 'absent'],
},
'host': {'type': 'str'},
'auth_port': {'default': '1812', 'type': 'int'},
'acct_port': {'default': '1813', 'type': 'int'},
'protocol': {
'default': 'MSCHAPv2',
'choices': ['PAP', 'CHAP_MD5', 'MSCHAPv1', 'MSCHAPv2'],
},
'secret': {'type': 'str', 'no_log': True},
'timeout': {'default': '5', 'type': 'int'},
'nasip_attribute': {'default': 'lan', 'type': 'str'},
}
class PFSenseAuthserverRADIUSModule(PFSenseModuleBase):
""" module managing pfsense RADIUS authentication """
@staticmethod
def get_argument_spec():
""" return argument spec """
return AUTHSERVER_RADIUS_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseAuthserverRADIUSModule, self).__init__(module, pfsense)
self.name = "pfsense_authserver_radius"
self.root_elt = self.pfsense.get_element('system')
self.authservers = self.root_elt.findall('authserver')
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
if int(self.params['timeout']) < 1:
self.module.fail_json(msg='timeout {0} must be greater than 1'.format(self.params['timeout']))
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['name'] = params['name']
if params['state'] == 'present':
obj['type'] = 'radius'
self._get_ansible_param(obj, 'host')
self._get_ansible_param(obj, 'auth_port', fname='radius_auth_port')
self._get_ansible_param(obj, 'acct_port', fname='radius_acct_port')
self._get_ansible_param(obj, 'protocol', fname='radius_protocol')
self._get_ansible_param(obj, 'secret', fname='radius_secret')
self._get_ansible_param(obj, 'timeout', fname='radius_timeout')
self._get_ansible_param(obj, 'nasip_attribute', fname='radius_nasip_attribute')
return obj
##############################
# XML processing
#
def _find_target(self):
result = self.root_elt.findall("authserver[name='{0}'][type='radius']".format(self.obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple radius authentication servers for name {0}.'.format(self.obj['name']))
else:
return None
def _find_this_index(self):
return self.authservers.index(self.target_elt)
def _create_target(self):
""" create the XML target_elt """
elt = self.pfsense.new_element('authserver')
elt.append(self.pfsense.new_element('refid', text=self.pfsense.uniqid()))
return elt
def _copy_and_add_target(self):
""" populate the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.obj
if len(self.authservers) > 0:
self.root_elt.insert(list(self.root_elt).index(self.authservers[len(self.authservers) - 1]), self.target_elt)
else:
self.root_elt.append(self.target_elt)
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'{0}'".format(self.obj['name'])
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
return values
def main():
module = AnsibleModule(
argument_spec=AUTHSERVER_RADIUS_SPEC,
required_if=[
["state", "present", ["host", "secret"]],
],
supports_check_mode=True)
pfmodule = PFSenseAuthserverRADIUSModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_ca.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018-2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_ca
version_added: 0.1.0
short_description: Manage pfSense Certificate Authorities
description:
>
Manage pfSense Certificate Authorities
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the Certificate Authority
required: true
type: str
method:
description: The type of Certificate Authority to create
default: existing
choices: [ "internal", "existing", "intermediate" ]
type: str
state:
description: State in which to leave the Certificate Authority
default: present
choices: [ "present", "absent" ]
type: str
trust:
description: Add this Certificate Authority to the Operating System Trust Store. Defaults to false.
type: bool
version_added: 0.5.0
randomserial:
description: Use random serial numbers when signing certifices. Defaults to false.
type: bool
version_added: 0.5.0
certificate:
description:
>
The certificate for the Certificate Authority. This can be in PEM form or Base64
encoded PEM as a single string (which is how pfSense stores it).
type: str
crl:
description:
>
The Certificate Revocation List for the Certificate Authority. This can be in PEM
form or Base64 encoded PEM as a single string (which is how pfSense stores it).
required: false
type: str
crlname:
description:
>
The name of the CRL. This will default to name + ' CRL'. If multiple CRLs exist
with this name, you must specify crlrefid.
required: false
type: str
version_added: 0.5.0
crlrefid:
description: The refrence ID of the CRL. This will default to a unique id based on time.
required: false
type: str
version_added: 0.5.0
key:
description:
>
The private key for the Certificate Authority. This can be in PEM form or Base64
encoded PEM as a single string (which is how pfSense stores it).
type: str
version_added: 0.6.2
keytype:
description: The key type for the internal Certificate Authority.
default: RSA
choices: [ "RSA", "ECDSA" ]
type: str
ecname:
description: The Elliptic Curve Name to use when generating a new ECDSA key.
default: 'prime256v1'
choices: ['secp112r1', 'secp112r2', 'secp128r1', 'secp128r2', 'secp160k1', 'secp160r1', 'secp160r2', 'secp192k1', 'secp224k1', 'secp224r1',
'secp256k1', 'secp384r1', 'secp521r1', 'prime192v1', 'prime192v2', 'prime192v3', 'prime239v1', 'prime239v2', 'prime239v3', 'prime256v1',
'sect113r1', 'sect113r2', 'sect131r1', 'sect131r2', 'sect163k1', 'sect163r1', 'sect163r2', 'sect193r1', 'sect193r2', 'sect233k1', 'sect233r1',
'sect239k1', 'sect283k1', 'sect283r1', 'sect409k1', 'sect409r1', 'sect571k1', 'sect571r1', 'c2pnb163v1', 'c2pnb163v2', 'c2pnb163v3', 'c2pnb176v1',
'c2tnb191v1', 'c2tnb191v2', 'c2tnb191v3', 'c2pnb208w1', 'c2tnb239v1', 'c2tnb239v2', 'c2tnb239v3', 'c2pnb272w1', 'c2pnb304w1', 'c2tnb359v1',
'c2pnb368w1', 'c2tnb431r1', 'wap-wsg-idm-ecid-wtls1', 'wap-wsg-idm-ecid-wtls3', 'wap-wsg-idm-ecid-wtls4', 'wap-wsg-idm-ecid-wtls5',
'wap-wsg-idm-ecid-wtls6', 'wap-wsg-idm-ecid-wtls7', 'wap-wsg-idm-ecid-wtls8', 'wap-wsg-idm-ecid-wtls9', 'wap-wsg-idm-ecid-wtls10',
'wap-wsg-idm-ecid-wtls11', 'wap-wsg-idm-ecid-wtls12', 'Oakley-EC2N-3', 'Oakley-EC2N-4', 'brainpoolP160r1', 'brainpoolP160t1', 'brainpoolP192r1',
'brainpoolP192t1', 'brainpoolP224r1', 'brainpoolP224t1', 'brainpoolP256r1', 'brainpoolP256t1', 'brainpoolP320r1', 'brainpoolP320t1',
'brainpoolP384r1', 'brainpoolP384t1', 'brainpoolP512r1', 'brainpoolP512t1', 'SM2']
type: str
keylen:
description: The length to use when generating a new RSA key, in bits
default: '2048'
choices: [ "1024", "2048", "3072", "4096", "6144", "7680", "8192", "15360", "16384" ]
type: str
digest_alg:
description: The digest algorithm for the internal Certificate Authority.
default: sha256
choices: [ "sha1", "sha224", "sha256", "sha384", "sha512" ]
type: str
lifetime:
description: The lifetime in days for the internal Certificate Authority certificate. Between 1 and 12000.
default: 3650
type: int
dn_commonname:
description: The Common Name of the internal Certificate Authority certificate.
default: internal-ca
type: str
dn_country:
description: The 2-letter country code of the internal Certificate Authority certificate.
default: ''
type: str
dn_state:
description: The State or Province of the internal Certificate Authority certificate.
default: ''
type: str
dn_city:
description: The City of the internal Certificate Authority certificate.
default: ''
type: str
dn_organization:
description: The Organization of the internal Certificate Authority certificate.
default: ''
type: str
dn_organizationalunit:
description: The Organizational Unit of the internal Certificate Authority certificate.
default: ''
type: str
serial:
description: Number to be used as a sequential serial number for the next certificate to be signed by this CA.
type: int
version_added: 0.5.0
"""
EXAMPLES = """
- name: Add AD Certificate Authority
pfsense_ca:
name: AD CA
certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGcXpDQ0E1T2dB...
crl: |
-----BEGIN X509 CRL-----
MIICazCCAVMCAQEwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAxMPTldSQSBPcGVu
...
r0hUUy3w1trKtymlyhmd5XmYzINYp8p/Ws+boST+Fcw3chWTep/J8nKMeKESO0w=
-----END X509 CRL-----
state: present
- name: Remove AD Certificate Authority
pfsense_ca:
name: AD CA
state: absent
"""
RETURN = """
"""
import base64
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
PFSENSE_CA_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
method=dict(type='str', default='existing', choices=['internal', 'existing', 'intermediate']),
state=dict(type='str', default='present', choices=['present', 'absent']),
trust=dict(type='bool'),
randomserial=dict(type='bool'),
certificate=dict(type='str'),
crl=dict(default=None, type='str'),
crlname=dict(default=None, type='str'),
crlrefid=dict(default=None, type='str'),
key=dict(type='str', no_log=True),
keytype=dict(type='str', default='RSA', choices=['RSA', 'ECDSA']),
ecname=dict(
type='str',
default='prime256v1',
choices=[
'secp112r1', 'secp112r2', 'secp128r1', 'secp128r2', 'secp160k1', 'secp160r1', 'secp160r2',
'secp192k1', 'secp224k1', 'secp224r1', 'secp256k1', 'secp384r1', 'secp521r1', 'prime192v1', 'prime192v2', 'prime192v3', 'prime239v1',
'prime239v2', 'prime239v3', 'prime256v1', 'sect113r1', 'sect113r2', 'sect131r1', 'sect131r2', 'sect163k1', 'sect163r1', 'sect163r2',
'sect193r1', 'sect193r2', 'sect233k1', 'sect233r1', 'sect239k1', 'sect283k1', 'sect283r1', 'sect409k1', 'sect409r1', 'sect571k1', 'sect571r1',
'c2pnb163v1', 'c2pnb163v2', 'c2pnb163v3', 'c2pnb176v1', 'c2tnb191v1', 'c2tnb191v2', 'c2tnb191v3', 'c2pnb208w1', 'c2tnb239v1', 'c2tnb239v2',
'c2tnb239v3', 'c2pnb272w1', 'c2pnb304w1', 'c2tnb359v1', 'c2pnb368w1', 'c2tnb431r1', 'wap-wsg-idm-ecid-wtls1', 'wap-wsg-idm-ecid-wtls3',
'wap-wsg-idm-ecid-wtls4', 'wap-wsg-idm-ecid-wtls5', 'wap-wsg-idm-ecid-wtls6', 'wap-wsg-idm-ecid-wtls7', 'wap-wsg-idm-ecid-wtls8',
'wap-wsg-idm-ecid-wtls9', 'wap-wsg-idm-ecid-wtls10', 'wap-wsg-idm-ecid-wtls11', 'wap-wsg-idm-ecid-wtls12', 'Oakley-EC2N-3', 'Oakley-EC2N-4',
'brainpoolP160r1', 'brainpoolP160t1', 'brainpoolP192r1', 'brainpoolP192t1', 'brainpoolP224r1', 'brainpoolP224t1', 'brainpoolP256r1',
'brainpoolP256t1', 'brainpoolP320r1', 'brainpoolP320t1', 'brainpoolP384r1', 'brainpoolP384t1', 'brainpoolP512r1', 'brainpoolP512t1', 'SM2']),
keylen=dict(type='str', default='2048', choices=["1024", "2048", "3072", "4096", "6144", "7680", "8192", "15360", "16384"]),
digest_alg=dict(type='str', default='sha256', choices=['sha1', 'sha224', 'sha256', 'sha384', 'sha512']),
lifetime=dict(default=3650, type='int'),
dn_commonname=dict(default='internal-ca', type='str'),
dn_country=dict(default='', type='str'),
dn_state=dict(default='', type='str'),
dn_city=dict(default='', type='str'),
dn_organization=dict(default='', type='str'),
dn_organizationalunit=dict(default='', type='str'),
serial=dict(type='int'),
)
# These are default but not enforced values
CA_CREATE_DEFAULT = dict(
randomserial='disabled',
serial='0',
trust='disabled',
)
# Booleans that map to different values
CA_BOOL_VALUES = dict(
randomserial=('disabled', 'enabled'),
trust=('disabled', 'enabled'),
)
class PFSenseCAModule(PFSenseModuleBase):
""" module managing pfsense certificate authorities """
@staticmethod
def get_argument_spec():
""" return argument spec """
return PFSENSE_CA_ARGUMENT_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseCAModule, self).__init__(module, pfsense, root='pfsense', node='ca', have_refid=True, create_default=CA_CREATE_DEFAULT,
bool_values=CA_BOOL_VALUES)
self.name = "pfsense_ca"
self.refresh_crls = False
self.crl = None
cmd = ('require_once("certs.inc");'
'$max_lifetime = cert_get_max_lifetime();'
'echo json_encode($max_lifetime);')
self.max_lifetime = int(self.pfsense.php(cmd))
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params['state'] == 'absent':
return
if re.search(r"[\?\>\<\&\/\\\"\']", params['name']):
self.module.fail_json(msg='name contains invalid characters')
pattern = re.compile(r"[^a-zA-Z0-9 '/~`!@#$%\^&*()_\-+={}[\]|;:\"<>,.?\\]")
for param in ['dn_commonname', 'dn_state', 'dn_city', 'dn_organization', 'dn_organizationalunit']:
if re.search(pattern, self.params[param]):
self.module.fail_json(msg=f'{param} contains invalid characters')
if params['lifetime'] > self.max_lifetime:
self.module.fail_json(msg=f'Lifetime is longer than the maximum allowed value ({self.max_lifetime})')
if params['method'] == 'existing':
if params['certificate'] is None:
self.module.fail_json(msg='Missing required argument "certificate"')
# TODO - Make sure certificate purpose includes CA
cert = params['certificate']
if re.match('LS0', cert):
cert = base64.b64decode(cert.encode()).decode()
lines = cert.splitlines()
if lines[0] == '-----BEGIN CERTIFICATE-----' and lines[-1] == '-----END CERTIFICATE-----':
params['certificate'] = base64.b64encode(cert.encode()).decode()
else:
self.module.fail_json(msg='Could not recognize certificate format: %s' % (cert))
if params['crl'] is not None:
crl = params['crl']
if re.match('LS0', crl):
crl = base64.b64decode(crl.encode()).decode()
lines = crl.splitlines()
if lines[0] == '-----BEGIN X509 CRL-----' and lines[-1] == '-----END X509 CRL-----':
params['crl'] = base64.b64encode(crl.encode()).decode()
else:
self.module.fail_json(msg='Could not recognize CRL format: %s' % (crl))
if params['key'] is not None:
ca_key = params['key']
if re.match('LS0', ca_key):
ca_key = base64.b64decode(ca_key.encode()).decode()
lines = ca_key.splitlines()
if lines[0] == '-----BEGIN PRIVATE KEY-----' and lines[-1] == '-----END PRIVATE KEY-----':
params['key'] = base64.b64encode(ca_key.encode()).decode()
else:
self.module.fail_json(msg='Could not recognize CA key format: %s' % (ca_key))
if params['serial'] is not None:
if int(params['serial']) < 1:
self.module.fail_json(msg='serial must be greater than 0')
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
obj['descr'] = params['name']
if params['state'] == 'present':
if params['method'] == 'existing':
if 'certificate' in params and params['certificate'] is not None:
obj['crt'] = params['certificate']
if params['crl'] is not None:
self.crl = {}
self.crl['method'] = 'existing'
self.crl['text'] = params['crl']
self._get_ansible_param(self.crl, 'crlname', fname='descr', force=True, force_value=obj['descr'] + ' CRL')
self._get_ansible_param(self.crl, 'crlrefid', fname='refid')
if params['key'] is not None:
obj['prv'] = params['key']
for arg in CA_BOOL_VALUES:
self._get_ansible_param_bool(obj, arg, value=CA_BOOL_VALUES[arg][1], value_false=CA_BOOL_VALUES[arg][0])
self._get_ansible_param(obj, 'serial')
return obj
##############################
# XML processing
#
def _find_crl_for_ca(self, caref):
result = self.root_elt.findall("crl[caref='{0}']".format(caref))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple CRLs for caref {0}, you must specify crlname or crlrefid.'.format(caref))
else:
return None
def _find_crl_by_name(self, crlname):
result = self.root_elt.findall("crl[descr='{0}']".format(crlname))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple CRLs for name {0}, you must specify crlrefid.'.format(crlname))
else:
return None
def _find_crl_by_refid(self, crlrefid):
result = self.root_elt.findall("crl[refid='{0}']".format(crlrefid))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple CRLs for refid {0}. This is an unsupported condition'.format(crlrefid))
else:
return None
def _copy_and_add_target(self):
""" populate the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
self.root_elt.insert(self._find_last_element_index(), self.target_elt)
if self.crl is not None:
crl_elt = self.pfsense.new_element('crl')
self.crl['caref'] = self.obj['refid']
if 'refid' not in self.crl:
self.crl['refid'] = self.pfsense.uniqid()
self.pfsense.copy_dict_to_element(self.crl, crl_elt)
self.diff['after']['crl'] = self.crl['text']
self.pfsense.root.append(crl_elt)
self.refresh_crls = True
def _copy_and_update_target(self):
""" update the XML target_elt """
(before, changed) = super(PFSenseCAModule, self)._copy_and_update_target()
if self.crl is not None:
crl_elt = None
# If a crlrefid is specified, update it or create a new one with that refid
if self.params['crlrefid'] is not None:
crl_elt = self._find_crl_by_refid(self.params['crlrefid'])
self.crl['refid'] = self.params['crlrefid']
else:
if self.params['crlname'] is not None:
crl_elt = self._find_crl_by_name(self.params['crlname'])
if crl_elt is None:
crl_elt = self._find_crl_for_ca(self.target_elt.find('refid').text)
if crl_elt is None:
changed = True
crl_elt = self.pfsense.new_element('crl')
self.crl['caref'] = self.target_elt.find('refid').text
if 'refid' not in self.crl:
self.crl['refid'] = self.pfsense.uniqid()
self.pfsense.copy_dict_to_element(self.crl, crl_elt)
# Add after the existing ca entry
self.pfsense.root.insert(self._find_this_element_index() + 1, crl_elt)
self.refresh_crls = True
else:
before['crl'] = crl_elt.find('text').text
before['crlname'] = crl_elt.find('descr').text
if 'crlname' not in self.crl:
self.crl['descr'] = before['crlname']
before['crlrefid'] = crl_elt.find('refid').text
if 'refid' not in self.crl:
self.crl['refid'] = before['crlrefid']
if self.pfsense.copy_dict_to_element(self.crl, crl_elt):
changed = True
self.refresh_crls = True
self.diff['after']['crl'] = self.crl['text']
self.diff['after']['crlname'] = self.crl['descr']
self.diff['after']['crlrefid'] = self.crl['refid']
return (before, changed)
##############################
# run
#
def _update(self):
(dummy, stdout, stderr) = ('', '', '')
if self.params['state'] == 'present':
if self.params['method'] == 'existing':
# ca_import will base64 encode the cert + key and will fix 'caref' for CAs that reference each other
# $ca needs to be an existing reference (particularly 'refid' must be set) before calling ca_import
# key and serial are optional arguments. TODO - handle key and serial
(dummy, stdout, stderr) = self.pfsense.phpshell("""
$ca =& lookup_ca('{refid}')['item'];
ca_import($ca, '{cert}');
write_config('Update CA reference');
ca_setup_trust_store();
cert_restart_services(ca_get_all_services('{refid}'));""".format(refid=self.target_elt.find('refid').text,
cert=base64.b64decode(self.target_elt.find('crt').text.encode()).decode()))
if self.refresh_crls:
(dummy, crl_stdout, crl_stderr) = self.pfsense.phpshell("""
require_once("openvpn.inc");
openvpn_refresh_crls();
require_once("vpn.inc");
ipsec_configure();""")
stdout += crl_stdout
stderr += crl_stderr
if self.params['method'] == 'internal':
# Create an internal CA
(dummy, stdout, stderr) = self.pfsense.phpshell("""
$caent =& lookup_ca('{refid}');
$ca =& $caent['item'];
$dn = array('commonName' => '{dn_commonname}');
$pconfig = array( 'dn_country' => '{dn_country}',
'dn_state' => '{dn_state}',
'dn_city' => '{dn_city}',
'dn_organization' => '{dn_organization}',
'dn_organizationalunit' => '{dn_organizationalunit}' );
if (!empty($pconfig['dn_country'])) {{
$dn['countryName'] = $pconfig['dn_country'];
}}
if (!empty($pconfig['dn_state'])) {{
$dn['stateOrProvinceName'] = $pconfig['dn_state'];
}}
if (!empty($pconfig['dn_city'])) {{
$dn['localityName'] = $pconfig['dn_city'];
}}
if (!empty($pconfig['dn_organization'])) {{
$dn['organizationName'] = $pconfig['dn_organization'];
}}
if (!empty($pconfig['dn_organizationalunit'])) {{
$dn['organizationalUnitName'] = $pconfig['dn_organizationalunit'];
}}
print_r($dn);
if (!ca_create($ca, '{keylen}', '{lifetime}', $dn, '{digest_alg}', '{keytype}', '{ecname}')) {{
print("ca_create failed");
$input_errors = array();
while ($ssl_err = openssl_error_string()) {{
if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {{
array_push($input_errors, "openssl library returns: " . $ssl_err);
}}
}}
print_r($input_errors);
}}
$savemsg = sprintf(gettext("Created internal Certificate Authority %s"), $ca['descr']);
config_set_path("ca/{{$caent['idx']}}", $ca);
write_config($savemsg);
ca_setup_trust_store();""".format(refid=self.target_elt.find('refid').text,
dn_commonname=self.params['dn_commonname'],
dn_country=self.params['dn_country'],
dn_state=self.params['dn_state'],
dn_city=self.params['dn_city'],
dn_organization=self.params['dn_organization'],
dn_organizationalunit=self.params['dn_organizationalunit'],
keylen=self.params['keylen'],
lifetime=self.params['lifetime'],
keytype=self.params['keytype'],
digest_alg=self.params['digest_alg'],
ecname=self.params['ecname']))
return (dummy, stdout, stderr)
def _pre_remove_target_elt(self):
self.diff['after'] = {}
if self.target_elt is not None:
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
crl_elt = self._find_crl_for_ca(self.target_elt.find('refid').text)
self.elements.remove(self.target_elt)
if crl_elt is not None:
self.diff['before']['crl'] = crl_elt.find('text').text
self.root_elt.remove(crl_elt)
else:
self.diff['before'] = {}
def main():
module = AnsibleModule(
argument_spec=PFSENSE_CA_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseCAModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_cert.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Carlos Rodrigues
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_cert
version_added: 0.5.0
author: Carlos Rodrigues (@cmarodrigues)
short_description: Manage pfSense certificates
description:
- Manage pfSense certificates
notes:
options:
name:
description: The name of the certificate
required: true
type: str
ca:
description: The Certificate Authority
type: str
keytype:
description: The type of key to generate
default: 'RSA'
choices: [ 'RSA', 'ECDSA' ]
type: str
digestalg:
description: The digest method used when the certificate is signed
default: 'sha256'
choices: ['sha1', 'sha224', 'sha256', 'sha384', 'sha512']
type: str
ecname:
description: The Elliptic Curve Name to use when generating a new ECDSA key
default: 'prime256v1'
choices: ['secp112r1', 'secp112r2', 'secp128r1', 'secp128r2', 'secp160k1', 'secp160r1', 'secp160r2', 'secp192k1', 'secp224k1', 'secp224r1',
'secp256k1', 'secp384r1', 'secp521r1', 'prime192v1', 'prime192v2', 'prime192v3', 'prime239v1', 'prime239v2', 'prime239v3', 'prime256v1',
'sect113r1', 'sect113r2', 'sect131r1', 'sect131r2', 'sect163k1', 'sect163r1', 'sect163r2', 'sect193r1', 'sect193r2', 'sect233k1', 'sect233r1',
'sect239k1', 'sect283k1', 'sect283r1', 'sect409k1', 'sect409r1', 'sect571k1', 'sect571r1', 'c2pnb163v1', 'c2pnb163v2', 'c2pnb163v3', 'c2pnb176v1',
'c2tnb191v1', 'c2tnb191v2', 'c2tnb191v3', 'c2pnb208w1', 'c2tnb239v1', 'c2tnb239v2', 'c2tnb239v3', 'c2pnb272w1', 'c2pnb304w1', 'c2tnb359v1',
'c2pnb368w1', 'c2tnb431r1', 'wap-wsg-idm-ecid-wtls1', 'wap-wsg-idm-ecid-wtls3', 'wap-wsg-idm-ecid-wtls4', 'wap-wsg-idm-ecid-wtls5',
'wap-wsg-idm-ecid-wtls6', 'wap-wsg-idm-ecid-wtls7', 'wap-wsg-idm-ecid-wtls8', 'wap-wsg-idm-ecid-wtls9', 'wap-wsg-idm-ecid-wtls10',
'wap-wsg-idm-ecid-wtls11', 'wap-wsg-idm-ecid-wtls12', 'Oakley-EC2N-3', 'Oakley-EC2N-4', 'brainpoolP160r1', 'brainpoolP160t1', 'brainpoolP192r1',
'brainpoolP192t1', 'brainpoolP224r1', 'brainpoolP224t1', 'brainpoolP256r1', 'brainpoolP256t1', 'brainpoolP320r1', 'brainpoolP320t1',
'brainpoolP384r1', 'brainpoolP384t1', 'brainpoolP512r1', 'brainpoolP512t1', 'SM2']
type: str
keylen:
description: The length to use when generating a new RSA key, in bits
default: '2048'
type: str
lifetime:
description: The length of time the signed certificate will be valid, in days
default: '3650'
type: str
dn_country:
description: The Country Code
type: str
dn_state:
description: The State or Province
type: str
dn_city:
description: The City
type: str
dn_organization:
description: The Organization
type: str
dn_organizationalunit:
description: The Organizational Unit
type: str
altnames:
description:
>
The Alternative Names. A list of aditional identifiers for the certificate.
A comma separed values with format: DNS:hostname,IP:X.X.X.X,email:user@mail,URI:url
type: str
certificate:
description:
>
The certificate to import. This can be in PEM form or Base64
encoded PEM as a single string (which is how pfSense stores it).
type: str
key:
description:
>
The key to import. This can be in PEM form or Base64
encoded PEM as a single string (which is how pfSense stores it).
type: str
state:
description: State in which to leave the certificate
default: 'present'
choices: [ 'present', 'absent' ]
type: str
method:
description: Method of the certificate created
default: 'internal'
choices: [ 'internal', 'import' ]
type: str
certtype:
description: Type of the certificate ('user' is a certificate for the user)
default: 'user'
choices: [ 'user', 'server' ]
type: str
"""
EXAMPLES = """
- name: Generate new internal certificate
pfsense_cert:
method: "internal"
name: "test"
ca: "internal-ca"
keytype: "RSA"
keylen: 2048
lifetime: 3650
dn_country: "PT"
dn_organization: "Dummy"
certtype: "user"
state: present
- name: Import certificate
pfsense_cert:
method: "import"
name: "test"
certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUUxVENDQXIyZ0F3...
key: |
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4yY0SI8lWNN2B
...
i0LiJ+QOek6Qy+51kMK3rXNsQQ==
-----END PRIVATE KEY-----
certtype: "user"
state: present
- name: Remove certificate
pfsense_cert:
name: "test"
state: absent
"""
RETURN = """
"""
import base64
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
CERT_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
ca=dict(type='str'),
keytype=dict(type='str', default='RSA', choices=['RSA', 'ECDSA']),
digestalg=dict(type='str', default='sha256', choices=['sha1', 'sha224', 'sha256', 'sha384', 'sha512']),
ecname=dict(
type='str',
default='prime256v1',
choices=[
'secp112r1', 'secp112r2', 'secp128r1', 'secp128r2', 'secp160k1', 'secp160r1', 'secp160r2',
'secp192k1', 'secp224k1', 'secp224r1', 'secp256k1', 'secp384r1', 'secp521r1', 'prime192v1', 'prime192v2', 'prime192v3', 'prime239v1',
'prime239v2', 'prime239v3', 'prime256v1', 'sect113r1', 'sect113r2', 'sect131r1', 'sect131r2', 'sect163k1', 'sect163r1', 'sect163r2',
'sect193r1', 'sect193r2', 'sect233k1', 'sect233r1', 'sect239k1', 'sect283k1', 'sect283r1', 'sect409k1', 'sect409r1', 'sect571k1', 'sect571r1',
'c2pnb163v1', 'c2pnb163v2', 'c2pnb163v3', 'c2pnb176v1', 'c2tnb191v1', 'c2tnb191v2', 'c2tnb191v3', 'c2pnb208w1', 'c2tnb239v1', 'c2tnb239v2',
'c2tnb239v3', 'c2pnb272w1', 'c2pnb304w1', 'c2tnb359v1', 'c2pnb368w1', 'c2tnb431r1', 'wap-wsg-idm-ecid-wtls1', 'wap-wsg-idm-ecid-wtls3',
'wap-wsg-idm-ecid-wtls4', 'wap-wsg-idm-ecid-wtls5', 'wap-wsg-idm-ecid-wtls6', 'wap-wsg-idm-ecid-wtls7', 'wap-wsg-idm-ecid-wtls8',
'wap-wsg-idm-ecid-wtls9', 'wap-wsg-idm-ecid-wtls10', 'wap-wsg-idm-ecid-wtls11', 'wap-wsg-idm-ecid-wtls12', 'Oakley-EC2N-3', 'Oakley-EC2N-4',
'brainpoolP160r1', 'brainpoolP160t1', 'brainpoolP192r1', 'brainpoolP192t1', 'brainpoolP224r1', 'brainpoolP224t1', 'brainpoolP256r1',
'brainpoolP256t1', 'brainpoolP320r1', 'brainpoolP320t1', 'brainpoolP384r1', 'brainpoolP384t1', 'brainpoolP512r1', 'brainpoolP512t1', 'SM2']),
keylen=dict(type='str', default='2048'),
lifetime=dict(type='str', default='3650'),
dn_country=dict(type='str'),
dn_state=dict(type='str'),
dn_city=dict(type='str'),
dn_organization=dict(type='str'),
dn_organizationalunit=dict(type='str'),
altnames=dict(type='str'),
certificate=dict(type='str'),
key=dict(type='str', no_log=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
method=dict(type='str', default='internal', choices=['internal', 'import']),
certtype=dict(type='str', default='user', choices=['user', 'server']),
)
CERT_PHP_COMMAND_PREFIX = """
require_once('certs.inc');
"""
class PFSenseCertModule(PFSenseModuleBase):
""" module managing pfsense certificates """
@staticmethod
def get_argument_spec():
""" return argument spec """
return CERT_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseCertModule, self).__init__(module, pfsense, root='pfsense', node='cert')
self.name = "pfsense_cert"
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params['state'] == 'absent':
return
if params['method'] == 'internal':
# An internal CA is required for internal certificate
if params['ca'] is None:
self.module.fail_json(msg='CA is required.')
else:
ca = self._find_ca(params['ca'])
if ca is not None:
if ca.find('prv') is None:
self.module.fail_json(msg='CA (%s) is not an internal CA' % params['ca'])
else:
self.module.fail_json(msg='CA (%s) not found' % params['ca'])
# validate Certificate
if params['certificate'] is not None:
cert = params['certificate']
if re.match('LS0', cert):
cert = base64.b64decode(cert.encode()).decode()
lines = cert.splitlines()
if lines[0] == '-----BEGIN CERTIFICATE-----' and lines[-1] == '-----END CERTIFICATE-----':
params['certificate'] = base64.b64encode(cert.encode()).decode()
else:
self.module.fail_json(msg='Could not recognize certificate format: %s' % (cert))
# validate key
if params['key'] is not None:
key = params['key']
if re.match('LS0', key):
key = base64.b64decode(key.encode()).decode()
lines = key.splitlines()
if re.match('^-----BEGIN ((EC|RSA) )?PRIVATE KEY-----$', lines[0]) and re.match('^-----END ((EC|RSA) )?PRIVATE KEY-----$', lines[-1]):
params['key'] = base64.b64encode(key.encode()).decode()
else:
self.module.fail_json(msg='Could not recognize key format: %s' % (key))
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
# certificate description
obj['descr'] = params['name']
if params['state'] == 'present':
if params['ca'] is not None:
# found CA
ca = self._find_ca(params['ca'])
if ca is not None:
# get CA refid
obj['caref'] = ca.find('refid').text
else:
self.module.fail_json(msg='CA (%s) not found' % params['ca'])
if 'certificate' in params and params['certificate'] is not None:
obj['crt'] = params['certificate']
if 'key' in params and params['key'] is not None:
obj['prv'] = params['key']
return obj
##############################
# XML processing
#
def _find_ca(self, caref):
result = self.root_elt.findall("ca[descr='{0}']".format(caref))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple CAs for caref {0}.'.format(caref))
else:
result = self.root_elt.findall("ca[refid='{0}']".format(caref))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple CAs for caref {0}.'.format(caref))
else:
return None
def _copy_and_add_target(self):
""" populate the XML target_elt """
obj = self.obj
obj['refid'] = self.pfsense.uniqid()
self.diff['after'] = obj
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.root_elt.insert(self._find_last_element_index(), self.target_elt)
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
return (before, changed)
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'" + self.obj['descr'] + "'"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'descr')
else:
values += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(values))
return values
##############################
# run
#
def _update(self):
if self.params['state'] == 'present':
if self.params['method'] == 'import':
# import certificate
return self.pfsense.phpshell("""
require_once('certs.inc');
$cert =& lookup_cert('{refid}');
cert_import($cert, '{cert}', '{key}');
$savemsg = sprintf(gettext("Imported certificate %s"), $cert['descr']);
write_config($savemsg);
cert_restart_services(cert_get_all_services('{refid}'));
""".format(refid=self.target_elt.find('refid').text,
cert=base64.b64decode(self.target_elt.find('crt').text.encode()).decode(),
key=base64.b64decode(self.target_elt.find('prv').text.encode()).decode()))
else:
# generate internal certificate
return self.pfsense.phpshell("""
require_once('certs.inc');
$certent =& lookup_cert('{refid}');
$cert =& $certent['item'];
$pconfig = array( 'dn_commonname' => '{dn_commonname}',
'dn_country' => '{dn_country}',
'dn_state' => '{dn_state}',
'dn_city' => '{dn_city}',
'dn_organization' => '{dn_organization}',
'dn_organizationalunit' => '{dn_organizationalunit}',
'dn_altnames' => '{altnames}' );
/* Create an internal certificate */
$dn = array('commonName' => $pconfig['dn_commonname']);
if (!empty($pconfig['dn_country']) && ($pconfig['dn_country']!=='None')) {{
$dn['countryName'] = $pconfig['dn_country'];
}}
if (!empty($pconfig['dn_state']) && ($pconfig['dn_state']!=='None')) {{
$dn['stateOrProvinceName'] = $pconfig['dn_state'];
}}
if (!empty($pconfig['dn_city']) && ($pconfig['dn_city']!=='None')) {{
$dn['localityName'] = $pconfig['dn_city'];
}}
if (!empty($pconfig['dn_organization']) && ($pconfig['dn_organization']!=='None')) {{
$dn['organizationName'] = $pconfig['dn_organization'];
}}
if (!empty($pconfig['dn_organizationalunit']) && ($pconfig['dn_organizationalunit']!=='None')) {{
$dn['organizationalUnitName'] = $pconfig['dn_organizationalunit'];
}}
$altnames_tmp = array();
$cn_altname = cert_add_altname_type($pconfig['dn_commonname']);
if (!empty($cn_altname)) {{
$altnames_tmp[] = $cn_altname;
}}
if (!empty($pconfig['dn_altnames']) && ($pconfig['dn_altnames']!=='None')) {{
$altnames_tmp[] = $pconfig['dn_altnames'];
}}
if (!empty($altnames_tmp)) {{
$dn['subjectAltName'] = implode(",", $altnames_tmp);
}}
if (!cert_create($cert, '{caref}', '{keylen}', '{lifetime}', $dn, '{certtype}', '{digest_alg}', '{keytype}', '{ecname}')) {{
$input_errors = array();
while ($ssl_err = openssl_error_string()) {{
if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {{
$input_errors[] = sprintf(gettext("OpenSSL Library Error: %s"), $ssl_err);
}}
}}
print_r($input_errors);
}}
config_set_path("cert/${{certent['idx']}}", $cert);
$savemsg = sprintf(gettext("Created internal certificate %s"), $cert['descr']);
write_config($savemsg);
cert_restart_services(cert_get_all_services('{refid}'));
""".format(refid=self.target_elt.find('refid').text,
dn_commonname=self.params['name'],
dn_country=self.params['dn_country'],
dn_state=self.params['dn_state'],
dn_city=self.params['dn_city'],
dn_organization=self.params['dn_organization'],
dn_organizationalunit=self.params['dn_organizationalunit'],
altnames=self.params['altnames'],
caref=self.target_elt.find('caref').text,
keylen=self.params['keylen'],
lifetime=self.params['lifetime'],
certtype=self.params['certtype'],
keytype=self.params['keytype'],
digest_alg=self.params['digestalg'],
ecname=self.params['ecname']))
else:
return (None, '', '')
def _pre_remove_target_elt(self):
self.diff['after'] = {}
if self.target_elt is not None:
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
self.elements.remove(self.target_elt)
else:
self.diff['before'] = {}
def main():
module = AnsibleModule(
argument_spec=CERT_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseCertModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_default_gateway.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2023, Nicolas Zagulajew
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_default_gateway
version_added: 0.6.0
author: "Nicolas Zagulajew (@freeeflyer)"
short_description: Manage pfSense default gateway
description: Check and update pfSense default gateway
notes:
options:
gateway:
description: Default gateway name
required: false
type: str
ipprotocol:
description: Choose the Internet Protocol Version for this gateway.
required: false
choices: [ "inet", "inet6" ]
default: inet
type: str
"""
EXAMPLES = """
- name: Sets default gateway to automatic
pfsense_default_gateway:
gateway: automatic
ipprotocol: inet
- name: Remove gateway (ie setting it to None)
pfsense_default_gateway:
gateway: none
ipprotocol: inet
- name: return gateways
pfsense_default_gateway:
"""
RETURN = """
defaultgw4:
description: default gateway for ipv4
returned: always
type: str
sample: INTERNET_GW4
defaultgw6:
description: default gateway for ipv6
returned: always
type: str
sample: INTERNET_GW4
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI). If state=read, also returns defaultgw4 and defaultgw6.
returned: always
type: list
sample: [update default_gateway name='my_gw', protocol='inet6' ]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.default_gateway import PFSenseDefaultGatewayModule, \
DEFAULT_GATEWAY_ARGUMENT_SPEC
def main():
module = AnsibleModule(
argument_spec=DEFAULT_GATEWAY_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseDefaultGatewayModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_dhcp_server.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2024, David Rosado
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '6.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_dhcp_server
version_added: "0.7.0"
author: "David Rosado (@davrosza)"
short_description: Manage pfSense DHCP servers
description:
- Manage DHCP servers on pfSense
notes:
options:
state:
description: State in which to leave the DHCP server
choices: [ 'present', 'absent' ]
default: 'present'
type: str
interface:
description: Interface on which to configure the DHCP server
required: true
type: str
enable:
description: Enable DHCP server on the interface
type: bool
default: true
range_from:
description: Start of IP address range
type: str
range_to:
description: End of IP address range
type: str
failover_peerip:
description: Failover peer IP address
type: str
defaultleasetime:
description: Default lease time in seconds
type: int
maxleasetime:
description: Maximum lease time in seconds
type: int
netmask:
description: Subnet mask
type: str
gateway:
description: Gateway IP address
type: str
domain:
description: Domain name
type: str
domainsearchlist:
description: Domain search list
type: str
ddnsdomain:
description: DDNS domain
type: str
ddnsdomainprimary:
description: DDNS domain primary server
type: str
ddnsdomainkeyname:
description: DDNS domain key name
type: str
ddnsdomainkeyalgorithm:
description: DDNS domain key algorithm
type: str
choices: [ 'hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512' ]
default: hmac-md5
ddnsdomainkey:
description: DDNS domain key
type: str
mac_allow:
description: Allowed MAC addresses
type: list
elements: str
mac_deny:
description: Denied MAC addresses
type: list
elements: str
ddnsclientupdates:
description: DDNS client updates
type: str
default: 'allow'
choices: [ 'allow', 'deny', 'ignore' ]
tftp:
description: TFTP server
type: str
ldap:
description: LDAP server
type: str
nextserver:
description: Next server
type: str
filename:
description: Filename
type: str
filename32:
description: 32-bit filename
type: str
filename64:
description: 64-bit filename
type: str
rootpath:
description: Root path
type: str
numberoptions:
description: DHCP options currently non applicable
type: str
ignorebootp:
description: Disable BOOTP
type: bool
denyunknown:
description: >
Enable DHCP to ignore unknown clients. Choices are `disabled` - "Allow all clients", `enabled` - "Allow known clients from any
interface", and `class` - "Allow known clients from only this interface". Default is `disabled`.
type: str
choices: ['disabled', 'enabled', 'class']
nonak:
description: Ignore denied clients
type: bool
ignoreclientuids:
description: Ignore client identifiers
type: bool
staticarp:
description: Enable Static ARP entries
type: bool
dhcpinlocaltime:
description: Change DHCP display lease time from UTC to local time
type: bool
statsgraph:
description: Enable monitoring graphs for lease DHCP statistics
type: bool
disablepingcheck:
description: Enable DHCP ping check
type: bool
winsserver:
description: The WINS server
type: list
elements: str
dnsserver:
description: The dns server
type: list
elements: str
ntpserver:
description: The ntpserver
type: list
elements: str
"""
EXAMPLES = """
- name: Configure DHCP server on IOT interface
pfsense_dhcp_server:
interface: IOT
enable: true
range_from: 192.168.1.100
range_to: 192.168.1.200
netmask: 255.255.255.0
gateway: 192.168.1.1
domain: example.com
defaultleasetime: 86400
maxleasetime: 172800
- name: Remove DHCP server from opt1 interface
pfsense_dhcp_server:
interface: opt1
state: absent
"""
RETURN = """
commands:
description: The set of commands that would be pushed to the remote device.
returned: always
type: list
sample: [
"create dhcp_server 'IOT', range_from='192.168.1.100', range_to='192.168.1.200', enable='True'",
"update dhcp_server 'IOT' set domain='example.com'",
"delete dhcp_server 'opt1'"
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.dhcp_server import PFSenseDHCPServerModule, DHCPSERVER_ARGUMENT_SPEC
def main():
module = AnsibleModule(
argument_spec=DHCPSERVER_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseDHCPServerModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_dhcp_static.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2023, Carlos Rodrigues
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_dhcp_static
version_added: "0.5.0"
author: Carlos Rodrigues (@cmarodrigues)
short_description: Manage pfSense DHCP static mapping
description:
- Manage pfSense DHCP static mapping
notes:
options:
name:
description: The client name identifier. At least one of I(name) or I(macaddr) is required.
type: str
aliases:
- cid
netif:
description: >
The network interface. This defaults to the only enabled DHCP interface if there is only one.
type: str
macaddr:
description: The mac address. At least one of I(name) or I(macaddr) is required.
type: str
ipaddr:
description: The IP address
type: str
hostname:
description: The hostname
type: str
descr:
description: The description
type: str
filename:
description: The filename
type: str
rootpath:
description: The roothpath
type: str
defaultleasetime:
description: the default lease time
type: str
maxleasetime:
description: The max lease time
type: str
gateway:
description: The gateway
type: str
domain:
description: The domain
type: str
winsserver:
description: The WINS server
type: list
elements: str
dnsserver:
description: The dns server
type: list
elements: str
ntpserver:
description: The ntpserver
type: list
elements: str
domainsearchlist:
description: The domain search list servers
type: str
ddnsdomain:
description: The ddns domain
type: str
ddnsdomainprimary:
description: The ddns primary domain
type: str
ddnsdomainsecondary:
description: The ddns secondary domain
type: str
ddnsdomainkeyname:
description: The ddns domain key name
type: str
ddnsdomainkeyalgorithm:
description: The ddns key algorithm
type: str
choices: [ 'hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512' ]
ddnsdomainkey:
description: The ddns domain key
type: str
tftp:
description: The TFTP server
type: str
ldap:
description: The ldap server
type: str
nextserver:
description: The next server
type: str
filename32:
description: The filename for 32bits
type: str
filename64:
description: The filename for 64bits
type: str
filename32arm:
description: The filename for 32arm
type: str
filename64arm:
description: The filename for 64arm
type: str
uefihttpboot:
description: UEFI HTTPBoot URL
type: str
version_added: "0.5.2"
numberoptions:
description: The number options
type: str
arp_table_static_entry:
description: Create an ARP Table Static Entry for this MAC & IP Address pair
type: bool
required: false
default: false
state:
description: State in which to leave the configuration
default: present
choices: [ "present", "absent" ]
type: str
"""
EXAMPLES = """
- name: Create DHCP static mapping
pfsense_dhcp_static:
name: "test"
macaddr: "aa:aa:aa:aa:aa:aa"
ipaddr: "192.168.1.10"
state: present
- name: Remove DHCP static mapping
pfsense_dhcp_static:
name: "test"
state: absent
"""
RETURN = """
netif:
description: The selected interface
returned: success
type: str
sample: 'lan'
"""
from ipaddress import ip_address, ip_network
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
DHCP_STATIC_ARGUMENT_SPEC = dict(
name=dict(type='str', aliases=['cid']),
macaddr=dict(type='str'),
netif=dict(type='str'),
ipaddr=dict(type='str'),
hostname=dict(type='str'),
descr=dict(type='str'),
filename=dict(type='str'),
rootpath=dict(type='str'),
defaultleasetime=dict(type='str'),
maxleasetime=dict(type='str'),
gateway=dict(type='str'),
domain=dict(type='str'),
domainsearchlist=dict(type='str'),
winsserver=dict(type='list', elements='str'),
dnsserver=dict(type='list', elements='str'),
ntpserver=dict(type='list', elements='str'),
ddnsdomain=dict(type='str'),
ddnsdomainprimary=dict(type='str'),
ddnsdomainsecondary=dict(type='str'),
ddnsdomainkeyname=dict(type='str'),
ddnsdomainkeyalgorithm=dict(type='str', choices=['hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512']),
ddnsdomainkey=dict(type='str', no_log=True),
tftp=dict(type='str'),
ldap=dict(type='str'),
nextserver=dict(type='str'),
filename32=dict(type='str'),
filename64=dict(type='str'),
filename32arm=dict(type='str'),
filename64arm=dict(type='str'),
uefihttpboot=dict(type='str'),
numberoptions=dict(type='str'),
arp_table_static_entry=dict(default=False, type='bool'),
state=dict(type='str', default='present', choices=['present', 'absent']),
)
DHCP_STATIC_REQUIRED_IF = [
['arp_table_static_entry', True, ['ipaddr']],
]
DHCP_STATIC_REQUIRED_ONE_OF = [
('name', 'macaddr'),
]
class PFSenseDHCPStaticModule(PFSenseModuleBase):
""" module managing pfsense dhcp static configuration """
@staticmethod
def get_argument_spec():
""" return argument spec """
return DHCP_STATIC_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseDHCPStaticModule, self).__init__(module, pfsense)
self.name = "pfsense_dhcp_static"
self.dhcpd = self.pfsense.get_element('dhcpd')
self.root_elt = None
self.staticmaps = None
##############################
# params processing
#
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params['macaddr'] is not None and re.fullmatch(r'(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}', params['macaddr']) is None:
self.module.fail_json(msg='A valid MAC address must be specified.')
if params['netif'] is not None:
if self.pfsense.is_interface_group(params['netif']):
self.module.fail_json(msg='DHCP cannot be configured for interface groups')
else:
netif = self.pfsense.parse_interface(params['netif'])
else:
netif = None
# find staticmaps and determine interface
self._find_staticmaps(netif)
if params['ipaddr'] is not None:
addr = ip_address(u'{0}'.format(params['ipaddr']))
if addr not in self.network:
self.module.fail_json(msg='The IP address must lie in the {0} subnet.'.format(self.netif))
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
# client identifier
self._get_ansible_param(obj, 'name', fname='cid', force=True)
if params['state'] == 'present':
self._get_ansible_param(obj, 'macaddr', fname='mac', force=True)
# Forced options
for option in ['ipaddr', 'hostname', 'descr', 'filename',
'rootpath', 'defaultleasetime', 'maxleasetime',
'gateway', 'domain', 'domainsearchlist',
'ddnsdomain', 'ddnsdomainprimary', 'ddnsdomainsecondary',
'ddnsdomainkeyname', 'ddnsdomainkeyalgorithm', 'ddnsdomainkey',
'tftp', 'ldap', 'nextserver', 'filename32', 'filename64',
'filename32arm', 'filename64arm', 'uefihttpboot', 'numberoptions']:
self._get_ansible_param(obj, option, force=True)
# Non-forced options
for option in ['winsserver', 'dnsserver', 'ntpserver']:
self._get_ansible_param(obj, option)
# Defaulted options
self._get_ansible_param(obj, 'ddnsdomainkeyalgorithm', force_value='hmac-md5', force=True)
self._get_ansible_param_bool(obj, "arp_table_static_entry", value="")
return obj
##############################
# XML processing
#
def _is_valid_netif(self, netif):
for nic in self.pfsense.interfaces:
if nic.tag == netif:
if nic.find('ipaddr') is not None:
ipaddr = nic.find('ipaddr').text
if ipaddr is not None:
if nic.find('subnet') is not None:
subnet = int(nic.find('subnet').text)
if subnet < 31:
self.network = ip_network(u'{0}/{1}'.format(ipaddr, subnet), strict=False)
return True
return False
def _find_staticmaps(self, netif=None):
for e in self.dhcpd:
if netif is None or e.tag == netif:
if e.find('enable') is not None:
if self._is_valid_netif(e.tag):
if self.root_elt is not None:
self.module.fail_json(msg='Multiple DHCP servers enabled and no netif specified')
self.root_elt = e
self.netif = e.tag
self.staticmaps = self.root_elt.findall('staticmap')
if netif is not None:
break
if self.root_elt is None:
if netif is None:
self.module.fail_json(msg="No DHCP configuration")
else:
self.module.fail_json(msg="No DHCP configuration found for netif='{0}'".format(netif))
self.result['netif'] = netif
def _find_target(self):
if self.params['name'] is not None and self.params['macaddr'] is not None:
result = self.root_elt.findall("staticmap[cid='{0}'][mac='{1}']".format(self.params['name'], self.params['macaddr']))
elif self.params['name'] is not None:
result = self.root_elt.findall("staticmap[cid='{0}']".format(self.params['name']))
else:
result = self.root_elt.findall("staticmap[mac='{0}']".format(self.params['macaddr']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple static maps for cid {0}.'.format(self.obj['cid']))
else:
return None
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('staticmap')
def _copy_and_add_target(self):
""" populate the XML target_elt """
super(PFSenseDHCPStaticModule, self)._copy_and_add_target()
# Reset static map list
self.staticmaps = self.root_elt.findall('staticmap')
@staticmethod
def _get_params_to_remove():
""" returns the list of params to remove if they are not set """
return ['arp_table_static_entry']
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return "'" + self.obj['cid'] + "'"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
if before is None:
values += self.format_cli_field(self.params, 'macaddr')
values += self.format_cli_field(self.params, 'ipaddr')
values += self.format_cli_field(self.params, 'arp_table_static_entry', fvalue=self.fvalue_bool, default=False)
else:
values += self.format_updated_cli_field(self.obj, before, 'macaddr', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'ipaddr', add_comma=(values))
values += self.format_updated_cli_field(self.obj, before, 'arp_table_static_entry', fvalue=self.fvalue_bool, add_comma=(values))
return values
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell("""
require_once("util.inc");
require_once("services.inc");
$retvaldhcp = services_dhcpd_configure();
if ($retvaldhcp == 0) {
clear_subsystem_dirty('dhcpd');
}""")
def _pre_remove_target_elt(self):
self.diff['after'] = {}
if self.target_elt is not None:
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
self.staticmaps.remove(self.target_elt)
else:
self.diff['before'] = {}
def main():
module = AnsibleModule(
argument_spec=DHCP_STATIC_ARGUMENT_SPEC,
required_if=DHCP_STATIC_REQUIRED_IF,
required_one_of=DHCP_STATIC_REQUIRED_ONE_OF,
supports_check_mode=True)
pfmodule = PFSenseDHCPStaticModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_dns_resolver.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Chris Liu
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_dns_resolver
version_added: 0.6.0
author: Chris liu (@chris-cyliu), Daniel Huss (@danhuss)
short_description: Manage pfSense DNS resolver (unbound) settings
description:
- Manage pfSense DNS resolver (unbound) settings
notes:
options:
state:
description: Enable/Disable DNS Resolver
default: present
choices: [ "present", "absent" ]
type: str
port:
description: Listen Port
required: false
default: null
type: int
enablessl:
description: Enable SSL/TLS Service
required: false
default: false
type: bool
sslcert:
description: Description of the server certificate to use for SSL/TLS service.
required: false
default: ""
type: str
tlsport:
description: SSL/TLS Listen Port
required: false
default: null
type: int
active_interface:
description:
- Interface IPs used by the DNS Resolver for responding to queries from clients.
- For Virtual IPs you can specify either the IP, Description, or "IP (Description)".
required: false
default: [ "all" ]
type: list
elements: str
outgoing_interface:
description:
- Utilize different network interface(s) that the DNS Resolver will use to send queries to authoritative servers and receive their replies.
- For Virtual IPs you can specify either the IP, Description, or "IP (Description)".
required: false
default: [ "all" ]
type: list
elements: str
system_domain_local_zone_type:
description: The local-zone type used for the pfSense system domain.
required: false
default: "transparent"
type: str
choices: [ "deny", "refuse", "static", "transparent", "typetransparent", "redirect", "inform", "inform_deny", "nodefault" ]
dnssec:
description: Enable DNSSEC Support
required: false
default: true
type: bool
forwarding:
description: DNS Query Forwarding.
required: false
default: false
type: bool
forward_tls_upstream:
description: Use SSL/TLS for DNS Query Forwarding.
required: false
default: false
type: bool
regdhcp:
description: Register DHCP leases in the DNS Resolver
required: false
default: false
type: bool
regdhcpstatic:
description: Register DHCP static mappings in the DNS Resolver
required: false
default: false
type: bool
regovpnclients:
description: Register OpenVPN clients in the DNS Resolver
required: false
default: false
type: bool
custom_options:
description: additional configuration parameters
required: false
default: ""
type: str
hosts:
description: Individual hosts for which the resolver's standard DNS lookup should be overridden.
required: false
default: []
type: list
elements: dict
suboptions:
host:
description: Name of the host, without the domain part.
required: true
type: str
domain:
description: Parent domain of the host.
required: true
type: str
ip:
description: IPv4 or IPv6 comma-separated addresses to be returned for the host
required: true
type: str
descr:
description: A description may be entered here for administrative reference.
required: false
default: ""
type: str
aliases:
description: Additional names for this host.
required: false
default: []
type: list
elements: dict
suboptions:
host:
description: Name of the host, without the domain part.
required: true
type: str
domain:
description: Parent domain of the host.
required: true
type: str
description:
description: A description may be entered here for administrative reference.
required: true
type: str
domainoverrides:
description: Domains for which the resolver's standard DNS lookup should be overridden.
required: false
type: list
elements: dict
suboptions:
domain:
description: Domain whose lookups will be directed to a user-specified DNS lookup server.
required: true
type: str
ip:
description: IPv4 or IPv6 address of the authoritative DNS server for this domain.
required: true
type: str
forward_tls_upstream:
description: Use SSL/TLS for DNS Queries forwarded to this server
required: false
default: false
type: bool
tls_hostname:
description: An optional TLS hostname used to verify the server certificate when performing TLS Queries.
required: false
default: ''
type: str
descr:
description: A description may be entered here for administrative reference.
required: false
type: str
hideidentity:
description: id.server and hostname.bind queries are refused.
required: false
default: true
type: bool
hideversion:
description: version.server and version.bind queries are refused.
required: false
default: true
type: bool
prefetch:
description: Message cache elements are prefetched before they expire to help keep the cache up to date.
required: false
default: false
type: bool
prefetchkey:
description: DNSKEYs are fetched earlier in the validation process when a Delegation signer is encountered.
required: false
default: false
type: bool
dnssecstripped:
description: If enabled, DNSSEC data is required for trust-anchored zones.
required: false
default: true
type: bool
msgcachesize:
description: Message cache size in MB
required: false
default: 4
choices: [ 4, 10, 20, 50, 100, 250, 512 ]
type: int
outgoing_num_tcp:
description: Number of outgoing TCP buffers to allocate per thread.
required: false
default: 10
choices: [ 0, 10, 20, 30, 50 ]
type: int
incoming_num_tcp:
description: Number of incoming TCP buffers to allocate per thread.
required: false
default: 10
choices: [ 0, 10, 20, 30, 50 ]
type: int
edns_buffer_size:
description: Number of bytes to advertise as the EDNS reassembly buffer size.
required: false
default: "auto"
choices: [ "auto", "512", "1220", "1232", "1432", "1480", "4096" ]
type: str
num_queries_per_thread:
description: Number of queries that every thread will service simultaneously.
required: false
default: 512
choices: [ 512, 1024, 2048 ]
type: int
jostle_timeout:
description: This timeout (in milliseconds) is used for when the server is very busy.
required: false
default: 200
choices: [ 100, 200, 500, 1000 ]
type: int
cache_max_ttl:
description: The Maximum Time to Live (in seconds) for RRsets and messages in the cache.
required: false
default: 86400
type: int
cache_min_ttl:
description: The Minimum Time to Live (in seconds) for RRsets and messages in the cache.
required: false
default: 0
type: int
infra_host_ttl:
description: Time to Live, in seconds, for entries in the infrastructure host cache.
required: false
default: 900
choices: [ 60, 120, 300, 600, 900 ]
type: int
infra_cache_numhosts:
description: Number of infrastructure hosts for which information is cached.
required: false
default: 10000
choices: [ 1000, 5000, 10000, 20000, 50000, 100000, 200000 ]
type: int
unwanted_reply_threshold:
description: If enabled, a total number of unwanted replies is kept track of in every thread.
required: false
default: "disabled"
choices: [ "disabled", "5000000", "10000000", "20000000", "40000000", "50000000" ]
type: str
log_verbosity:
description: The level of detail to be logged.
required: false
default: 1
choices: [ 0, 1, 2, 3, 4, 5 ]
type: int
"""
EXAMPLES = """
- name: Enable DNS Resolver
pfsense_dns_resolver:
state: present
- name: Enable DNS Resolver with some options
pfsense_dns_resolver:
state: present
enablessl: true
sslcert: "webConfigurator default"
dnssec: true
regdhcp: true
regdhcpstatic: true
hosts:
- { host: test, domain: home.local, ip: 192.168.1.100, descr: "Example host override",
aliases: [{ host: test-admin, domain: home.local, description: "Example aliases" }] }
- name: Disable DNS Resolver
pfsense_dns_resolver:
state: absent
"""
RETURN = """
"""
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
from ansible.module_utils.basic import AnsibleModule
import base64
import re
# TODO: access control is not done here
# TODO: alias for DNS record
DNS_RESOLVER_DOMAIN_OVERRIDE_SPEC = dict(
domain=dict(required=True, type='str'),
ip=dict(required=True, type='str'),
descr=dict(type='str'),
tls_hostname=dict(default='', type='str'),
forward_tls_upstream=dict(default=False, type='bool'),
)
DNS_RESOLVER_HOST_ALIAS_SPEC = dict(
host=dict(required=True, type='str'),
domain=dict(required=True, type='str'),
description=dict(required=True, type='str'),
)
DNS_RESOLVER_HOST_SPEC = dict(
host=dict(required=True, type='str'),
domain=dict(required=True, type='str'),
ip=dict(required=True, type='str'),
descr=dict(default="", type='str'),
aliases=dict(default=[], type='list', elements='dict', options=DNS_RESOLVER_HOST_ALIAS_SPEC),
)
DNS_RESOLVER_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
# General Settings
port=dict(default=None, type='int'),
enablessl=dict(default=False, type='bool'),
sslcert=dict(default="", type='str'), # need transform
tlsport=dict(default=None, type='int'),
active_interface=dict(default=["all"], type='list', elements='str'),
outgoing_interface=dict(default=["all"], type='list', elements='str'),
# TODO: Strict Outgoing Network interface Binding: check box option
system_domain_local_zone_type=dict(default='transparent', choices=['deny', 'refuse', 'static', 'transparent', 'typetransparent', 'redirect', 'inform',
'inform_deny', 'nodefault']),
dnssec=dict(default=True, type='bool'),
# TODO: Python Module: Enable the Python Module. These 3 options omited when disabled
# python=dict(default=False, type='bool'),
# python_order=dict(default="pre_validator", type='str', choices=["pre_validator", "post_validator"]),
# python_script=dict(default="", type='str'), #Not sure what this is or how to handle it.
forwarding=dict(default=False, type='bool'),
forward_tls_upstream=dict(default=False, type='bool'),
regdhcp=dict(default=False, type='bool'),
regdhcpstatic=dict(default=False, type='bool'),
regovpnclients=dict(default=False, type='bool'),
custom_options=dict(default="", type='str'),
hosts=dict(default=[], type='list', elements='dict', options=DNS_RESOLVER_HOST_SPEC),
domainoverrides=dict(type='list', elements='dict', options=DNS_RESOLVER_DOMAIN_OVERRIDE_SPEC),
# Advanced Settings
hideidentity=dict(default=True, type='bool'),
hideversion=dict(default=True, type='bool'),
# TODO: Query Name Minimization
# TODO: Strict Query Name Minimization
prefetch=dict(default=False, type='bool'),
prefetchkey=dict(default=False, type='bool'),
dnssecstripped=dict(default=True, type='bool'),
# TODO: Serve Expired
# TODO: Aggressive NSEC
msgcachesize=dict(default=4, type='int', choices=[4, 10, 20, 50, 100, 250, 512]),
outgoing_num_tcp=dict(default=10, type='int', choices=[0, 10, 20, 30, 50]),
incoming_num_tcp=dict(default=10, type='int', choices=[0, 10, 20, 30, 50]),
edns_buffer_size=dict(default="auto", type='str', choices=["auto", "512", "1220", "1232", "1432", "1480", "4096"]),
num_queries_per_thread=dict(default=512, type='int', choices=[512, 1024, 2048]),
jostle_timeout=dict(default=200, type='int', choices=[100, 200, 500, 1000]),
cache_max_ttl=dict(default=86400, type='int'),
cache_min_ttl=dict(default=0, type='int'),
infra_host_ttl=dict(default=900, type='int', choices=[60, 120, 300, 600, 900]),
infra_cache_numhosts=dict(default=10000, type='int', choices=[1000, 5000, 10000, 20000, 50000, 100000, 200000]),
unwanted_reply_threshold=dict(default="disabled", type='str', choices=["disabled", "5000000", "10000000", "20000000", "40000000", "50000000"]),
log_verbosity=dict(default=1, type='int', choices=[0, 1, 2, 3, 4, 5])
# TODO: Disable Auto-added Access Control
# TODO: Disable Auto-added Host Entries
# TODO: Experimental Bit 0x20 Support
# TODO: DNS64 Support
)
DNS_RESOLVER_REQUIRED_IF = []
class PFSenseDNSResolverModule(PFSenseModuleBase):
""" module managing pfsense dns resolver (unbound) """
@staticmethod
def get_argument_spec():
""" return argument spec """
return DNS_RESOLVER_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseDNSResolverModule, self).__init__(module, pfsense)
self.name = "pfsense_dns_resolver"
self.root_elt = self.pfsense.get_element('unbound')
self.obj = dict()
self.interface_elt = None
self.dynamic = False
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('unbound')
self.pfsense.root.append(self.root_elt)
cmd = ('require_once("interfaces.inc");;'
'$iflist = get_possible_listen_ips(true);'
'echo json_encode($iflist);')
self.iflist = self.pfsense.php(cmd)
def _get_interface_name(self, iface: str):
ifacelow = iface.lower()
if ifacelow == "all":
return "all"
else:
for iname, idescr in self.iflist.items():
if ifacelow == iname.lower() or ifacelow == idescr.lower():
return iname
# Virtual IPs are listed in the format "IP" or "IP (Description)" - allow specifying either IP or Description
if re.match(f"{re.escape(ifacelow)}(?: \\(|$)", idescr.lower()) or re.search(f" \\({re.escape(ifacelow)}\\)$", idescr.lower()):
return iname
self.module.fail_json(msg=f"Invalid interface '{iface}'")
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
if params["state"] == "present":
obj["enable"] = ""
obj["active_interface"] = ",".join(self._get_interface_name(x) for x in params["active_interface"])
obj["outgoing_interface"] = ",".join(self._get_interface_name(x) for x in params["outgoing_interface"])
obj["custom_options"] = base64.b64encode(bytes(params['custom_options'], 'utf-8')).decode()
self._get_ansible_param_bool(obj, "hideidentity", value="")
self._get_ansible_param_bool(obj, "hideversion", value="")
self._get_ansible_param_bool(obj, "dnssecstripped", value="")
self._get_ansible_param(obj, "port")
self._get_ansible_param(obj, "tlsport")
if params["sslcert"]:
obj["sslcertref"] = self.pfsense.find_cert_elt(params["sslcert"]).find("refid").text
self._get_ansible_param_bool(obj, "forwarding", value="")
self._get_ansible_param(obj, "system_domain_local_zone_type")
self._get_ansible_param_bool(obj, "regdhcp", value="")
self._get_ansible_param_bool(obj, "regdhcpstatic", value="")
self._get_ansible_param_bool(obj, "regovpnclients", value="")
self._get_ansible_param_bool(obj, "enablessl", value="")
self._get_ansible_param_bool(obj, "dnssec", value="")
self._get_ansible_param_bool(obj, "forward_tls_upstream", value="")
self._get_ansible_param_bool(obj, "prefetch", value="")
self._get_ansible_param_bool(obj, "prefetchkey", value="")
self._get_ansible_param(obj, "msgcachesize")
self._get_ansible_param(obj, "outgoing_num_tcp")
self._get_ansible_param(obj, "incoming_num_tcp")
self._get_ansible_param(obj, "edns_buffer_size")
self._get_ansible_param(obj, "num_queries_per_thread")
self._get_ansible_param(obj, "jostle_timeout")
self._get_ansible_param(obj, "cache_max_ttl")
self._get_ansible_param(obj, "cache_min_ttl")
self._get_ansible_param(obj, "infra_host_ttl")
self._get_ansible_param(obj, "infra_cache_numhosts")
self._get_ansible_param(obj, "unwanted_reply_threshold")
self._get_ansible_param(obj, "log_verbosity")
self._get_ansible_param(obj, "hosts")
self._get_ansible_param(obj, "domainoverrides")
for domainoverride in obj.get("domainoverrides", []):
self._get_ansible_param_bool(domainoverride, "forward_tls_upstream", value="", params=domainoverride)
if ((self.pfsense.config_get_path('system/dnslocalhost') != 'remote') and ("lo0" not in obj['active_interface']) and
("all" not in obj['active_interface'])):
self.module.fail_json(msg="This system is configured to use the DNS Resolver as its DNS server, so Localhost or All must be selected in"
" active_interface.")
# wrap - to all hosts.alias
for host in obj["hosts"]:
if host["aliases"]:
tmp_aliases = host["aliases"]
host["aliases"] = {
"item": tmp_aliases
}
else:
# Default is an empty element
host["aliases"] = ""
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params["sslcert"] and not self.pfsense.find_cert_elt(params["sslcert"]):
self.module.fail_json(msg=f'sslcert, {params["sslcert"]} is not a valid description of cert')
for host in params["hosts"]:
for ipaddr in host["ip"].split(","):
if not self.pfsense.is_ipv4_address(ipaddr) and not self.pfsense.is_ipv6_address(ipaddr):
self.module.fail_json(msg=f'ip, {ipaddr} is not a ipv4/ipv6 address')
if params["domainoverrides"] is not None:
for domain in params["domainoverrides"]:
if not self.pfsense.is_ipv4_address(domain["ip"]) and not self.pfsense.is_ipv6_address(domain["ip"]):
self.module.fail_json(msg=f'ip, {domain["ip"]} is not a ipv4/ipv6 address')
##############################
# XML processing
#
def _create_target(self):
""" create the XML target_elt """
return self.root_elt
def _find_target(self):
""" find the XML target_elt """
return self.root_elt
def _get_params_to_remove(self):
""" returns the list of params to remove if they are not set """
if self.params["state"] == "absent":
return ["enable"]
else:
return ["hideidentity", "hideversion", "dnssecstripped", "forwarding", "regdhcp", "regdhcpstatic", "regovpnclients", "enablessl", "dnssec",
"forward_tls_upstream", "prefetch", "prefetchkey"]
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
return self.pfsense.phpshell('''
require_once("unbound.inc");
require_once("pfsense-utils.inc");
require_once("system.inc");
services_unbound_configure();
system_resolvconf_generate();
system_dhcpleases_configure();
clear_subsystem_dirty("unbound");
''')
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return self.name
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
values += self.format_updated_cli_field(self.obj, before, 'enable', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'active_interface', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'outgoing_interface', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'custom_options', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'hideidentity', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'hideversion', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'dnssecstripped', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'port', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'tlsport', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'sslcertref', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'forwarding', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'system_domain_local_zone_type', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'regdhcp', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'regdhcpstatic', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'prefetch', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'prefetchkey', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'msgcachesize', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'outgoing_num_tcp', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'incoming_num_tcp', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'edns_buffer_size', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'num_queries_per_thread', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'jostle_timeout', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'cache_max_ttl', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'cache_min_ttl', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'infra_host_ttl', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'infra_cache_numhosts', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'unwanted_reply_threshold', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, before, 'log_verbosity', add_comma=(values), log_none=False)
# todo: hosts and domainoverrides is not logged
return values
def main():
module = AnsibleModule(
argument_spec=DNS_RESOLVER_ARGUMENT_SPEC,
required_if=DNS_RESOLVER_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseDNSResolverModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_gateway.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_gateway
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense gateways
description:
- Manage pfSense gateways
notes:
options:
name:
description: Gateway name
required: true
type: str
interface:
description: Choose which interface this gateway applies to.
required: false
type: str
ipprotocol:
description: Choose the Internet Protocol this gateway uses.
required: false
choices: [ "inet", "inet6" ]
default: inet
type: str
gateway:
description: Gateway IP address
required: false
type: str
descr:
description: The description of the gateway
required: false
default: ''
type: str
disabled:
description: Set this option to disable this gateway without removing it from the list.
default: false
type: bool
monitor:
description: Enter an alternative address here to be used to monitor the link.
required: false
type: str
monitor_disable:
description: This will consider this gateway as always being up.
default: false
type: bool
action_disable:
description: No action will be taken on gateway events. The gateway is always considered up.
default: false
type: bool
force_down:
description: This will force this gateway to be considered down.
default: false
type: bool
weight:
description: Weight for this gateway when used in a Gateway Group. Must be between 1 and 30.
default: 1
type: int
version_added: 0.7.2
losslow:
description: Low threshold for packet loss in %. Default is 10.
type: int
version_added: 0.7.2
losshigh:
description: High threshold for packet loss in %. Default is 20.
type: int
nonlocalgateway:
description:
- This will allow use of a gateway outside of this interface's subnet.
- This is usually indicative of a configuration error, but is required for some scenarios.
default: false
type: bool
state:
description: State in which to leave the gateway
choices: [ "present", "absent" ]
default: present
type: str
"""
EXAMPLES = """
- name: Add gateway
pfsense_gateway:
name: default_gw
interface: wan
gateway: 1.2.3.4
state: present
- name: Remove gateway
pfsense_gateway:
name: vpn_gw
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create gateway 'default_gw', interface='wan', address='1.2.3.4'", "delete gateway 'vpn_gw'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.gateway import PFSenseGatewayModule, GATEWAY_ARGUMENT_SPEC, GATEWAY_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=GATEWAY_ARGUMENT_SPEC,
required_if=GATEWAY_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseGatewayModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_gateway_group.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2026, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: pfsense_gateway_group
short_description: Manage pfSense gateway groups
version_added: "0.7.2"
description:
- Manage pfSense gateway groups.
options:
name:
description: The name of the gateway group.
required: true
type: str
state:
description: State in which to leave the gateway group.
default: present
choices: ['present', 'absent']
type: str
keep_failover_states:
description: Keep Failover States of the gateway group. Defaults to unset.
choices: ['', 'keep', 'kill']
type: str
trigger:
description: Trigger Level of the gateway group. When to trigger exclusion of a member. Defaults to down.
default: down
choices: ['down', 'downloss', 'downlatency', 'downlosslatency']
type: str
descr:
description: Description of the gateway group. Used to identify the gateway group.
type: str
members:
description: The members of the gateway group.
type: list
elements: dict
suboptions:
gateway:
type: str
required: true
description: The name of the gateway.
tier:
type: int
required: true
description: The tier of the gateway. This should be a number between 1 and the number of members.
virtualip:
type: str
required: true
description: The virtual IP of the gateway. This should either be `address` or the name of a virtual IP.
author: Orion Poplawski (@opoplawski)
'''
EXAMPLES = r'''
- name: Add WANGW_FAILOVER gateway group
pfsensible.core.pfsense_gateway group:
name: WANGW_FAILOVER
keep_failover_states: keep
trigger: downlosslatency
descr: Item full
members:
- gateway: WANGW
tier: 1
virtualip: address
- gateway: WAN1GW
tier: 2
virtualip: WAN1 CARP
state: present
- name: Remove WANGW_FAILOVER gateway group
pfsensible.core.pfsense_gateway group:
name: WANGW_FAILOVER
state: absent
'''
RETURN = r'''
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI).
returned: always
type: list
sample: ["create gateway_group 'WANGW_FAILOVER'", "update gateway_group 'WANGW_FAILOVER' set ...", "delete gateway_group 'WANGW_FAILOVER'"]
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
GATEWAY_GROUP_ARGUMENT_SPEC = dict(
# Only name should be required here - othewise you cannot remove an item with just 'name'
# Required arguments for creation should be noted in GATEWAY_GROUP_REQUIRED_IF = ['state', 'present', ...] below
name=dict(required=True, type='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
keep_failover_states=dict(type='str', choices=['', 'keep', 'kill']),
trigger=dict(type='str', choices=['down', 'downloss', 'downlatency', 'downlosslatency'], default='down'),
descr=dict(type='str'),
members=dict(type='list', elements='dict'),
)
GATEWAY_GROUP_REQUIRED_IF = [
['state', 'present', ['members']],
]
def p2o_members(self, name, params, obj):
""" parse the list of members into format required for the XML element """
obj['item'] = []
for member in params[name]:
if member["virtualip"] != "address":
if (vip := self.pfsense.get_virtual_ip_interface(member["virtualip"])) is None:
self.module.fail_json(msg=f"Cannot find virtual IP '{member['virtualip']}'")
else:
vip = 'address'
obj['item'].append(f"{member['gateway']}|{member['tier']}|{vip}")
GATEWAY_GROUP_ARG_ROUTE = dict(
members=dict(parse=p2o_members),
)
GATEWAY_GROUP_CREATE_DEFAULT = dict(
trigger='down',
)
class PFSenseGatewayGroupModule(PFSenseModuleBase):
""" module managing pfsense gateway groups """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return GATEWAY_GROUP_ARGUMENT_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseGatewayGroupModule, self).__init__(module, pfsense, root='gateways', node='gateway_group', key='name',
arg_route=GATEWAY_GROUP_ARG_ROUTE, create_default=GATEWAY_GROUP_CREATE_DEFAULT)
def main():
module = AnsibleModule(
argument_spec=GATEWAY_GROUP_ARGUMENT_SPEC,
required_if=GATEWAY_GROUP_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseGatewayGroupModule(module)
# Pass params for testing framework
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_group.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018-2020, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_group
version_added: 0.1.0
short_description: Manage pfSense user groups
description:
>
Manage pfSense groups
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the group
required: true
type: str
state:
description: State in which to leave the group
required: true
choices: [ "present", "absent" ]
type: str
descr:
description: Description of the group
type: str
scope:
description: Scope of the group
default: local
choices: ["local", "remote", "system" ]
type: str
gid:
description:
- GID of the group.
- Will use next available GID if not specified.
type: str
priv:
description:
- A list of privileges to assign.
- Allowed values include page-all, user-shell-access.
type: list
elements: str
"""
EXAMPLES = """
- name: Add adservers group
pfsense_group:
name: Domain Admins
descr: Remote Admins
scope: remote
priv: [ 'page-all', 'user-shell-access' ]
state: present
- name: Remove group
pfsense_group:
name: Domain Admins
state: absent
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
GROUP_PHP_COMMAND_PREFIX = """
require_once('auth.inc');
"""
GROUP_PHP_COMMAND_SET = GROUP_PHP_COMMAND_PREFIX + """
$group = config_get_path('system/group')[{idx}];
local_group_set($group);
"""
# This runs after we remove the group from the config so we can't use it
GROUP_PHP_COMMAND_DEL = GROUP_PHP_COMMAND_PREFIX + """
$group['name'] = '{name}';
local_group_del($group);
"""
class PFSenseGroupModule(PFSenseModuleBase):
""" module managing pfsense user groups """
def __init__(self, module, pfsense=None):
super(PFSenseGroupModule, self).__init__(module, pfsense)
self.name = "pfsense_group"
self.root_elt = self.pfsense.get_element('system')
self.groups = self.root_elt.findall('group')
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = dict()
self.obj = obj
obj['name'] = params['name']
state = params['state']
if state == 'present':
obj['description'] = params['descr']
for option in ['scope', 'gid', 'priv']:
if option in params and params[option] is not None:
obj[option] = params[option]
return obj
def _validate_params(self):
""" do some extra checks on input parameters """
def _nextgid(self):
""" return and update netgid counter """
nextgid_elt = self.root_elt.find('nextgid')
nextgid = nextgid_elt.text
nextgid_elt.text = str(int(nextgid) + 1)
return nextgid
##############################
# XML processing
#
def _copy_and_add_target(self):
""" create the XML target_elt """
if 'gid' not in self.obj:
# Search for an open gid
while True:
self.obj['gid'] = self._nextgid()
if self._find_group_by_gid(self.obj['gid']) is None:
break
else:
if self._find_group_by_gid(self.obj['gid']) is not None:
self.module.fail_json(msg='A different group already exists with gid {0}.'.format(self.obj['gid']))
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
self.root_elt.insert(self._find_last_group_index(), self.target_elt)
# Reset groups list
self.groups = self.root_elt.findall('group')
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'].update(self.pfsense.element_to_dict(self.target_elt))
return (before, changed)
def _create_target(self):
""" create the XML target_elt """
return self.pfsense.new_element('group')
def _find_target(self):
return self.pfsense.find_elt('group', self.obj['name'], search_field='name', root_elt=self.root_elt)
def _find_group_by_gid(self, gid):
return self.pfsense.find_elt('group', gid, search_field='gid', root_elt=self.root_elt)
def _find_this_group_index(self):
return self.groups.index(self.target_elt)
def _find_last_group_index(self):
return list(self.root_elt).index(self.groups[len(self.groups) - 1])
##############################
# run
#
def _update(self):
if self.params['state'] == 'present':
return self.pfsense.phpshell(GROUP_PHP_COMMAND_SET.format(idx=self._find_this_group_index()))
else:
return self.pfsense.phpshell(GROUP_PHP_COMMAND_DEL.format(name=self.obj['name']))
##############################
# Logging
#
def _get_obj_name(self):
""" return obj's name """
return self.obj['name']
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
return values
def main():
module = AnsibleModule(
argument_spec={
'name': {'required': True, 'type': 'str'},
'state': {
'required': True,
'choices': ['present', 'absent']
},
'descr': {'required': False, 'type': 'str'},
'scope': {
'default': 'local',
'choices': ['local', 'remote', 'system']
},
'gid': {'required': False, 'type': 'str'},
'priv': {'required': False, 'type': 'list', 'elements': 'str'},
},
supports_check_mode=True)
pfmodule = PFSenseGroupModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_haproxy_backend.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_haproxy_backend
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense HAProxy backends
description:
- Manage pfSense HAProxy backends
notes:
deprecated:
removed_in: 0.8.0
why: Moved to `pfsensible.haproxy`
alternative: Use M(pfsensible.haproxy.pfsense_haproxy_backend) instead.
options:
name:
description: The backend name.
required: true
type: str
balance:
description: The load balancing option.
required: false
type: str
choices: ['none', 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri']
default: 'none'
balance_urilen:
description: Indicates that the algorithm should only consider that many characters at the beginning of the URI to compute the hash.
required: false
type: int
balance_uridepth:
description: Indicates the maximum directory depth to be used to compute the hash. One level is counted for each slash in the request.
required: false
type: int
balance_uriwhole:
description: Allow using whole URI including url parameters behind a question mark.
required: false
type: bool
connection_timeout:
description: The time (in milliseconds) we give up if the connection does not complete within (default 30000).
required: false
type: int
server_timeout:
description: The time (in milliseconds) we accept to wait for data from the server, or for the server to accept data (default 30000).
required: false
type: int
retries:
description: After a connection failure to a server, it is possible to retry, potentially on another server.
required: false
type: int
check_type:
description: Health check method.
type: str
choices: ['none', 'Basic', 'HTTP', 'Agent', 'LDAP', 'MySQL', 'PostgreSQL', 'Redis', 'SMTP', 'ESMTP', 'SSL']
default: 'none'
check_frequency:
description: The check interval (in milliseconds). For HTTP/HTTPS defaults to 1000 if left blank. For TCP no check will be performed if left empty.
required: false
type: int
log_checks:
description: When this option is enabled, any change of the health check status or to the server's health will be logged.
required: false
type: bool
httpcheck_method:
description: HTTP check method.
required: false
type: str
choices: ['OPTIONS', 'HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE']
monitor_uri:
description: Url used by http check requests.
required: false
type: str
monitor_httpversion:
description: Defaults to "HTTP/1.0" if left blank.
required: false
type: str
monitor_username:
description: Username used in checks (MySQL and PostgreSQL)
required: false
type: str
monitor_domain:
description: Domain used in checks (SMTP and ESMTP)
required: false
type: str
state:
description: State in which to leave the backend
choices: [ "present", "absent" ]
default: present
type: str
"""
EXAMPLES = """
- name: Add backend
pfsense_haproxy_backend:
name: exchange
balance: leastconn
httpcheck_method: HTTP
state: present
- name: Remove backend
pfsense_haproxy_backend:
name: exchange
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create haproxy_backend 'exchange', balance='leastconn', httpcheck_method='HTTP'", "delete haproxy_backend 'exchange'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.haproxy_backend import PFSenseHaproxyBackendModule, HAPROXY_BACKEND_ARGUMENT_SPEC
def main():
module = AnsibleModule(
argument_spec=HAPROXY_BACKEND_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseHaproxyBackendModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_haproxy_backend_server.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_haproxy_backend_server
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense haproxy backend servers
description:
- Manage pfSense haproxy servers
notes:
deprecated:
removed_in: 0.8.0
why: Moved to `pfsensible.haproxy`
alternative: Use M(pfsensible.haproxy.pfsense_haproxy_backend) instead.
options:
backend:
description: The backend name.
required: true
type: str
name:
description: The server name.
required: true
type: str
mode:
description: How to use the server.
required: false
type: str
choices: ['active', 'backup', 'disabled', 'inactive']
default: 'active'
forwardto:
description: The name of the frontend to forward. When None, forwards to address and port
required: false
type: str
address:
description: IP or hostname of the backend (only resolved on start-up.)
required: false
type: str
port:
description: The port of the backend.
required: false
type: int
ssl:
description: Should haproxy encrypt the traffic to the backend with SSL (commonly used with mode http on frontend and a port 443 on backend).
required: false
type: bool
checkssl:
description: This can be used with for example a LDAPS health-checks where LDAPS is passed along with mode TCP
required: false
type: bool
weight:
description: >
A weight between 0 and 256, this setting can be used when multiple servers on different hardware need to be balanced with a different part the traffic.
A server with weight 0 wont get new traffic. Default if empty: 1
required: false
type: int
sslserververify:
description: SSL servers only, The server certificate will be verified against the CA and CRL certificate configured below.
required: false
type: bool
verifyhost:
description: SSL servers only, when set, must match the hostnames in the subject and subjectAlternateNames of the certificate provided by the server.
required: false
type: str
ca:
description: SSL servers only, set the CA authority to check the server certificate against.
required: false
type: str
crl:
description: SSL servers only, set the CRL to check revoked certificates.
required: false
type: str
clientcert:
description: SSL servers only, This certificate will be sent if the server send a client certificate request.
required: false
type: str
cookie:
description: Persistence only, Used to identify server when cookie persistence is configured for the backend.
required: false
type: str
maxconn:
description: Tuning, If the number of incoming concurrent requests goes higher than this value, they will be queued
required: false
type: int
advanced:
description: Allows for adding custom HAProxy settings to the server. These are passed as written, use escaping where needed.
required: false
type: str
istemplate:
description: If set, configures this server item as a template to provision servers from dns/srv responses.
required: false
type: str
state:
description: State in which to leave the backend server
choices: [ "present", "absent" ]
default: present
type: str
"""
EXAMPLES = """
- name: Add backend server
pfsense_haproxy_backend_server:
backend: exchange
name: exchange.acme.org
address: exchange.acme.org
port: 443
state: present
- name: Remove backend server
pfsense_haproxy_backend_server:
backend: exchange
name: exchange.acme.org
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: [
"create haproxy_backend_server 'exchange.acme.org' on 'exchange', status='active', address='exchange.acme.org', port=443",
"delete haproxy_backend_server 'exchange.acme.org' on 'exchange'"
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.haproxy_backend_server import (
PFSenseHaproxyBackendServerModule,
HAPROXY_BACKEND_SERVER_ARGUMENT_SPEC,
HAPROXY_BACKEND_SERVER_MUTUALLY_EXCLUSIVE,
)
def main():
module = AnsibleModule(
argument_spec=HAPROXY_BACKEND_SERVER_ARGUMENT_SPEC,
mutually_exclusive=HAPROXY_BACKEND_SERVER_MUTUALLY_EXCLUSIVE,
supports_check_mode=True)
pfmodule = PFSenseHaproxyBackendServerModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_interface.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2021-2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_interface
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense interfaces
description:
- Manage pfSense interfaces.
notes:
options:
state:
description: State in which to leave the interface.
choices: [ "present", "absent" ]
default: present
type: str
descr:
description: Description (name) for the interface.
required: true
type: str
interface:
description: Network port to which assign the interface.
type: str
interface_descr:
description: Network port descr to which assign the interface.
type: str
enable:
description: Enable interface.
default: no
type: bool
ipv4_type:
description: IPv4 Configuration Type.
choices: [ "none", "static", "dhcp" ]
default: 'none'
type: str
ipv6_type:
description: IPv4 Configuration Type.
choices: [ "none", "static", "slaac" ]
default: 'none'
type: str
mac:
description: Used to modify ("spoof") the MAC address of this interface.
required: false
type: str
mtu:
description: Maximum transmission unit
required: false
type: int
mss:
description: MSS clamping for TCP connections.
required: false
type: int
speed_duplex:
description: Set speed and duplex mode for this interface.
required: false
default: autoselect
type: str
ipv4_address:
description: IPv4 Address.
required: false
type: str
ipv4_prefixlen:
description: IPv4 subnet prefix length.
required: false
default: 24
type: int
ipv4_gateway:
description: IPv4 gateway for this interface.
required: false
type: str
ipv6_address:
description: IPv6 Address.
required: false
type: str
ipv6_prefixlen:
description: IPv6 subnet prefix length.
required: false
default: 128
type: int
ipv6_gateway:
description: IPv6 gateway for this interface.
required: false
type: str
blockpriv:
description: Blocks traffic from IP addresses that are reserved for private networks.
required: false
type: bool
blockbogons:
description: Blocks traffic from reserved IP addresses (but not RFC 1918) or not yet assigned by IANA.
required: false
type: bool
slaacusev4iface:
description: IPv6 will use the IPv4 connectivity link (PPPoE). Only used when ipv6_type is slaac.
required: false
type: bool
version_added: 0.6.2
"""
EXAMPLES = """
- name: Add interface
pfsense_interface:
descr: voice
interface: mvneta0.100
enable: True
- name: Remove interface
pfsense_interface:
state: absent
descr: voice
interface: mvneta0.100
"""
RETURN = """
commands:
description: The set of commands that would be pushed to the remote device (if pfSense had a CLI).
returned: always
type: list
sample: [
"create interface 'voice', port='mvneta0.100', speed_duplex='autoselect', enable='True'",
"delete interface 'voice'"
]
ifname:
description: The pseudo-device name of the interface.
returned: always
type: str
sample: opt1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.interface import (
PFSenseInterfaceModule,
INTERFACE_ARGUMENT_SPEC,
INTERFACE_REQUIRED_IF,
INTERFACE_MUTUALLY_EXCLUSIVE
)
def main():
module = AnsibleModule(
argument_spec=INTERFACE_ARGUMENT_SPEC,
required_if=INTERFACE_REQUIRED_IF,
mutually_exclusive=INTERFACE_MUTUALLY_EXCLUSIVE,
supports_check_mode=True)
pfmodule = PFSenseInterfaceModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_interface_group.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_interface_group
version_added: 0.5.0
author: Orion Poplawski (@opoplawski)
short_description: Manage pfSense interface groups
description:
- Manage pfSense interface groups.
notes:
options:
state:
description: State in which to leave the interface group.
choices: [ "present", "absent" ]
default: present
type: str
name:
description: The name of the interface group.
type: str
required: yes
descr:
description: Description of the interface group.
type: str
members:
description: The members of the interface group.
type: list
elements: str
"""
EXAMPLES = """
- name: Add interface group
pfsense_interface_group:
name: VPN
members:
- VPN1
- VPN2
descr: All VPN interfaces
- name: Remove interface group
pfsense_interface_group:
state: absent
name: VPN
"""
RETURN = """
commands:
description: The set of commands that would be pushed to the remote device (if pfSense had a CLI).
returned: always
type: list
sample: [
"create interface-group 'VPN'",
"delete interface-group 'VPN'"
]
member_ifnames:
description: The pseudo-device interface names of all of the members.
returned: always
type: list
sample: [
"opt1",
"opt2"
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.interface_group import (
PFSenseInterfaceGroupModule,
INTERFACE_GROUP_ARGUMENT_SPEC,
INTERFACE_GROUP_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=INTERFACE_GROUP_ARGUMENT_SPEC,
required_if=INTERFACE_GROUP_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseInterfaceGroupModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_ipsec.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_ipsec
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense IPsec tunnels and phase 1 options
description:
- Manage pfSense IPsec tunnels and phase 1 options
notes:
options:
iketype:
description: Internet Key Exchange protocol version to be used. Auto uses IKEv2 when initiator, and accepts either IKEv1 or IKEv2 as responder.
required: false
choices: [ 'ikev1', 'ikev2', 'auto' ]
type: str
protocol:
description: IP family
default: 'inet'
choices: [ 'inet', 'inet6', 'both' ]
type: str
interface:
description: Interface for the local endpoint of this phase1 entry. Can be a virtual IP name or address prefixed with "vip:".
required: false
type: str
remote_gateway:
description: Public IP address or host name of the remote gateway.
required: false
type: str
nattport:
description: UDP port for NAT-T on the remote gateway.
required: false
type: int
disabled:
description: Set this option to disable this phase1 without removing it from the list.
required: false
type: bool
authentication_method:
description: Authenticatin method. Must match the setting chosen on the remote side.
choices: [ 'pre_shared_key', 'rsasig' ]
type: str
mode:
description: Negotiation mode. Aggressive is more flexible, but less secure. Only for IkeV1 or Auto.
choices: [ 'main', 'aggressive' ]
type: str
myid_type:
description: Local identifier type.
default: 'myaddress'
choices: [ 'myaddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'dyn_dns', 'auto' ]
type: str
myid_data:
description: Local identifier value.
required: false
type: str
peerid_type:
description: Remote identifier type.
default: 'peeraddress'
choices: [ 'any', 'peeraddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'auto' ]
type: str
peerid_data:
description: Remote identifier value.
required: false
type: str
certificate:
description: a certificate previously configured
required: false
type: str
certificate_authority:
description: a certificate authority previously configured
required: false
type: str
preshared_key:
description: This key must match on both peers.
required: false
type: str
lifetime:
description: The lifetime defines how often the connection will be rekeyed, in seconds.
default: 28800
type: int
rekey_time:
description: Time, in seconds, before an IKE SA establishes new keys.
required: False
type: int
reauth_time:
description: Time, in seconds, before an IKE SA is torn down and recreated from scratch, including authentication.
required: False
type: int
rand_time:
description: A random value up to this amount will be subtracted from Rekey Time/Reauth Time to avoid simultaneous renegotiation.
required: False
type: int
disable_rekey:
description: Disables renegotiation when a connection is about to expire (deprecated with pfSense 2.5.0)
required: false
type: bool
margintime:
description: How long before connection expiry or keying-channel expiry should attempt to negotiate a replacement begin (deprecated with pfSense 2.5.0)
required: false
type: int
startaction:
description: Set this option to force specific initiation/responder behavior for child SA (P2) entries. New in pfSense 2.5.2.
default: ''
choices: [ '', 'none', 'start', 'trap' ]
type: str
version_added: 0.5.2
closeaction:
description: Set this option to control the behavior when the remote peer unexpectedly closes a child SA (P2). New in pfSense 2.5.2.
default: ''
choices: [ '', 'none', 'start', 'trap' ]
type: str
version_added: 0.5.2
responderonly:
description: Enable this option to never initiate this connection from this side, only respond to incoming requests. Removed in pfSense 2.5.2.
required: false
type: bool
disable_reauth:
description: (IKEv2 only) Whether rekeying of an IKE_SA should also reauthenticate the peer. In IKEv1, reauthentication is always done.
default: false
type: bool
mobike:
description: (IKEv2 only) Set this option to control the use of MOBIKE
default: 'off'
choices: [ 'on', 'off' ]
type: str
gw_duplicates:
description: Allow multiple phase 1 configurations with the same endpoint
required: false
type: bool
splitconn:
description: (IKEv2 only) Enable this to split connection entries with multiple phase 2 configurations
default: false
type: bool
nat_traversal:
description:
Set this option to enable the use of NAT-T (i.e. the encapsulation of ESP in UDP packets) if needed,
which can help with clients that are behind restrictive firewalls.
default: 'on'
choices: [ 'on', 'force' ]
type: str
enable_dpd:
description: Enable dead peer detection
default: True
type: bool
dpd_delay:
description: Delay between requesting peer acknowledgement.
default: 10
type: int
dpd_maxfail:
description: Number of consecutive failures allowed before disconnect.
default: 5
type: int
descr:
description: The description of the IPsec tunnel
required: true
default: null
type: str
state:
description: State in which to leave the IPsec tunnel
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
"""
EXAMPLES = """
- name: Add simple tunnel
pfsense_ipsec:
state: present
descr: test_tunnel
interface: wan
remote_gateway: 1.2.3.4
iketype: ikev2
authentication_method: pre_shared_key
preshared_key: azerty123
- name: Remove tunnel
pfsense_ipsec:
state: absent
descr: test_tunnel
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create ipsec 'test_tunnel', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.2.3.4', ...", "delete ipsec 'test_tunnel'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec import PFSenseIpsecModule, IPSEC_ARGUMENT_SPEC, IPSEC_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=IPSEC_ARGUMENT_SPEC,
required_if=IPSEC_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseIpsecModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_ipsec_aggregate.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_ipsec_aggregate
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage multiple pfSense IPsec tunnels, phases 1, phases 2 and proposals
description:
- Manage multiple pfSense IPsec tunnels, phases 1, phases 2 and proposals
notes:
- aggregated_* use the same options definitions than pfsense corresponding module
options:
aggregated_ipsecs:
description: Dict of IPsec tunnels and phase 1 options to apply on the target
required: False
type: list
elements: dict
suboptions:
iketype:
description: Internet Key Exchange protocol version to be used. Auto uses IKEv2 when initiator, and accepts either IKEv1 or IKEv2 as responder.
required: false
choices: [ 'ikev1', 'ikev2', 'auto' ]
type: str
protocol:
description: IP family
default: 'inet'
choices: [ 'inet', 'inet6', 'both' ]
type: str
interface:
description: Interface for the local endpoint of this phase1 entry.
required: false
type: str
remote_gateway:
description: Public IP address or host name of the remote gateway.
required: false
type: str
nattport:
description: UDP port for NAT-T on the remote gateway.
required: false
type: int
disabled:
description: Set this option to disable this phase1 without removing it from the list.
required: false
type: bool
authentication_method:
description: Authenticatin method. Must match the setting chosen on the remote side.
choices: [ 'pre_shared_key', 'rsasig' ]
type: str
mode:
description: Negotiation mode. Aggressive is more flexible, but less secure. Only for IkeV1 or Auto.
choices: [ 'main', 'aggressive' ]
type: str
myid_type:
description: Local identifier type.
default: 'myaddress'
choices: [ 'myaddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'dyn_dns', 'auto' ]
type: str
myid_data:
description: Local identifier value.
required: false
type: str
peerid_type:
description: Remote identifier type.
default: 'peeraddress'
choices: [ 'any', 'peeraddress', 'address', 'fqdn', 'user_fqdn', 'asn1dn', 'keyid tag', 'auto' ]
type: str
peerid_data:
description: Remote identifier value.
required: false
type: str
certificate:
description: a certificate previously configured
required: false
type: str
certificate_authority:
description: a certificate authority previously configured
required: false
type: str
preshared_key:
description: This key must match on both peers.
required: false
type: str
lifetime:
description: The lifetime defines how often the connection will be rekeyed, in seconds.
default: 28800
type: int
rekey_time:
description: Time, in seconds, before an IKE SA establishes new keys.
required: False
type: int
reauth_time:
description: Time, in seconds, before an IKE SA is torn down and recreated from scratch, including authentication.
required: False
type: int
rand_time:
description: A random value up to this amount will be subtracted from Rekey Time/Reauth Time to avoid simultaneous renegotiation.
required: False
type: int
disable_rekey:
description: Disables renegotiation when a connection is about to expire (deprecated with pfSense 2.5.0)
required: false
type: bool
margintime:
description: How long before connection expiry or keying-channel expiry should attempt to negotiate a replacement begin (deprecated with pfSense 2.5.0)
required: false
type: int
startaction:
description: Set this option to force specific initiation/responder behavior for child SA (P2) entries. New in pfSense 2.5.2.
default: ''
choices: [ '', 'none', 'start', 'trap' ]
type: str
closeaction:
description: Set this option to control the behavior when the remote peer unexpectedly closes a child SA (P2). New in pfSense 2.5.2.
default: ''
choices: [ '', 'none', 'start', 'trap' ]
type: str
version_added: 0.5.2
responderonly:
description: Enable this option to never initiate this connection from this side, only respond to incoming requests. Removed in pfSense 2.5.2.
required: false
type: bool
disable_reauth:
description: (IKEv2 only) Whether rekeying of an IKE_SA should also reauthenticate the peer. In IKEv1, reauthentication is always done.
default: false
type: bool
mobike:
description: (IKEv2 only) Set this option to control the use of MOBIKE
default: 'off'
choices: [ 'on', 'off' ]
type: str
gw_duplicates:
description: Allow multiple phase 1 configurations with the same endpoint
required: false
type: bool
splitconn:
description: (IKEv2 only) Enable this to split connection entries with multiple phase 2 configurations
default: false
type: bool
nat_traversal:
description:
Set this option to enable the use of NAT-T (i.e. the encapsulation of ESP in UDP packets) if needed,
which can help with clients that are behind restrictive firewalls.
default: 'on'
choices: [ 'on', 'force' ]
type: str
enable_dpd:
description: Enable dead peer detection
default: True
type: bool
dpd_delay:
description: Delay between requesting peer acknowledgement.
default: 10
type: int
dpd_maxfail:
description: Number of consecutive failures allowed before disconnect.
default: 5
type: int
descr:
description: The description of the IPsec tunnel
default: null
required: True
type: str
state:
description: State in which to leave the IPsec tunnel
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
aggregated_ipsec_proposals:
description: Dict of IPsec proposals to apply on the target
required: False
type: list
elements: dict
suboptions:
encryption:
description:
Encryption algorithm. aes128gcm, aes192gcm and aes256gcm can only be used with IKEv2 tunnels.
Blowfish, 3DES and CAST128 provide weak security and should be avoided.
required: True
choices: [ 'aes', 'aes128gcm', 'aes192gcm', 'aes256gcm', 'blowfish', '3des', 'cast128' ]
type: str
key_length:
description: Encryption key length
required: False
choices: [ 64, 96, 128, 192, 256 ]
type: int
hash:
description: Hash algorithm. MD5 and SHA1 provide weak security and should be avoided.
required: True
choices: [ 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc' ]
type: str
prf:
description: PRF algorithm. Manual PRF selection is not required, but can be useful in combination with AEAD Encryption Algorithms such as AES-GCM
required: False
choices: [ 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc' ]
type: str
dhgroup:
description: DH group. DH groups 1, 2, 22, 23, and 24 provide weak security and should be avoided.
required: True
choices: [ 1, 2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 28, 29, 30, 31, 32 ]
type: int
descr:
description: The description of the IPsec tunnel on which to create/delete the proposal.
default: null
type: str
state:
description: State in which to leave the IPsec proposal.
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
aggregated_ipsec_p2s:
description: Dict of IPsec tunnels phase 2 options to apply on the target
required: False
type: list
elements: dict
suboptions:
disabled:
description: Set this option to disable this phase2 without removing it from the list.
required: false
type: bool
default: false
mode:
description: Method for managing IPsec traffic
required: False
choices: [ 'tunnel', 'tunnel6', 'transport', 'vti' ]
type: str
local:
description: Local network component of this IPsec security association.
required: False
type: str
nat:
description: If NAT/BINAT is required on the local network specify the address to be translated
required: False
type: str
remote:
description: Remote network component of this IPsec security association.
required: False
type: str
protocol:
description: Encapsulating Security Payload (ESP) is encryption, Authentication Header (AH) is authentication only.
default: 'esp'
choices: [ 'esp', 'ah' ]
type: str
aes:
description: Set this option to enable AES encryption.
required: false
type: bool
aes_len:
description: AES encryption key length
required: False
choices: [ 'auto', '128', '192', '256' ]
type: str
aes128gcm:
description: Set this option to enable AES128-GCM encryption.
required: false
type: bool
aes128gcm_len:
description: AES128-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
aes192gcm:
description: Set this option to enable AES192-GCM encryption.
required: false
type: bool
aes192gcm_len:
description: AES192-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
aes256gcm:
description: Set this option to enable AES256-GCM encryption.
required: false
type: bool
aes256gcm_len:
description: AES256-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
blowfish:
description: Set this option to enable Blowfish encryption.
required: false
type: bool
blowfish_len:
description: AES encryption key length
required: False
choices: [ 'auto', '128', '192', '256' ]
type: str
des:
description: Set this option to enable 3DES encryption.
required: false
type: bool
cast128:
description: Set this option to enable CAST128 encryption.
required: false
type: bool
sha1:
description: Set this option to enable SHA1 hashing.
required: false
type: bool
sha256:
description: Set this option to enable SHA256 hashing.
required: false
type: bool
sha384:
description: Set this option to enable SHA384 hashing.
required: false
type: bool
sha512:
description: Set this option to enable SHA512 hashing.
required: false
type: bool
aesxcbc:
description: Set this option to enable AES-XCBC hashing.
required: false
type: bool
pfsgroup:
description: PFS key group, 0 for off. DH groups 1, 2, 22, 23, and 24 provide weak security and should be avoided.
default: '14'
choices: [ '0', '1', '2', '5', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '28', '29', '30', '31', '32' ]
type: str
lifetime:
description: Specifies how often the connection must be rekeyed, in seconds
default: 3600
type: int
pinghost:
description: Automatically ping host
required: False
type: str
descr:
description: The description of the IPsec tunnel phase2
required: True
type: str
p1_descr:
description: The description of the IPsec tunnel
required: true
type: str
state:
description: State in which to leave the IPsec tunnel phase2
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
purge_ipsecs:
description: delete all the IPsec tunnels that are not defined into aggregated_ipsecs
required: False
default: False
type: bool
purge_ipsec_proposals:
description: delete all the phase1 proposals that are not defined into aggregated_ipsec_proposals
required: False
default: False
type: bool
purge_ipsec_p2s:
description: delete all the phase2 that are not defined into aggregated_ipsec_p2s
required: False
default: False
type: bool
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
"""
EXAMPLES = """
- name: "Setup two tunnels with two proposals and and two phase 2 each, and delete everything else"
pfsense_ipsec_aggregate:
purge_ipsecs: true
purge_ipsec_proposals: true
purge_ipsec_p2s: true
aggregated_ipsecs:
- { descr: t1, interface: wan, remote_gateway: 1.3.3.1, iketype: ikev2, authentication_method: pre_shared_key, preshared_key: azerty123 }
- { descr: t2, interface: wan, remote_gateway: 1.3.4.1, iketype: ikev2, authentication_method: pre_shared_key, preshared_key: qwerty123 }
aggregated_ipsec_proposals:
- { descr: t1, encryption: aes, key_length: 128, hash: md5, dhgroup: 14}
- { descr: t2, encryption: 3des, hash: sha512, dhgroup: 14}
aggregated_ipsec_p2s:
- { descr: t1_p2_1, p1_descr: t1, mode: tunnel, local: 1.2.3.4/24, remote: 10.20.30.40/24, aes: True, aes_len: auto, sha256: True }
- { descr: t1_p2_2, p1_descr: t1, mode: tunnel, local: 1.2.3.4/24, remote: 10.20.30.50/24, aes: True, aes_len: auto, sha256: True }
- { descr: t2_p2_1, p1_descr: t2, mode: tunnel, local: 1.2.3.4/24, remote: 10.20.40.40/24, aes: True, aes_len: auto, sha256: True }
- { descr: t2_p2_2, p1_descr: t2, mode: tunnel, local: 1.2.3.4/24, remote: 10.20.40.50/24, aes: True, aes_len: auto, sha256: True }
"""
RETURN = """
result_ipsecs:
description: the set of separators commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create ipsec 'test_tunnel', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.2.3.4', ...", "delete ipsec 'test_tunnel'"]
result_ipsec_proposals:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: [
"create ipsec_proposal on 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14'",
"delete ipsec_proposal on 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14'",
]
result_ipsec_p2s:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create ipsec_p2 'test_p2' on 'test_tunnel', disabled='False', mode='vti', local='1.2.3.1', ...", "delete ipsec_p2 'test_p2' on 'test_tunnel'"]
"""
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec import PFSenseIpsecModule, IPSEC_ARGUMENT_SPEC, IPSEC_REQUIRED_IF
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_proposal import PFSenseIpsecProposalModule
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_proposal import IPSEC_PROPOSAL_ARGUMENT_SPEC
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_proposal import IPSEC_PROPOSAL_REQUIRED_IF
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_p2 import PFSenseIpsecP2Module
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_p2 import IPSEC_P2_ARGUMENT_SPEC
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_p2 import IPSEC_P2_REQUIRED_IF
from ansible.module_utils.basic import AnsibleModule
from copy import deepcopy
class PFSenseModuleIpsecAggregate(object):
""" module managing pfsense aggregated IPsec tunnels, phases 1, phases 2 and proposals """
def __init__(self, module):
self.module = module
self.pfsense = PFSenseModule(module)
self.pfsense_ipsec = PFSenseIpsecModule(module, self.pfsense)
self.pfsense_ipsec_proposal = PFSenseIpsecProposalModule(module, self.pfsense)
self.pfsense_ipsec_p2 = PFSenseIpsecP2Module(module, self.pfsense)
def _update(self):
if self.pfsense_ipsec.result['changed'] or self.pfsense_ipsec_proposal.result['changed'] or self.pfsense_ipsec_p2.result['changed']:
return self.pfsense.apply_ipsec_changes()
return ('', '', '')
@staticmethod
def want_ipsec(ipsec_elt, ipsecs):
""" return True if we want to keep ipsec_elt """
descr = ipsec_elt.find('descr')
if descr is None:
return True
for ipsec in ipsecs:
if ipsec['state'] == 'absent':
continue
if ipsec['descr'] == descr.text:
return True
return False
def proposal_elt_to_params(self, ipsec_elt, proposal_elt):
""" return the pfsense_ipsec_proposal params corresponding the proposal_elt """
params = {}
proposal = self.pfsense.element_to_dict(proposal_elt)
params['encryption'] = proposal['encryption-algorithm']['name']
params['key_length'] = proposal['encryption-algorithm'].get('keylen')
if params['key_length'] is not None:
if params['key_length'] == '':
params['key_length'] = None
else:
params['key_length'] = int(params['key_length'])
params['hash'] = proposal['hash-algorithm']
params['dhgroup'] = int(proposal['dhgroup'])
descr_elt = ipsec_elt.find('descr')
if descr_elt is None:
params['descr'] = ''
else:
params['descr'] = descr_elt.text
if self.pfsense.is_at_least_2_5_0():
params['prf'] = proposal['prf-algorithm']
return params
def want_ipsec_proposal(self, ipsec_elt, proposal_elt, proposals):
""" return True if we want to keep proposal_elt """
params_from_elt = self.proposal_elt_to_params(ipsec_elt, proposal_elt)
params_from_elt['state'] = 'present'
if proposals is not None:
for proposal in proposals:
_proposal = deepcopy(proposal)
_proposal.pop('apply', None)
if not self.pfsense.is_at_least_2_5_0():
_proposal.pop('prf', None)
elif _proposal.get('prf') is None:
_proposal.pop('prf', None)
params_from_elt.pop('prf', None)
if params_from_elt == _proposal:
return True
return False
def want_ipsec_phase2(self, phase2_elt, phases2):
""" return True if we want to keep proposal_elt """
ikeid_elt = phase2_elt.find('ikeid')
descr = phase2_elt.find('descr')
if descr is None or ikeid_elt is None:
return True
phase1_elt = self.pfsense.find_ipsec_phase1(ikeid_elt.text, 'ikeid')
if phase1_elt is None:
return True
phase1_descr_elt = phase1_elt.find('descr')
if phase1_descr_elt is None:
return True
p1_descr = phase1_descr_elt.text
if phases2 is not None:
for phase2 in phases2:
if phase2['state'] == 'absent':
continue
if phase2['descr'] == descr.text and phase2['p1_descr'] == p1_descr:
return True
return False
def run_ipsecs(self):
""" process input params to add/update/delete all IPsec tunnels """
want = self.module.params['aggregated_ipsecs']
# processing aggregated parameter
if want is not None:
for param in want:
self.pfsense_ipsec.run(param)
# delete every other if required
if self.module.params['purge_ipsecs']:
todel = []
for ipsec_elt in self.pfsense_ipsec.root_elt:
if ipsec_elt.tag != 'phase1':
continue
if not self.want_ipsec(ipsec_elt, want):
params = {}
params['state'] = 'absent'
params['apply'] = False
params['descr'] = ipsec_elt.find('descr').text
params['ikeid'] = ipsec_elt.find('ikeid').text
todel.append(params)
for params in todel:
self.pfsense_ipsec.run(params)
def run_ipsec_proposals(self):
""" process input params to add/update/delete all IPsec tunnels """
want = self.module.params['aggregated_ipsec_proposals']
# processing aggregated parameter
if want is not None:
for param in want:
self.pfsense_ipsec_proposal.run(param)
# delete every other if required
if self.module.params['purge_ipsec_proposals']:
todel = []
for ipsec_elt in self.pfsense_ipsec_proposal.ipsec:
if ipsec_elt.tag != 'phase1':
continue
encryption_elt = ipsec_elt.find('encryption')
if encryption_elt is None:
continue
items_elt = encryption_elt.findall('item')
for proposal_elt in items_elt:
if not self.want_ipsec_proposal(ipsec_elt, proposal_elt, want):
params = self.proposal_elt_to_params(ipsec_elt, proposal_elt)
params['state'] = 'absent'
params['apply'] = False
params['descr'] = ipsec_elt.find('descr').text
params['ikeid'] = ipsec_elt.find('ikeid').text
todel.append(params)
for params in todel:
self.pfsense_ipsec_proposal.run(params)
def run_ipsec_p2s(self):
""" process input params to add/update/delete all IPsec tunnels """
want = self.module.params['aggregated_ipsec_p2s']
# processing aggregated parameter
if want is not None:
for param in want:
self.pfsense_ipsec_p2.run(param)
# delete every other if required
if self.module.params['purge_ipsec_p2s']:
todel = []
for phase2_elt in self.pfsense_ipsec_p2.root_elt:
if phase2_elt.tag != 'phase2':
continue
if not self.want_ipsec_phase2(phase2_elt, want):
params = {}
params['state'] = 'absent'
params['apply'] = False
params['descr'] = phase2_elt.find('descr').text
params['p1_descr'] = self.pfsense.find_ipsec_phase1(phase2_elt.find('ikeid').text, 'ikeid').find('descr').text
params['ikeid'] = phase2_elt.find('ikeid').text
todel.append(params)
for params in todel:
self.pfsense_ipsec_p2.run(params)
def commit_changes(self):
""" apply changes and exit module """
stdout = ''
stderr = ''
changed = self.pfsense_ipsec.result['changed'] or self.pfsense_ipsec_proposal.result['changed'] or self.pfsense_ipsec_p2.result['changed']
if changed and not self.module.check_mode:
self.pfsense.write_config(descr='aggregated change')
if self.module.params['apply']:
(dummy, stdout, stderr) = self._update()
result = {}
result['result_ipsecs'] = self.pfsense_ipsec.result['commands']
result['result_ipsec_proposals'] = self.pfsense_ipsec_proposal.result['commands']
result['result_ipsec_p2s'] = self.pfsense_ipsec_p2.result['commands']
result['changed'] = changed
result['stdout'] = stdout
result['stderr'] = stderr
self.module.exit_json(**result)
def main():
argument_spec = dict(
aggregated_ipsecs=dict(type='list', elements='dict', options=IPSEC_ARGUMENT_SPEC, required_if=IPSEC_REQUIRED_IF),
aggregated_ipsec_proposals=dict(type='list', elements='dict', options=IPSEC_PROPOSAL_ARGUMENT_SPEC, required_if=IPSEC_PROPOSAL_REQUIRED_IF),
aggregated_ipsec_p2s=dict(type='list', elements='dict', options=IPSEC_P2_ARGUMENT_SPEC, required_if=IPSEC_P2_REQUIRED_IF),
purge_ipsecs=dict(default=False, type='bool'),
purge_ipsec_proposals=dict(default=False, type='bool'),
purge_ipsec_p2s=dict(default=False, type='bool'),
apply=dict(default=True, type='bool'),
)
required_one_of = [['aggregated_ipsecs', 'aggregated_ipsec_proposals', 'aggregated_ipsec_p2s']]
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
supports_check_mode=True)
pfmodule = PFSenseModuleIpsecAggregate(module)
pfmodule.run_ipsecs()
pfmodule.run_ipsec_proposals()
pfmodule.run_ipsec_p2s()
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_ipsec_p2.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_ipsec_p2
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense IPsec tunnels phase 2 options
description:
- Manage pfSense IPsec tunnels phase 2 options
notes:
options:
disabled:
description: Set this option to disable this phase2 without removing it from the list.
required: false
type: bool
default: false
mode:
description: Method for managing IPsec traffic
required: False
choices: [ 'tunnel', 'tunnel6', 'transport', 'vti' ]
type: str
local:
description: Local network component of this IPsec security association.
required: False
type: str
nat:
description: If NAT/BINAT is required on the local network specify the address to be translated
required: False
type: str
remote:
description: Remote network component of this IPsec security association.
required: False
type: str
protocol:
description: Encapsulating Security Payload (ESP) is encryption, Authentication Header (AH) is authentication only.
default: 'esp'
choices: [ 'esp', 'ah' ]
type: str
aes:
description: Set this option to enable AES encryption.
required: false
type: bool
aes_len:
description: AES encryption key length
required: False
choices: [ 'auto', '128', '192', '256' ]
type: str
aes128gcm:
description: Set this option to enable AES128-GCM encryption.
required: false
type: bool
aes128gcm_len:
description: AES128-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
aes192gcm:
description: Set this option to enable AES192-GCM encryption.
required: false
type: bool
aes192gcm_len:
description: AES192-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
aes256gcm:
description: Set this option to enable AES256-GCM encryption.
required: false
type: bool
aes256gcm_len:
description: AES256-GCM encryption key length
required: False
choices: [ 'auto', '64', '96', '128' ]
type: str
blowfish:
description: Set this option to enable Blowfish encryption.
required: false
type: bool
blowfish_len:
description: AES encryption key length
required: False
choices: [ 'auto', '128', '192', '256' ]
type: str
des:
description: Set this option to enable 3DES encryption.
required: false
type: bool
cast128:
description: Set this option to enable CAST128 encryption.
required: false
type: bool
sha1:
description: Set this option to enable SHA1 hashing.
required: false
type: bool
sha256:
description: Set this option to enable SHA256 hashing.
required: false
type: bool
sha384:
description: Set this option to enable SHA384 hashing.
required: false
type: bool
sha512:
description: Set this option to enable SHA512 hashing.
required: false
type: bool
aesxcbc:
description: Set this option to enable AES-XCBC hashing.
required: false
type: bool
pfsgroup:
description: PFS key group, 0 for off. DH groups 1, 2, 22, 23, and 24 provide weak security and should be avoided.
default: '14'
choices: [ '0', '1', '2', '5', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '28', '29', '30', '31', '32' ]
type: str
lifetime:
description: Specifies how often the connection must be rekeyed, in seconds
default: 3600
type: int
pinghost:
description: Automatically ping host
required: False
type: str
descr:
description: The description of the IPsec tunnel phase2
required: true
type: str
p1_descr:
description: The description of the IPsec tunnel
required: true
type: str
state:
description: State in which to leave the IPsec tunnel phase2
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
"""
EXAMPLES = """
- name: Add simple phase2
pfsense_ipsec_p2:
p1_descr: test_tunnel
descr: test_p2
state: present
apply: False
mode: vti
local: 1.2.3.1
remote: 1.2.3.2
aes: True
aes_len: auto
sha256: True
- name: Remove phase2
pfsense_ipsec_p2:
state: absent
p1_descr: test_tunnel
descr: test_p2
apply: False
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create ipsec_p2 'test_p2' on 'test_tunnel', disabled='False', mode='vti', local='1.2.3.1', ...", "delete ipsec_p2 'test_p2' on 'test_tunnel'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_p2 import PFSenseIpsecP2Module, IPSEC_P2_ARGUMENT_SPEC, IPSEC_P2_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=IPSEC_P2_ARGUMENT_SPEC,
required_if=IPSEC_P2_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseIpsecP2Module(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_ipsec_proposal.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_ipsec_proposal
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense IPsec proposals
description:
- Manage pfSense IPsec proposals
notes:
options:
encryption:
description:
Encryption algorithm. aes128gcm, aes192gcm and aes256gcm can only be used with IKEv2 tunnels.
Blowfish, 3DES and CAST128 provide weak security and should be avoided.
required: True
choices: [ 'aes', 'aes128gcm', 'aes192gcm', 'aes256gcm', 'blowfish', '3des', 'cast128' ]
type: str
key_length:
description: Encryption key length
required: False
choices: [ 64, 96, 128, 192, 256 ]
type: int
hash:
description: Hash algorithm. MD5 and SHA1 provide weak security and should be avoided.
required: True
choices: [ 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc' ]
type: str
prf:
description: PRF algorithm. Manual PRF selection is typically not required, but can be useful in combination with AEAD Encryption Algorithms such as AES-GCM
required: False
choices: [ 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'aesxcbc' ]
type: str
dhgroup:
description: DH group. DH groups 1, 2, 22, 23, and 24 provide weak security and should be avoided.
required: True
choices: [ 1, 2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 28, 29, 30, 31, 32 ]
type: int
descr:
description: The description of the IPsec tunnel on which to create/delete the proposal.
default: null
type: str
state:
description: State in which to leave the IPsec proposal.
choices: [ "present", "absent" ]
default: present
type: str
apply:
description: Apply VPN configuration on target pfSense
default: True
type: bool
"""
EXAMPLES = """
- name: Add proposal
pfsense_ipsec_proposal:
descr: test_tunnel
state: present
encryption: aes128gcm
key_length: 128
hash: sha256
dhgroup: 14
apply: False
- name: Remove proposal
pfsense_ipsec_proposal:
descr: test_tunnel
state: absent
encryption: aes128gcm
key_length: 128
hash: sha256
dhgroup: 14
apply: False
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: [
"create ipsec_proposal on 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14'",
"delete ipsec_proposal on 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14'",
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_proposal import (
PFSenseIpsecProposalModule,
IPSEC_PROPOSAL_ARGUMENT_SPEC,
IPSEC_PROPOSAL_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=IPSEC_PROPOSAL_ARGUMENT_SPEC,
required_if=IPSEC_PROPOSAL_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseIpsecProposalModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_log_settings.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2021, Jan Wenzel
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_log_settings
version_added: "0.4.2"
author: Jan Wenzel (@coffeelover)
short_description: Manage pfSense syslog settings
description:
- Manage pfSense syslog settings
notes:
options:
logformat:
description: Log Message Format
required: false
type: str
choices: ['rfc3164', 'rfc5424']
reverse:
description: Show log entries in reverse order (newest entries on top)
required: false
type: bool
nentries:
description: GUI Log Entries
required: false
type: int
nologdefaultblock:
description: Don't log packets that are blocked by the implicit default block rule.
required: false
type: bool
nologdefaultpass:
description: Don't log packets that are allowed by the implicit default pass rule.
required: false
type: bool
nologbogons:
description: Don't log packets blocked by 'Block Bogon Networks' rules.
required: false
type: bool
nologprivatenets:
description: Don't log packets blocked by 'Block Private Networks' rules.
required: false
type: bool
nologlinklocal4:
description: Don't log packets blocked by the default 'Block IPv4 link-local' rules.
required: false
type: bool
version_added: "0.7.1"
nologsnort2c:
description: Don't log packets that are blocked by IDS.
required: false
type: bool
version_added: "0.7.1"
nolognginx:
description: Don't log errors from the web server process.
required: false
type: bool
logconfigchanges:
description: Log changes to the configuration.
required: false
type: bool
version_added: "0.7.1"
rawfilter:
description: Show raw filter logs.
required: false
type: bool
filterdescriptions:
description: Where to show rule descriptions.
required: false
type: int
choices: [0,1,2]
disablelocallogging:
description: Disable writing log files to the local disk
required: false
type: bool
logfilesize:
description: Log Rotation Size (Bytes)
required: false
type: int
logcompressiontype:
description: The type of compression to use when rotating log files
required: false
type: str
choices: ['bzip2', 'gzip', 'xz', 'zstd', 'none']
rotatecount:
description: The number of log files to keep before the oldest copy is removed on rotation
required: false
type: int
enable:
description: Enable Remote logging
required: false
type: bool
sourceip:
description: Source Address
required: false
type: str
ipproto:
description: IP Protocol
required: false
type: str
choices: ['ipv4', 'ipv6']
remoteserver:
description: First Remote log server (IP Address or Hostname/FQDN)
required: false
type: str
remoteserver2:
description: Second Remote log server (IP Address or Hostname/FQDN)
required: false
type: str
remoteserver3:
description: Third Remote log server (IP Address or Hostname/FQDN)
required: false
type: str
logall:
description: Log Everything
required: false
type: bool
system:
description: Include System Events
required: false
type: bool
logfilter:
description: Include Firewall Events
required: false
type: bool
resolver:
description: Include DNS Events (Resolver/unbound, Forwarder/dnsmasq, filterdns)
required: false
type: bool
dhcp:
description: Include DHCP Events (DHCP Daemon, DHCP Relay, DHCP Client)
required: false
type: bool
ppp:
description: Include PPP Events (PPPoE WAN Client, L2TP WAN Client, PPTP WAN Client)
required: false
type: bool
auth:
description: Include General Authentication Events
required: false
type: bool
portalauth:
description: Include Captive Portal Events
required: false
type: bool
vpn:
description: Include VPN Events (IPsec, OpenVPN, L2TP, PPPoE Server)
required: false
type: bool
dpinger:
description: Include Gateway Monitor Events
required: false
type: bool
routing:
description: Include Routing Daemon Events (RADVD, UPnP, RIP, OSPF, BGP)
required: false
type: bool
ntpd:
description: Include Network Time Protocol Events (NTP Daemon, NTP Client)
required: false
type: bool
hostapd:
description: Wireless Events (hostapd)
required: false
type: bool
"""
EXAMPLES = """
- name: setup remote syslog
pfsense_log_settings:
enable: true
remoteserver: syslog.example.com
disablelocallogging: true
logall: true
- name: always log default pass traffic
pfsense_log_settings:
nologdefaultpass: false
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["update log_settings syslog set logformat='rfc5424', rotatecount='8'"]
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
LOG_SETTINGS_ARGUMENT_SPEC = dict(
logformat=dict(required=False, type='str',
choices=['rfc3164', 'rfc5424']),
reverse=dict(required=False, type='bool'),
nentries=dict(required=False, type='int'),
nologdefaultblock=dict(required=False, type='bool'),
nologdefaultpass=dict(required=False, type='bool'),
nologbogons=dict(required=False, type='bool'),
nologprivatenets=dict(required=False, type='bool'),
nologlinklocal4=dict(required=False, type='bool'),
nologsnort2c=dict(required=False, type='bool'),
nolognginx=dict(required=False, type='bool'),
logconfigchanges=dict(required=False, type='bool'),
rawfilter=dict(required=False, type='bool'),
filterdescriptions=dict(required=False, type='int',
choices=[0, 1, 2]),
disablelocallogging=dict(required=False, type='bool'),
logfilesize=dict(required=False, type='int'),
logcompressiontype=dict(required=False, type='str',
choices=['bzip2', 'gzip', 'xz', 'zstd', 'none']),
rotatecount=dict(required=False, type='int'),
enable=dict(required=False, type='bool'),
sourceip=dict(required=False, type='str'),
ipproto=dict(required=False, type='str',
choices=['ipv4', 'ipv6']),
remoteserver=dict(required=False, type='str'),
remoteserver2=dict(required=False, type='str'),
remoteserver3=dict(required=False, type='str'),
logall=dict(required=False, type='bool'),
system=dict(required=False, type='bool'),
logfilter=dict(required=False, type='bool'),
resolver=dict(required=False, type='bool'),
dhcp=dict(required=False, type='bool'),
ppp=dict(required=False, type='bool'),
auth=dict(required=False, type='bool'),
portalauth=dict(required=False, type='bool'),
vpn=dict(required=False, type='bool'),
dpinger=dict(required=False, type='bool'),
routing=dict(required=False, type='bool'),
ntpd=dict(required=False, type='bool'),
hostapd=dict(required=False, type='bool'),
)
# rename the reserved words with log prefix
params_map = {
'logformat': 'format',
'logfilter': 'filter',
}
# fields with inverted logic
inverted_list = ['nologdefaultpass']
class PFSenseLogSettingsModule(PFSenseModuleBase):
""" module managing pfsense log settings """
@staticmethod
def get_argument_spec():
""" return argument spec """
return LOG_SETTINGS_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseLogSettingsModule, self).__init__(module, pfsense)
self.name = "log_settings"
self.root_elt = self.pfsense.get_element('syslog')
self.target_elt = self.root_elt
self.params = dict()
self.obj = dict()
self.before = None
self.before_elt = None
self.route_cmds = list()
self.params_to_delete = list()
##############################
# params processing
#
def _params_to_obj(self):
""" return a dict from module params """
params = self.params
obj = self.pfsense.element_to_dict(self.root_elt)
self.before = deepcopy(obj)
self.before_elt = deepcopy(self.root_elt)
def _set_param(target, param):
# get possibly mapped settings name
_param = params_map.get(param, param)
if params.get(param) is not None:
if param == 'sourceip':
target[param] = self._get_source_ip_interface(params[param])
else:
if isinstance(params[param], str):
target[_param] = params[param]
else:
target[_param] = str(params[param])
def _set_param_bool(target, param):
# get possibly mapped settings name
_param = params_map.get(param, param)
if params.get(param) is not None:
value = not params.get(param) if param in inverted_list else params.get(param)
if value is True and _param not in target:
target[_param] = ''
elif value is False and _param in target:
del target[_param]
for param in LOG_SETTINGS_ARGUMENT_SPEC:
if LOG_SETTINGS_ARGUMENT_SPEC[param]['type'] == 'bool':
_set_param_bool(obj, param)
else:
_set_param(obj, param)
return obj
def _is_interface_ip_or_descr(self, address):
result = False
if address in ['127.0.0.1', 'Localhost']:
return True
for interface_elt in self.pfsense.interfaces:
descr = interface_elt.find('descr')
ipaddr = interface_elt.find('ipaddr')
if descr is not None and descr.text == address:
return True
elif ipaddr is not None and ipaddr.text == address:
return True
return result
def _get_interface_by_ip_or_display_name(self, address):
""" return interface_id by ip address or name """
if address in ['127.0.0.1', 'Localhost']:
return 'lo0'
for interface_elt in self.pfsense.interfaces:
descr = interface_elt.find('descr')
ipaddr = interface_elt.find('ipaddr')
if descr is not None and descr.text == address:
return interface_elt.tag
elif ipaddr is not None and ipaddr.text == address:
return interface_elt.tag
return None
def _get_source_ip_interface(self, address):
result = None
if self._is_interface_ip_or_descr(address):
result = self._get_interface_by_ip_or_display_name(address)
elif self.pfsense.is_virtual_ip(address):
result = self.pfsense.get_virtual_ip_interface(address)
return result
def _validate_syslog_server(self, hostname, name):
""" check hostname / ip address combinations with optional port """
if not hostname:
return
host = hostname.lower()
contains_port = re.match(r'^(\[.+\]|[^:]+):[0-9]+$', host)
if contains_port is not None:
host, port = host.rsplit(':', 1)
# check if we got a ipv6 address with port - need to remove '[' and ']'
host = host.strip('[]')
if port is not None and (int(port) <= 0 or int(port) >= 65536):
self.module.fail_json(msg="Invalid port {0}".format(port))
if self.pfsense.is_ipv4_address(host):
return
if self.pfsense.is_ipv6_address(host):
return
groups = re.match(r'^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$', host)
if groups is None:
self.module.fail_json(msg="The {0} can only contain the characters A-Z, 0-9 and '-'. It may not start or end with '-'".format(name))
def _validate_params(self):
""" do some extra checks on input parameters """
params = self.params
if params.get('sourceip') is not None:
address = params.get('sourceip')
if address == '':
return
if not self.pfsense.is_virtual_ip(address) and not self._is_interface_ip_or_descr(address):
self.module.fail_json(msg="sourceip: Invalid address {address}!".format(address=params.get('sourceip')))
if params.get('logall') is True:
for log_param in ['system', 'logfilter', 'resolver',
'dhcp', 'ppp', 'auth', 'portalauth',
'vpn', 'dpinger', 'routing', 'ntpd', 'hostapd']:
if params.get(log_param) is True:
self.module.fail_json(msg="{log_param} = True is invalid when logall is True".format(log_param=log_param))
if params.get('enable') is True:
remote_params = ['remoteserver', 'remoteserver2', 'remoteserver3']
if params.get('remoteserver') is None and params.get('remoteserver2') is None and params.get('remoteserver3') is None:
self.module.fail_json(msg="Need at least one remote syslog server when remote logging is enabled")
else:
for param in remote_params:
self._validate_syslog_server(params.get(param), param)
if params.get('nentries') is not None:
nentries = int(params.get('nentries'))
if nentries < 5 or nentries > 200000:
self.module.fail_json(msg="nentries must be an integer from 5 to 200000")
if params.get('logfilesize') is not None:
logfilesize = int(params.get('logfilesize'))
if logfilesize < 100000:
self.module.fail_json(msg="logfilesize must be an integer greater or equal than 100000")
elif logfilesize >= (2 ** 32) / 2:
self.module.fail_json(msg="logfilesize is too large: {logfilesize}".format(logfilesize=logfilesize))
if params.get('rotatecount') is not None:
rotatecount = int(params.get('rotatecount'))
if rotatecount < 0 or rotatecount > 99:
self.module.fail_json(msg="rotatecount must be an integer from 0 to 99")
##############################
# XML processing
#
def _remove_deleted_params(self):
""" Remove from target_elt a few deleted params """
changed = False
for param in LOG_SETTINGS_ARGUMENT_SPEC:
if LOG_SETTINGS_ARGUMENT_SPEC[param]['type'] == 'bool':
_param = params_map.get(param, param)
if self.pfsense.remove_deleted_param_from_elt(self.target_elt, _param, self.obj):
changed = True
return changed
##############################
# run
#
def run(self, params):
""" process input params to add/update/delete """
self.params = params
self.target_elt = self.root_elt
self._validate_params()
self.obj = self._params_to_obj()
self._add()
def _update(self):
""" make the target pfsense reload """
for cmd in self.route_cmds:
self.module.run_command(cmd)
cmd = '''
require_once("filter.inc");
$retval = 0;
$retval |= system_syslogd_start();'''
for param in ['nologdefaultblock', 'nologdefaultpass', 'nologbogons', 'nologprivatenets', 'nologlinklocal4', 'nologsnort2c']:
if self.params.get(param) is not None:
if (self.params[param] and param not in self.before or not self.params[param] and param in self.before):
cmd += '$retval |= filter_configure();\n'
break
if self.params.get('nolognginx') is not None:
if (self.params['nolognginx'] and 'nolognginx' not in self.before or not self.params['nolognginx'] and 'nolognginx' in self.before):
cmd += 'ob_flush();\n'
cmd += 'flush();\n'
cmd += 'send_event("service restart webgui");\n'
cmd += '$retval |= filter_pflog_start(true);\n'
return self.pfsense.phpshell(cmd)
##############################
# Logging
#
@staticmethod
def _get_obj_name():
""" return obj's name """
return "syslog"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
values = ''
for param in LOG_SETTINGS_ARGUMENT_SPEC:
_param = params_map.get(param, param)
if LOG_SETTINGS_ARGUMENT_SPEC[param]['type'] == 'bool':
values += self.format_updated_cli_field(self.obj, self.before, _param, fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
else:
values += self.format_updated_cli_field(self.obj, self.before, _param, add_comma=(values), log_none=False)
return values
def main():
module = AnsibleModule(
argument_spec=LOG_SETTINGS_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseLogSettingsModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_nat_outbound.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_nat_outbound
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense Outbound NAT (SNAT) rules
description:
- Manage pfSense Outbound NAT Entries
notes:
options:
descr:
description: The name of the nat rule
required: true
default: null
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
nonat:
description: This option will disable NAT for traffic matching this rule and stop processing Outbound NAT rules
default: false
type: bool
interface:
description: The interface for the rule
required: false
type: str
ipprotocol:
description: The Internet Protocol version this rule applies to.
default: inet46
choices: [ "inet", "inet46", "inet6" ]
type: str
protocol:
description: Which protocol this rule should match.
default: any
choices: [ "any", "tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "carp", "pfsync" ]
type: str
source:
description: The matching source address, in {any,(self),ALIAS,NETWORK,NET:INTERFACE}[:port] format.
required: false
default: null
type: str
destination:
description: The matching destination address, in {any,ALIAS,NETWORK,NET:INTERFACE}[:port] format.
required: false
default: null
type: str
invert:
description: Invert the sense of the destination match.
default: false
type: bool
address:
description: The translated to address, in {ALIAS,NETWORK}[:port] format. Leave address part empty to use interface address.
required: false
default: null
type: str
poolopts:
description: When an address pool is used, there are several options available that control how NAT translations happen on the pool.
default: ""
choices: [ "", "round-robin", "round-robin sticky-address", "random", "random sticky-address", "source-hash", "bitmask" ]
type: str
source_hash_key:
description: >
The key that is fed to the hashing algorithm in hex format, preceeded by "0x", or any string.
A non-hex string is hashed using md5 to a hexadecimal key. Defaults to a randomly generated value.
required: false
default: ''
type: str
staticnatport:
description: Do not randomize source port
default: false
type: bool
nosync:
description: Prevents the rule on Master from automatically syncing to other CARP members. This does NOT prevent the rule from being overwritten on Slave.
default: false
type: bool
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
"""
EXAMPLES = """
- name: "Add NAT outbound traffic rule"
pfsense_nat_outbound:
descr: 'NAT outbound traffic'
interface: wan
source: any
destination: any
state: present
- name: "Delete NAT outbound traffic rule"
pfsense_nat_outbound:
descr: 'NAT outbound traffic'
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create nat_outbound 'NAT outbound traffic', interface='wan', source='any', destination='any'", "delete nat_outbound 'NAT outbound traffic'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.nat_outbound import (
PFSenseNatOutboundModule,
NAT_OUTBOUND_ARGUMENT_SPEC,
NAT_OUTBOUND_MUTUALLY_EXCLUSIVE,
NAT_OUTBOUND_REQUIRED_IF,
)
def main():
module = AnsibleModule(
argument_spec=NAT_OUTBOUND_ARGUMENT_SPEC,
mutually_exclusive=NAT_OUTBOUND_MUTUALLY_EXCLUSIVE,
required_if=NAT_OUTBOUND_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseNatOutboundModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_nat_port_forward.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Frederic Bor
# Copyright: (c) 2023, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_nat_port_forward
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense port forwarding NAT (DNAT) rules
description:
- Manage pfSense port forwarding NAT (DNAT) rules.
notes:
options:
descr:
description: The name of the NAT rule
required: true
default: null
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
nordr:
description: Disable redirection for traffic matching this rule
default: false
type: bool
interface:
description: The interface for the rule
required: false
type: str
ipprotocol:
description: The IP protocol
default: inet
choices: [ "inet", "inet6" ]
type: str
protocol:
description: Which protocol this rule should match.
default: tcp
choices: [ "tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "pim", "ospf" ]
type: str
source:
description: The source address, in [!]{IP,HOST,ALIAS,any,IP:INTERFACE,NET:INTERFACE}[:port] format.
default: null
type: str
destination:
description: The destination address, in [!]{IP,HOST,ALIAS,any,IP:INTERFACE,NET:INTERFACE}[:port] format.
default: null
type: str
target:
description: The translated to address, in {ALIAS,IP}[:port] format.
required: false
default: null
type: str
natreflection:
description: Allows NAT reflection to be enabled or disabled on a per-port forward basis.
default: system-default
choices: [ "system-default", "enable", "purenat", "disable" ]
type: str
associated_rule:
description: >
Choose one of Add an associated filter rule gets updated when the port forward is updated,
or Add an unassociated filter rule, or pass which passes all traffic that matches the entry without having a firewall rule at all.
default: associated
choices: [ "associated", "unassociated", "pass", "none" ]
type: str
nosync:
description: Prevents the rule on Master from automatically syncing to other CARP members. This does NOT prevent the rule from being overwritten on Slave.
default: false
type: bool
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
"""
EXAMPLES = """
- name: "Add NAT port forward traffic rule"
pfsense_nat_port_forward:
descr: 'ssh'
interface: wan
source: any
destination: any:22
target: 1.2.3.4:22
associated_rule: pass
state: present
- name: "Delete NAT port forward traffic rule"
pfsense_nat_port_forward:
descr: 'ssh'
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: [
"create nat_port_forward 'ssh', interface='wan', source='any', destination='any:22', target='1.2.3.4:22', associated_rule='pass'",
"delete nat_port_forward 'ssh'"
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.nat_port_forward import (
PFSenseNatPortForwardModule,
NAT_PORT_FORWARD_ARGUMENT_SPEC,
NAT_PORT_FORWARD_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=NAT_PORT_FORWARD_ARGUMENT_SPEC,
required_if=NAT_PORT_FORWARD_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseNatPortForwardModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_openvpn_client.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019-2021, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: pfsense_openvpn_client
short_description: Manage pfSense OpenVPN configuration
description:
- Manage pfSense OpenVPN configuration
version_added: 0.5.0
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the OpenVPN configuration.
required: true
type: str
mode:
description: The client mode.
required: false
default: p2p_tls
choices: [ "p2p_tls", "p2p_shared_key" ]
type: str
authmode:
description: Authentication clients. Required if mode == client_tls_user.
default: []
type: list
elements: str
state:
description: State in which to leave the OpenVPN config.
default: present
choices: [ "present", "absent" ]
type: str
disable:
description: Is the OpenVPN config disabled.
default: false
type: bool
interface:
description: The interface for OpenVPN to listen on.
required: false
default: wan
type: str
server_addr:
description: The address for OpenVPN to connect to.
required: true
type: str
server_port:
description: The port for OpenVPN to connect to.
required: false
default: 1194
type: int
protocol:
description: The protocol.
default: UDP4
choices: [ 'UDP4', 'TCP4' ]
type: str
dev_mode:
description: Device mode.
default: tun
choices: [ 'tun', 'tap' ]
type: str
tls:
description: TLS Key. If set to 'generate' it will create a key if one does not already exist. Not valid with p2p_shared_key mode.
type: str
tls_type:
description: Use TLS for authentication ('auth') or encyprtion and authentication ('crypt'). Only used when tls is set.
default: 'auth'
required: false
choices: ["auth", "crypt"]
type: str
version_added: 0.6.2
ca:
description: Certificate Authority name.
type: str
crl:
description: Certificate Revocation List name.
type: str
cert:
description: Client certificate name.
type: str
cert_depth:
description: Depth of certificates to check.
required: false
default: 1
type: int
strictusercn:
description: Enforce a match between the common name of the client certificate and the username given at login.
default: false
type: bool
shared_key:
description: Pre-shared key for shared key modes. If set to 'generate' it will create a key if one does not already exist.
type: str
dh_length:
description: DH parameter length.
required: false
default: 2048
type: int
ecdh_curve:
description: Elliptic Curve to use for key exchange.
required: false
default: none
choices: [ "none", "prime256v1", "secp384r1", "secp521r1" ]
type: str
data_ciphers_fallback:
description: Fallback cryptographic algorithm.
default: AES-256-CBC
choices: [ 'AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305' ]
type: str
data_ciphers:
description: Allowed cryptographic algorithms.
choices: [ 'AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305' ]
type: list
elements: str
ncp_enable:
description: Enable data encryption negotiation.
default: no
type: bool
digest:
description:
- 'Auth digest algorithm. The list of valid digest algorithms is determined from the output of C(openvpn --show-digests), but curently includes:'
- BLAKE2b512, BLAKE2s256, KECCAK-KMAC-128, KECCAK-KMAC-256, MD5, MD5-SHA1, NULL, RIPEMD160, SHA1, SHA224, SHA256, SHA3-224, SHA3-256, SHA3-384, SHA3-512,
- SHA384, SHA512, SHA512-224, SHA512-256, SHAKE128, SHAKE256
default: SHA256
type: str
tunnel_network:
description: IPv4 virtual network used for private communications between this client and client hosts expressed using CIDR notation.
default: ''
type: str
tunnel_networkv6:
description: IPv6 virtual network used for private communications between this client and client hosts expressed using CIDR notation.
default: ''
type: str
remote_network:
description: IPv4 networks that will be routed through the tunnel.
default: ''
type: str
remote_networkv6:
description: IPv6 networks that will be routed through the tunnel.
default: ''
type: str
gwredir:
description: Redirect IPv4 gateway.
default: no
type: bool
gwredir6:
description: Redirect IPv6 gateway.
default: no
type: bool
maxclients:
description: The maximum number of clients allowed to concurrently connect to this client.
default: null
type: int
compression:
description: Allowed compression to be used with this VPN instance.
default: adaptive
choices: ['adaptive', '']
type: str
compression_push:
description: Push the selected Compression setting to connecting clients.
default: no
type: bool
passtos:
description: Set the TOS IP header value of tunnel packets to match the encapsulated packet value.
default: no
type: bool
client2client:
description: Allow communication between clients connected to this client.
default: no
type: bool
dynamic_ip:
description: Allow connected clients to retain their connections if their IP address changes.
default: no
type: bool
topology:
description: The method used to supply a virtual adapter IP address to clients when using TUN mode on IPv4.
default: subnet
choices: ['net30','subnet']
type: str
dns_domain:
description: DNS default domain.
default: ''
type: str
dns_client1:
description: DNS client 1.
default: ''
type: str
dns_client2:
description: DNS client 2.
default: ''
type: str
dns_client3:
description: DNS client 3.
default: ''
type: str
dns_client4:
description: DNS client 4.
default: ''
type: str
push_register_dns:
description: Push DNS to client.
default: no
type: bool
create_gw:
description: Which gateway types to create.
default: both
choices: ['both','v4only','v6only']
type: str
verbosity_level:
description: Verbosity level.
default: 3
type: int
custom_options:
description: Custom openvpn options.
required: false
default: null
type: str
'''
EXAMPLES = r'''
- name: "Add OpenVPN client"
pfsense_openvpn_client:
name: 'OpenVPN Client'
'''
RETURN = r'''
shared_key:
description: The generated shared key, base64 encoded
returned: when `generate` is passed as the shared_key argument and a key is generated.
type: str
sample: |-
IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZ
Tc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MT
kzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ
4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFh
OTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3Z
jA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K
tls:
description: The generated tls key, base64 encoded
returned: when `generate` is passed as the tls argument and a key is generated.
type: str
sample: |-
IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZ
Tc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MT
kzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ
4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFh
OTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3Z
jA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.openvpn_client import (
PFSenseOpenVPNClientModule,
OPENVPN_CLIENT_ARGUMENT_SPEC,
OPENVPN_CLIENT_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=OPENVPN_CLIENT_ARGUMENT_SPEC,
required_if=OPENVPN_CLIENT_REQUIRED_IF,
supports_check_mode=True)
pfopenvpn = PFSenseOpenVPNClientModule(module)
pfopenvpn.run(module.params)
pfopenvpn.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_openvpn_override.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020-2022, Orion Poplawski
# Copyright: (c) 2020, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_openvpn_override
version_added: 0.5.0
author: Orion Poplawski (@opoplawski)
short_description: Manage pfSense OpenVPN Client Specific Overrides
description:
- Manage pfSense OpenVPN Client Specific Overrides
notes:
options:
name:
description:
- The name of the override. The X.509 common name for the client certificate, or the username for VPNs utilizing password authentication.
- This match is case sensitive.
required: true
type: str
state:
description: State in which to leave the override
choices: ["present", "absent"]
default: present
type: str
disable:
description: Disable this override.
default: false
type: bool
descr:
description: The description of the override.
default: null
type: str
server_list:
description: A list of OpenVPN servers this applies to.
type: list
elements: str
block:
description: Block this client connection based on its common name.
default: false
type: bool
tunnel_network:
description: IPv4 virtual network used for private communications between this server and client hosts expressed using CIDR notation.
default: null
type: str
tunnel_networkv6:
description: IPv6 virtual network used for private communications between this server and client hosts expressed using CIDR notation.
default: null
type: str
local_network:
description: IPv4 networks that will be accessible from the remote endpoint.
default: null
type: str
local_networkv6:
description: IPv6 networks that will be accessible from the remote endpoint.
default: null
type: str
remote_network:
description: IPv4 networks that will be routed through the tunnel.
default: null
type: str
remote_networkv6:
description: IPv6 networks that will be routed through the tunnel.
default: null
type: str
gwredir:
description: Redirect IPv4 gateway.
default: no
type: bool
push_reset:
description: Prevent this client from receiving any server-defined client settings.
default: no
type: bool
netbios_enable:
description: Enable NetBIOS over TCP/IP.
default: no
type: bool
netbios_ntype:
description:
- 'NetBIOS Node Type. Possible options: b-node (broadcasts), p-node (point-to-point name queries to a WINS server),'
- m-node (broadcast then query name server), and h-node (query name server, then broadcast). Default is 'none'.
type: str
choices: ['none', 'b-node', 'p-node', 'm-node', 'h-node']
netbios_scope:
description:
- A NetBIOS Scope ID provides an extended naming service for NetBIOS over TCP/IP. The NetBIOS scope ID isolates NetBIOS traffic on a single network to
- only those nodes with the same NetBIOS scope ID.
type: str
wins_server_enable:
description: Provide a WINS server list to clients,
type: bool
default: no
custom_options:
description: Additional options to add for this client specific override, separated by a semicolon.
type: str
"""
EXAMPLES = """
- name: Set IP address for user
pfsense_openvpn_override:
name: username
custom_options: ifconfig-push 10.8.0.2 255.255.255.0
state: present
- name: Remove override for user
pfsense_opevpn_override:
name: username
state: absent
"""
RETURN = """
commands:
description: The set of commands that would be pushed to the remote device (if pfSense had a CLI).
returned: always
type: list
sample: ["create OpenVPN override 'username'"]
vpnids:
description: A list of VPN IDs that the override applies to.
returned: always
type: list
sample: [1,2]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.openvpn_override import (
PFSenseOpenVPNOverrideModule,
OPENVPN_OVERRIDE_ARGUMENT_SPEC,
OPENVPN_OVERRIDE_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=OPENVPN_OVERRIDE_ARGUMENT_SPEC,
required_if=OPENVPN_OVERRIDE_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseOpenVPNOverrideModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_openvpn_server.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019-2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_openvpn_server
version_added: 0.5.0
author: Orion Poplawski (@opoplawski)
short_description: Manage pfSense OpenVPN server configuration
description:
- Manage pfSense OpenVPN server configuration
notes:
options:
name:
description: The name of the OpenVPN server.
required: true
type: str
mode:
description: The server mode.
choices: ["p2p_tls", "p2p_shared_key", "server_tls", "server_tls_user", "server_user"]
type: str
dco:
description: Enable Data Channel Offload (Supported on Plus versions only)
default: false
required: false
type: bool
version_added: 0.7.2
authmode:
description:
- Authentication servers. Required if mode == server_tls_user.
- Use 'Local Database' for authentication against the local pfSense user database.
default: []
type: list
elements: str
state:
description: State in which to leave the OpenVPN config.
default: present
choices: ["present", "absent"]
type: str
disable:
description: Is the OpenVPN config disabled?
default: false
type: bool
interface:
description: The interface for OpenVPN to listen on.
required: false
default: wan
type: str
local_port:
description: The port for OpenVPN to listen on.
required: false
default: 1194
type: int
protocol:
description: The protocol used for the connection.
default: 'UDP4'
choices: ['UDP4', 'TCP4']
type: str
dev_mode:
description: Device mode.
default: tun
choices: ['tun', 'tap']
type: str
tls:
description: TLS Key. If set to 'generate' it will create a key if one does not already exist. Not valid with p2p_shared_key mode.
type: str
tls_type:
description: Use TLS for authentication ('auth') or encyprtion and authentication ('crypt'). Only used when tls is set.
default: 'auth'
required: false
choices: ["auth", "crypt"]
type: str
ca:
description: Certificate Authority name.
type: str
crl:
description: Certificate Revocation List name.
type: str
cert:
description: Server certificate name.
type: str
cert_depth:
description: Depth of certificates to check.
required: false
default: 1
type: int
strictusercn:
description: Enforce a match between the common name of the client certificate and the username given at login.
default: false
type: bool
remote_cert_tls:
description: 'Enforce that only hosts with a client certificate can connect (EKU: TLS Web Client Authentication).'
default: false
type: bool
version_added: 0.7.0
shared_key:
description: Pre-shared key for shared key modes. If set to 'generate' it will create a key if one does not already exist.
type: str
dh_length:
description: DH parameter length.
required: false
default: 2048
type: int
ecdh_curve:
description: Elliptic Curve to use for key exchange.
required: false
default: none
choices: ["none", "prime256v1", "secp384r1", "secp521r1"]
type: str
data_ciphers_fallback:
description: Fallback cryptographic algorithm.
default: AES-256-CBC
choices: ['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305']
type: str
data_ciphers:
description: Allowed cryptographic algorithms.
default: ['AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305']
choices: ['AES-256-CBC', 'AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305']
type: list
elements: str
digest:
description:
- 'Auth digest algorithm. The list of valid digest algorithms is determined from the output of C(openvpn --show-digests), but curently includes:'
- BLAKE2b512, BLAKE2s256, KECCAK-KMAC-128, KECCAK-KMAC-256, MD5, MD5-SHA1, NULL, RIPEMD160, SHA1, SHA224, SHA256, SHA3-224, SHA3-256, SHA3-384, SHA3-512,
- SHA384, SHA512, SHA512-224, SHA512-256, SHAKE128, SHAKE256
default: SHA256
type: str
tunnel_network:
description: IPv4 virtual network used for private communications between this server and client hosts expressed using CIDR notation.
default: ''
type: str
tunnel_networkv6:
description: IPv6 virtual network used for private communications between this server and client hosts expressed using CIDR notation.
default: ''
type: str
local_network:
description: IPv4 networks that will be accessible from the remote endpoint.
default: ''
type: str
local_networkv6:
description: IPv6 networks that will be accessible from the remote endpoint.
default: ''
type: str
remote_network:
description: IPv4 networks that will be routed through the tunnel.
default: ''
type: str
remote_networkv6:
description: IPv6 networks that will be routed through the tunnel.
default: ''
type: str
gwredir:
description: Redirect IPv4 gateway.
default: no
type: bool
gwredir6:
description: Redirect IPv6 gateway.
default: no
type: bool
maxclients:
description: The maximum number of clients allowed to concurrently connect to this server.
default: null
type: int
allow_compression:
description:
- Allow compression to be used with this VPN instance.
- Compression can potentially increase throughput but may allow an attacker to extract secrets if they can control compressed plaintext traversing the
- VPN (e.g. HTTP). Before enabling compression, consult information about the VORACLE, CRIME, TIME, and BREACH attacks against TLS to decide if the use
- case for this specific VPN is vulnerable to attack.
- Asymmetric compression allows an easier transition when connecting with older peers.
default: 'no'
choices: ['no', 'asym', 'yes']
type: str
compression:
description:
- Allowed compression to be used with this VPN instance.
- "'' => Disable Compression [Omit Preference]"
- "'none' => Disable Compression, retain compression packet framing [compress]"
- "'stub' => Enable Compression (stub) [compress stub]"
- "'stub-v2' => Enable Compression (stub v2) [compress stub-v2]"
- "'lz4' => LZ4 Compression [compress lz4]"
- "'lz4-v2' => LZ4 Compression v2 [compress lz4-v2]"
- "'lzo' => LZO Compression [compress lzo, equivalent to comp-lzo yes for compatibility]"
- "'noadapt' => Omit Preference, + Disable Adaptive LZO Compression [Legacy style, comp-noadapt]"
- "'adaptive' => Adaptive LZO Compression [Legacy style, comp-lzo adaptive]"
- "'yes' => LZO Compression [Legacy style, comp-lzo yes]"
- "'no' => No LZO Compression [Legacy style, comp-lzo no]"
default: ''
choices: ['', 'none', 'stub', 'stub-v2', 'lz4', 'lz4-v2', 'lzo', 'noadapt', 'adaptive', 'yes', 'no']
type: str
compression_push:
description: Push the selected Compression setting to connecting clients.
default: no
type: bool
passtos:
description: Set the TOS IP header value of tunnel packets to match the encapsulated packet value.
default: no
type: bool
client2client:
description: Allow communication between clients connected to this server.
default: no
type: bool
dynamic_ip:
description: Allow connected clients to retain their connections if their IP address changes.
default: no
type: bool
topology:
description: The method used to supply a virtual adapter IP address to clients when using TUN mode on IPv4.
default: subnet
choices: ['net30', 'subnet']
type: str
inactive_seconds:
description: Causes OpenVPN to close a client connection after n seconds of inactivity on the TUN/TAP device.
default: 0
type: int
version_added: 0.7.0
keepalive_interval:
description:
- 'keepalive helper uses interval and timeout parameters to define ping and ping-restart values as follows:'
- ping = interval
- ping-restart = timeout*2
- push ping = interval
- push ping-restart = timeout
default: 10
type: int
version_added: 0.7.0
keepalive_timeout:
description:
- 'keepalive helper uses interval and timeout parameters to define ping and ping-restart values as follows:'
- ping = interval
- ping-restart = timeout*2
- push ping = interval
- push ping-restart = timeout
default: 60
type: int
version_added: 0.7.0
exit_notify:
description:
- Send an explicit exit notification to connected clients/peers when restarting or shutting down, so they may immediately disconnect rather than waiting
- for a timeout. In SSL/TLS Server modes, clients may be directed to reconnect or use the next server. This option is ignored in Peer-to-Peer Shared Key
- mode and in SSL/TLS mode with a blank or /30 tunnel network as it will cause the server to exit and not restart. This feature is not currently
- compatible with DCO mode.
- "'none' => Disabled"
- "'1' => Reconnect to this server / Retry once"
- "'2' => Reconnect to next server / Retry twice"
default: 'none'
choices: ['none', '1', '2']
type: str
version_added: 0.7.0
dns_domain:
description: DNS default domain.
default: ''
type: str
dns_server1:
description: DNS server 1.
default: ''
type: str
dns_server2:
description: DNS server 2.
default: ''
type: str
dns_server3:
description: DNS server 3.
default: ''
type: str
dns_server4:
description: DNS server 4.
default: ''
type: str
push_register_dns:
description: Push DNS.
type: bool
default: no
create_gw:
description: Which gateway types to create.
default: both
choices: ['both', 'v4only', 'v6only']
type: str
verbosity_level:
description: Verbosity level.
default: 1
type: int
custom_options:
description: Custom openvpn options.
required: false
default: null
type: str
username_as_common_name:
description: Use the authenticated client username instead of the certificate common name (CN).
default: false
type: bool
"""
EXAMPLES = """
- name: "Add OpenVPN server"
pfsense_openvpn_server:
name: 'OpenVPN Server'
mode: server_user
- name: "Add OpenVPN server with basic configuration"
pfsense_openvpn_server:
name: 'OpenVPN Server Ansible'
ca: name-your-ca-authority
cert: name-your-server-certificate
tunnel_network: 10.21.40.0/24
local_network: 172.16.3.0/24
mode: server_tls_user
- name: "Add OpenVPN server with verbose mode and Cloudflare DNS"
pfsense_openvpn_server:
name: 'OpenVPN Server Ansible Cloudflare'
ca: name-your-ca-authority
cert: name-your-server-certificate
tunnel_network: 10.10.10.0/24
local_network: 10.72.40.0/24
dns_server1: 1.1.1.1
verbosity_level: 4
mode: server_user
"""
RETURN = r'''
shared_key:
description: The generated shared key, base64 encoded
returned: when `generate` is passed as the shared_key argument and a key is generated.
type: str
sample: |-
IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZ
Tc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MT
kzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ
4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFh
OTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3Z
jA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K
tls:
description: The generated tls key, base64 encoded
returned: when `generate` is passed as the tls argument and a key is generated.
type: str
sample: |-
IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZ
Tc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MT
kzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ
4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFh
OTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3Z
jA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K
vpnid:
description: The vpnid number of the OpenVPN server instance.
returned: always
type: int
sample: 1
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.openvpn_server import (
PFSenseOpenVPNServerModule,
OPENVPN_SERVER_ARGUMENT_SPEC,
OPENVPN_SERVER_REQUIRED_IF
)
def main():
module = AnsibleModule(
argument_spec=OPENVPN_SERVER_ARGUMENT_SPEC,
required_if=OPENVPN_SERVER_REQUIRED_IF,
supports_check_mode=True)
pfopenvpn = PFSenseOpenVPNServerModule(module)
pfopenvpn.run(module.params)
pfopenvpn.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_phpshell.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2023, genofire
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_phpshell
version_added: 0.7.0
author: Geno (@genofire)
short_description: PHP Shell
description:
- Run a php shell
options:
cmd:
description: PHP Code to run
required: true
type: str
"""
EXAMPLES = """
- name: run phpshell with code pfSense config.xml
pfsense_phpshell:
cmd: |
require_once("filter.inc");
require_once("squid.inc");
squid_resync("yes");
"""
RETURN = """
rc:
description: Status code after run php-shell (could be changed using `exit(x)`)
returned: always
type: int
sample:
- 0
stdout:
description: Output of the php-shell (include your code)
returned: always
type: str
sample: |
pfSense shell: global $debug;
pfSense shell: $debug = 1;
pfSense shell: require_once("filter.inc");
pfSense shell: require_once("squid.inc");
pfSense shell: squid_resync("yes");
pfSense shell:
pfSense shell: exec
pfSense shell: exit
stderr:
description: Output on error of the php-shell
returned: always
type: str
sample: ""
changed:
description: It returns always true (you could overwrite with changed_when)
returned: always
type: bool
failed:
description: rc is not 0 or stderr contains output (you still could overwrite with failed_when)
returned: failure
type: bool
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
PHP_SHELL_ARGUMENT_SPEC = dict(
cmd=dict(required=True, type='str')
)
class PFSensePHPShellModule(PFSenseModuleBase):
""" module run php code on pfsense """
@staticmethod
def get_argument_spec():
""" return argument spec """
return PHP_SHELL_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSensePHPShellModule, self).__init__(module, pfsense)
self.name = "pfsense_phpshell"
self.result['changed'] = True
##############################
# run
#
def run(self, params):
(rc, stdout, stderr) = self.pfsense.phpshell(params['cmd'])
self.result.update({
'rc': rc,
'stdout': stdout,
'stderr': stderr,
})
if int(rc) != 0 or len(stderr) > 0:
self.module.fail_json(msg='rc is not 0 or stderr contains output (you still could overwrite with failed_when)')
else:
self.module.exit_json(**self.result)
def main():
module = AnsibleModule(
argument_spec=PHP_SHELL_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSensePHPShellModule(module)
pfmodule.run(module.params)
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_rewrite_config.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2023, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_rewrite_config
version_added: 0.5.3
author: Orion Poplawski (@opoplawski)
short_description: Rewrite pfSense config.xml
description:
- Rewrites pfSense's config.xml using native tools to reproduce formatting.
- This also triggers an XMLRPC configuration sync to a high availability secondary system.
notes:
"""
EXAMPLES = """
- name: Rewrite pfSense config.xml
pfsense_rewrite_config:
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
REWRITE_CONFIG_ARGUMENT_SPEC = dict()
class PFSenseRewriteConfigModule(PFSenseModuleBase):
""" module managing pfsense routes """
@staticmethod
def get_argument_spec():
""" return argument spec """
return REWRITE_CONFIG_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseRewriteConfigModule, self).__init__(module, pfsense)
self.name = "pfsense_rewrite_config"
self.result['changed'] = True
##############################
# run
#
def commit_changes(self):
""" apply changes and exit module """
self.result['stdout'] = ''
self.result['stderr'] = ''
if self.result['changed'] and not self.module.check_mode:
(dummy, self.result['stdout'], self.result['stderr']) = self._update()
self.module.exit_json(**self.result)
def _update(self):
""" make the target pfsense rewrite the config.xml file """
cmd = '''write_config('pfsense_rewrite_config');'''
return self.pfsense.phpshell(cmd)
def main():
module = AnsibleModule(
argument_spec=REWRITE_CONFIG_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseRewriteConfigModule(module)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_route.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_route
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense routes
description:
- Manage pfSense routes
notes:
options:
descr:
description: The description of the route
required: true
type: str
network:
description: Destination network for this static route
required: false
type: str
gateway:
description: Gateway this route applies to
required: false
type: str
disabled:
description: Set this option to disable this static route without removing it from the list.
default: false
type: bool
state:
description: State in which to leave the route
choices: [ "present", "absent" ]
default: present
type: str
"""
EXAMPLES = """
- name: Add route
pfsense_route:
descr: vpn_route
gateway: VPN_GW
network: 10.100.0.0/16
state: present
- name: Remove route
pfsense_route:
name: vpn_route
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create route 'vpn_route', gateway='VPN_GW', network='10.100.0.0/16'", "delete route 'vpn_route'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.route import PFSenseRouteModule, ROUTE_ARGUMENT_SPEC, ROUTE_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=ROUTE_ARGUMENT_SPEC,
required_if=ROUTE_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseRouteModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_rule.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_rule
version_added: 0.1.0
author: Orion Poplawski (@opoplawski), Frederic Bor (@f-bor)
short_description: Manage pfSense firewall rules
description:
- Manage pfSense firewall rules
notes:
options:
name:
description: The name the rule
required: true
default: null
type: str
action:
description: The action of the rule
default: pass
choices: [ "pass", "block", "match", "reject" ]
type: str
state:
description: State in which to leave the rule
default: present
choices: [ "present", "absent" ]
type: str
disabled:
description: Is the rule disabled
default: false
type: bool
interface:
description: The interface for the rule. Use 'any' to apply to all interface (for floating rules only).
required: true
type: str
floating:
description: Is the rule floating
type: bool
direction:
description: Direction floating rule applies to
choices: [ "any", "in", "out" ]
type: str
ipprotocol:
description: The IP protocol
default: inet
choices: [ "inet", "inet46", "inet6" ]
type: str
protocol:
description: The protocol
default: any
choices: [ "any", "tcp", "udp", "tcp/udp", "icmp", "igmp", "ospf", "esp", "ah", "gre", "pim", "sctp", "pfsync", "carp" ]
type: str
source:
description: The source address, in [!]{IP,HOST,ALIAS,any,(self),IP:INTERFACE,NET:INTERFACE} format.
default: null
type: str
source_port:
description:
- Source port or port range specification.
- This can either be a alias or a port number.
- An inclusive range can also be specified, using the format C(first-last)..
default: null
type: str
destination:
description: The destination address, in [!]{IP,HOST,ALIAS,any,(self),IP:INTERFACE,NET:INTERFACE} format.
default: null
type: str
destination_port:
description:
- Destination port or port range specification.
- This can either be a alias or a port number.
- An inclusive range can also be specified, using the format C(first-last)..
default: null
type: str
log:
description: Log packets matched by rule
type: bool
after:
description: Rule to go after, or C(top)
type: str
before:
description: Rule to go before, or C(bottom)
type: str
tcpflags_any:
description: Allow TCP packets with any flags set.
type: bool
statetype:
description: State type
default: keep state
choices: ["keep state", "sloppy state", "synproxy state", "none"]
type: str
queue:
description: QOS default queue
type: str
ackqueue:
description: QOS acknowledge queue
type: str
in_queue:
description: Limiter queue for traffic coming into the chosen interface
type: str
out_queue:
description: Limiter queue for traffic leaving the chosen interface
type: str
queue_error:
description: Raise an error if a specified queue is missing
type: bool
default: True
gateway:
description: Leave as C(default) to use the system routing table or choose a gateway to utilize policy based routing.
type: str
default: default
tracker:
description: Rule tracking ID. Defaults to timestamp of rule creation and not modified if not set or set to 0.
type: str
icmptype:
description:
- One or more of these ICMP subtypes may be specified, separated by comma, or C(any) for all of them.
- The types must match ip protocol.
- althost, dataconv, echorep, echoreq, fqdnrep, fqdnreq, groupqry, grouprep, groupterm, inforep, inforeq, ipv6-here,
- ipv6-where, listendone, listenrep, listqry, maskrep, maskreq, mobredir, mobregrep, mobregreq, mtrace, mtraceresp,
- neighbradv, neighbrsol, niqry, nirep, paramprob, photuris, redir, routeradv, routersol, routrrenum, skip, squench,
- timerep, timereq, timex, toobig, trace, unreach, wrurep, wrureq
default: any
type: str
sched:
description: Schedule day/time when the rule must be active
required: False
type: str
quick:
description: Set this option to apply this action to traffic that matches this rule immediately
type: bool
default: False
"""
EXAMPLES = """
- name: "Add Internal DNS out rule"
pfsense_rule:
name: 'Allow Internal DNS traffic out'
action: pass
interface: lan
ipprotocol: inet
protocol: udp
source: dns_int
destination: any
destination_port: 53
after: 'Allow proxies out'
state: present
- name: "Allow inbound port range"
pfsense_rule:
name: 'Allow inbound port range'
action: pass
interface: wan
ipprotocol: inet
protocol: tcp
source: any
destination: NET:lan
destination_port: 4000-5000
after: 'Allow Internal DNS traffic out'
state: present
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule, RULE_ARGUMENT_SPEC, RULE_REQUIRED_IF
def main():
module = AnsibleModule(
argument_spec=RULE_ARGUMENT_SPEC,
required_if=RULE_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseRuleModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_rule_separator.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_rule_separator
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense firewall rule separators
description:
- Manage pfSense firewall rule separators
notes:
options:
name:
description: The name of the separator
required: true
type: str
state:
description: State in which to leave the separator
choices: [ "present", "absent" ]
default: present
type: str
interface:
description: The interface for the separator
type: str
floating:
description: Is the rule on floating tab
type: bool
after:
description: Rule to go after, or "top"
type: str
before:
description: Rule to go before, or "bottom"
type: str
color:
description: The separator's color
default: info
choices: [ 'info', 'warning', 'danger', 'success' ]
type: str
"""
EXAMPLES = """
- name: Add rule separator voip
pfsense_rule_separator:
name: voip
state: present
interface: lan_100
- name: Remove rule separator voip
pfsense_rule_separator:
name: voip
state: absent
interface: lan_100
"""
RETURN = """
commands:
description: the set of separators commands that would be pushed to the remote device (if pfSense had a CLI)
returned: success
type: list
sample: ["create rule_separator 'SSH', interface='lan', color='info'", "update rule_separator 'SSH' set color='warning'", "delete rule_separator 'SSH'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import PFSenseRuleSeparatorModule
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import RULE_SEPARATOR_ARGUMENT_SPEC
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import RULE_SEPARATOR_REQUIRED_ONE_OF
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import RULE_SEPARATOR_MUTUALLY_EXCLUSIVE
def main():
module = AnsibleModule(
argument_spec=RULE_SEPARATOR_ARGUMENT_SPEC,
required_one_of=RULE_SEPARATOR_REQUIRED_ONE_OF,
mutually_exclusive=RULE_SEPARATOR_MUTUALLY_EXCLUSIVE,
supports_check_mode=True)
pfmodule = PFSenseRuleSeparatorModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_setup.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_setup
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense general setup
description:
- Manage pfSense general setup
notes:
options:
hostname:
description: Hostname of the firewall host, without domain part
required: false
type: str
domain:
description: Domain name of the firewall host
required: false
type: str
dns_addresses:
description: DNS IP addresses, separated by space
required: false
type: str
dns_hostnames:
description: DNS hostnames, separated by space. You can use none for empty values.
required: false
type: str
dns_gateways:
description: DNS gateways, separated by space. You can use none for empty values.
required: false
type: str
dnsallowoverride:
description: Allow DNS server list to be overridden by DHCP/PPP on WAN
required: false
type: bool
dnslocalhost:
required: false
description: >
Do not use the DNS Forwarder/DNS Resolver as a DNS server for the firewall.
"" Use local DNS (127.0.0.1), fall back to remote DNS servers (Default)
"local" Use local DNS (127.0.0), ignore remote DNS servers
"remote" Use remote DNS server, ignore local DNS
true will be mapped to "remote"
false will be mapped to ""
type: str
choices: ["", "local", "remote", "true", "false"]
timezone:
description: Select a geographic region name (Continent/Location) to determine the timezone for the firewall.
required: false
type: str
timeservers:
description: Time servers, separated by space
required: false
type: str
language:
description: Language for the webConfigurator.
required: false
type: str
choices: ['bs', 'de_DE', 'en_US', 'es_AR', 'es_ES', 'fr_FR', 'it_IT', 'ko_FR', 'nb_NO', 'nl_NL', 'pl_PL', 'pt_BR', 'pt_PT', 'ru_RU', 'zh_CN', 'zh_Hans_CN',
'zh_Hans_HK', 'zh_Hant_TW']
webguicert:
description: SSL/TLS certificate for the web GUI.
required: false
type: str
version_added: 0.7.2
webguicss:
description: >
Choose an alternative CSS file (if installed) to change the appearance of the webConfigurator. Custom themes are also supported.
If you want to use a custom pfsense CSS theme you need to upload it to the appliance first.
Standard choices are 'pfsense', 'pfsense-dark','pfsense-dark-BETA','pfsense-BETA', and 'Compact-RED'.
required: false
type: str
webguifixedmenu:
description: When enabled, menu remains visible at top of page
required: false
type: bool
webguihostnamemenu:
description: Replaces the Help menu title in the Navbar with the system hostname or FQDN.
required: false
choices: ['nohost', 'hostonly', 'fqdn']
type: str
session_timeout:
description: >
Time in minutes to expire idle management sessions. The default is 4 hours (240 minutes).
Use 0 to never expire sessions. NOTE: This is a security risk!
required: false
type: int
authmode:
description: Authentication Server ('Local Database' means local (Default)), use name of configured ldap or radius server
required: false
type: str
shellauth:
description: Use Authentication Server for Shell Authentication. Default is false.
type: bool
dashboardcolumns:
description: Dashboard columns
required: false
type: int
interfacessort:
description: If selected, lists of interfaces will be sorted by description, otherwise they are listed wan,lan,optn...
required: false
type: bool
dashboardavailablewidgetspanel:
description: Show the Available Widgets panel on the Dashboard.
required: false
type: bool
systemlogsfilterpanel:
description: Show the Log Filter panel in System Logs.
required: false
type: bool
systemlogsmanagelogpanel:
description: Show the Manage Log panel in System Logs.
required: false
type: bool
statusmonitoringsettingspanel:
description: Show the Settings panel in Status Monitoring.
required: false
type: bool
requirestatefilter:
description: This option requires a filter to be entered before the states are displayed.
required: false
type: bool
webguileftcolumnhyper:
description: If selected, clicking a label in the left column will select/toggle the first item of the group.
required: false
type: bool
disablealiaspopupdetail:
description: If selected, the details in alias popups will not be shown, just the alias description (e.g. in Firewall Rules).
required: false
type: bool
roworderdragging:
description: Disables dragging rows to allow selecting and copying row contents and avoid accidental changes.
required: false
type: bool
logincss:
description: Color for the login page as six digit hexadecimal string e.g. V(33ffb2)
required: false
type: str
loginshowhost:
description: Show hostname on login banner
required: false
type: bool
sshguard_whitelist:
description: Addresses (in CIDR notation) listed will bypass login protection.
required: false
type: list
elements: str
version_added: 0.7.2
crypto_hardware:
description: >
A cryptographic accelerator module will use hardware support to speed up some cryptographic functions on systems which have the chip. Loading the BSD
Crypto Device module will allow access to acceleration devices using drivers built into the kernel, such as Hifn or ubsec chipsets. If the firewall does
not contain a crypto chip, this option will have no effect. To unload the selected module, set this option to "" and then reboot.
required: false
type: str
choices: ['', 'aesni', 'aesni_cryptodev', 'qat']
version_added: 0.7.2
thermal_hardware:
description: >
With a supported CPU, selecting a thermal sensor will load the appropriate driver to read its temperature. Setting this to "None" will attempt to read
the temperature from an ACPI-compliant motherboard sensor instead, if one is present. If there is not a supported thermal sensor chip in the system, this
option will have no effect. To unload the selected module, set this option to "" and then reboot.
required: false
type: str
choices: ['', 'coretemp', 'amdtemp']
version_added: 0.7.2
"""
EXAMPLES = """
- name: setup hostname and domain
pfsense_setup:
hostname: acme
domain: corp.com
- name: setup theme
pfsense_setup:
webguicss: pfSense-dark
- name: timezone and language
pfsense_setup:
timezone: Europe/Paris
language: fr
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["update setup general set hostname='acme', domain='corp.com'"]
"""
import re
from os import listdir
from os.path import isfile, join
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.arg_route import p2o_cert
from ansible_collections.pfsensible.core.plugins.module_utils.arg_validate import validate_cert
from ansible_collections.pfsensible.core.plugins.module_utils.module_config_base import PFSenseModuleConfigBase
SETUP_ARGUMENT_SPEC = dict(
hostname=dict(required=False, type='str'),
domain=dict(required=False, type='str'),
dns_addresses=dict(required=False, type='str'),
dns_hostnames=dict(required=False, type='str'),
dns_gateways=dict(required=False, type='str'),
dnsallowoverride=dict(required=False, type='bool'),
dnslocalhost=dict(required=False, type='str', choices=[
'',
'local',
'remote',
'true',
'false',
]),
timezone=dict(required=False, type='str'),
timeservers=dict(required=False, type='str'),
language=dict(
required=False,
type='str',
choices=['bs', 'de_DE', 'en_US', 'es_AR', 'es_ES', 'fr_FR', 'it_IT', 'ko_FR', 'nb_NO', 'nl_NL', 'pl_PL', 'pt_BR', 'pt_PT', 'ru_RU', 'zh_CN',
'zh_Hans_CN', 'zh_Hans_HK', 'zh_Hant_TW'],
),
session_timeout=dict(required=False, type='int'),
authmode=dict(required=False, type='str'),
shellauth=dict(required=False, type='bool'),
webguicert=dict(required=False, type='str'),
webguicss=dict(required=False, type='str'),
webguifixedmenu=dict(required=False, type='bool'),
webguihostnamemenu=dict(required=False, type='str', choices=['nohost', 'hostonly', 'fqdn']),
dashboardcolumns=dict(required=False, type='int'),
interfacessort=dict(required=False, type='bool'),
dashboardavailablewidgetspanel=dict(required=False, type='bool'),
systemlogsfilterpanel=dict(required=False, type='bool'),
systemlogsmanagelogpanel=dict(required=False, type='bool'),
statusmonitoringsettingspanel=dict(required=False, type='bool'),
requirestatefilter=dict(required=False, type='bool'),
webguileftcolumnhyper=dict(required=False, type='bool'),
disablealiaspopupdetail=dict(required=False, type='bool'),
roworderdragging=dict(required=False, type='bool'),
logincss=dict(required=False, type='str'),
loginshowhost=dict(required=False, type='bool'),
sshguard_whitelist=dict(required=False, type='list', elements='str'),
crypto_hardware=dict(required=False, type='str', choices=['', 'aesni', 'aesni_cryptodev', 'qat']),
thermal_hardware=dict(required=False, type='str', choices=['', 'coretemp', 'amdtemp']),
)
def p2o_dnslocalhost(self, name, params, obj):
if params[name] is not None:
if str(params.get(name)).lower() in ['', 'false']:
obj[name] = ''
elif str(params.get(name)).lower() in ['remote', 'true']:
obj[name] = 'remote'
elif params.get(name).lower() == 'local':
obj[name] = 'local'
def p2o_network_list_to_space_separated(self, name, params, obj):
if params[name] is not None:
for net in params[name]:
if not (self.pfsense.is_ipv4_network(net, strict=False) or self.pfsense.is_ipv6_network(net, strict=False)):
self.module.fail_json(msg=f"Address {net} is not a valid network")
obj[name] = ' '.join(params[name])
def p2o_webguicss(self, name, params, obj):
if params[name] is not None:
# Add .css suffix if not present
if params[name][-4:] != '.css':
obj[name] = params[name] + '.css'
else:
obj[name] = params[name]
def validate_webguicss(self, webguicss):
""" check css style """
path = '/usr/local/www/css/'
themes = [f for f in listdir(path) if isfile(join(path, f)) and f.endswith('.css') and f.find('login') == -1 and f.find('logo') == -1]
themes = map(lambda x: x.replace('.css', ''), themes)
if webguicss.rstrip('.css') not in themes:
raise ValueError("The submitted theme '%s' could not be found. Pick a different theme." % webguicss)
# TODO - validate crypto_hardware against $crypto_modules = getSystemAdvancedMiscCryptoModules();
SETUP_ARG_ROUTE = dict(
dnslocalhost=dict(parse=p2o_dnslocalhost),
sshguard_whitelist=dict(parse=p2o_network_list_to_space_separated),
webguicert=dict(parse=p2o_cert, validate=validate_cert),
webguicss=dict(parse=p2o_webguicss, validate=validate_webguicss),
)
# Booleans that map to different values
SETUP_BOOL_VALUES = dict(
webguifixedmenu=(None, 'fixed'),
)
SETUP_MAP_PARAM = [
('authmode', 'webgui/authmode'),
('dashboardavailablewidgetspanel', 'webgui/dashboardavailablewidgetspanel'),
('dashboardcolumns', 'webgui/dashboardcolumns'),
('disablealiaspopupdetail', 'webgui/disablealiaspopupdetail'),
('interfacessort', 'webgui/interfacessort'),
('logincss', 'webgui/logincss'),
('loginshowhost', 'webgui/loginshowhost'),
('requirestatefilter', 'webgui/requirestatefilter'),
('roworderdragging', 'webgui/roworderdragging'),
('session_timeout', 'webgui/session_timeout'),
('shellauth', 'webgui/shellauth'),
('statusmonitoringsettingspanel', 'webgui/statusmonitoringsettingspanel'),
('systemlogsfilterpanel', 'webgui/systemlogsfilterpanel'),
('systemlogsmanagelogpanel', 'webgui/systemlogsmanagelogpanel'),
('webguicert', 'webgui/ssl-certref'),
('webguicss', 'webgui/webguicss'),
('webguifixedmenu', 'webgui/webguifixedmenu'),
('webguihostnamemenu', 'webgui/webguihostnamemenu'),
('webguileftcolumnhyper', 'webgui/webguileftcolumnhyper'),
]
class PFSenseSetupModule(PFSenseModuleConfigBase):
""" module managing pfsense routes """
@staticmethod
def get_argument_spec():
""" return argument spec """
return SETUP_ARGUMENT_SPEC
##############################
# init
#
def __init__(self, module, pfsense=None):
super(PFSenseSetupModule, self).__init__(module, pfsense, name='pfsense_setup', root='pfsense', node='system', arg_route=SETUP_ARG_ROUTE,
bool_style='absent/present', bool_values=SETUP_BOOL_VALUES, map_param=SETUP_MAP_PARAM)
self.route_cmds = list()
self.params_to_delete = list()
##############################
# params processing
#
def _dns_params_to_obj(self, params, obj):
""" set the dns servers from params to obj """
dns_addresses = None
dns_hostnames = []
dns_gateways = []
idx = 0
if params.get('dns_addresses') is not None:
dns_addresses = params['dns_addresses'].split()
del obj['dns_addresses']
if params.get('dns_hostnames') is not None:
dns_hostnames = params['dns_hostnames'].split()
del obj['dns_hostnames']
if params.get('dns_gateways') is not None:
dns_gateways = params['dns_gateways'].split()
del obj['dns_gateways']
if dns_addresses is not None:
# set the servers
obj['dnsserver'] = dns_addresses
# set the names & gateways
for address in dns_addresses:
gateway = 'none'
if idx < len(dns_hostnames) and dns_hostnames[idx] != 'none':
obj['dns{0}host'.format(idx + 1)] = dns_hostnames[idx]
if idx < len(dns_gateways) and dns_gateways[idx] != 'none':
gateway = dns_gateways[idx]
gw_key = 'dns{0}gw'.format(idx + 1)
if gw_key not in obj or gateway != obj[gw_key]:
obj[gw_key] = gateway
if self.pfsense.is_ipv4_address(address):
self.route_cmds.append('/sbin/route delete {0}'.format(address))
elif self.pfsense.is_ipv6_address(address):
self.route_cmds.append('/sbin/route delete -inet6 {0}'.format(address))
idx += 1
elif 'dnsserver' in obj:
# no servers
del obj['dnsserver']
idx += 1
# delete everything required
while True:
host = 'dns{0}host'.format(idx)
gateway = 'dns{0}gw'.format(idx)
if host not in obj and gateway not in obj:
break
if host in obj:
del obj[host]
self.params_to_delete.append(host)
if gateway in obj:
del obj[gateway]
self.params_to_delete.append(gateway)
idx += 1
def _params_to_obj(self):
""" return a dict from module params """
obj = super(PFSenseSetupModule, self)._params_to_obj()
self._dns_params_to_obj(self.params, obj)
return obj
def _validate_hostname(self, hostname, name, strict=False):
""" check hostname, if strict is true, check if domain is omitted """
host = hostname.lower()
groups = re.match(r'^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$', host)
if groups is None:
self.module.fail_json(msg="The {0} can only contain the characters A-Z, 0-9 and '-'. It may not start or end with '-'".format(name))
if strict:
groups = re.match(r'^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$', host)
if groups is None:
self.module.fail_json(msg='A valid {0} is specified, but the domain name part should be omitted'.format(name))
def _validate_params(self):
""" do some extra checks on input parameters """
super(PFSenseSetupModule, self)._validate_params()
params = self.params
if params.get('dashboardcolumns') is not None and (params['dashboardcolumns'] < 1 or params['dashboardcolumns'] > 6):
self.module.fail_json(msg='The submitted Dashboard Columns value is invalid.')
if params.get('domain') is not None:
domain = params['domain'].lower()
groups = re.match(r'^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$', domain)
if groups is None:
self.module.fail_json(msg="The domain may only contain the characters a-z, 0-9, '-' and '.'")
if params.get('hostname') is not None:
self._validate_hostname(params['hostname'], 'hostname', True)
if params.get('logincss') is not None:
error = False
try:
int(params['logincss'], 16)
except ValueError:
error = True
if error or len(params['logincss']) != 6:
self.module.fail_json(msg="logincss must be a six digits hexadecimal string.")
if params.get('timezone') is not None:
self._validate_timezone(params['timezone'])
if params.get('timeservers') is not None:
for timeserver in params['timeservers'].split(' '):
self._validate_hostname(timeserver, 'timeserver')
if params.get('authmode') is not None:
value = params.get('authmode')
if value != 'Local Database':
authserver_elt = self.pfsense.find_elt('authserver', value, search_field='name', root_elt=self.root_elt.find(self.node))
if authserver_elt is None:
self.module.fail_json(msg="Given authserver '{0}' could not be found.".format(value))
if params.get('shellauth') is not None and params.get('shellauth') is True:
if authserver_elt.find('type').text == 'ldap':
# check if ldap_pam_groupdn is set
if authserver_elt.find('ldap_pam_groupdn') is None or \
authserver_elt.find('ldap_pam_groupdn').text is None or \
authserver_elt.find('ldap_pam_groupdn').text == '':
self.module.fail_json(msg="ldap_pam_groupdn not set for authserver '{0}'.".format(value))
# DNS
ip_types = []
dns_addresses = []
if params.get('dns_addresses') is not None:
dns_addresses = params['dns_addresses'].split()
for address in dns_addresses:
if dns_addresses.count(address) > 1:
self.module.fail_json(msg='Each configured DNS server must have a unique IP address. Remove the duplicated IP.')
if self.pfsense.is_ipv4_address(address):
ip_types.append(4)
elif self.pfsense.is_ipv6_address(address):
ip_types.append(6)
else:
self.module.fail_json(msg='A valid IP address must be specified for DNS server {0}.'.format(address))
if params.get('dns_hostnames') is not None:
for hostname in params['dns_hostnames'].split(' '):
if hostname != 'none':
self._validate_hostname(hostname, 'DNS hostname')
if params.get('dns_gateways') is not None:
for idx, address in enumerate(params['dns_gateways'].split(' ')):
if idx >= len(dns_addresses) or address == 'none':
continue
if self.pfsense.find_gateway_elt(address, protocol='inet') is not None:
if ip_types[idx] == 6:
self.module.fail_json(msg='The IPv4 gateway "{0}" can not be specified for IPv6 DNS server "{1}".'.format(address, dns_addresses[idx]))
elif self.pfsense.find_gateway_elt(address, protocol='inet6') is not None:
if ip_types[idx] == 4:
self.module.fail_json(msg='The IPv6 gateway "{0}" can not be specified for IPv4 DNS server "{1}".'.format(address, dns_addresses[idx]))
else:
self.module.fail_json(msg='The gateway "{0}" does not exist.'.format(address))
if self.pfsense.is_within_local_networks(dns_addresses[idx]):
self.module.fail_json(
msg="A gateway can not be assigned to DNS '{0}' server which is on a directly connected network.".format(dns_addresses[idx])
)
def _validate_timezone(self, timezone):
""" check timezone """
path = '/usr/share/zoneinfo/'
if not isfile(path + timezone) or timezone[:1] < 'A' or timezone[:1] > 'Z':
self.module.fail_json(msg='The submitted timezone is invalid')
##############################
# XML processing
#
def _get_params_to_remove(self):
""" returns the list of params to remove if they are not set """
to_remove = super(PFSenseSetupModule, self)._get_params_to_remove()
to_remove.extend(self.params_to_delete)
return to_remove
##############################
# run
#
def _update(self):
""" make the target pfsense reload """
for cmd in self.route_cmds:
self.module.run_command(cmd)
cmd = '''
require_once("auth.inc");
require_once("filter.inc");
require_once("pfsense-utils.inc");
require_once("system_advanced_admin.inc");
$retval = 0;
$retval |= system_hostname_configure();
$retval |= system_hosts_generate();
$retval |= system_resolvconf_generate();
if (config_path_enabled('dnsmasq')) {
$retval |= services_dnsmasq_configure();
} elseif (config_path_enabled('unbound')) {
$retval |= services_unbound_configure();
}
$retval |= system_timezone_configure();
$retval |= system_ntp_configure();
load_crypto();
load_thermal_hardware();'''
if self.params.get('dnsallowoverride') is not None:
if (self.params['dnsallowoverride'] and 'dnsallowoverride' not in self.diff['before'] or
not self.params['dnsallowoverride'] and 'dnsallowoverride' in self.diff['before']):
cmd += '$retval |= send_event("service reload dns");\n'
if self.params.get('shellauth') is not None:
cmd += '$retval |= set_pam_auth();'
cmd += '$retval |= filter_configure();\n'
restart_sshguard = False
for param in ['sshguard_whitelist']:
if self.obj.get(param) != self.diff['before'].get(param):
restart_sshguard = True
if restart_sshguard:
cmd += 'system_sshguard_stop();$retval |= system_syslogd_start(true);\n'
restart_webgui = False
for param in ['ssl-certref']:
if self.obj['webgui'].get(param) != self.diff['before']['webgui'].get(param):
restart_webgui = True
if restart_webgui:
cmd += 'restart_GUI();\n'
self.result['cmd'] = cmd
return self.pfsense.phpshell(cmd)
##############################
# Logging
#
@staticmethod
def _get_obj_name():
""" return obj's name """
return "general"
def _log_fields(self, before=None):
""" generate pseudo-CLI command fields parameters to create an obj """
bwebgui = self.diff['before']['webgui']
webgui = self.obj['webgui']
obj_before = self._prepare_dns_log(self.diff['before'])
obj_after = self._prepare_dns_log(self.obj)
values = ''
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'hostname', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'domain', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(obj_after, obj_before, 'dns_addresses', add_comma=(values), log_none=True)
values += self.format_updated_cli_field(obj_after, obj_before, 'dns_hostnames', add_comma=(values), log_none=True)
values += self.format_updated_cli_field(obj_after, obj_before, 'dns_gateways', add_comma=(values), log_none=True)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'timezone', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'timeservers', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'language', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'dnsallowoverride', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(self.obj, self.diff['before'], 'dnslocalhost', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(obj_after, obj_before, 'webguicert', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(obj_after, obj_before, 'webguicss', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'webguifixedmenu', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'webguihostnamemenu', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'dashboardcolumns', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'interfacessort', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'dashboardavailablewidgetspanel', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'systemlogsfilterpanel', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'systemlogsmanagelogpanel', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'statusmonitoringsettingspanel', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'requirestatefilter', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'webguileftcolumnhyper', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'disablealiaspopupdetail', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'roworderdragging', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'logincss', add_comma=(values), log_none=False)
values += self.format_updated_cli_field(webgui, bwebgui, 'loginshowhost', fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
return values
@staticmethod
def _prepare_dns_log(obj):
""" construct dict for logging """
ret = dict()
webgui = obj['webgui']
ret['webguicss'] = webgui['webguicss'].replace('.css', '') if 'webguicss' in webgui else None
if 'dnsserver' in obj:
ret['dns_addresses'] = ' '.join(obj['dnsserver'])
else:
ret['dns_addresses'] = None
ret['dns_hostnames'] = None
ret['dns_gateways'] = None
idx = 1
hosts = list()
gateways = list()
while True:
host = 'dns{0}host'.format(idx)
gateway = 'dns{0}gw'.format(idx)
if host not in obj or gateway not in obj:
break
hosts.append(obj[host] if obj[host] != '' else 'none')
gateways.append(obj[gateway] if obj[gateway] != '' else 'none')
idx += 1
# we have multiple string that can give the same configuration
# we remove the ending nones (assuming the user won't specify them for nothing)
while True:
if len(hosts) and hosts[-1] == 'none':
hosts.pop()
continue
if len(gateways) and gateways[-1] == 'none':
gateways.pop()
continue
break
if len(hosts):
ret['dns_hostnames'] = ' '.join(hosts)
if len(gateways):
ret['dns_gateways'] = ' '.join(gateways)
return ret
def main():
module = AnsibleModule(
argument_spec=SETUP_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseSetupModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_shellcmd.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: pfsense_shellcmd
short_description: Manage pfSense shellcmds
version_added: "0.7.0"
description:
- Manage pfSense shellcmds. This requires the pfSense shellcmd package to be installed.
options:
description:
description: The description of the shellcmd.
required: true
type: str
state:
description: State in which to leave the shellcmd.
default: present
choices: ['present', 'absent']
type: str
cmd:
description: The command to run.
type: str
cmdtype:
description: Type of the shell command, defaults to `shellcmd`. There can only be one `afterfilterchangeshellcmd` command. If there is an existing one, it
will be replaced.
choices: ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled']
type: str
author: Orion Poplawski (@opoplawski)
'''
EXAMPLES = r'''
- name: Add myitem shellcmd
pfsensible.core.pfsense_shellcmd:
description: myitem
cmd: echo hi
cmdtype: shellcmd
state: present
- name: Remove myitem shellcmd
pfsensible.core.pfsense_shellcmd:
description: myitem
state: absent
'''
RETURN = r'''
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create shellcmd 'myitem'", "update shellcmd 'myitem' set ...", "delete shellcmd 'myitem'"]
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
# Compact style
SHELLCMD_ARGUMENT_SPEC = dict(
# Only description should be required here - othewise you cannot remove an item with just 'description'
# Required arguments for creation should be noted in SHELLCMD_REQUIRED_IF = ['state', 'present', ...] below
description=dict(required=True, type='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
cmd=dict(type='str'),
cmdtype=dict(type='str', choices=['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'],),
)
SHELLCMD_REQUIRED_IF = [
['state', 'present', ['cmd']],
]
# default values when creating a new shellcmd
SHELLCMD_CREATE_DEFAULT = dict(
cmdtype='shellcmd',
)
SHELLCMD_PHP_COMMAND_SET = r'''
require_once("shellcmd.inc");
shellcmd_sync_package();
'''
class PFSenseShellcmdModule(PFSenseModuleBase):
""" module managing pfsense shellcmds """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return SHELLCMD_ARGUMENT_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseShellcmdModule, self).__init__(module, pfsense, package='Shellcmd', root='shellcmdsettings', node='config', key='description',
update_php=SHELLCMD_PHP_COMMAND_SET, create_default=SHELLCMD_CREATE_DEFAULT)
##############################
# XML processing
#
def _find_target(self):
""" find the XML target_elt """
# There can be only one 'afterfilterchangeshellcmd' shellcmd
if self.params['cmdtype'] == 'afterfilterchangeshellcmd':
result = self.root_elt.findall("{node}[{key}='{value}']".format(node=self.node, key='cmdtype', value='afterfilterchangeshellcmd'))
else:
result = self.root_elt.findall("{node}[{key}='{value}']".format(node=self.node, key=self.key, value=self.obj[self.key]))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.module.fail_json(msg='Found multiple {node}s for {key} {value}.'.format(node=self.node, key=self.key, value=self.obj[self.key]))
else:
return None
def main():
module = AnsibleModule(
argument_spec=SHELLCMD_ARGUMENT_SPEC,
required_if=SHELLCMD_REQUIRED_IF,
supports_check_mode=True)
pfmodule = PFSenseShellcmdModule(module)
# Pass params for testing framework
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_user.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019-2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: pfsense_user
version_added: 0.1.0
short_description: Manage pfSense users
description:
>
Manage pfSense users
author: Orion Poplawski (@opoplawski)
notes:
options:
name:
description: The name of the user.
required: true
type: str
state:
description: State in which to leave the user.
default: present
choices: [ "present", "absent" ]
type: str
descr:
description: Description of the user.
type: str
scope:
description: Scope of the user ('user' is a normal user, use 'system' for 'admin' user). Defaults to `user`.
choices: [ "user", "system" ]
type: str
uid:
description:
- UID of the user.
- Will use next available UID if not specified.
type: str
groups:
description: Groups of the user.
type: list
elements: str
password:
description: bcrypt encrypted password of the user.
type: str
priv:
description:
- A list of privileges to assign.
- Allowed values include page-all, user-shell-access.
type: list
elements: str
authorizedkeys:
description: Authorized SSH Keys of the user. Can be base64 encoded.
type: str
disabled:
description: Disables the user, so that they cannot login.
default: false
type: bool
version_added: 0.7.1
'''
EXAMPLES = r'''
- name: Add operator user
pfsense_user:
name: operator
descr: Operator
scope: user
groups: [ 'Operators' ]
priv: [ 'page-all', 'user-shell-access' ]
- name: Remove user
pfsense_user:
name: operator
state: absent
'''
RETURN = r'''
'''
import base64
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
USER_ARGUMENT_SPEC = dict(
name=dict(required=True, type='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
descr=dict(type='str'),
scope=dict(type='str', choices=['user', 'system']),
uid=dict(type='str'),
password=dict(type='str', no_log=True),
groups=dict(type='list', elements='str'),
priv=dict(type='list', elements='str'),
authorizedkeys=dict(type='str'),
disabled=dict(type='bool', default=False),
)
USER_CREATE_DEFAULT = dict(
scope='user',
)
USER_MAP_PARAM = [
('password', 'bcrypt-hash'),
]
def parse_groups(self, name, params, obj):
# Groups are not stored in the user object
if params[name] is not None:
self.user_groups = params[name]
def p2o_ssh_pub_key(self, name, params, obj):
# Allow ssh keys to be clear or base64 encoded
if params[name] is not None and 'ssh-' in params[name]:
obj[name] = base64.b64encode(params[name].encode()).decode()
def validate_password(self, password):
if not re.match(r'\$2[aby]\$', str(password)):
raise ValueError('Password (%s) does not appear to be a bcrypt hash' % (password))
USER_ARG_ROUTE = dict(
authorizedkeys=dict(parse=p2o_ssh_pub_key),
groups=dict(parse=parse_groups),
password=dict(validate=validate_password),
)
USER_PHP_COMMAND_PREFIX = """
require_once('auth.inc');
$groupindex = index_groups();
$group_config = config_get_path('system/group');
"""
USER_PHP_COMMAND_SET = USER_PHP_COMMAND_PREFIX + """
$userent = config_get_path('system/user')[{idx}];
local_user_set($userent);
foreach ({mod_groups} as $groupname) {{
$group = $group_config[$groupindex[$groupname]];
local_group_set($group);
}}
if (is_dir("/etc/inc/privhooks")) {{
run_plugins("/etc/inc/privhooks");
}}
"""
# This runs after we remove the group from the config so we can't use $config
USER_PHP_COMMAND_DEL = USER_PHP_COMMAND_PREFIX + """
$userent['name'] = '{name}';
$userent['uid'] = {uid};
foreach ({mod_groups} as $groupname) {{
$group = $group_config[$groupindex[$groupname]];
local_group_set($group);
}}
local_user_del($userent);
"""
class PFSenseUserModule(PFSenseModuleBase):
""" module managing pfsense users """
##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return USER_ARGUMENT_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseUserModule, self).__init__(module, pfsense, root='system', node='user', key='name',
arg_route=USER_ARG_ROUTE, map_param=USER_MAP_PARAM, create_default=USER_CREATE_DEFAULT)
self.groups = self.root_elt.findall('group')
self.user_groups = None
self.mod_groups = []
##############################
# XML processing
#
def _find_group_elt(self, name):
return self.pfsense.find_elt('group', name, search_field='name', root_elt=self.root_elt)
def _find_group_names_for_uid(self, uid):
groups = []
for group_elt in self.pfsense.find_elt("group", uid, search_field="member", root_elt=self.root_elt, multiple_ok=True):
groups.append(group_elt.find('name').text)
return groups
def _nextuid(self):
nextuid_elt = self.root_elt.find('nextuid')
nextuid = nextuid_elt.text
nextuid_elt.text = str(int(nextuid) + 1)
return nextuid
def _format_diff_priv(self, priv):
if isinstance(priv, str):
return [priv]
else:
return priv
def _copy_and_add_target(self):
""" populate the XML target_elt """
obj = self.obj
if 'bcrypt-hash' not in obj:
self.module.fail_json(msg='Password is required when adding a user')
if 'uid' not in obj:
obj['uid'] = self._nextuid()
self.diff['after'] = obj
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self._update_groups()
self.root_elt.insert(self._find_last_element_index(), self.target_elt)
# Reset users list
self.elements = self.root_elt.findall(self.node)
def _copy_and_update_target(self):
""" update the XML target_elt """
before = self.pfsense.element_to_dict(self.target_elt)
self.diff['before'] = before
if 'priv' in before:
before['priv'] = self._format_diff_priv(before['priv'])
changed = self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
if 'priv' in self.diff['after']:
self.diff['after']['priv'] = self._format_diff_priv(self.diff['after']['priv'])
if self._remove_deleted_disabled_param():
changed = True
if self._update_groups():
changed = True
return (before, changed)
def _update_groups(self):
user = self.obj
changed = False
# Only modify group membership is groups was specified
if self.user_groups is not None:
# Handle group member element - need uid set or retrieved above
uid = self.target_elt.find('uid').text
# Get current group membership
self.diff['before']['groups'] = self._find_group_names_for_uid(uid)
# Add user to groups if needed
for group in self.user_groups:
group_elt = self._find_group_elt(group)
if group_elt is None:
self.module.fail_json(msg='Group (%s) does not exist' % group)
if len(group_elt.findall("[member='{0}']".format(uid))) == 0:
changed = True
self.mod_groups.append(group)
group_elt.append(self.pfsense.new_element('member', uid))
# Remove user from groups if needed
for group in self.diff['before']['groups']:
if group not in self.user_groups:
group_elt = self._find_group_elt(group)
if group_elt is None:
self.module.fail_json(msg='Group (%s) does not exist' % group)
for member_elt in group_elt.findall('member'):
if member_elt.text == uid:
changed = True
self.mod_groups.append(group)
group_elt.remove(member_elt)
break
# Groups are not stored in the user element
self.diff['after']['groups'] = self.user_groups
# Decode keys for diff
for k in self.diff:
if 'authorizedkeys' in self.diff[k]:
self.diff[k]['authorizedkeys'] = base64.b64decode(self.diff[k]['authorizedkeys'])
return changed
def _remove_deleted_disabled_param(self):
""" Remove disabled param if user is re-enabled """
changed = False
if self.pfsense.remove_deleted_param_from_elt(self.target_elt, 'disabled', self.obj):
changed = True
return changed
##############################
# run
#
def _update(self):
if self.params['state'] == 'present':
return self.pfsense.phpshell(USER_PHP_COMMAND_SET.format(idx=self._find_this_element_index(), mod_groups=self.mod_groups))
else:
return self.pfsense.phpshell(USER_PHP_COMMAND_DEL.format(name=self.obj['name'], uid=self.obj['uid'], mod_groups=self.mod_groups))
def _pre_remove_target_elt(self):
self.diff['after'] = {}
if self.target_elt is not None:
self.diff['before'] = self.pfsense.element_to_dict(self.target_elt)
# Store uid for _update()
self.obj['uid'] = self.target_elt.find('uid').text
# Get current group membership
self.diff['before']['groups'] = self._find_group_names_for_uid(self.obj['uid'])
# Remove user from groups if needed
for group in self.diff['before']['groups']:
group_elt = self._find_group_elt(group)
if group_elt is None:
self.module.fail_json(msg='Group (%s) does not exist' % group)
for member_elt in group_elt.findall('member'):
if member_elt.text == self.obj['uid']:
self.mod_groups.append(group)
group_elt.remove(member_elt)
break
def main():
module = AnsibleModule(
argument_spec=USER_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseUserModule(module)
# Pass params for testing framework
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: plugins/modules/pfsense_vlan.py
================================================
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Orion Poplawski
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: pfsense_vlan
version_added: 0.1.0
author: Frederic Bor (@f-bor)
short_description: Manage pfSense VLANs
description:
- Manage pfSense VLANs
notes:
options:
vlan_id:
description: The VLAN tag. Must be between 1 and 4094.
required: true
type: int
interface:
description: The interface on which to declare the VLAN. Friendly name (assignments) can be used.
required: true
type: str
priority:
description: 802.1Q VLAN Priority code point. Must be between 0 and 7.
required: false
type: int
descr:
description: The description of the VLAN
default: ''
type: str
state:
description: State in which to leave the VLAN
choices: [ "present", "absent" ]
default: present
type: str
"""
EXAMPLES = """
- name: Add voice VLAN
pfsense_vlan:
interface: mvneta0
vlan_id: 100
descr: voice
priority: 5
state: present
- name: Remove voice VLAN
pfsense_vlan:
interface: mvneta0
vlan_id: 100
state: absent
"""
RETURN = """
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
returned: always
type: list
sample: ["create vlan 'mvneta.100', descr='voice', priority='5'", "update vlan 'mvneta.100', set priority='6'", "delete vlan 'mvneta.100'"]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.vlan import PFSenseVlanModule, VLAN_ARGUMENT_SPEC
def main():
module = AnsibleModule(
argument_spec=VLAN_ARGUMENT_SPEC,
supports_check_mode=True)
pfmodule = PFSenseVlanModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
================================================
FILE: setup.cfg
================================================
[pycodestyle]
ignore = E402,W503,W504,E741
max-line-length = 160
================================================
FILE: tests/plays/README.md
================================================
# Testing pfsensible/core with plays
You must checkout this repository into a path of the form ../ansible_collections/pfsensible/core/.
The following collection dependencies are needed:
* ansible.utils
You will need a fresh pfSense install available as `pfsense-test` or adjust the `hosts` file as needed.
You need to be able to ssh to it as `root` without a password or use `--ask-pass`, which you can use
the configure.yml play to do.
Update `host_vars/pfsense-test.yml` with IP addresses of your test pfSense install.
================================================
FILE: tests/plays/ansible.cfg
================================================
# config file for ansible -- https://ansible.com/
# ===============================================
[defaults]
inventory = hosts
collections_path = ../../../..
remote_user = root
================================================
FILE: tests/plays/host_vars/pfsense-test.yml
================================================
---
# IP address of the interfaces
# TODO - get this from ansible facts
# "ansible_vtnet0": {
# "device": "vtnet0",
# "ipv4": [ { "address": "192.168.122.128",
interface_ips:
wan: 192.168.122.228
================================================
FILE: tests/plays/hosts
================================================
[pfsense]
pfsense-test
================================================
FILE: tests/plays/openvpn.yml
================================================
---
- hosts: pfsense
vars:
openvpn_aliases:
TUNNEL_NET: 10.100.0.0/24
LOCAL_NETS: 10.10.0.0/24 10.11.0.0/24
tasks:
- name: OpenVPN CA
pfsensible.core.pfsense_ca:
name: OpenVPN CA
method: internal
state: present
register: openvpn
tags:
- openvpn
- openvpn_ca
- name: Generate new internal certificate
pfsensible.core.pfsense_cert:
method: internal
name: pfsense-test
ca: OpenVPN CA
keytype: RSA
keylen: 2048
lifetime: 3650
dn_country: US
certtype: server
state: present
tags: openvpn_cert
- name: Setup RADIUS
pfsensible.core.pfsense_authserver_radius:
name: RADIUS
protocol: MSCHAPv2
host: radius.example.com
secret: item_secret
- name: Setup LDAP
pfsensible.core.pfsense_authserver_ldap:
name: ASLDAP
host: ldap.example.com
transport: tcp
authcn: CN=Users
scope: one
- set_fact:
openvpn_psk: |-
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
f896b014f220bcf9a3023b5b68a5cd88
62421f044956dad4f94264211b121bcf
7e2f5f82e11964575a3f39af8c196931
dd63f3ff13615363257bcaa4e46b60cd
93a2a73027575d0cc2ed83927af11b9f
1122b6acdab05bb7c9de36851470ee2b
3d160a0ee03e3f31d32ac018a602916b
c8db1791029a5ab1ffd7d93ff5a91b0a
46050a804ff7207d46f4f61d33d09e79
56cd4c6748e5e5f1236f7a6770954303
1ef9b2154f2f3b22a5eb34079f4c1872
4dee88ca57ff95da93642f8e59c1bc40
d9793cdff43848960625f3d335264f72
1e6c2fdd02f16e2b95b1cde182f7099b
c32e314105631627e15e113885240ab1
199fbbf0ed739df6ad3617691531de43
-----END OpenVPN Static key V1-----
tags: always
- name: Define tunnel network alias
pfsensible.core.pfsense_alias:
name: TUNNEL_NET
address: "{{ openvpn_aliases['TUNNEL_NET'] }}"
type: network
tags: openvpn
- name: Define local network alias
pfsensible.core.pfsense_alias:
name: LOCAL_NETS
address: "{{ openvpn_aliases['LOCAL_NETS'] }}"
type: network
tags: openvpn
- name: Create OpenVPN Server 1
import_tasks: tasks/test_openvpn_server_create.yml
vars:
openvpn_server_args:
name: OpenVPN Server 1
mode: server_tls_user
authmode:
- RADIUS
interface: wan
local_port: 1194
tls: "{{ openvpn_psk }}"
tls_type: auth
ca: OpenVPN CA
cert: pfsense-test
data_ciphers:
- AES-256-GCM
- AES-128-GCM
- AES-256-CBC
tunnel_network: TUNNEL_NET
local_network: LOCAL_NETS
compression: ""
gwredir: yes
passtos: yes
dns_domain: example.com
dns_server1: 10.10.10.10
dns_server2: 10.10.10.11
custom_options: |-
tls-version-min 1.2;
username_as_common_name: no
openvpn_server_vpnid: 1
tags: openvpn
- name: Create OpenVPN Server 2
import_tasks: tasks/test_openvpn_server_create.yml
vars:
openvpn_server_args:
name: OpenVPN Server 2
mode: server_tls_user
authmode:
- RADIUS
interface: any
local_port: 1195
ca: OpenVPN CA
cert: pfsense-test
crl: OpenVPN CA CRL
data_ciphers:
- AES-256-GCM
- AES-128-GCM
- AES-256-CBC
digest: SHA256 (256-bit)
tunnel_network: 10.100.0.0/24
compression: ""
gwredir: no
passtos: no
dns_domain: example.com
dns_server1: 10.10.10.10
dns_server2: 10.10.10.11
custom_options: |-
server 10.100.1.0 255.255.255.0 'nopool';
ifconfig-pool 10.110.1.2 10.110.1.62;
tls-export-cert /tmp;
tls-version-min 1.2;
# Use manual vs redirect gateway above to add block-local
push "redirect-gateway def1 block-local";
push "block-outside-dns";
push "dhcp-option DOMAIN example.com";
username_as_common_name: no
openvpn_server_vpnid: 2
tags: openvpn
- name: Create OpenVPN Server 3
import_tasks: tasks/test_openvpn_server_create.yml
vars:
openvpn_server_args:
name: OpenVPN Server 3
mode: p2p_shared_key
interface: wan
local_port: 1196
shared_key: "{{ openvpn_psk }}"
tunnel_network: 10.1.0.1/28
remote_network: 10.20.0.0/24
compression: ""
digest: RIPEMD160
passtos: yes
custom_options: ping-restart 0
verbosity_level: 3
openvpn_server_vpnid: 3
tags:
- openvpn
- openvpn_psk
- name: Create OpenVPN Server generate
import_tasks: tasks/test_openvpn_server_create.yml
vars:
openvpn_server_args:
name: OpenVPN Server generate
mode: server_tls_user
authmode:
- RADIUS
interface: wan
local_port: 1197
tls: generate
tls_type: auth
ca: OpenVPN CA
cert: pfsense-test
data_ciphers:
- AES-256-GCM
- AES-128-GCM
- AES-256-CBC
tunnel_network: 10.100.1.0/24
compression: ""
gwredir: yes
passtos: yes
dns_domain: example.com
dns_server1: 10.10.10.10
dns_server2: 10.10.10.11
custom_options: |-
tls-version-min 1.2;
username_as_common_name: no
openvpn_server_vpnid: 4
tags:
- openvpn
- openvpn_generate
- name: Create OpenVPN override vpnuser
import_tasks: tasks/test_openvpn_override_create.yml
vars:
openvpn_override_args:
name: vpnuser
server_list:
- OpenVPN Server 1
custom_options: ifconfig-push 10.100.0.100 255.255.255.0
state: present
openvpn_override_vpnids:
- 1
tags:
- openvpn
- openvpn_override
- name: Create VPN1 interface
import_tasks: tasks/test_interface_create.yml
vars:
interface_args:
interface: ovpns1
descr: VPN1
enable: yes
interface_ifname: opt1
tags:
- openvpn
- openvpn_interface
- name: Create VPN2 interface
import_tasks: tasks/test_interface_create.yml
vars:
interface_args:
interface: ovpns2
descr: VPN2
enable: yes
interface_ifname: opt2
tags:
- openvpn
- openvpn_interface
- name: Create VPN3 interface
import_tasks: tasks/test_interface_create.yml
vars:
interface_args:
interface: ovpns3
descr: VPN3
enable: yes
interface_ifname: opt3
tags:
- openvpn
- openvpn_interface
- set_fact:
ifname_map:
opt1: ovpns1
opt2: ovpns2
opt3: ovpns3
tags:
- openvpn
- openvpn_interface
- openvpn_interface_group
- name: Create VPN interface group
import_tasks: tasks/test_interface_group_create.yml
vars:
interface_group_args:
name: VPN
members:
- VPN1
- VPN2
- VPN3
interface_group_member_ifnames:
- opt1
- opt2
- opt3
tags:
- openvpn
- openvpn_interface
- name: Delete OpenVPN override vpnuser
import_tasks: tasks/test_openvpn_override_delete.yml
vars:
openvpn_override_args:
name: vpnuser
tags:
- openvpn
- openvpn_override
- name: Delete VPN1 interfce (fails)
pfsensible.core.pfsense_interface:
descr: VPN1
state: absent
register: interface
failed_when: interface.msg != "The interface is part of the group VPN. Please remove it from the group first."
tags:
- openvpn
- openvpn_interface_delete
- name: Delete OpenVPN Server 1 (fails)
pfsensible.core.pfsense_openvpn_server:
name: OpenVPN Server 1
state: absent
tags:
- openvpn
- openvpn_delete
register: openvpn_server
failed_when: openvpn_server.msg != "Cannot delete the OpenVPN instance while the interface ovpns1 is assigned. Remove the interface assignment first."
- name: Delete VPN interface_group
pfsensible.core.pfsense_interface_group:
name: VPN
state: absent
tags:
- openvpn
- openvpn_interface_delete
- name: Delete VPN1 interfce
pfsensible.core.pfsense_interface:
descr: VPN1
state: absent
register: interface
tags:
- openvpn
- openvpn_interface_delete
- name: Delete OpenVPN Server 1
import_tasks: tasks/test_openvpn_server_delete.yml
vars:
openvpn_server_args:
name: OpenVPN Server 1
openvpn_server_vpnid: 1
tags:
- openvpn
- openvpn_delete
- name: Delete VPN2 interfce
pfsensible.core.pfsense_interface:
descr: VPN2
state: absent
register: interface
tags:
- openvpn
- openvpn_interface_delete
- name: Delete OpenVPN Server 2
import_tasks: tasks/test_openvpn_server_delete.yml
vars:
openvpn_server_args:
name: OpenVPN Server 2
openvpn_server_vpnid: 2
tags:
- openvpn
- openvpn_delete
- name: Delete VPN3 interfce
pfsensible.core.pfsense_interface:
descr: VPN3
state: absent
register: interface
tags:
- openvpn
- openvpn_interface_delete
- name: Delete OpenVPN Server 3
import_tasks: tasks/test_openvpn_server_delete.yml
vars:
openvpn_server_args:
name: OpenVPN Server 3
openvpn_server_vpnid: 3
tags:
- openvpn
- openvpn_delete
- name: Delete OpenVPN Server generate
import_tasks: tasks/test_openvpn_server_delete.yml
vars:
openvpn_server_args:
name: OpenVPN Server generate
openvpn_server_vpnid: 4
tags:
- openvpn
- openvpn_delete
================================================
FILE: tests/plays/tasks/test_interface_create.yml
================================================
---
- name: "Define interface {{ interface_args.descr }}"
pfsensible.core.pfsense_interface: "{{ interface_args }}"
register: interface
- fail:
msg: Interface ifname {{ interface.ifname }} does not match expected value {{ interface_ifname }}
when: interface.ifname != interface_ifname
- name: Get interface configuration for {{ interface_args.interface }}
command: /sbin/ifconfig {{ interface_args.interface }}
changed_when: no
register: ifconfig
- name: Get interface description
set_fact:
if_description: "{{ ifconfig.stdout_lines | select('search', 'description:') | map('regex_replace', '^\\s*description:\\s*', '') | first }}"
- fail:
msg: "Unexpected interface description found: {{ if_description }} != {{ interface_args.descr }}"
when: if_description != interface_args.descr
================================================
FILE: tests/plays/tasks/test_interface_group_create.yml
================================================
---
- name: "Define interface group {{ interface_group_args.name }}"
pfsensible.core.pfsense_interface_group: "{{ interface_group_args }}"
register: interface_group
- fail:
msg: Interface group member ifnames {{ interface_group.member_ifnames|join(',') }} does not match expected value {{ interface_groug_member_ifnames|join(',') }}
when: interface_group.member_ifnames | difference(interface_group_member_ifnames) | length > 1
- include_tasks: tasks/test_interface_group_ifconfig_groups.yml
vars:
name: "{{ interface_group_args.name }}"
loop_control:
loop_var: ifname
loop: "{{ interface_group.member_ifnames }}"
================================================
FILE: tests/plays/tasks/test_interface_group_ifconfig_groups.yml
================================================
---
- command: /sbin/ifconfig {{ ifname_map[ifname] }}
changed_when: no
register: ifconfig
- set_fact:
if_groups_line: "{{ ifconfig.stdout_lines | select('search', 'groups:') | map('regex_replace', '^\\s*groups:\\s*', '') | first }}"
- set_fact:
if_groups: "{{ if_groups_line.split(' ') }}"
- fail:
msg: "Group name {{ name }} not found in {{ ifname_map[ifname] }} groups: {{ if_groups | join(' ') }}"
when: name is not in if_groups
================================================
FILE: tests/plays/tasks/test_openvpn_override_create.yml
================================================
---
- name: "Define openvpn override {{ openvpn_override_args.name }}"
pfsensible.core.pfsense_openvpn_override: "{{ openvpn_override_args }}"
register: openvpn_override
tags: openvpn
- fail:
msg: OpenVPN override vpnids {{ openvpn_override.vpnids|join(',') }} does not match expected value {{ openvpn_override_vpnids|join(',') }}
when: openvpn_override.vpnids | difference(openvpn_override_vpnids) | length > 1
- include_tasks: tasks/test_openvpn_override_file_exists.yml
loop_control:
loop_var: vpnid
loop: "{{ openvpn_override.vpnids }}"
- set_fact:
expected_csc_files: "{{ openvpn_override.vpnids | map('regex_replace', '(.+)', '/var/etc/openvpn/server\\1/csc/' + openvpn_override_args.name) | list }}"
- find:
paths: /var/etc/openvpn
patterns: csc
recurse: yes
depth: 2
file_type: directory
register: csc_dirs
- find:
paths: "{{ csc_dirs.files | map(attribute='path') | list }}"
patterns: "{{ openvpn_override_args.name }}"
file_type: file
register: csc_files
- fail:
msg: "Unexpected override files found: {{ csc_files.files | map(attribute='path') | difference(expected_csc_files) | join(',') }}"
when: "csc_files.files | map(attribute='path') | difference(expected_csc_files) | length > 0"
================================================
FILE: tests/plays/tasks/test_openvpn_override_delete.yml
================================================
---
- name: "Remove openvpn override {{ openvpn_override_args.name }}"
pfsensible.core.pfsense_openvpn_override:
name: "{{ openvpn_override_args.name }}"
state: absent
register: openvpn_override
tags: openvpn
- find:
paths: /var/etc/openvpn
patterns: csc
recurse: yes
depth: 2
file_type: directory
register: csc_dirs
- find:
paths: "{{ csc_dirs.files | map(attribute='path') | list }}"
patterns: "{{ openvpn_override_args.name }}"
file_type: file
register: csc_files
- fail:
msg: "Unexpected override files found: {{ csc_files.files | map(attribute='path') | join(',') }}"
when: "(csc_files.files | map(attribute='path') | list | length) > 0"
================================================
FILE: tests/plays/tasks/test_openvpn_override_file_exists.yml
================================================
---
- wait_for:
path: "/var/etc/openvpn/server{{ vpnid }}/csc/{{ openvpn_override_args.name }}"
- slurp:
src: "/var/etc/openvpn/server{{ vpnid }}/csc/{{ openvpn_override_args.name }}"
register: openvpn_config_file
- debug: msg="{{ openvpn_config_file['content'] | b64decode }}"
- template:
src: openvpn-override.j2
dest: /var/etc/openvpn/server{{ vpnid }}/csc/{{ openvpn_override_args.name }}
owner: nobody
group: nobody
mode: 0644
check_mode: yes
register: config
- fail:
msg: OpenVPN config files differ
when: config.changed
================================================
FILE: tests/plays/tasks/test_openvpn_server_create.yml
================================================
---
- name: "Define openvpn server {{ openvpn_server_args.name }}"
pfsensible.core.pfsense_openvpn_server: "{{ openvpn_server_args }}"
register: openvpn_server
tags: openvpn
- fail:
msg: OpenVPN server vpnid {{ openvpn_server.vpnid }} does not match expected value {{ openvpn_server_vpnid }}
when: openvpn_server.vpnid != openvpn_server_vpnid
- wait_for:
path: "/var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn"
- name: Retrieve config.ovpn
slurp:
src: "/var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn"
register: openvpn_config_file
- name: Contents of config.ovpn
debug: msg="{{ openvpn_config_file['content'] | b64decode }}"
- name: Check if config.ovpn matches expected content
template:
src: openvpn-server-config.ovpn.j2
dest: /var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn
owner: root
group: wheel
mode: 0600
check_mode: true
diff: true
register: config
- fail:
msg: OpenVPN config files differ
when: config.changed
# TODO - Use community.general.pids with pattern (need version 3.0.0)
- name: Check if openvpn server is running
shell: "ps xo command | grep '/openvpn --config /var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn' | grep -v grep"
register: openvpn_server_process
ignore_errors: true
changed_when: false
- fail:
msg: OpenVPN server process is not running
when:
- openvpn_server_args.disable is not defined or not openvpn_server_args.disable
- openvpn_server_process.stdout_lines | length != 1
- fail:
msg: OpenVPN server process is running
when:
- openvpn_server_args.disable is defined and openvpn_server_args.disable
- openvpn_server_process.stdout_lines | length != 0
================================================
FILE: tests/plays/tasks/test_openvpn_server_delete.yml
================================================
---
- name: "Remove openvpn server {{ openvpn_server_args.name }}"
pfsensible.core.pfsense_openvpn_server:
name: "{{ openvpn_server_args.name }}"
state: absent
register: openvpn_server
- fail:
msg: OpenVPN server vpnid {{ openvpn_server.vpnid }} does not match expected value {{ openvpn_server_vpnid }}
when: openvpn_server.vpnid != openvpn_server_vpnid
- name: Wait for config.ovpn to be removed
wait_for:
path: "/var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn"
state: absent
# TODO - Use community.general.pids with pattern (need version 3.0.0)
- name: Check for running openvpn server
shell: "ps xo command | grep '/openvpn --config /var/etc/openvpn/server{{ openvpn_server.vpnid }}/config.ovpn' | grep -v grep"
ignore_errors: yes
register: openvpn_server_process
changed_when: no
- fail:
msg: OpenVPN server process is still running
when: openvpn_server_process.stdout_lines | length != 0
================================================
FILE: tests/plays/templates/openvpn-override.j2
================================================
{% if openvpn_override_args.tunnel_network is defined %}
ifconfig {{ openvpn_override_args.tunnel_network | ansible.utils.nthhost(1) }} {{ openvpn_override_args.tunnel_network | ansible.utils.nthhost(2) }}
{% endif %}
{% if openvpn_override_args.remote_network is defined %}
route {{ openvpn_override_args.remote_network | ansible.utils.ipaddr('network') }} {{ openvpn_override_args.remote_network | ansible.utils.ipaddr('netmask') }}
{% endif %}
{% if openvpn_override_args.gwredir is defined and openvpn_override_args.gwredir %}
push "redirect-gateway def1"
{% endif %}
{% if openvpn_override_args.custom_options is defined %}
{{ openvpn_override_args.custom_options | replace(';','\n') }}
{% endif %}
================================================
FILE: tests/plays/templates/openvpn-server-config.ovpn.j2
================================================
dev ovpns{{ openvpn_server.vpnid }}
verb {{ openvpn_server_args.verbosity_level if openvpn_server_args.verbosity_level is defined else '1' }}
dev-type tun
dev-node /dev/tun{{ openvpn_server.vpnid }}
writepid /var/run/openvpn_server{{ openvpn_server.vpnid }}.pid
#user nobody
#group nobody
script-security 3
daemon
keepalive 10 60
ping-timer-rem
persist-tun
persist-key
proto udp4
auth {{ openvpn_server_args.digest | default('SHA256') | regex_replace(' \(.*\)', '') }}
up /usr/local/sbin/ovpn-linkup
down /usr/local/sbin/ovpn-linkdown
{% if 'user' in openvpn_server_args.mode %}
client-connect /usr/local/sbin/openvpn.attributes.sh
client-disconnect /usr/local/sbin/openvpn.attributes.sh
{% endif %}
{% if openvpn_server_args.interface == 'any' %}
multihome
{% else %}
local {{ interface_ips[openvpn_server_args.interface] }}
{% endif %}
{% if 'tls' in openvpn_server_args.mode %}
tls-server
{% endif %}
{% if openvpn_server_args.tunnel_network in openvpn_aliases %}
{% set tunnel_network = openvpn_aliases[openvpn_server_args.tunnel_network] %}
{% else %}
{% set tunnel_network = openvpn_server_args.tunnel_network %}
{% endif %}
{% if 'p2p' in openvpn_server_args.mode %}
ifconfig {{ tunnel_network | ansible.utils.nthhost(1) }} {{ tunnel_network | ansible.utils.nthhost(2) }}
{% else %}
server {{ tunnel_network | ansible.utils.ipaddr('network') }} {{ tunnel_network | ansible.utils.ipaddr('netmask') }}
{% endif %}
{% if 'user' in openvpn_server_args.mode %}
client-config-dir /var/etc/openvpn/server{{ openvpn_server.vpnid }}/csc
{% endif %}
{% if openvpn_server_args.username_as_common_name is defined and openvpn_server_args.username_as_common_name %}
username-as-common-name
{% endif %}
{% if 'user' in openvpn_server_args.mode %}
plugin /usr/local/lib/openvpn/plugins/openvpn-plugin-auth-script.so /usr/local/sbin/ovpn_auth_verify_async user {{ openvpn_server_args.authmode | join(',') | b64encode }} false server{{ openvpn_server.vpnid }} {{ openvpn_server_args.local_port }}
{% endif %}
{% if 'tls' in openvpn_server_args.mode %}
tls-verify "/usr/local/sbin/ovpn_auth_verify tls 'pfsense-test' 1"
{% endif %}
lport {{ openvpn_server_args.local_port }}
management /var/etc/openvpn/server{{ openvpn_server.vpnid }}/sock unix
{% if 'user' in openvpn_server_args.mode %}
push "dhcp-option DOMAIN {{ openvpn_server_args.dns_domain }}"
push "dhcp-option DNS {{ openvpn_server_args.dns_server1 }}"
push "dhcp-option DNS {{ openvpn_server_args.dns_server2 }}"
{% endif %}
{% if openvpn_server_args.remote_network is defined %}
route {{ openvpn_server_args.remote_network | ansible.utils.ipaddr('network') }} {{ openvpn_server_args.remote_network | ansible.utils.ipaddr('netmask') }}
{% endif %}
{% if 'shared_key' in openvpn_server_args.mode %}
secret /var/etc/openvpn/server{{ openvpn_server.vpnid }}/secret
{% endif %}
{% if openvpn_server_args.gwredir is defined and openvpn_server_args.gwredir %}
push "redirect-gateway def1"
{% endif %}
{% if 'tls' in openvpn_server_args.mode %}
capath /var/etc/openvpn/server{{ openvpn_server.vpnid }}/ca
cert /var/etc/openvpn/server{{ openvpn_server.vpnid }}/cert
key /var/etc/openvpn/server{{ openvpn_server.vpnid }}/key
dh /etc/dh-parameters.2048
{% if openvpn_server_args.tls is defined %}
tls-auth /var/etc/openvpn/server{{ openvpn_server.vpnid }}/tls-auth 0
{% endif %}
{% endif %}
{% if 'p2p' in openvpn_server_args.mode %}
cipher {{ openvpn_server_args.data_ciphers_fallback if openvpn_server_args.data_ciphers_fallback is defined else 'AES-256-CBC' }}
{% else %}
data-ciphers {{ openvpn_server_args.data_ciphers | join(':') if openvpn_server_args.data_ciphers is defined else 'AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-256-CBC' }}
data-ciphers-fallback {{ openvpn_server_args.data_ciphers_fallback if openvpn_server_args.data_ciphers_fallback is defined else 'AES-256-CBC' }}
{% endif %}
allow-compression no
{% if openvpn_server_args.passtos %}
passtos
{% endif %}
{% if 'user' in openvpn_server_args.mode %}
topology subnet
{% endif %}
{% if openvpn_server_args.custom_options is defined %}
{{ openvpn_server_args.custom_options | replace(';','\n') }}
{% endif %}
================================================
FILE: tests/sanity/ignore-2.14.txt
================================================
misc/pfsensible-generate-module shebang # This is not a module
misc/pfsense_module.py.j2 shebang # This is not a module
plugins/modules/pfsense_cert.py validate-modules:no-log-needed # Argument 'keylen' is not sensitive
plugins/modules/pfsense_dhcp_static.py validate-modules:no-log-needed # Arguments 'ddnsdomainkeyname' and 'ddnsdomainkeyalgorithm' are not sensitive
plugins/modules/pfsense_ipsec.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_ipsec_aggregate.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_user.py validate-modules:no-log-needed # Argument 'authorizedkeys' is not sensitive
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:E101 # inline noqa is not working
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:W191 # inline noqa is not working
================================================
FILE: tests/sanity/ignore-2.15.txt
================================================
misc/pfsensible-generate-module shebang # This is not a module
misc/pfsense_module.py.j2 shebang # This is not a module
plugins/modules/pfsense_cert.py validate-modules:no-log-needed # Argument 'keylen' is not sensitive
plugins/modules/pfsense_dhcp_static.py validate-modules:no-log-needed # Arguments 'ddnsdomainkeyname' and 'ddnsdomainkeyalgorithm' are not sensitive
plugins/modules/pfsense_ipsec.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_ipsec_aggregate.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_user.py validate-modules:no-log-needed # Argument 'authorizedkeys' is not sensitive
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:E101 # inline noqa is not working
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:W191 # inline noqa is not working
================================================
FILE: tests/sanity/ignore-2.16.txt
================================================
misc/pfsensible-generate-module shebang # This is not a module
misc/pfsense_module.py.j2 shebang # This is not a module
plugins/modules/pfsense_cert.py validate-modules:no-log-needed # Argument 'keylen' is not sensitive
plugins/modules/pfsense_dhcp_static.py validate-modules:no-log-needed # Arguments 'ddnsdomainkeyname' and 'ddnsdomainkeyalgorithm' are not sensitive
plugins/modules/pfsense_ipsec.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_ipsec_aggregate.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_user.py validate-modules:no-log-needed # Argument 'authorizedkeys' is not sensitive
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:E101 # inline noqa is not working
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:W191 # inline noqa is not working
================================================
FILE: tests/sanity/ignore-2.17.txt
================================================
misc/pfsensible-generate-module shebang # This is not a module
misc/pfsense_module.py.j2 shebang # This is not a module
plugins/modules/pfsense_cert.py validate-modules:no-log-needed # Argument 'keylen' is not sensitive
plugins/modules/pfsense_dhcp_static.py validate-modules:no-log-needed # Arguments 'ddnsdomainkeyname' and 'ddnsdomainkeyalgorithm' are not sensitive
plugins/modules/pfsense_ipsec.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_ipsec_aggregate.py validate-modules:no-log-needed # Argument 'rekey_time' is not sensitive
plugins/modules/pfsense_user.py validate-modules:no-log-needed # Argument 'authorizedkeys' is not sensitive
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:E101 # inline noqa is not working
tests/unit/plugins/modules/test_pfsense_dns_resolver.py pep8:W191 # inline noqa is not working
================================================
FILE: tests/unit/plugins/lookup/test_pfsense.py
================================================
# Copyright: (c) 2020, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from collections import OrderedDict
import yaml
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
from ansible.plugins.loader import lookup_loader
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import ModuleTestCase
def ordered_dump(data, dumper_cls=yaml.Dumper):
""" dump and return yaml string from data using ordered dicts """
class OrderedDumper(dumper_cls):
pass
def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
OrderedDumper.add_representer(OrderedDict, dict_representer)
return yaml.dump(data, Dumper=OrderedDumper)
class TestPFSenseLookup(ModuleTestCase):
##############################
# init
#
def __init__(self, *args, **kwargs):
super(TestPFSenseLookup, self).__init__(*args, **kwargs)
self.rules = None
self.definitions = None
self.interfaces = None
def setUp(self):
""" mocking up """
super(TestPFSenseLookup, self).setUp()
self.build_definitions()
self.mock_get_hostname = patch('ansible_collections.pfsensible.core.plugins.lookup.pfsense.LookupModule.get_hostname')
get_hostname = self.mock_get_hostname.start()
get_hostname.return_value = ('pf_test1')
self.mock_get_definitions = patch('ansible_collections.pfsensible.core.plugins.lookup.pfsense.LookupModule.get_definitions')
self.get_definitions = self.mock_get_definitions.start()
self.get_definitions.return_value = self.definitions
def tearDown(self):
""" mocking down """
super(TestPFSenseLookup, self).tearDown()
self.mock_get_hostname.stop()
self.mock_get_definitions.stop()
def build_definitions(self):
""" build definitions base for tests """
self.definitions = OrderedDict()
self.definitions['hosts_aliases'] = OrderedDict()
self.definitions['ports_aliases'] = OrderedDict()
self.definitions['rules'] = OrderedDict()
self.definitions['pfsenses'] = OrderedDict()
self.definitions['pfsenses']['pf_test1'] = OrderedDict()
self.definitions['pfsenses']['pf_test1'] = OrderedDict()
self.definitions['pfsenses']['pf_test1']['interfaces'] = OrderedDict()
self.interfaces = dict(
WAN=dict(remote_networks='0.0.0.0/0'),
LANA=dict(base='10.20.30.x', remote_base='10.120.x', adjacent_base='10.220.x'),
LANB=dict(base='10.20.40.x', remote_base='10.130.x', adjacent_base='10.230.x'),
)
for name, defs in self.interfaces.items():
self.definitions['pfsenses']['pf_test1']['interfaces'][name] = OrderedDict()
if 'base' in defs:
self.definitions['pfsenses']['pf_test1']['interfaces'][name]['ip'] = defs['base'].replace('x', '1/24')
for param in ['remote_networks', 'adjacent_networks']:
if param in defs:
self.definitions['pfsenses']['pf_test1']['interfaces'][name][param] = defs[param]
if 'remote_base' in defs:
self.definitions['pfsenses']['pf_test1']['interfaces'][name]['remote_networks'] = defs['remote_base'].replace('x', '0.0/16')
if 'adjacent_base' in defs:
self.definitions['pfsenses']['pf_test1']['interfaces'][name]['adjacent_networks'] = defs['adjacent_base'].replace('x', '0.0/16')
def save_definitions(self, filename='test_definitions.yml'):
""" save generated definitions to file for debbuging """
with open(filename, 'w') as outfile:
outfile.write(ordered_dump(self.definitions))
def run_rules(self):
""" run the plugin for rules """
pfsense_lookup = lookup_loader.get('pfsensible.core.pfsense')
self.rules = pfsense_lookup.run(['dummy.yml', 'rules'], {})[0]
def assert_get_rule(self, rule_name, count=1):
""" check that rule_name is defined """
rules = []
for rule in self.rules:
if rule['name'] == rule_name:
rules.append(rule)
if count == 1 and len(rules) == 0:
self.fail('{0} not found'.format(rule_name))
if count == 1 and len(rules) > 1:
self.fail('Multiples {0} found: {1}'.format(rule_name, rules))
self.assertEqual(len(rules), count)
if count == 1:
return rules[0]
return rules
def assert_rule_not_found(self, rule_name):
""" check that rule_name is not defined """
for rule in self.rules:
if rule['name'] == rule_name:
self.fail('{0} found'.format(rule_name))
@staticmethod
def add_missing_fields(expected_rule, rule):
""" add missing generated field with default values """
for param in ['ackqueue', 'gateway', 'icmptype', 'in_queue', 'out_queue', 'queue', 'log', 'sched']:
if param not in expected_rule and param in rule:
expected_rule[param] = None
if 'action' not in expected_rule:
expected_rule['action'] = 'pass'
if 'state' not in expected_rule:
expected_rule['state'] = 'present'
@staticmethod
def correct_aliases(expected_rule):
""" we correct IP values with interface names """
translations = {
'10.20.30.1': 'IP:LANA',
# '10.20.30.3': 'IP:LANB',
}
for field in ['source', 'destination']:
if expected_rule[field] in translations:
expected_rule[field] = translations[expected_rule[field]]
def compare_rules(self, expected_rule, rule):
""" compare rule with the expected result """
if 'after' in rule:
del rule['after']
self.add_missing_fields(expected_rule, rule)
self.correct_aliases(expected_rule)
self.assertEqual(expected_rule, rule)
def gen_rule(self, src, dst, interface, action):
""" generate rule definition according parameters """
rule = OrderedDict()
rule['protocol'] = 'any'
rule['name'] = src + '_' + dst + '_' + interface + '_' + action
if src == 'l':
rule['src'] = self.interfaces['LANA']['base'].replace('x', '2')
elif src == 's':
rule['src'] = self.interfaces['LANA']['base'].replace('x', '1')
elif src == 'r':
rule['src'] = self.interfaces['LANA']['remote_base'].replace('x', '30.30')
elif src == 'a':
rule['src'] = self.interfaces['LANA']['adjacent_base'].replace('x', '30.30')
if interface == 's':
if dst == 'l':
rule['dst'] = self.interfaces['LANA']['base'].replace('x', '3')
elif dst == 's':
rule['dst'] = self.interfaces['LANA']['base'].replace('x', '1')
elif dst == 'r':
rule['dst'] = self.interfaces['LANA']['remote_base'].replace('x', '30.40')
elif dst == 'a':
rule['dst'] = self.interfaces['LANA']['adjacent_base'].replace('x', '30.40')
else:
if dst == 'l':
rule['dst'] = self.interfaces['LANB']['base'].replace('x', '3')
elif dst == 's':
rule['dst'] = self.interfaces['LANB']['base'].replace('x', '1')
elif dst == 'r':
rule['dst'] = self.interfaces['LANB']['remote_base'].replace('x', '30.40')
elif dst == 'a':
rule['dst'] = self.interfaces['LANB']['adjacent_base'].replace('x', '30.40')
if action == 'p':
rule['action'] = 'pass'
elif action == 'dr':
rule['action'] = 'drop'
elif action == 'dn':
rule['action'] = 'deny'
return rule
def test_basic_generation(self):
""" test simple rules generatation for verifying that remote to remote rules are not generated and almost everything else is """
expected_rules = list()
not_expected_rules = list()
rules = self.definitions['rules']
# we want to generate some rules to check
# l => local, r => remote, a => adjacent, s => self
# s => same interface, o => other interface
# p => pass, dr => drop, dn => deny
for src in ['l', 'r', 'a', 's']:
for dst in ['l', 'r', 'a', 's']:
for interface in ['s', 'o']:
for action in ['p', 'dr', 'dn']:
rule = self.gen_rule(src, dst, interface, action)
rules[rule['name']] = rule
generated_rule = dict(
name=rule['name'],
interface='LANA',
source=rule['src'],
destination=rule['dst'],
protocol='any',
action=rule['action']
)
# we won't generate remote to remote rules or local to local on the same interface if the traffic is allowed
# when the traffic is denied or dropped, we consider for now that every rule should be generated, even if it's seems dumb
if rule['name'] in ['r_r_s_p', 'r_r_o_p', 'l_l_s_p']:
not_expected_rules.append(generated_rule)
else:
expected_rules.append(generated_rule)
del rule['name']
self.run_rules()
for expected_rule in expected_rules:
rule = self.assert_get_rule(expected_rule['name'])
self.compare_rules(expected_rule, rule)
for rule in not_expected_rules:
self.assert_rule_not_found(rule['name'])
================================================
FILE: tests/unit/plugins/module_utils/fixtures/pfsense_setup_config.xml
================================================
18.9
normal
pfSense
acme.com
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
2001::2001:22
64
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_1100
vmx1.1100
172.16.151.210
24
vmx4
vt2
dhcp
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1560930241
match
lan
inet
in
yes
yes
tcp
port_ssh
floating_rule_1
1560930241
match
lan,wan,opt3
inet
in
yes
yes
tcp
port_ssh
floating_rule_2
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
lan
2001::1
GW_LAN6
1
inet6
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx0
100
vmx0.100
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/module_utils/test_pfsense.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from ansible_collections.pfsensible.core.tests.unit.plugins.modules.pfsense_module import TestPFSenseModule
class TestPFSense(TestPFSenseModule):
def __init__(self, *args, **kwargs):
super(TestPFSense, self).__init__(*args, **kwargs)
def setUp(self):
super(TestPFSense, self).setUp()
self.pfsense = PFSenseModule(None)
self.mock_get_version = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.get_version', wraps=self.my_get_version)
self.get_version = self.mock_get_version.start()
def tearDown(self):
super(TestPFSense, self).tearDown()
self.mock_get_version.stop()
def my_get_version(self):
return self.version
def test_is_version(self):
self.pfsense.pfsense_version = None
self.version = '2.6.0'
assert self.pfsense.is_version([2, 5, 0])
assert self.pfsense.is_version([2, 6, 0])
assert not self.pfsense.is_version([2, 7, 0])
assert not self.pfsense.is_version([22, 2])
assert not self.pfsense.is_version([2, 5, 0], or_more=False)
assert not self.pfsense.is_version([21, 2])
self.pfsense.pfsense_version = None
self.version = '22.02'
assert not self.pfsense.is_version([2, 6, 0])
assert not self.pfsense.is_version([2, 7, 0])
assert self.pfsense.is_version([21, 1])
assert self.pfsense.is_version([21, 3])
assert self.pfsense.is_version([22, 2])
assert not self.pfsense.is_version([22, 7])
assert not self.pfsense.is_version([23, 1])
assert not self.pfsense.is_version([21, 2], or_more=False)
def test_is_at_least_2_5_0(self):
self.pfsense.pfsense_version = None
self.version = '2.6.0'
assert self.pfsense.is_at_least_2_5_0()
self.pfsense.pfsense_version = None
self.version = '22.01'
assert self.pfsense.is_at_least_2_5_0()
================================================
FILE: tests/unit/plugins/modules/__init__.py
================================================
================================================
FILE: tests/unit/plugins/modules/fixtures/2.4/pfsense_ipsec_aggregate_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
one_p2
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
another_p2
1
5db7be3c0502f
tunnel
3
network
1.2.3.4/24
24
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
third_p2
1
5db7be207c846
tunnel
4
lan
network
10.20.30.40
24
network
1.2.3.4
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
nat_p2
2
ikev2
opt3
1.2.3.6
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/2.4/pfsense_ipsec_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
2
ikev2
opt3
1.2.3.6
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
carp
wan
90
100
1
602874de0ff00
single
29
151.25.19.11
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/2.4/pfsense_ipsec_proposal_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
2
ikev1
opt3
1.2.3.6
inet
myaddress
peeraddress
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_aggregate_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
wan
fr1
test_separator
bg-info
lan
fr1
another_test_separator
bg-info
lan
fr1
last_test_separator
bg-info
lan
fr0
test_sep_floating
bg-info
floatingrules
1545574416
tcp
any2any_ssh
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
any2any_http
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
any2any_https
1545574416
443
keep state
1545574416
opt1
inet
pass
1545574416
tcp
any2any_ssh
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
any2any_http
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
any2any_https
1545574416
443
keep state
1545574416
lan
inet
pass
port
port_ssh
22
port
port_http
80
host
one_server
192.168.1.165
port
port_dns
51
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx0
100
vmx0.100
vmx1
1100
vmx1.1100
vmx1
1200
vmx1.1200
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_alias_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_alias_null_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_authserver_config.xml
================================================
21.7
normal
pfSense
home.arpa
all
system
1998
0
admins
system
1999
0
page-all
admin
system
admins
$2b$10$13u6qwCOwODv34GyCMgdWub6oQF3RX0rG7c3d3X4JvzuEmAXLYDd2
0
user-shell-access
2000
2000
2.pfsense.pool.ntp.org
https
62083679cd3d4
yes
400000
hadp
hadp
hadp
monthly
DELRADIUS
radius
radius.example.com
1812
1813
MSCHAPv2
5
lan
620c5c2a8fab5
password1
620c5f9d00ba4
ldap
DELLDAP
global
ldap.example.com
389
Standard TCP
3
one
25
re0
dhcp
dhcp6
0
re1
192.168.100.2
24
wan
0
192.168.1.100
192.168.1.199
::1000
::2000
disabled
medium
public
1
automatic
pass
inet
lan
0100000101
lan
pass
inet6
lan
0100000102
lan
-
*/1
*
*
*
*
root
/usr/sbin/newsyslog
-
1
3
*
*
*
root
/etc/rc.periodic daily
-
15
4
*
*
6
root
/etc/rc.periodic weekly
-
30
5
1
*
*
root
/etc/rc.periodic monthly
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
system_information:col1:show,netgate_services_and_support:col2:show,interfaces:col2:show
10
1644705489
62083679cd3d4
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsRENDQTN5Z0F3SUJBZ0lJU2JlZ00zSWZSTEV3RFFZSktvWklodmNOQVFFTEJRQXdXakU0TURZR0ExVUUKQ2hNdmNHWlRaVzV6WlNCM1pXSkRiMjVtYVdkMWNtRjBiM0lnVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaApkR1V4SGpBY0JnTlZCQU1URlhCbVUyVnVjMlV0TmpJd09ETTJOemxqWkROa05EQWVGdzB5TWpBeU1USXlNak0yCk5ERmFGdzB5TXpBek1UY3lNak0yTkRGYU1Gb3hPREEyQmdOVkJBb1RMM0JtVTJWdWMyVWdkMlZpUTI5dVptbG4KZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnBabWxqWVhSbE1SNHdIQVlEVlFRREV4VndabE5sYm5ObApMVFl5TURnek5qYzVZMlF6WkRRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURHCjkzVW5PMm1MbTRFVTRwOUtlMCtmbkZCQTZGV245eTBDcjhaY0dxVFZiN0E3ejY4Y1JBd0l4bEVDYmtWaUx1ejcKSnNUK2gwd1Jiclo3RWI1dkpvZ3JaUHZSV1ByeW9JQWwrT2w1b1dFbE0ycTBQMVJQdjZETndaYjFsUHgzNXdXOApBa1gxQkhLL3FEWWJibjVVRXdqOVdmaVpmcnNJeFBHM21ubmpHMHpiMktOaGJGNDM4Tk1xVTVadVRvQW5PYW9PCmJqb2tveWhNUUh1N0JGV2tQS25lTFJGN3lmN0Q3TVp1QnBBbWVCbnllYkl1ZkpzS1ViWGE3OTNFanVPcjN4disKb2J3QmhtMUN3NWRpc1o2SDZzS2ZudFJ6UWFUZXhZWEo3WGo2MDdtMWc2YkpqR2FweG9WTTlvU1VlUGlzVWNCOQpNbEp1WkpnVmVOYVlFQkJPbEgxWkFnTUJBQUdqZ2dGY01JSUJXREFKQmdOVkhSTUVBakFBTUJFR0NXQ0dTQUdHCitFSUJBUVFFQXdJR1FEQUxCZ05WSFE4RUJBTUNCYUF3TXdZSllJWklBWWI0UWdFTkJDWVdKRTl3Wlc1VFUwd2cKUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRBZEJnTlZIUTRFRmdRVWNHMlpNNHdKRlpQRQoxV1ZndXd0eFRUT0RhOGd3Z1lzR0ExVWRJd1NCZ3pDQmdJQVVjRzJaTTR3SkZaUEUxV1ZndXd0eFRUT0RhOGloClhxUmNNRm94T0RBMkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG4KYm1Wa0lFTmxjblJwWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFl5TURnek5qYzVZMlF6WkRTQwpDRW0zb0ROeUgwU3hNQ2NHQTFVZEpRUWdNQjRHQ0NzR0FRVUZCd01CQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVJCkFnSXdJQVlEVlIwUkJCa3dGNElWY0daVFpXNXpaUzAyTWpBNE16WTNPV05rTTJRME1BMEdDU3FHU0liM0RRRUIKQ3dVQUE0SUJBUUFzazBrTU12dVR0T3c2Ymx5a1U5cWNkRnQvVDlGOFZBZ0taNHgzYXNxNlArRG96N1FGVFpKVwprdmlrQVVUekpMMys4c0NKRDdjV3BZa2ZpdDRBYndhWFIyRzVsczhjL0JRcUdmY1ZOUnJVdWRscG12UUYrYk5iClMxZ2xjS2hYYXZuYnlQdkRMem9CZGVlTmhqYXIzcWc1TTV6T3I0aXYyM0hCZVc2aEY2c0FrV3dpVkU5NEJmZ00KOS9qeW5GalVYTkJheStMODM2TXBpNDhpNnE4OHdlQ25UdDdaTFFjWlZXb0IwcWNQSS96SExTUFlTNlhhcmdvdgpva3E1M3ZQSG9HNnRGUHpFSkpFVmNmOTV1bVcwaUpFR3hCQ3dTeVlnd2xSY0pEeGJ1QklFY0xWb2JKclVveHNLClJXcW13SHdQYkFxRjBOMUZ0cFJ6K3Yvd0lQYWdSQ2lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREc5M1VuTzJtTG00RVUKNHA5S2UwK2ZuRkJBNkZXbjl5MENyOFpjR3FUVmI3QTd6NjhjUkF3SXhsRUNia1ZpTHV6N0pzVCtoMHdSYnJaNwpFYjV2Sm9nclpQdlJXUHJ5b0lBbCtPbDVvV0VsTTJxMFAxUlB2NkROd1piMWxQeDM1d1c4QWtYMUJISy9xRFliCmJuNVVFd2o5V2ZpWmZyc0l4UEczbW5uakcwemIyS05oYkY0MzhOTXFVNVp1VG9Bbk9hb09iam9rb3loTVFIdTcKQkZXa1BLbmVMUkY3eWY3RDdNWnVCcEFtZUJueWViSXVmSnNLVWJYYTc5M0VqdU9yM3h2K29id0JobTFDdzVkaQpzWjZINnNLZm50UnpRYVRleFlYSjdYajYwN20xZzZiSmpHYXB4b1ZNOW9TVWVQaXNVY0I5TWxKdVpKZ1ZlTmFZCkVCQk9sSDFaQWdNQkFBRUNnZ0VCQUxldGVIeVlUMjV2UnpIVnFFSGxKbk50cFhUV1IwVUJYWThPWUN0aytXaUUKYkFnN1NTZnA5Y1lmOW1jdEQyWjlkWTdCa3JoNmhKSFBTQ3pEQzYrbXZheDUxRExHVnh5bmFNWWxUTHhaYThvZwo5aytoNng2WUJFWU9nbU1DZ0RQY2xTR2tZNXEyMll2dktNd1lMQTFIYVZRaHUrdFA0REJQUitvOGRHdGhKNG9ICmlna3Z4OWhQOVAzc1VBZjFQQWtKMzkyYjZ6dGJOYkRWdXhGSTFVbklCKzF1c2s5VUduWlM5bUVXTkxDRTEwYngKT2U1YlVJaTlCWkkzM3pTamdra3ZFQVdJMlBzVWJDaEFqa0RPUStFT1V1dktyQVJoUnBXUDVWUmdxVUJpc0ZuaApmeWFFMUNtTTQ4RWZyVGZ4QklXaTA0cHZ2U0I3Zm4rbXN3RkRCcjcvUGtVQ2dZRUE3L05NcHlZdGw2dng5b043ClNWUmdIY01JZlVZR2xJSVNvTGNJbmRURGQ3K3krR0pvRkFBclhBcVMvR2dKUFgxZkMxbWVYZWJDY0dqeHVGZEgKTlNNbnMxVk1OVHlEdjNMWVhhV0lBSlVvMWtTMlVYaWZHcUN3WEpSckVmVE11U1NlRXpXOGJaNE5Cdmk1R1VsdQpyTnJWNnBqTFQ5eS85ZENUbitHeWthamNMZzhDZ1lFQTFFWmpZTldhTXltbHgydDlpS3hsVTR2SlcxVDMrbW1DCkhWRDM5bDlUSS9BWlQ4Ti9VVGRYVXhjdDN2MnA4OTdPRWJ6cThBRno1dFRrNUV6Y1dJZDcwRmYwRUYxTFpMaXkKME1NZlVLVUlBTExwekh4T294T3YxNVRJTzlXSVF4d2J2TWcxaDh2M2M1MkNCVEpsVzFUektJM3dKMWV2RXBrRwpzcDVqN1NMSUJoY0NnWUVBeTdqTHlkWldPMEhYU3k3U2k2M0JkVU5UZjlqbVdVd2VPS2x0L1dMWkdtQjl1UGtECjJJZFVTTzhKWUplTDBOTVMwUFlqeVNIVXo4K3ArcExQZUVRQ3Z2V2FvRkJpb3pjRWtHMnNES0tYYTJRblR3Q1UKUk8xTkR5MUx3cEVQQjlvWkE4SkoydCtudTlXTWdmV2dxODJZZFhlSWxxT2JyejZKTitOTjB2R0ZEdTBDZ1lCcQp5a1p3anNVV2ZCdEVhZFVyanQ4aTJxNGYzbTBxY3kzY0pjRzVGbGV6T3JUaEpjN0ZRdndSZHhYQ213YUhBMDNVCktxQmV3YnhYSWo5TWcwWk8yMG4wbEdyYVdMVDNKTndBbmtrQXZ5VjVoSWlPTVBNMm8wN1JPNjVJTzdKallKNnIKcUdVVnZnenRBdzVJSXdST29Edjc2UHdxTHJpS3VLVmY4c0wrcDRMTlhRS0JnUUNrbXlIR3dielVJLzN6Z1FNSApxY1RkMUttMDhxTXJjUzZXdU03RGJxTEpIQXdlcFRSYUpuVmVLcnRRS2t4SGRlcjZsK2VWUjNvWXArUE9EbTVECjBGSXMxbXd2RFp3TjI2RVhmZnZkWG9EK1luNnVEeGlvVU9QZ1A2U1hLT3dxcnBuenVFZTFBNFpDQTFnRXJhd2MKTTNmejdiV3d0a1JUUE5uaGxybkVJNXY5Qmc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_ca_config.xml
================================================
21.7
normal
pfSense
home.arpa
all
system
1998
0
admins
system
1999
0
page-all
admin
system
admins
$2b$10$13u6qwCOwODv34GyCMgdWub6oQF3RX0rG7c3d3X4JvzuEmAXLYDd2
0
user-shell-access
2000
2000
2.pfsense.pool.ntp.org
https
62083679cd3d4
yes
400000
hadp
hadp
hadp
monthly
re0
dhcp
dhcp6
0
re1
192.168.100.2
24
wan
0
192.168.1.100
192.168.1.199
::1000
::2000
disabled
medium
public
1
automatic
pass
inet
lan
0100000101
lan
pass
inet6
lan
0100000102
lan
-
*/1
*
*
*
*
root
/usr/sbin/newsyslog
-
1
3
*
*
*
root
/etc/rc.periodic daily
-
15
4
*
*
6
root
/etc/rc.periodic weekly
-
30
5
1
*
*
root
/etc/rc.periodic monthly
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
system_information:col1:show,netgate_services_and_support:col2:show,interfaces:col2:show
10
1644705489
62083679cd3d4
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsRENDQTN5Z0F3SUJBZ0lJU2JlZ00zSWZSTEV3RFFZSktvWklodmNOQVFFTEJRQXdXakU0TURZR0ExVUUKQ2hNdmNHWlRaVzV6WlNCM1pXSkRiMjVtYVdkMWNtRjBiM0lnVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaApkR1V4SGpBY0JnTlZCQU1URlhCbVUyVnVjMlV0TmpJd09ETTJOemxqWkROa05EQWVGdzB5TWpBeU1USXlNak0yCk5ERmFGdzB5TXpBek1UY3lNak0yTkRGYU1Gb3hPREEyQmdOVkJBb1RMM0JtVTJWdWMyVWdkMlZpUTI5dVptbG4KZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnBabWxqWVhSbE1SNHdIQVlEVlFRREV4VndabE5sYm5ObApMVFl5TURnek5qYzVZMlF6WkRRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURHCjkzVW5PMm1MbTRFVTRwOUtlMCtmbkZCQTZGV245eTBDcjhaY0dxVFZiN0E3ejY4Y1JBd0l4bEVDYmtWaUx1ejcKSnNUK2gwd1Jiclo3RWI1dkpvZ3JaUHZSV1ByeW9JQWwrT2w1b1dFbE0ycTBQMVJQdjZETndaYjFsUHgzNXdXOApBa1gxQkhLL3FEWWJibjVVRXdqOVdmaVpmcnNJeFBHM21ubmpHMHpiMktOaGJGNDM4Tk1xVTVadVRvQW5PYW9PCmJqb2tveWhNUUh1N0JGV2tQS25lTFJGN3lmN0Q3TVp1QnBBbWVCbnllYkl1ZkpzS1ViWGE3OTNFanVPcjN4disKb2J3QmhtMUN3NWRpc1o2SDZzS2ZudFJ6UWFUZXhZWEo3WGo2MDdtMWc2YkpqR2FweG9WTTlvU1VlUGlzVWNCOQpNbEp1WkpnVmVOYVlFQkJPbEgxWkFnTUJBQUdqZ2dGY01JSUJXREFKQmdOVkhSTUVBakFBTUJFR0NXQ0dTQUdHCitFSUJBUVFFQXdJR1FEQUxCZ05WSFE4RUJBTUNCYUF3TXdZSllJWklBWWI0UWdFTkJDWVdKRTl3Wlc1VFUwd2cKUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRBZEJnTlZIUTRFRmdRVWNHMlpNNHdKRlpQRQoxV1ZndXd0eFRUT0RhOGd3Z1lzR0ExVWRJd1NCZ3pDQmdJQVVjRzJaTTR3SkZaUEUxV1ZndXd0eFRUT0RhOGloClhxUmNNRm94T0RBMkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG4KYm1Wa0lFTmxjblJwWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFl5TURnek5qYzVZMlF6WkRTQwpDRW0zb0ROeUgwU3hNQ2NHQTFVZEpRUWdNQjRHQ0NzR0FRVUZCd01CQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVJCkFnSXdJQVlEVlIwUkJCa3dGNElWY0daVFpXNXpaUzAyTWpBNE16WTNPV05rTTJRME1BMEdDU3FHU0liM0RRRUIKQ3dVQUE0SUJBUUFzazBrTU12dVR0T3c2Ymx5a1U5cWNkRnQvVDlGOFZBZ0taNHgzYXNxNlArRG96N1FGVFpKVwprdmlrQVVUekpMMys4c0NKRDdjV3BZa2ZpdDRBYndhWFIyRzVsczhjL0JRcUdmY1ZOUnJVdWRscG12UUYrYk5iClMxZ2xjS2hYYXZuYnlQdkRMem9CZGVlTmhqYXIzcWc1TTV6T3I0aXYyM0hCZVc2aEY2c0FrV3dpVkU5NEJmZ00KOS9qeW5GalVYTkJheStMODM2TXBpNDhpNnE4OHdlQ25UdDdaTFFjWlZXb0IwcWNQSS96SExTUFlTNlhhcmdvdgpva3E1M3ZQSG9HNnRGUHpFSkpFVmNmOTV1bVcwaUpFR3hCQ3dTeVlnd2xSY0pEeGJ1QklFY0xWb2JKclVveHNLClJXcW13SHdQYkFxRjBOMUZ0cFJ6K3Yvd0lQYWdSQ2lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREc5M1VuTzJtTG00RVUKNHA5S2UwK2ZuRkJBNkZXbjl5MENyOFpjR3FUVmI3QTd6NjhjUkF3SXhsRUNia1ZpTHV6N0pzVCtoMHdSYnJaNwpFYjV2Sm9nclpQdlJXUHJ5b0lBbCtPbDVvV0VsTTJxMFAxUlB2NkROd1piMWxQeDM1d1c4QWtYMUJISy9xRFliCmJuNVVFd2o5V2ZpWmZyc0l4UEczbW5uakcwemIyS05oYkY0MzhOTXFVNVp1VG9Bbk9hb09iam9rb3loTVFIdTcKQkZXa1BLbmVMUkY3eWY3RDdNWnVCcEFtZUJueWViSXVmSnNLVWJYYTc5M0VqdU9yM3h2K29id0JobTFDdzVkaQpzWjZINnNLZm50UnpRYVRleFlYSjdYajYwN20xZzZiSmpHYXB4b1ZNOW9TVWVQaXNVY0I5TWxKdVpKZ1ZlTmFZCkVCQk9sSDFaQWdNQkFBRUNnZ0VCQUxldGVIeVlUMjV2UnpIVnFFSGxKbk50cFhUV1IwVUJYWThPWUN0aytXaUUKYkFnN1NTZnA5Y1lmOW1jdEQyWjlkWTdCa3JoNmhKSFBTQ3pEQzYrbXZheDUxRExHVnh5bmFNWWxUTHhaYThvZwo5aytoNng2WUJFWU9nbU1DZ0RQY2xTR2tZNXEyMll2dktNd1lMQTFIYVZRaHUrdFA0REJQUitvOGRHdGhKNG9ICmlna3Z4OWhQOVAzc1VBZjFQQWtKMzkyYjZ6dGJOYkRWdXhGSTFVbklCKzF1c2s5VUduWlM5bUVXTkxDRTEwYngKT2U1YlVJaTlCWkkzM3pTamdra3ZFQVdJMlBzVWJDaEFqa0RPUStFT1V1dktyQVJoUnBXUDVWUmdxVUJpc0ZuaApmeWFFMUNtTTQ4RWZyVGZ4QklXaTA0cHZ2U0I3Zm4rbXN3RkRCcjcvUGtVQ2dZRUE3L05NcHlZdGw2dng5b043ClNWUmdIY01JZlVZR2xJSVNvTGNJbmRURGQ3K3krR0pvRkFBclhBcVMvR2dKUFgxZkMxbWVYZWJDY0dqeHVGZEgKTlNNbnMxVk1OVHlEdjNMWVhhV0lBSlVvMWtTMlVYaWZHcUN3WEpSckVmVE11U1NlRXpXOGJaNE5Cdmk1R1VsdQpyTnJWNnBqTFQ5eS85ZENUbitHeWthamNMZzhDZ1lFQTFFWmpZTldhTXltbHgydDlpS3hsVTR2SlcxVDMrbW1DCkhWRDM5bDlUSS9BWlQ4Ti9VVGRYVXhjdDN2MnA4OTdPRWJ6cThBRno1dFRrNUV6Y1dJZDcwRmYwRUYxTFpMaXkKME1NZlVLVUlBTExwekh4T294T3YxNVRJTzlXSVF4d2J2TWcxaDh2M2M1MkNCVEpsVzFUektJM3dKMWV2RXBrRwpzcDVqN1NMSUJoY0NnWUVBeTdqTHlkWldPMEhYU3k3U2k2M0JkVU5UZjlqbVdVd2VPS2x0L1dMWkdtQjl1UGtECjJJZFVTTzhKWUplTDBOTVMwUFlqeVNIVXo4K3ArcExQZUVRQ3Z2V2FvRkJpb3pjRWtHMnNES0tYYTJRblR3Q1UKUk8xTkR5MUx3cEVQQjlvWkE4SkoydCtudTlXTWdmV2dxODJZZFhlSWxxT2JyejZKTitOTjB2R0ZEdTBDZ1lCcQp5a1p3anNVV2ZCdEVhZFVyanQ4aTJxNGYzbTBxY3kzY0pjRzVGbGV6T3JUaEpjN0ZRdndSZHhYQ213YUhBMDNVCktxQmV3YnhYSWo5TWcwWk8yMG4wbEdyYVdMVDNKTndBbmtrQXZ5VjVoSWlPTVBNMm8wN1JPNjVJTzdKallKNnIKcUdVVnZnenRBdzVJSXdST29Edjc2UHdxTHJpS3VLVmY4c0wrcDRMTlhRS0JnUUNrbXlIR3dielVJLzN6Z1FNSApxY1RkMUttMDhxTXJjUzZXdU03RGJxTEpIQXdlcFRSYUpuVmVLcnRRS2t4SGRlcjZsK2VWUjNvWXArUE9EbTVECjBGSXMxbXd2RFp3TjI2RVhmZnZkWG9EK1luNnVEeGlvVU9QZ1A2U1hLT3dxcnBuenVFZTFBNFpDQTFnRXJhd2MKTTNmejdiV3d0a1JUUE5uaGxybkVJNXY5Qmc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
6209e3cef1e81
enabled
enabled
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVDRENDQXZDZ0F3SUJBZ0lJRmpGT2hzMW5NelF3RFFZSktvWklodmNOQVFFTEJRQXdYREVUTUJFR0ExVUUKQXhNS2IzQmxiblp3YmkxallURUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBdwpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExCk1EZ3pNVm9YRFRNeU1ESXhNakExTURnek1Wb3dYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0cKQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNdwpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDCkFRRUFtc3ZpSk1FMUVUZWQ0Zk90YmtIcEYzZDllTSs2NDA4WFBuYTh0SkdkQnEzVUFDeEV6b2FCS1J0MnkxY3QKNnpRRGU1RkY0QUF2dFYxdWNacHNsNW80RFMvSUdTYm42ZDNZTWsrajhqQVEzRW16UjhHT29obmdmMVE5QVhDNgpvaDRyQlA1c1g0WTh1WThrSjNZclg1cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTCmNOTFppVnhDUVFLNXA4azltQThiZ3hscVlrZjBtQXlCTnc5TUFmUFVjVWtxRjZQMGdXUEhsSXJIWi91aGc3ZFUKKzIyYW9jS1VFTml2OW1xYStCNmNVZ0xURlQ2czBWU0VzWC9kQWVoNjJZTGdmbVhKZzZkTkhRSStNZzZTa2VscAprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIwR0ExVWREZ1FXQkJSazVvQS8wcWEyCktQd2dvWEpxS010K0FvS0pnVENCalFZRFZSMGpCSUdGTUlHQ2dCUms1b0EvMHFhMktQd2dvWEpxS010K0FvS0oKZ2FGZ3BGNHdYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVgpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wCllteGxnZ2dXTVU2R3pXY3pOREFNQmdOVkhSTUVCVEFEQVFIL01Bc0dBMVVkRHdRRUF3SUJCakFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVVIOUtDZG1KZG9BSmxVMHdCSkhZeGpMcktsbFBZNk9OYnpyNUpiaENNNjlIeHhZTgpCa2lpbXd1N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCClRJcmlIeXRORFFhb3VOYS9LV3M3RmF1b2JjdEJsMXc5YXRvSFpzTjVvZWhUM3JBVHYxQ0NBdGpwYVRKSWZKUjMKMElRT1lrZTRvWTZEa0l3SHAydlBQbW9vR2dJdGJUdzNVK0U0MVlaZTdxQ21FLzd6TFRTWmtJTTJseDZ6RDQ2agpEZjRyZ044TVVMNnhpd09MbzlyQUp5ckRNM2JEeTJ1QjY0QkVzRFFMa2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4CmgxL2d6RFpJRGkrV1hDWUFjYmdMaFpWQnF0bjYydW1GcE1SSXV3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUNheStJa3dUVVJONTNoDQo4NjF1UWVrWGQzMTR6N3JqVHhjK2RyeTBrWjBHcmRRQUxFVE9ob0VwRzNiTFZ5M3JOQU43a1VYZ0FDKzFYVzV4DQptbXlYbWpnTkw4Z1pKdWZwM2RneVQ2UHlNQkRjU2JOSHdZNmlHZUIvVkQwQmNMcWlIaXNFL214ZmhqeTVqeVFuDQpkaXRmbXBPa1Zqa29jdFRVQ2c5N0pEdkxJeGtjdWlTMzJkejRVVm5EQlR0RUlaSncwdG1KWEVKQkFybW55VDJZDQpEeHVER1dwaVIvU1lESUUzRDB3Qjg5UnhTU29Yby9TQlk4ZVVpc2RuKzZHRHQxVDdiWnFod3BRUTJLLzJhcHI0DQpIcHhTQXRNVlBxelJWSVN4ZjkwQjZIclpndUIrWmNtRHAwMGRBajR5RHBLUjZXbVQxVkZONk9JUk1nUlh3bUIyDQovWTN0RktibkFnTUJBQUVDZ2dFQUNYN0FIR2tOakVUUkZtOFFFRmRTcVBIWGJIV3hqUWZvOFJmdmMxUUxRY0dmDQo0M0xUdGFkaWZOY0dibXFta21yYVc5WUpaemdidFJCS0dnWFM2Mm0yVG5qRDJXY2RpcWJsQUJFS2lXeVJYREhaDQpJV21xQ2g5ME9kczg4cjJyZFE1TXJUMitBQTRINDRuNE9jTngzYWRwcndicThxUTRrZGtjSWYyUy9WN2x4M0U3DQpLWVNyVnNZT3hUS0dwQVl4MzVtU3oyckRadXFhcVhyRGtwSTYxa2tFVDluaGJzQStVZDE5aXZrZ3J1WlU3bVp3DQpmRzQ0V3JDeEhSNjdiYW1LQ3VPei81SnlKNUt5US9VaFkzak9lczkxZXRpeHJXUkRpaXRCVVIwc01kMXZuQ2cyDQpTSkpjQnAvYXRwbWNjMGY1OVZFSDRGOTNqY01jR3V5cE1mQmkwNExFd1FLQmdRRE1BUSsyVk5aQWJ5aVM0cWZqDQpXelE0UWl6MDNSdmUybVhaNHc1aEdKSmk0QnkvdmI4K2xoOE9acGFWSFBjRTVya0tBbExEaFRwUEl0dTFwNWZzDQpic05MTHpJYkIwUmVBdGUxc3JGOU9SRHp0VThKblhrNDEwcnFMbmdBZmhVTGUvSUttNjBtUG4vWWU0UnhVSVI3DQp2TjFNemJXMnlYdHJCSlZ5VG1jbFRkRDZ5UUtCZ1FEQ1FCa1B2NnFpYXZXaG1IRnBiZ3QvMklpSUlBUk5iSUJjDQpmVnVUYVFJbXlOZlVSVzlUVG10UHFiYXRuOGhaWFFaL1ZCbWdaMklPOCtCMXBFYW5zYjZCS2xJMXFUMFdMUzR4DQpaV0hLQ3hEdVFiMXNUYmNRMnd3NW5jbk50c1dlWlkxUlV1dlpYcVQwZFU4WUIwb2FsdGVYWTNVbDJrUWJuWUcvDQpINUpJeWJPOEx3S0JnUURKNE1zQnJoYVBrUERmMm5nMW4wMmYxcXpTYS9SbXBrMWdQemM5a3FsYU8xbDN6WGZ4DQpvWEYrT0xzUE9LaWlLd2cyQlhLTmxjdk1BRHpZR005WTQ0dFRYWk1CK0VFSm4xcURyaC9DUWJTcTEyTXRxcTRKDQpOOVFreG5OdVdWYk9GSXZEUDZjclQzSUljc0x2dDdSREZ2VVFTZ2xtcHlBQkdYb2lzYitVeE5ybk1RS0JnR09QDQpZc2oxbmNsOU5NUk1VK1NMcUkwd09Gbzh2cmZJSXNwRTNnamh5MTZCbGsyUUFRMGJwbGpBVFljVDNDWWhUZEU1DQpFNkZwRzVNNllCTXJ6YUxwc1JDVzFtZjJnLzYzelhNMzJUVXJFdFJyRVdGUE84TUI0blF0Y1Y5a2pFa3hGNHRWDQpDdGp3YjI5MEtNUFNDS00wS08vSTVDUXdpTFAydUtkeTBSRkpnRHUxQW9HQUxqSTdGZDl0ckFrY1dSenMyVHpRDQptaDRTZWxHRDFvdFAxRXpFa1hYVlZwK1lMNXlxOWM3V3hsa09RY1lFVmd1N2huNVFncnBRUFZPTFVmbkJoajdXDQo1MC9IVmR6V21wSXg5NUlqZXFDZklBV1U3N0I5cmJUR2hvWWMzbTdJcEdObzl2WlhHYWgrc2JHY3BEK1phV3UzDQp1Q25pTnJpZEhORGgzWHZQVFZkRTRlVT0NCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0NCg==
1
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_cert_config.xml
================================================
21.7
normal
pfSense
home.arpa
all
system
1998
0
admins
system
1999
0
page-all
admin
system
admins
$2b$10$13u6qwCOwODv34GyCMgdWub6oQF3RX0rG7c3d3X4JvzuEmAXLYDd2
0
user-shell-access
2000
2000
2.pfsense.pool.ntp.org
https
62083679cd3d4
yes
400000
hadp
hadp
hadp
monthly
re0
dhcp
dhcp6
0
re1
192.168.100.2
24
wan
0
192.168.1.100
192.168.1.199
::1000
::2000
disabled
medium
public
1
automatic
pass
inet
lan
0100000101
lan
pass
inet6
lan
0100000102
lan
-
*/1
*
*
*
*
root
/usr/sbin/newsyslog
-
1
3
*
*
*
root
/etc/rc.periodic daily
-
15
4
*
*
6
root
/etc/rc.periodic weekly
-
30
5
1
*
*
root
/etc/rc.periodic monthly
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
system_information:col1:show,netgate_services_and_support:col2:show,interfaces:col2:show
10
1644705489
62083679cd3d4
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsRENDQTN5Z0F3SUJBZ0lJU2JlZ00zSWZSTEV3RFFZSktvWklodmNOQVFFTEJRQXdXakU0TURZR0ExVUUKQ2hNdmNHWlRaVzV6WlNCM1pXSkRiMjVtYVdkMWNtRjBiM0lnVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaApkR1V4SGpBY0JnTlZCQU1URlhCbVUyVnVjMlV0TmpJd09ETTJOemxqWkROa05EQWVGdzB5TWpBeU1USXlNak0yCk5ERmFGdzB5TXpBek1UY3lNak0yTkRGYU1Gb3hPREEyQmdOVkJBb1RMM0JtVTJWdWMyVWdkMlZpUTI5dVptbG4KZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnBabWxqWVhSbE1SNHdIQVlEVlFRREV4VndabE5sYm5ObApMVFl5TURnek5qYzVZMlF6WkRRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURHCjkzVW5PMm1MbTRFVTRwOUtlMCtmbkZCQTZGV245eTBDcjhaY0dxVFZiN0E3ejY4Y1JBd0l4bEVDYmtWaUx1ejcKSnNUK2gwd1Jiclo3RWI1dkpvZ3JaUHZSV1ByeW9JQWwrT2w1b1dFbE0ycTBQMVJQdjZETndaYjFsUHgzNXdXOApBa1gxQkhLL3FEWWJibjVVRXdqOVdmaVpmcnNJeFBHM21ubmpHMHpiMktOaGJGNDM4Tk1xVTVadVRvQW5PYW9PCmJqb2tveWhNUUh1N0JGV2tQS25lTFJGN3lmN0Q3TVp1QnBBbWVCbnllYkl1ZkpzS1ViWGE3OTNFanVPcjN4disKb2J3QmhtMUN3NWRpc1o2SDZzS2ZudFJ6UWFUZXhZWEo3WGo2MDdtMWc2YkpqR2FweG9WTTlvU1VlUGlzVWNCOQpNbEp1WkpnVmVOYVlFQkJPbEgxWkFnTUJBQUdqZ2dGY01JSUJXREFKQmdOVkhSTUVBakFBTUJFR0NXQ0dTQUdHCitFSUJBUVFFQXdJR1FEQUxCZ05WSFE4RUJBTUNCYUF3TXdZSllJWklBWWI0UWdFTkJDWVdKRTl3Wlc1VFUwd2cKUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRBZEJnTlZIUTRFRmdRVWNHMlpNNHdKRlpQRQoxV1ZndXd0eFRUT0RhOGd3Z1lzR0ExVWRJd1NCZ3pDQmdJQVVjRzJaTTR3SkZaUEUxV1ZndXd0eFRUT0RhOGloClhxUmNNRm94T0RBMkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG4KYm1Wa0lFTmxjblJwWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFl5TURnek5qYzVZMlF6WkRTQwpDRW0zb0ROeUgwU3hNQ2NHQTFVZEpRUWdNQjRHQ0NzR0FRVUZCd01CQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVJCkFnSXdJQVlEVlIwUkJCa3dGNElWY0daVFpXNXpaUzAyTWpBNE16WTNPV05rTTJRME1BMEdDU3FHU0liM0RRRUIKQ3dVQUE0SUJBUUFzazBrTU12dVR0T3c2Ymx5a1U5cWNkRnQvVDlGOFZBZ0taNHgzYXNxNlArRG96N1FGVFpKVwprdmlrQVVUekpMMys4c0NKRDdjV3BZa2ZpdDRBYndhWFIyRzVsczhjL0JRcUdmY1ZOUnJVdWRscG12UUYrYk5iClMxZ2xjS2hYYXZuYnlQdkRMem9CZGVlTmhqYXIzcWc1TTV6T3I0aXYyM0hCZVc2aEY2c0FrV3dpVkU5NEJmZ00KOS9qeW5GalVYTkJheStMODM2TXBpNDhpNnE4OHdlQ25UdDdaTFFjWlZXb0IwcWNQSS96SExTUFlTNlhhcmdvdgpva3E1M3ZQSG9HNnRGUHpFSkpFVmNmOTV1bVcwaUpFR3hCQ3dTeVlnd2xSY0pEeGJ1QklFY0xWb2JKclVveHNLClJXcW13SHdQYkFxRjBOMUZ0cFJ6K3Yvd0lQYWdSQ2lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREc5M1VuTzJtTG00RVUKNHA5S2UwK2ZuRkJBNkZXbjl5MENyOFpjR3FUVmI3QTd6NjhjUkF3SXhsRUNia1ZpTHV6N0pzVCtoMHdSYnJaNwpFYjV2Sm9nclpQdlJXUHJ5b0lBbCtPbDVvV0VsTTJxMFAxUlB2NkROd1piMWxQeDM1d1c4QWtYMUJISy9xRFliCmJuNVVFd2o5V2ZpWmZyc0l4UEczbW5uakcwemIyS05oYkY0MzhOTXFVNVp1VG9Bbk9hb09iam9rb3loTVFIdTcKQkZXa1BLbmVMUkY3eWY3RDdNWnVCcEFtZUJueWViSXVmSnNLVWJYYTc5M0VqdU9yM3h2K29id0JobTFDdzVkaQpzWjZINnNLZm50UnpRYVRleFlYSjdYajYwN20xZzZiSmpHYXB4b1ZNOW9TVWVQaXNVY0I5TWxKdVpKZ1ZlTmFZCkVCQk9sSDFaQWdNQkFBRUNnZ0VCQUxldGVIeVlUMjV2UnpIVnFFSGxKbk50cFhUV1IwVUJYWThPWUN0aytXaUUKYkFnN1NTZnA5Y1lmOW1jdEQyWjlkWTdCa3JoNmhKSFBTQ3pEQzYrbXZheDUxRExHVnh5bmFNWWxUTHhaYThvZwo5aytoNng2WUJFWU9nbU1DZ0RQY2xTR2tZNXEyMll2dktNd1lMQTFIYVZRaHUrdFA0REJQUitvOGRHdGhKNG9ICmlna3Z4OWhQOVAzc1VBZjFQQWtKMzkyYjZ6dGJOYkRWdXhGSTFVbklCKzF1c2s5VUduWlM5bUVXTkxDRTEwYngKT2U1YlVJaTlCWkkzM3pTamdra3ZFQVdJMlBzVWJDaEFqa0RPUStFT1V1dktyQVJoUnBXUDVWUmdxVUJpc0ZuaApmeWFFMUNtTTQ4RWZyVGZ4QklXaTA0cHZ2U0I3Zm4rbXN3RkRCcjcvUGtVQ2dZRUE3L05NcHlZdGw2dng5b043ClNWUmdIY01JZlVZR2xJSVNvTGNJbmRURGQ3K3krR0pvRkFBclhBcVMvR2dKUFgxZkMxbWVYZWJDY0dqeHVGZEgKTlNNbnMxVk1OVHlEdjNMWVhhV0lBSlVvMWtTMlVYaWZHcUN3WEpSckVmVE11U1NlRXpXOGJaNE5Cdmk1R1VsdQpyTnJWNnBqTFQ5eS85ZENUbitHeWthamNMZzhDZ1lFQTFFWmpZTldhTXltbHgydDlpS3hsVTR2SlcxVDMrbW1DCkhWRDM5bDlUSS9BWlQ4Ti9VVGRYVXhjdDN2MnA4OTdPRWJ6cThBRno1dFRrNUV6Y1dJZDcwRmYwRUYxTFpMaXkKME1NZlVLVUlBTExwekh4T294T3YxNVRJTzlXSVF4d2J2TWcxaDh2M2M1MkNCVEpsVzFUektJM3dKMWV2RXBrRwpzcDVqN1NMSUJoY0NnWUVBeTdqTHlkWldPMEhYU3k3U2k2M0JkVU5UZjlqbVdVd2VPS2x0L1dMWkdtQjl1UGtECjJJZFVTTzhKWUplTDBOTVMwUFlqeVNIVXo4K3ArcExQZUVRQ3Z2V2FvRkJpb3pjRWtHMnNES0tYYTJRblR3Q1UKUk8xTkR5MUx3cEVQQjlvWkE4SkoydCtudTlXTWdmV2dxODJZZFhlSWxxT2JyejZKTitOTjB2R0ZEdTBDZ1lCcQp5a1p3anNVV2ZCdEVhZFVyanQ4aTJxNGYzbTBxY3kzY0pjRzVGbGV6T3JUaEpjN0ZRdndSZHhYQ213YUhBMDNVCktxQmV3YnhYSWo5TWcwWk8yMG4wbEdyYVdMVDNKTndBbmtrQXZ5VjVoSWlPTVBNMm8wN1JPNjVJTzdKallKNnIKcUdVVnZnenRBdzVJSXdST29Edjc2UHdxTHJpS3VLVmY4c0wrcDRMTlhRS0JnUUNrbXlIR3dielVJLzN6Z1FNSApxY1RkMUttMDhxTXJjUzZXdU03RGJxTEpIQXdlcFRSYUpuVmVLcnRRS2t4SGRlcjZsK2VWUjNvWXArUE9EbTVECjBGSXMxbXd2RFp3TjI2RVhmZnZkWG9EK1luNnVEeGlvVU9QZ1A2U1hLT3dxcnBuenVFZTFBNFpDQTFnRXJhd2MKTTNmejdiV3d0a1JUUE5uaGxybkVJNXY5Qmc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
6209e3cef1e81
enabled
enabled
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVDRENDQXZDZ0F3SUJBZ0lJRmpGT2hzMW5NelF3RFFZSktvWklodmNOQVFFTEJRQXdYREVUTUJFR0ExVUUKQXhNS2IzQmxiblp3YmkxallURUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBdwpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExCk1EZ3pNVm9YRFRNeU1ESXhNakExTURnek1Wb3dYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0cKQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNdwpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDCkFRRUFtc3ZpSk1FMUVUZWQ0Zk90YmtIcEYzZDllTSs2NDA4WFBuYTh0SkdkQnEzVUFDeEV6b2FCS1J0MnkxY3QKNnpRRGU1RkY0QUF2dFYxdWNacHNsNW80RFMvSUdTYm42ZDNZTWsrajhqQVEzRW16UjhHT29obmdmMVE5QVhDNgpvaDRyQlA1c1g0WTh1WThrSjNZclg1cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTCmNOTFppVnhDUVFLNXA4azltQThiZ3hscVlrZjBtQXlCTnc5TUFmUFVjVWtxRjZQMGdXUEhsSXJIWi91aGc3ZFUKKzIyYW9jS1VFTml2OW1xYStCNmNVZ0xURlQ2czBWU0VzWC9kQWVoNjJZTGdmbVhKZzZkTkhRSStNZzZTa2VscAprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIwR0ExVWREZ1FXQkJSazVvQS8wcWEyCktQd2dvWEpxS010K0FvS0pnVENCalFZRFZSMGpCSUdGTUlHQ2dCUms1b0EvMHFhMktQd2dvWEpxS010K0FvS0oKZ2FGZ3BGNHdYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVgpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wCllteGxnZ2dXTVU2R3pXY3pOREFNQmdOVkhSTUVCVEFEQVFIL01Bc0dBMVVkRHdRRUF3SUJCakFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVVIOUtDZG1KZG9BSmxVMHdCSkhZeGpMcktsbFBZNk9OYnpyNUpiaENNNjlIeHhZTgpCa2lpbXd1N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCClRJcmlIeXRORFFhb3VOYS9LV3M3RmF1b2JjdEJsMXc5YXRvSFpzTjVvZWhUM3JBVHYxQ0NBdGpwYVRKSWZKUjMKMElRT1lrZTRvWTZEa0l3SHAydlBQbW9vR2dJdGJUdzNVK0U0MVlaZTdxQ21FLzd6TFRTWmtJTTJseDZ6RDQ2agpEZjRyZ044TVVMNnhpd09MbzlyQUp5ckRNM2JEeTJ1QjY0QkVzRFFMa2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4CmgxL2d6RFpJRGkrV1hDWUFjYmdMaFpWQnF0bjYydW1GcE1SSXV3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUNheStJa3dUVVJONTNoDQo4NjF1UWVrWGQzMTR6N3JqVHhjK2RyeTBrWjBHcmRRQUxFVE9ob0VwRzNiTFZ5M3JOQU43a1VYZ0FDKzFYVzV4DQptbXlYbWpnTkw4Z1pKdWZwM2RneVQ2UHlNQkRjU2JOSHdZNmlHZUIvVkQwQmNMcWlIaXNFL214ZmhqeTVqeVFuDQpkaXRmbXBPa1Zqa29jdFRVQ2c5N0pEdkxJeGtjdWlTMzJkejRVVm5EQlR0RUlaSncwdG1KWEVKQkFybW55VDJZDQpEeHVER1dwaVIvU1lESUUzRDB3Qjg5UnhTU29Yby9TQlk4ZVVpc2RuKzZHRHQxVDdiWnFod3BRUTJLLzJhcHI0DQpIcHhTQXRNVlBxelJWSVN4ZjkwQjZIclpndUIrWmNtRHAwMGRBajR5RHBLUjZXbVQxVkZONk9JUk1nUlh3bUIyDQovWTN0RktibkFnTUJBQUVDZ2dFQUNYN0FIR2tOakVUUkZtOFFFRmRTcVBIWGJIV3hqUWZvOFJmdmMxUUxRY0dmDQo0M0xUdGFkaWZOY0dibXFta21yYVc5WUpaemdidFJCS0dnWFM2Mm0yVG5qRDJXY2RpcWJsQUJFS2lXeVJYREhaDQpJV21xQ2g5ME9kczg4cjJyZFE1TXJUMitBQTRINDRuNE9jTngzYWRwcndicThxUTRrZGtjSWYyUy9WN2x4M0U3DQpLWVNyVnNZT3hUS0dwQVl4MzVtU3oyckRadXFhcVhyRGtwSTYxa2tFVDluaGJzQStVZDE5aXZrZ3J1WlU3bVp3DQpmRzQ0V3JDeEhSNjdiYW1LQ3VPei81SnlKNUt5US9VaFkzak9lczkxZXRpeHJXUkRpaXRCVVIwc01kMXZuQ2cyDQpTSkpjQnAvYXRwbWNjMGY1OVZFSDRGOTNqY01jR3V5cE1mQmkwNExFd1FLQmdRRE1BUSsyVk5aQWJ5aVM0cWZqDQpXelE0UWl6MDNSdmUybVhaNHc1aEdKSmk0QnkvdmI4K2xoOE9acGFWSFBjRTVya0tBbExEaFRwUEl0dTFwNWZzDQpic05MTHpJYkIwUmVBdGUxc3JGOU9SRHp0VThKblhrNDEwcnFMbmdBZmhVTGUvSUttNjBtUG4vWWU0UnhVSVI3DQp2TjFNemJXMnlYdHJCSlZ5VG1jbFRkRDZ5UUtCZ1FEQ1FCa1B2NnFpYXZXaG1IRnBiZ3QvMklpSUlBUk5iSUJjDQpmVnVUYVFJbXlOZlVSVzlUVG10UHFiYXRuOGhaWFFaL1ZCbWdaMklPOCtCMXBFYW5zYjZCS2xJMXFUMFdMUzR4DQpaV0hLQ3hEdVFiMXNUYmNRMnd3NW5jbk50c1dlWlkxUlV1dlpYcVQwZFU4WUIwb2FsdGVYWTNVbDJrUWJuWUcvDQpINUpJeWJPOEx3S0JnUURKNE1zQnJoYVBrUERmMm5nMW4wMmYxcXpTYS9SbXBrMWdQemM5a3FsYU8xbDN6WGZ4DQpvWEYrT0xzUE9LaWlLd2cyQlhLTmxjdk1BRHpZR005WTQ0dFRYWk1CK0VFSm4xcURyaC9DUWJTcTEyTXRxcTRKDQpOOVFreG5OdVdWYk9GSXZEUDZjclQzSUljc0x2dDdSREZ2VVFTZ2xtcHlBQkdYb2lzYitVeE5ybk1RS0JnR09QDQpZc2oxbmNsOU5NUk1VK1NMcUkwd09Gbzh2cmZJSXNwRTNnamh5MTZCbGsyUUFRMGJwbGpBVFljVDNDWWhUZEU1DQpFNkZwRzVNNllCTXJ6YUxwc1JDVzFtZjJnLzYzelhNMzJUVXJFdFJyRVdGUE84TUI0blF0Y1Y5a2pFa3hGNHRWDQpDdGp3YjI5MEtNUFNDS00wS08vSTVDUXdpTFAydUtkeTBSRkpnRHUxQW9HQUxqSTdGZDl0ckFrY1dSenMyVHpRDQptaDRTZWxHRDFvdFAxRXpFa1hYVlZwK1lMNXlxOWM3V3hsa09RY1lFVmd1N2huNVFncnBRUFZPTFVmbkJoajdXDQo1MC9IVmR6V21wSXg5NUlqZXFDZklBV1U3N0I5cmJUR2hvWWMzbTdJcEdObzl2WlhHYWgrc2JHY3BEK1phV3UzDQp1Q25pTnJpZEhORGgzWHZQVFZkRTRlVT0NCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0NCg==
1
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_dhcp_server_config.xml
================================================
23.3
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
Etc/UTC
em0
dhcp
dhcp6
0
em1
192.168.1.1
24
wan
0
em2
opt1
10.0.0.1
24
em1.100
VLAN 100
172.16.0.1
24
em1
100
VLAN 100 on LAN
em1.100
192.168.1.100
192.168.1.199
86400
172800
hmac-md5
allow
10.0.0.100
10.0.0.199
86400
172800
opt1.example.com
hmac-md5
allow
enabled
1545602758
aggregated change
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_dhcp_static_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
2001::2001:22
64
vmx2
pub
10.0.0.1
24
vmx3
vt1
10.10.0.1
16
lan_1100
vmx1.1100
172.16.151.210
24
lan_1200
vmx1.1200
172.16.152.210
24
vmx4
vt2
dhcp
192.168.1.100
192.168.1.199
10.0.0.2
10.0.0.99
hmac-md5
allow
ab:ab:ab:ab:ab:ab
dhcphostid
10.0.0.100
dhcphostname
hmac-md5
10.10.0.2
10.10.0.99
hmac-md5
allow
172.16.152.2
172.16.152.99
hmac-md5
allow
::1000
::2000
disabled
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1560930241
match
lan
inet
in
yes
yes
tcp
port_ssh
floating_rule_1
1560930241
match
lan,wan,opt3
inet
in
yes
yes
tcp
port_ssh
floating_rule_2
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
opt1 opt2 opt3
IFGROUP1
vmx0
100
vmx0.100
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_dns_resolver_config_full.xml
================================================
23.3
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
Etc/UTC
em0
dhcp
dhcp6
0
em1
192.168.1.1
24
wan
0
em2
opt1
10.0.0.1
24
em1.100
VLAN 100
172.16.0.1
24
em1
100
VLAN 100 on LAN
em1.100
192.168.1.100
192.168.1.199
86400
172800
hmac-md5
allow
10.0.0.100
10.0.0.199
86400
172800
opt1.example.com
hmac-md5
allow
enabled
all
all
transparent
4
10
10
auto
512
200
86400
0
900
10000
disabled
1
1545602758
aggregated change
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_dns_resolver_config_init.xml
================================================
23.3
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
Etc/UTC
em0
dhcp
dhcp6
0
em1
192.168.1.1
24
wan
0
em2
opt1
10.0.0.1
24
em1.100
VLAN 100
172.16.0.1
24
em1
100
VLAN 100 on LAN
em1.100
192.168.1.100
192.168.1.199
86400
172800
hmac-md5
allow
10.0.0.100
10.0.0.199
86400
172800
opt1.example.com
hmac-md5
allow
enabled
1545602758
aggregated change
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_gateway_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
2001::
64
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
10.3.0.0/16
GW_WAN
GW_WAN route
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
one rule
wan
1544027052
1544027052
any
another rule
wan
1544027052
1544027052
any
third rule
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
one
1544085353
1544085353
wanip
22022
tcp
10.255.1.20
22
wan
two
pass
1544085353
1544085353
wanip
22022
tcp
10.255.1.20
22
wan
last
nat_5e03858acc6b82.92150982
1544085353
1544085353
wanip
22022
tcp
wan
NAT last
nat_5e03858acc6b82.92150982
fr1
test_separator
bg-info
lan
fr0
bg-info
opt2
fr3
bg-info
opt2
1545574416
tcp
test_rule
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_rule_2
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_lan_100_1
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
tcp
test_lan_100_2
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
another_queue
1545574416
tcp
test_lan_100_3
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
1545574416
tcp
test_lan_100_4
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
another_limiter
1545574416
tcp
test_lan_100_5
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
1545574416
tcp
test_rule_3
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
fbor
1545907554
tcp
test_rule_floating
1545574416
keep state
1545574416
wan
inet
pass
any
yes
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
GW_LAN
1545574416
tcp
not_rule_dst
1545574416
srv_admin
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
not_rule_src
1545574416
port_ssh
keep state
srv_admin
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
port_ldap_ssl
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1546111216
pass
opt2
inet
tcp
1546111216
admin
1546111216
admin
1546111271
pass
opt2
inet
tcp
1546111271
admin
1546111271
admin
1546111294
pass
opt2
inet
tcp
1546111294
admin
1546111294
admin
opt2
opt2
CBQ
100
Mb
opt2
10000
0
one_queue
50
Mb
on
opt2
1000
1
another_queue
50
Mb
on
on
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
one_limiter
1
-
100
Mb
none
on
none
0
wf2q+
droptail
another_limiter
2
-
1
Mb
none
on
none
0
wf2q+
droptail
disabled_limiter
3
-
1
Kb
none
none
0
wf2q+
droptail
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.240.1
GW_WAN
1
inet
Interface wan Gateway
wan
192.168.240.1
GW_DEFAULT
1
inet
Interface wan Gateway
wan
192.168.240.1
GW_WAN2
1
inet
Interface wan Gateway
lan
192.168.1.1
GW_LAN
1
inet
Interface lan Gateway
8.8.8.8
opt3
dynamic
OPT3_VTIV4
1
inet
GW_DEFAULT
GWGroup
- OPT3_VTIV4|1|address
- GW_LAN|2|address
down
Failover group
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
other
lan
5c0a4b6139b05
network
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_haproxy_backend_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
-
-
active
exchange.acme.org
exchange.acme.org
8080
<_index />
101
test-backend
uri
yes
lan
SSL
123456
yes
OPTIONS
test backend
none
passive
100
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_haproxy_backend_server_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
-
test-frontend
active
http
http-keep-alive
5b6b8cfb32997
yes
-
wan_ipv4
80
<_index>
-
-
active
exchange.acme.org
exchange.acme.org
443
<_index />
101
-
active
exchange2.acme.org
exchange2.acme.org
443
<_index />
102
5d85d3071588f
5df5edf6cae0f
5df5ec78b3048
test-backend
uri
yes
lan
SSL
123456
yes
OPTIONS
test backend
none
passive
100
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
5d85d3071588f
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJekNDQWd1Z0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFXTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEFlRncweE9UQTVNakV3TnpNMk16bGFGdzB5T1RBNU1UZ3dOek0yTXpsYU1CWXhGREFTQmdOVgpCQU1U
QzJsdWRHVnlibUZzTFdOaE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCjdySzNPNzlUdkFQbFJZVVZyY3o1eGE2dlo5REZzSTROMTRIL0ltaUZRbDZ6OVNRTVZlL0Jra1BaMTRCbG9iQ2oKQUNmYklFd0g5d05ydGQ5ZUJhdGFDS0puVm9WMTR5YzJwTkpMM1lod0hUSlgwMmVhRi8ye
nU2STN4WGRUZkJoMgo2NU5GdGRrMDNEdmhBbUdUWExrNHJCNm82V1Z0ZkFGSzNrMTlqVWk4K0hSVUV6MHJnSTFNNlZjeUJ2V2lrblJUCjl5TTRmbWppUzd1amljSGtHUnhpKzV6MndFY21zV3pzamV2Rks3cHVpcmRGU0FtQWVPNHNVQ084dmpHQ3I4OUwKVzUrWjNuMklxT3ZQZkxzRC9FTm5tWVdyUGU3b3Z3WUl4U2
91MFdKWGMvb044VGVUMnBxa3pLMnhHcEpCdjdyQgpVUHJLWWl4VmtnZi96YkNqN2dhUlV3SURBUUFCbzN3d2VqQWRCZ05WSFE0RUZnUVUvYlVDTmNhSFNyVUVwWXpHCkNvTDJrNHJBNUE4d1BnWURWUjBqQkRjd05ZQVUvYlVDTmNhSFNyVUVwWXpHQ29MMms0ckE1QStoR3FRWU1CWXgKRkRBU0JnTlZCQU1UQzJsdWR
HVnlibUZzTFdOaGdnRUFNQXdHQTFVZEV3UUZNQU1CQWY4d0N3WURWUjBQQkFRRApBZ0VHTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDaWZOMlBTN3FJZHFZd3BKL2J2eEsyNkVYZk9RaEFjRlUxCjN1YS9qRUpySVRmYUtETWhPK0ZYNGZudWpzcXFrY21sV3ZhNnJvdkhtdmh0TEpteHNZTWZZdkNsNVF4Rzg1S1kK
V0IwRWp6aUwxKzVRTFhSY0tiVFhJSkYzQ2lDcklBeW5ybUh3RWI3TEJ6clNCZFdNODQ2eVhrLytxUzZ0WEdvTApGYVd5MUw1MlNXTVlKaE5uVWVraFVGTld5TUdwdVJNWkJLVU5MZkFmbGJBdEpwUFVnZXpIaWI2WnRiekdoR1o0CldiUStVTWZsNWFuQ08wRHp3OUVGMEpGbkhyWTRnOHVrMFJnZmJqY3Q1akx3c2RFO
ElFbHRjbmtuR1Ayc0M0L2UKRUlubW9ocytUSzJKbWdoTmZVbHZNdGJIZ3ZuNjY0ZlJoTWs3R0g1SWM4QUZvRXQ2TGtrTAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRRHVzcmM3djFPOEErVkYKaFJXdHpQbkZycTluME1Xd2pnM1hnZjhpYUlWQ1hyUDFKQXhWNzhHU1E5blhnR1doc0tNQUo5c2dUQWYzQTJ1MQozMTRG
cTFvSW9tZFdoWFhqSnphazBrdmRpSEFkTWxmVFo1b1gvYk83b2pmRmQxTjhHSGJyazBXMTJUVGNPK0VDCllaTmN1VGlzSHFqcFpXMThBVXJlVFgyTlNMejRkRlFUUFN1QWpVenBWeklHOWFLU2RGUDNJemgrYU9KTHU2T0oKd2VRWkhHTDduUGJBUnlheGJPeU42OFVydW02S3QwVklDWUI0N2l4UUk3eStNWUt2ejB0Y
m41bmVmWWlvNjg5OAp1d1A4UTJlWmhhczk3dWkvQmdqRktpN1JZbGR6K2czeE41UGFtcVRNcmJFYWtrRy91c0ZRK3NwaUxGV1NCLy9OCnNLUHVCcEZUQWdNQkFBRUNnZ0VBSWFzYnBWWExYYzIrM1J0MGYxdFdMOGYvL2NpNDJicHZ3V0lZdHFXL2hpa04KZ1JwZ2t3NktZek1tMnZyRld4VXJLSzUyZmd1N1krQWJwVC
tvWjJ4UCtyL05pNnF2Z2liWjBrM0hYYXdOQkZGQgppaFFtN0JOZzhxcGk2M3QxVHgrMFJtNHpldjcwTWN4UVpLR1d3NUZzcVBwNklOR3BxOEFTNjJCS3dVN3RPSzRLCkhMdzNoK0NndGtxM01LQUdYTlNFZEJTQVZlNWE1SDZMZ3VYaEw3ZFhDSFE3ZFIwWkpGYmdzZjNVNmxjLy9FWmQKZVp6MzZ0emVlSnBMcmx3blN
1TjN1QVpKK2dYOWVXOWlaWHNDN0szNEwwVFF6QktNZmxpYXVkU0Z0dGtIb1ZhTwpXN1ZOdlR6VndBRFdEUmVmQyt4QnBWOFRKNmIzbGNjeUpxN0czTDN1QVFLQmdRRDlBYXJNYWdKYVNsYTdJMG5PCitWcFNTdTF6enZ3TUtGWFFzcjU4WU9WQjlHN2dXbzlnSytwVEpPd0RYWmt4eUdPeTRJaU5MQUxCL1JKYVh2YzEK
Q1YwY2pHZG1aOURnK3gxSUVvNFJhcC9hR1hOQU1lOXRsY3VhMSt4VDl6OFQ2WWNvQnNuc0ZhRTE4RUVhcG9pdgpSdVNXS2lhK2ZVL1oybHVlWW1SajUwdG1rd0tCZ1FEeGhiV3pmdU5VV3F0MWxBdkpEUUMyNGlGOEs5eDVjNnRqCnpjZk9YUWlIRHA5Tk1ZY1ZQZzlkZFFEbEl6TTZxdU5QRGRIRi81UEJkcHIxV2dha
StSNUJHZS9QVUpHajBWZHoKYzQxcnc4dUV0djBKYmcwemsyNCtBVE90SHFHV3h4ZTlNdUpmSkVlcS9RZnlrSFhMaUpYOGxNNjI5eFNPYXZDNgorOVU4UmZZaVFRS0JnUUNNQmxNd0JPKytadXd2Y0MxV1JuenRsSmhzZ3I3T1A3aVc0NFkzTWkxUDNtZEs5ZlBxCkJ3UU45aDVGRXBWSmp4MVdqNXdqZlpzZEgrSDdjQm
1qR2NhVm1VTlUwdG93MmVudnpJMGlLSC9GNWxYNXpta3oKbmpic1FOcUQ4Zy9RQm13T0JBdlhCSFdQYzZPa2kweVVyVWl2cnk1NE9NUnJ6c1RzR0lwMUhEcUh6d0tCZ1FEYwpjamk0enZaYnhpNTlzVlZZZXYvTjdYUzJ5Nm4vU082RXVBekpkTzBWcU9rS0lreXp2T04xWUJnakRRL3pXb3NECld3Ky9jMW0vMmF2eWl
ldEkwYTBscXJEU1B5M1Bvb0g1cE56T0lmWWUrbzdYRHBZS0gyVWVQeWFWdTBHdVI2YUoKVUdxQ2dMRFJod2E4QmRNZXBWcVJJcG5UTW82V3QxRm11Zm1WZkIzK2dRS0JnUUNxVkF1MGV0T2lwZDdUeUcwTgo4TE5oZnBUblppeEZ1eUhPRWZzUE5wTlVQOVNWY2RudGNQUG1uZysycG1YQUp1N09GOHNTK09zL3NXK0xY
dk9oCnptSlp6OG9venBRUElwaTRxbksvYVMxUXFHSUp0dzg2KzBja0xKNVJaUlA0MjVoYXFvb29GQWgxMjBUUXNaSVUKOUdxQlZ0QXV5MTNsOW9vbHMxK2NJR1pmSXc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
2
5df5ec5668d9f
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJekNDQWd1Z0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFXTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEFlRncweE9URXlNVFV3T0RFNE16QmFGdzB5T1RFeU1USXdPREU0TXpCYU1CWXhGREFTQmdOVgpCQU1U
QzJsdWRHVnlibUZzTFdOaE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCm5Pd0pNak5ZajJLNTc0dE00WDdQWDlVRDVPTE55RlNwNCtPTVhEMUpHMFg4WWlkbFFFQW5FOUxVTE5JQUE2bVYKaHl3Z3prVGVvbDFvcnkwTkNMWHkxU1J0eEUvL2taaDNGMGx1cnUxNWJ6bVMxZ2I1b2x1b
3V4Tno0WTF0YmZleQpMVHVML0dpUHNJV2plYnBjUFdMUWpsZS93aC9Tc3JnYzFPeVNybDYwVnBZRHNaK25RTmM0Yy9qWnJvYXU3cEtSCjB0U1hQUzZXemhwb0x5aEtRQkFGZ0QwOUhQcDQ2VUg4bVVpYXdQcXpiMDhGQzd3elFIOVczck0xb1E2UXp3bGkKVUJRWjkrYnVvdUllL3dtN0VOWG5ibVFDSlk3VXV2aWttcn
dJRE5lakwrV2UxaVJnR1V3bG5JVHptOUlJYXBTVAp5bTZXYXo4WlBHdFo0SndoNjFsOVR3SURBUUFCbzN3d2VqQWRCZ05WSFE0RUZnUVVZSDRuNGNyOUtwU1U0aXNmCjliTG82Yk9HWTJFd1BnWURWUjBqQkRjd05ZQVVZSDRuNGNyOUtwU1U0aXNmOWJMbzZiT0dZMkdoR3FRWU1CWXgKRkRBU0JnTlZCQU1UQzJsdWR
HVnlibUZzTFdOaGdnRUFNQXdHQTFVZEV3UUZNQU1CQWY4d0N3WURWUjBQQkFRRApBZ0VHTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCS1AvTVQ1OUdTVmVKbnpBcEhPNW1aaUJlazJwT3pSd0cvCkZ3VVBjYWd2Vyt4UVlPcEp2eHFQTFZvOU91RjBOZTRFOG9aZU5HbDB0U2k2VWtpV2FqRVMyUnB2NllCSnFSVlAK
dmdPdHdVeUptSkV5UVZ1SUNDNEkvK0k1NUR4d0ZTZW05bDZJRjg4aEVaaXBQVEZvbUlCNGRwb1JHL2NOLzEydAp0UXpSem9lc05CQTJaeEZFcTBCU0JMazhsenBrN1RmOFFFVWJ1Yk5xcWUybmUxZ2oyeFlsRXlERVYzUHhTMVcxCnFhUjl2Y0h0d0pXSGoyblhpQlRnTjQ4aW5sVU1uQW5EWnozZm84RTNuRGk1SHFCW
FhOQWV4cDZobkxNRDBHYWcKVmtrS0J0U3BKUG9wSmlreWRGOXViYlo2NGF0QUd5WEd4MHRkdHZkY3dFSjJVcWdoYi9wKwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2M3QWt5TTFpUFlybnYKaTB6aGZzOWYxUVBrNHMzSVZLbmo0NHhjUFVrYlJmeGlKMlZBUUNjVDB0UXMwZ0FEcVpXSExDRE9STjZpWFdpdgpMUTBJ
dGZMVkpHM0VULytSbUhjWFNXNnU3WGx2T1pMV0J2bWlXNmk3RTNQaGpXMXQ5N0l0TzR2OGFJK3doYU41CnVsdzlZdENPVjcvQ0g5S3l1QnpVN0pLdVhyUldsZ094bjZkQTF6aHorTm11aHE3dWtwSFMxSmM5THBiT0dtZ3YKS0VwQUVBV0FQVDBjK25qcFFmeVpTSnJBK3JOdlR3VUx2RE5BZjFiZXN6V2hEcERQQ1dKU
UZCbjM1dTZpNGg3LwpDYnNRMWVkdVpBSWxqdFM2K0tTYXZBZ00xNk12NVo3V0pHQVpUQ1djaFBPYjBnaHFsSlBLYnBaclB4azhhMW5nCm5DSHJXWDFQQWdNQkFBRUNnZ0VBU2JIb0Y3VlpiKzFUbVJYNGd5WUV5ZUxHTTJYNGNvNmdRckFJcVFEM0ptSFYKeDFydENDR1l1SXlZckh2ckdjMDR0Z1NaVzRXb2NPUWhWN0
4yblpLbGVrZmpiUUNNNjVtSXhGenVLQ1RLbE51YgpBL3krbStzajhNZVA4bXR5NnVRYXYzZHBwSjZPYkw0MktlTEZrdWFuUDFJaWNrNXpxdzhnS3piM0pBREIvSzgyCnh0VFpxa0R6RTk5ZStSV3hEclNGYnFmOHlxbkZTenE3ZTZzMXJKc3JjbllISHU2TCtqRnptWDVLcUh5UEVzZUgKTmY1VVdQdXZkU0RJN1VsOVB
adUdIeVlOZkxuTHJsWW9jb3lZMzJZajQySThEVFhlMWs4ZjNGalV2anZDSnVhNgp1cWtxM2p3WnA2a1pCdUlKeE0xZmlHbDdkNHVvQzVuVWhwOE13R0x4QVFLQmdRREk0Wm5LMGxKTU13UlVVTVBmCmMrclFpdk42SVM1YTl3TytGanluQytKMjhkRXUvRUZORmhncEpQRUVyalZobGM4WnJYNk1HdGVpQ0o5RjhlQzAK
T1RBaUFHQ0FRaDdFUnY0Wm01bEdNdFRab2NwWTB6SXIzLzFwWEZ0aTZ6T21pdTgyZ0tXb0tyVC9mTXcwejJXbwpYN0p6QWZqcENOdkNUU0sxdWJEQ2M1anN3UUtCZ1FESCtxRC9Vc1E1d0NVRnVGbmZ3d0ZxKytDdmo0KzJsVnpHCnNLVVh4TU85VWpUeU1ISkg0ZVB0SmVobEY3Zk1oZ1RhRWttK2NUSU81SXhzekNlQ
XFwcGppZ283U0NsWVl6cHIKQmlQYkg4bkRwS2h3THltU2cwT1RwZGZUM2gwbnFGMk1tcGxGcytCbEZ5R3hBaEF0V1E4OWRzbnJVMUxrQmtaVAo3dHkwc2VVZUR3S0JnUUNaTU1JUmxCYkNtVjFCMTEyb09FUG55UzhvQWNuU0FQSFBzakdLeWx0bmpiMlNjVGh6ClN0Z2QrRmY4MDhWTnArc0pqKzhqZFF6Z09tY0w5Z3
NBSldSNWFZUXVDejVmUEN4bTV1d2FSb1ZmZWl4UFZLKzgKR2RLczJJdXdnZUVmcm5BQ1JsVU9TNU5BQ3JISHpDbWdIMnA4bmhKdDFCMlNyVFE5RldDZ3lYcmpnUUtCZ0FjMwpieW5ubGJjenpPSTQ5ZDFTVHpIS0p5Q093TFBwRGZFSm5OdVB1VFNTeStCVmlUbzQxNTNPT1dXMGhtaDJDYVRJCjlpcE9NVzFDS0NNZzl
1ZjR3QnVKZHJjTzhwY0M5YSs1Q0FBM1ZIT2Y5SWdmMjVNeTgzUlhqc05SaDZIUHZKaC8KVjdxaE5CN1J4K0I0WDVjVFdLNXlJL1UvQzRXNUNENnRKdi9LVDZkWEFvR0FVQjBJZlp4M24wLzRmMTE3U1puawpXMmp3REVUWVU1MUVTU3hlVTVBRW9DVGJOOWkyL0lEenM1RUNzaVVQMDZ3aGxaNU1wQkZndHVNOXJaYytH
TjRqCjlzbkMwTjVuT2c0TlNhUXNiNzVNVm5FOVoxVDYrWHIwd1dxcUw0WXZzZ01GMzhJdmJORGFqbU9jMVlIL3ZLbTEKYjdab3dMbmhDV1FPQXBoWkV0enhpME09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
2
5df5edf6cae0f
5d85d3071588f
internal
9999
3650
5df5ee048c106
5df5ec5668d9f
internal
9999
3650
5df5ec78b3048
user
5d85d3071588f
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURmakNDQW1hZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFXTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEFlRncweE9URXlNVFV3T0RFNU1EUmFGdzB5T1RFeU1USXdPREU1TURSYU1CUXhFakFRQmdOVgpCQU1V
Q1hSbGMzUmZZMlZ5ZERDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBUGtRCkVuT09zRXlqVGNaSHg1enVWUzMwUFlnU0JSZW5MUHFNV1NpWWhtYU9aeTcxM2h1QVFWRVlZOXNnSk9NeFBuL2kKeVR1a3l4VHNLNFBSOW9qUXpvVllVb2RkMU1Rajh2NENTbTlVc1VpNkJEWDl3cFcwdnp1T
HZFcUVMTzIzVDBrTQptMzRITlU5RXFVQXNXaW54VTY5TFlYZlJkYStrclhKcGNhbUQvMDNYTE54TlBQQXg5dHJhSURxSGwvY3pmcjlGCit0aWt2dDFDMENLMHZoUkh1cnJGZE91aEIyTHBBVVEvTnU1bjJoS3hvN1htekRjMjk0bmpCSDBSSUhscXdSY3EKblBqMlEvb2NWbG1vakhnMzdJcmx2R0F0RjdVaUJxdFF5aE
VvR1RmRUhHemJzM1ZtZ0piN3AyQzNBOW9reDBRWQozN2toV3pjUDI4dFRsTUdLNHo4Q0F3RUFBYU9CMkRDQjFUQUpCZ05WSFJNRUFqQUFNQXNHQTFVZER3UUVBd0lGCjREQXhCZ2xnaGtnQmh2aENBUTBFSkJZaVQzQmxibE5UVENCSFpXNWxjbUYwWldRZ1ZYTmxjaUJEWlhKMGFXWnAKWTJGMFpUQWRCZ05WSFE0RUZ
nUVVoYTVnQXFTZW5sY2VqMDlleFZzaDVHYVduRjh3UGdZRFZSMGpCRGN3TllBVQovYlVDTmNhSFNyVUVwWXpHQ29MMms0ckE1QStoR3FRWU1CWXhGREFTQmdOVkJBTVRDMmx1ZEdWeWJtRnNMV05oCmdnRUFNQk1HQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01DTUJRR0ExVWRFUVFOTUF1Q0NYUmxjM1JmWTJWeWREQU4K
QmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBVVFrU2lKNWlnUExVbDBiaW4vVGRoY2k3clloK2lqWURTN1Jra3FhZgpmTEtDTXpJMmw5UFZkZFlGQXAxYW93eVFleEUrRjNLMUdjOWpKRmRFTjRIWG9ZUGQwWk53VVJ4c0tGd0dvcTUrCllFdy85RUk2dEt3aEUwdHZqbFA1WmZtMVVXY2E5emdGQlYzMHdCcFZSRlVLRG1je
VBwKzA1cnhzK1ZscnRJa0IKWjVDYzZBT3BvZFh6RzNXTzJkdHRJT0xhT09hdkpIRUpGU0hvTmhOY3RzT3oySEx2eGRzRU9STXJlUXZzeTNXRgo4TzUwSGlNWUdjVnBETXFkSnc5dE5UZWZtcXd3UDVlSXFKb0xXeng3SnhaK1gzRlZCVzdlUTlhNnh4L0JmTkNzCkFnZVM0d3V0eG9Ca0UyV05pTU53d1BzWFFzdXdCVT
RPSFFnOXB5MTRmc0NpS3c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRDVFQkp6anJCTW8wM0cKUjhlYzdsVXQ5RDJJRWdVWHB5ejZqRmtvbUlabWptY3U5ZDRiZ0VGUkdHUGJJQ1RqTVQ1LzRzazdwTXNVN0N1RAowZmFJ
ME02RldGS0hYZFRFSS9MK0FrcHZWTEZJdWdRMS9jS1Z0TDg3aTd4S2hDenR0MDlKREp0K0J6VlBSS2xBCkxGb3A4Vk92UzJGMzBYV3ZwSzF5YVhHcGcvOU4xeXpjVFR6d01mYmEyaUE2aDVmM00zNi9SZnJZcEw3ZFF0QWkKdEw0VVI3cTZ4WFRyb1FkaTZRRkVQemJ1WjlvU3NhTzE1c3czTnZlSjR3UjlFU0I1YXNFW
EtwejQ5a1A2SEZaWgpxSXg0Tit5SzVieGdMUmUxSWdhclVNb1JLQmszeEJ4czI3TjFab0NXKzZkZ3R3UGFKTWRFR04rNUlWczNEOXZMClU1VEJpdU0vQWdNQkFBRUNnZ0VBT1VHQ25HSWZFVjh4VC9YTVk3MCtnN1AwT1VXN09mYktsa2FSY0kydngvL1EKcExFTkFGRjVzb1RpMzhzQjcrQ3dONElSTk03cmlNSEtOeU
diaFZSTFJjaEtJS2huY3plNGNzQmdFRHZ1RFlRRgoxOHVnWWY0TlFFa2RYaHdJb0JWVityc1ZPK0c0VmFLNUxmR0VRTVFqc3RhbmIzNE5pZlZYa2tlL1EzTCt6QXZKClAzVkdmK0FmTnZRS3RKeEdZL3VteVpmdUczNVFVdTE2SFlyQllXYldTN01jeEtNZ3hIclZmNEN3TkRLK05qVEsKTGRGa2VvTzMxVDBJa3FZc1R
5OVBJY3VzcnRzMXRvM0pVQ1lJK2FjYkJ5M0o5Y0tpbTJNUmo5R1lPTWY1UnZjLwoxTXU1SjBqTHQvcHBCQkNqWlNFMEZSU2hjREpuNTV5VGdzWmJ4YUxCY1FLQmdRRC9hS3FoOEVZcitBVTdDUURnCmdMRVZGREU0WnV4QkZDVks4R3J3dXJTNlZvYkpLd2hYNGxFaDlURTN5Y2tpdlVrQ0hKNW1YYjd2blNiSXg4d24K
cHBlVytDb3pNeXl1Qk9ac01kbEFGL3VyRHhBeFNHbmJHVmE3UHpBMktXbTgxQWo3Mk10S1ErWHc3dmgxVTJKWQpGNVlzS3NIaGRZcm8vUGt1TzNYR2psTE5YUUtCZ1FENW82VTVFcUsxZFQ4WnVLbkZXNkNsTDg5RWkwVHpFNXgzClVGQ3ZhRkhLc0NRRkNETzYwMW5BRW15bGd6K3d3alUxQzA1Rm9tbHBlZHIxTW5QO
VFKbGFTcGJSUG9paUQyTDYKdlo4WEdEanA3OFhrTkwyZmlQajhtVTVhYnFmdU8yclBXemQrUHdkUzZnSEFWWTFkZHhwVG9FKzJQUDlhTytwZwp4UVR5RFpjTlN3S0JnRVZwbWE0SEVkc1RQY0Nza3J0dzNpOS9YRjBhdzZ4d0lDNThwbU9rS2tmV0NpMlQ3MkdMCkZVZW9UbWIrQlJiVzRJZWRaWU1taVNTdnpnR1ZJeX
ZIZjcwODFxTXhqandCQXVwMnBiZGdqVG9SRHZLRjNwYjQKRFBVNG1BNndOSmRuL01VTlJ6cDR0MzVDUSs0RzZseE1sTGlPS2tEdTVBQ3I5OENUMGs5VEVMZlZBb0dBVVR2TgpNZ3M4WXl2cUFxYXlxQVF1L0ZIWWNvZVhRam9wZWpIcWI0blV3ODRaaW9HbXc2NnV6Kyt1NXhVVWpxSkR5aVVKCnJXTXlSaDVlLzljUkk
ySDRCUWVzbUI1dk83MGlDbEhXQ1IvYXRWVk80Zm0va3NCeHRWRDF6RHVoN2NYcjlkbksKN3d2M2lDV1p1RXFlbThGOEVhWmpDZnhEWTh1SGMwSHBZQkxJQ28wQ2dZQTA1M2dLcGRiWmZIdnFnL2E4c0E2dwp0cFlOTEJlcXJPd3BsMWNHOVpyc3hFMmEyT3dZWnB6ei9mdlp1bWhVRmZTTTJrM2Iyd2pBSG1uTkF4eWpT
QjJUCjlQUCt6NHFXOWtwNUdZWGJjdnE1cjY5bVM4MWJjdktMdW9kd3lLS2RjQ0NPRzB3bVpGZk1CajlVY3NrZ2hCcFAKZ2oxOVFuYU1iM3hFU2dqeXQ5clA2Zz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5df5ec97dfd07
user
5df5ec5668d9f
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURnRENDQW1pZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFXTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEFlRncweE9URXlNVFV3T0RFNU16VmFGdzB5T1RFeU1USXdPREU1TXpWYU1CVXhFekFSQmdOVgpCQU1V
Q25SbGMzUmZZMlZ5ZERJd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURiCkRQWi9zNDFmNkpqL0JIMXlzekVnb0xOVTF1OGxaQ1dEblczSjBuWFY5WnZ1REFFS1pBd3FHL1BpK09ZM0xLRmQKZlRiN1VoZ1ltdmZSVndkSVZGK2tPYkxEdHRmTGNLU0E0TkRISzJ0eSttaFpMWHQ4VGpXZ
zdrNzJ6SWxhVzdadQpXazhEaGlabTFvR0NJU2R2MWVYN3NsL2tJdXNwZkVFQW5rbUI0dUVrVjllWmZZaktDTzBKTG5ZMzZJeEx5bmxTCnBIZDBOS1pEdjZ2MzhpSnAvOGdYekxDWHdObUpCMzFwSit6NmVoL3g5QUx5OGphSnlzN2dQV1dQb2s2Q0JGZlUKRXhJV2FuRUhsbHdIYmNwSlJsbGpHbHplSGx3UVZUUUQxc3
J4aXpsT2xKUDFORGlmc25UWHEwSW1ENjlmZ2t6QQpnRnBnYmFvZWtUakhNbGdmQXgyL0FnTUJBQUdqZ2Rrd2dkWXdDUVlEVlIwVEJBSXdBREFMQmdOVkhROEVCQU1DCkJlQXdNUVlKWUlaSUFZYjRRZ0VOQkNRV0lrOXdaVzVUVTB3Z1IyVnVaWEpoZEdWa0lGVnpaWElnUTJWeWRHbG0KYVdOaGRHVXdIUVlEVlIwT0J
CWUVGRlRCMnZOM05SblBVSjhBcDlKT3J5REorOEdrTUQ0R0ExVWRJd1EzTURXQQpGR0IrSitISy9TcVVsT0lySC9XeTZPbXpobU5ob1Jxa0dEQVdNUlF3RWdZRFZRUURFd3RwYm5SbGNtNWhiQzFqCllZSUJBREFUQmdOVkhTVUVEREFLQmdnckJnRUZCUWNEQWpBVkJnTlZIUkVFRGpBTWdncDBaWE4wWDJObGNuUXkK
TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBbENGREFrS2JZVStPWWxkSkR4eklGRHlPN3FvZE1odjR3VDhJcwpURlgycWdSRGlvN0w0VnJtT0JqT3pMNjlVdlFLcHgvRjYrN1BidGticDJPSUNwT1dHR2xIN3FrM1NuUnA4bm5iCjVuQzdoc0l5RkdJNnpuSTJscHpzcFh2QzRDdUgySjYwMi9DY1V2YXBENDlPVmJ6U
3p0cW8wSHpLMnB2V04zMFQKSlJUaGxiN0JHZU5iKzJqNUpwS2F4SzEzRTlnbnJZQkZPL2RmeS9ldThWdytkQ0dLM3J6UUJJdkZ3ck0rK1RmSApNaE9MRXB0NE1rVkQxeWwxdWlHL281UUthT3lLb2RBL2VIVmdidlgvUFMxakZyVUcwUTZvdXJEa1FDNnJCdDYwClpKaHA3SzlxSU5rYVlQclZuY3NaeWJ0aUx1bnpMLz
A3QUZrR1Bacm13b1UvdXMxMgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRRGJEUFovczQxZjZKai8KQkgxeXN6RWdvTE5VMXU4bFpDV0RuVzNKMG5YVjladnVEQUVLWkF3cUcvUGkrT1kzTEtGZGZUYjdVaGdZbXZmUgpWd2RJ
VkYra09iTER0dGZMY0tTQTROREhLMnR5K21oWkxYdDhUaldnN2s3MnpJbGFXN1p1V2s4RGhpWm0xb0dDCklTZHYxZVg3c2wva0l1c3BmRUVBbmttQjR1RWtWOWVaZllqS0NPMEpMblkzNkl4THlubFNwSGQwTktaRHY2djMKOGlKcC84Z1h6TENYd05tSkIzMXBKK3o2ZWgveDlBTHk4amFKeXM3Z1BXV1BvazZDQkZmV
UV4SVdhbkVIbGx3SApiY3BKUmxsakdsemVIbHdRVlRRRDFzcnhpemxPbEpQMU5EaWZzblRYcTBJbUQ2OWZna3pBZ0ZwZ2Jhb2VrVGpICk1sZ2ZBeDIvQWdNQkFBRUNnZ0VBR21Hc1NtTUFrdDE3bDdNZHZMT3lKZ0RVdUZJT2lVMVBhMU9KaG5ZUGRRNVkKa2tubDBpK01lNEhVVkxDdllWUEJ0M1crS25TUHJGNzFtOF
BFZjhTUnBFRENzK21RZ2RMMkxRTm1nYVpNRVpQdwovMVNIKzhlYms5OC9xVittNXpnR2Q3SHV0RGp5NVEzT21US1BBbHdiajczQzZNSC8yb1k1ZWU2V1ljdEdYRTd5Ci9QSXAzaWVQZk5XM1lIUUt0ZmdWc3FrcFJTL3kyS09qVG5xMXpmNDlpOUozbWFUbnlzMVVScHVsMWw2V2t0MGkKUXY0bW8xMW83ODFLS3JUand
vYjRxNml0eE9BUGxXNEFMNWRUYmdOazN4Z0k3K2h3Z0VVVS9USlA2djZsVys4SwpyaG9xQU1ySERQeFJvNmZ4N3dsYVkzUWdVNzUrZUxORlh3VWVXb1NuWVFLQmdRRHlkeUVHMko5Ymh0ZHRmdFQvCmpicCtvcVdsd0hidEE3WlhzRGx0Z045SUJvQUlXMmJ0RmVhQjhBU1ppL0M2Z2I3Ym1lQUQ1YmFOdDExNURkRGUK
TGd2TEswenJrdFhqeHlXakN0RDhOczY5MUs1ZjFSK2J0RHpQNnZvaG9DbWxrTHg5dFBsNlJINFFwekVrajM4cQpYWTFWTlRkQlpXSjdqWEh6T1Nha3V0SVNUd0tCZ1FEblJ6dlNBd0tSKzVwMUE1QXdBNHJSSFpnQm1IVUoxRDVEClVFQkZVTWl2T1d0c1U2L3NoaW5kM1BqQ0lMYldmT1lUZGorRkVpaHJKNUhyN0dUT
mgzd0htR3FDMWNYbXNEQXgKR2pVK1ArbW1ESTlYR3d4czJpV0VyeDQwMXBHSkJRWDErWDhVQSs2eU81YithNG5Scm9jaDRHQUpEMUlUUUZhQgpEN3VMY2RpUmtRS0JnUUN4S1paNk9DSUljSGhNUzFZNi9FWCtiYjEzWlRBQWNxdnpXcEk3anVTVzdoanpBa1lzCmljb0p1MlRCQk4xNmlYNzVXbSt5cVZCQU1IRWVHSm
c3SDQ0Q2Y3c09qaFcyL3paaThXNU9PNFhJY3ZTT2xWMHAKR2dNdE1OWmZaM1hHd2dEWUlUb0F1cmNhVisreWRSOUh4MStUbVBTczFmV2xyVzVMZDhEaXNKYjZSUUtCZ1FDUQpOaUc5N1RTUUZJUVdUTGQreU9hcFNEdTlXdlEzd2NsbExjZzFzSTdDZkJwWXZNSW95T3VZTlM1RDFpUk1FQ3RpCm5VdzBUTU01TmI1T2p
1TzRLTEFWQ0Z2NTR4UjRxUW9UeEJUSkVROC9BL3ZwUTZ0MndIdStoWGRmV1IvOGdVNWIKTjB5ZkRXaXNhRzNaalBLdDlaYTdiKzVTRitTRXJveUNBMjVkSUFyN2tRS0JnUUNnVXhNUm1zWGhGanFjNVYyQwptbDh2VFFxc254dklkdzVQWjFPSkdBMHROcXh0dTc5dTliSnBUdGd0YVhGV1FBSGlnMlBESWdsYi9HdjFh
ekJXCmU3a1FUcHFaNmY4QjRmUWg5MDQwYnJlcm9aNlE2REtIQWlna0lsbmdaUXVVNEU5MDhjVzdWbk10dUhpNTN2WDEKRjNaci9nVE9BV2E3NkJuY3hkYzNQNTZndHc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_interface_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
2001::2001:22
64
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_1100
vmx1.1100
172.16.151.210
24
lan_1200
vmx1.1200
172.16.152.210
24
vmx4
vt2
dhcp
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1560930241
match
lan
inet
in
yes
yes
tcp
port_ssh
floating_rule_1
1560930241
match
lan,wan,opt3
inet
in
yes
yes
tcp
port_ssh
floating_rule_2
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
IFGROUP1
opt1 opt3
vmx0
100
vmx0.100
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_ipsec_aggregate_config.xml
================================================
22.2
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
sha256
14
-
aes
256
sha256
sha256
14
-
aes128gcm
128
sha256
sha256
14
-
blowfish
256
aesxcbc
sha256
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
one_p2
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
another_p2
1
5db7be3c0502f
tunnel
3
network
1.2.3.4/24
24
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
third_p2
1
5db7be207c846
tunnel
4
lan
network
10.20.30.40
24
network
1.2.3.4
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
nat_p2
2
ikev2
opt3
1.2.3.6
inet
myaddress
peeraddress
-
aes
128
sha256
sha256
14
-
aes
256
sha256
sha256
14
-
aes128gcm
128
sha256
sha256
14
-
blowfish
256
aesxcbc
sha256
14
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_ipsec_config.xml
================================================
22.2
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
25000
1234
pre_shared_key
test_tunnel
on
off
10
5
10
ikev2
opt3
1.2.4.16
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1000
1000
1000
4501
1234
pre_shared_key
test_tunnel_2_5_0
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
2
ikev2
opt3
1.2.3.6
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
carp
wan
90
100
1
602874de0ff00
single
29
151.25.19.11
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_ipsec_p2_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
one_p2
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
another_p2
1
5db7be3c0502f
tunnel
3
network
1.2.3.4/24
24
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
3des
hmac_sha256
14
3600
third_p2
1
5db7be207c846
tunnel
4
lan
network
10.20.30.40
24
network
1.2.3.4
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
nat_p2
2
ikev2
opt3
1.2.3.6
inet
myaddress
peeraddress
-
aes
128
sha256
14
-
aes
256
sha256
14
-
aes128gcm
128
sha256
14
-
blowfish
256
aesxcbc
14
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_ipsec_proposal_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1
ikev2
opt3
1.2.4.8
inet
myaddress
peeraddress
-
aes
128
sha256
sha256
14
-
aes
256
sha256
sha256
14
-
aes128gcm
128
sha256
sha256
14
-
blowfish
256
aesxcbc
sha256
14
28800
1234
pre_shared_key
test_tunnel
on
off
10
5
1
5db7be207c845
tunnel
1
lan
network
10.20.30.40
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
1
5db7be3c0502e
tunnel
2
lan
network
10.20.30.50
24
esp
aes
128
aes128gcm
128
hmac_sha256
14
3600
2
ikev1
opt3
1.2.3.6
inet
myaddress
peeraddress
28800
1234
5c00e5f9029df
5db509cfed87d
rsasig
test_tunnel2
on
off
10
5
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5c00e5f9029de
webConfigurator default copy
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5db509cfed87d
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
5db509cfed87e
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyVENDQXNHZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJTTVJRd0VnWURWUVFERXd0cGJuUmwKY201aGJDMWpZVEVOTUFzR0ExVUVDQk1FZEdWemRERU5NQXNHQTFVRUJ4TUVkR1Z6ZERFTk1Bc0dBMVVFQ2hNRQpkR1Z6
ZERFTk1Bc0dBMVVFQ3hNRWRHVnpkREFlRncweE9URXdNamN3TXpBMk5UWmFGdzB5T1RFd01qUXdNekEyCk5UWmFNRkl4RkRBU0JnTlZCQU1UQzJsdWRHVnlibUZzTFdOaE1RMHdDd1lEVlFRSUV3UjBaWE4wTVEwd0N3WUQKVlFRSEV3UjBaWE4wTVEwd0N3WURWUVFLRXdSMFpYTjBNUTB3Q3dZRFZRUUxFd1IwWlhOM
E1JSUJJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdpbHJ0bjJMaVpSc0g3Tit4MjZKK3BOcEhFL0FncWoyCkNYeG5wQUZKdWVCSlhaSVdPZWtEOEVteHRZUkQ2Wi9jMEEya0ZsTXk3VmwwTVZpL3Q3QUx1NEJVUWI3ZVNKQXgKaWd0VXMwTHJRbHhNSDh5S3VqcEg1RWtlZHZoYmtRcW0zNG
02T21oR2RwUDBsT3BxWUFJN3pwYzlwODl2M1FPcwptUHBibkNDNE9kRUR6WDNFWWZ5YVZNWXZCa3FTVTZPczZ4VXRRL3JCV2F2T3lzQXlGSzBJSmFYRHI3QjVzZVV3CkdZOG5laHVJTmpicjlXbkFXN0ozcHZ0ZHhRbS9JUENHRGJXYnBtQVpEalRkQ1p3ZTU0MTlnejBZNlJVUDV6bGgKZFNKY1F6cDZjalR4aytiM1Y
wd3Z5VlRDKzlmMC9wbDRMS3FIZE9ycWt4dHNEaUFzNUZjenlRSURBUUFCbzRHNQpNSUcyTUIwR0ExVWREZ1FXQkJUUnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnV6QjZCZ05WSFNNRWN6QnhnQlRSCnNyckN4cVUvMWxvTTBnS1RFNmlaMmxJNnU2RldwRlF3VWpFVU1CSUdBMVVFQXhNTGFXNTBaWEp1WVd3dFkyRXgK
RFRBTEJnTlZCQWdUQkhSbGMzUXhEVEFMQmdOVkJBY1RCSFJsYzNReERUQUxCZ05WQkFvVEJIUmxjM1F4RFRBTApCZ05WQkFzVEJIUmxjM1NDQVFBd0RBWURWUjBUQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DQVFZd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBQWd4MThXZk1ZVEcvdkw5d045clZmRmJNRnE3N2g0W
GhBYkJPK1ZGR1liMlBRRXQKcFdwWW9sUjl0aUwvSzhXMGFCcEt6SkRtam1zKzVkNEtlYkxFdnNzdGttSi8vSlFVODdmVU1MVGp3WTBremdHUgpSMkNaUlJJcEc1cEJ6M0ZWeGU4a211YVppWDNvbGlHM2FzV1ByYmpQUUdzU21aRTZ0Y3h1Unh2cW4zOVVoSzRyCnJ4VDZqKzdxRGVWbmFzcThkYWdqT3ZKVjh1MTk4eG
VyQmZwaFkzdDJwMEJjTDF4NmhrWWwvNnI2VGxTVmEwT1EKVUJXWHNDR1NGK1QrY2Z0VE43OEhOYTJFZlNRelMwRkVRTTJrY1VzMGV4cE9YZlo4UU1BdU5lVEpvc013NVh3ego0bERDUFEwZ09yWUxvdWJVWDlwK0NBSi9qeUNxc3FwRW53bmRiUEE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRENLV3UyZll1SmxHd2YKczM3SGJvbjZrMmtjVDhDQ3FQWUpmR2VrQVVtNTRFbGRraFk1NlFQd1NiRzFoRVBwbjl6UURhUVdVekx0V1hReApXTCsz
c0F1N2dGUkJ2dDVJa0RHS0MxU3pRdXRDWEV3ZnpJcTZPa2ZrU1I1MitGdVJDcWJmaWJvNmFFWjJrL1NVCjZtcGdBanZPbHoybnoyL2RBNnlZK2x1Y0lMZzUwUVBOZmNSaC9KcFV4aThHU3BKVG82enJGUzFEK3NGWnE4N0sKd0RJVXJRZ2xwY092c0hteDVUQVpqeWQ2RzRnMk51djFhY0Jic25lbSsxM0ZDYjhnOElZT
nRadW1ZQmtPTk4wSgpuQjdualgyRFBSanBGUS9uT1dGMUlseERPbnB5TlBHVDV2ZFhUQy9KVk1MNzEvVCttWGdzcW9kMDZ1cVRHMndPCklDemtWelBKQWdNQkFBRUNnZ0VBT3oyZ0c4SWFmUlBJR2JRT2pwTHZqb0REcFZ2QUJSM0FWQXlkSXJFeDZZREIKWWNkYytMWmIrVWpDNi9zN0xXRVZZbldIQnpqRFpSL1NEK3
hnTW8rSVJPRlcwK2lFc2VjczlrMld2a3RBdExLcwpsMWMxVExUVGtwZXNyK2YvS0RYenpHaWRaWXpEVXhLNW9XWVVwczZIcVBVRVh0c3Y2bU5nbWh4cEx5M2NoK2J4CjlWZ0daMm40NWhTU2NnQnNWay8rM0tEc3l5KzhlUHBrUGN4SS9nV3hCZkx1bWhMeGZSeTM5cy9yZUxla1hvdkkKNXp1Q0FYMEVRWithSHdhU2F
yOEMwRkRzZ01xZFdyR2xpcVVmMWhCZlpYNm5pWkhITWw1MkRKNExUN0liR1p1LwpDNFhTSXVxdXBGMkVyZmxabkQ1WnByV1BYdEJVU3R2TDNBQUg2Vkl4d1FLQmdRRG9HcFVRZXZJUXZkRG1peldqCjgyZmE5bGxEazd6bzdKMVYreS92bWYrdTFrVUh4YUl5M2prbXVtTHpQMDIwTUJEVmtkSnh1TEVwQkVSVEdlSVcK
TjJYYjRGZStRNkhQdDdlU3ZETC9EZGpHNjd5YW9EZEp3V1o3RkNjQ2hNTjVMYTREKzkzQk5meW5LUUFLQnRwYwpYZXQwVUVuanNHaUZNTEVCSU4yRnBwbklMUUtCZ1FEV0p0TFcxY2RMWGxvYi8xSXpLdDVMa29QNUdlN0grblpwCndLZVRhYjVQeWlFZnRjMHhyU0kzVURpRU5rSDFpZWtuZ1g0WWk4U200MlUyQW8vY
XEvZjZpT0g2Q3A4c3BMTkQKU0dpNzh4cUFJYVFTT21oK0pPNlphL0NMemlzVFdUc1R6bHZsRGd1T3lLR2dmQ2pmeWh2OWpHV3Uwd0YxeURoego4VUdVT1pDZmpRS0JnQng4SHEwdUdZVWFkb2ZFcittV0ZyblZuL0RlTWNINFAvcnNYOTZzN0tjZFhzMXNZTktUCldUbzZoNmhJclpXZWpJSlphaFZRMEZVelU5dExBdW
N0RjFBYndVajFiZWRiS2FsVmRZQzl4MHZxWkhRcGszOTIKdjhmOUdpaUIrS2RuaHNLd1oyK3QyM3I4V2lmZzNXMldqUzN5S0k5TCtCZlllUENsZ3VwREh2NWxBb0dCQU1EZQpsbVVHaEVkTTRycjBhSVBNeUFnb0hOUHNsekZrVWVGTXNhQUNUSFJ3QndVWUo0WGwrRHI0OU8yZU1DbUQyNGpKCkIxZjBDVmlFMUZLZ1h
BOEZjb0VoWnVSYlRLQllkVnJUakFBNklsUGorSEN5U2duU0dWSHo5T0QwL2JhZlZxV3YKNVBPV2dySkYzOHM2QjFZR0lNOXA4dXBRLzYra2M3TjRSU2ZKOEliQkFvR0FFMmRMdG1aazZMOHFpZ2tKT0Y4TQpjN1Z4K2FHaXBiMXNWVURhcmRxdy9WWmdZOW9LUE8zOFNRT2JnWjNHQVc3UXpEUDdRYU14c2hvUk15ZmZS
emFSCndVNi9BdTJFQU1vNHhZU2hYR1Z0M0gzUDE1TjlUTXN5UEltRXRTMmpEeER0d1lOSTBJUjhESGJQaEZUTG1lN2MKUE40T3BrYjUvd1B2QXoxVUExVWI1c2c9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
0
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_nat_outbound.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
one rule
wan
1544027052
1544027052
any
another rule
wan
1544027052
1544027052
any
third rule
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
fr0
bg-info
opt2
fr3
bg-info
opt2
1545574416
tcp
test_rule
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_rule_2
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_lan_100_1
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
tcp
test_lan_100_2
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
another_queue
1545574416
tcp
test_lan_100_3
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
1545574416
tcp
test_lan_100_4
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
another_limiter
1545574416
tcp
test_lan_100_5
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
1545574416
tcp
test_rule_3
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
fbor
1545907554
tcp
test_rule_floating
1545574416
keep state
1545574416
wan
inet
pass
any
yes
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
GW_LAN
1545574416
tcp
not_rule_dst
1545574416
srv_admin
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
not_rule_src
1545574416
port_ssh
keep state
srv_admin
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
port_ldap_ssl
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1546111216
pass
opt2
inet
tcp
1546111216
admin
1546111216
admin
1546111271
pass
opt2
inet
tcp
1546111271
admin
1546111271
admin
1546111294
pass
opt2
inet
tcp
1546111294
admin
1546111294
admin
opt2
opt2
CBQ
100
Mb
opt2
10000
0
one_queue
50
Mb
on
opt2
1000
1
another_queue
50
Mb
on
on
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
one_limiter
1
-
100
Mb
none
on
none
0
wf2q+
droptail
another_limiter
2
-
1
Mb
none
on
none
0
wf2q+
droptail
disabled_limiter
3
-
1
Kb
none
none
0
wf2q+
droptail
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
GWGroup
- GW_WAN|1|address
- GW_LAN|2|address
down
Failover group
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_nat_port_forward_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
one rule
wan
1544027052
1544027052
any
another rule
wan
1544027052
1544027052
any
third rule
wan
1544027052
1544027052
wanip
22022
inet
tcp
10.255.1.20
22
wan
one
1544085353
1544085353
wanip
22022
inet
tcp
10.255.1.20
22
wan
two
pass
1544085353
1544085353
wanip
22022
inet
tcp
10.255.1.20
22
wan
last
nat_5e03858acc6b82.92150982
1544085353
1544085353
wanip
22022
inet
tcp
wan
NAT last
nat_5e03858acc6b82.92150982
fr1
test_separator
bg-info
lan
fr0
bg-info
opt2
fr3
bg-info
opt2
1545574416
inet
tcp
test_rule
1545574416
keep state
1545574416
wan
inet
pass
1545574416
inet
tcp
test_rule_2
1545574416
keep state
1545574416
wan
inet
pass
1545574416
inet
tcp
test_lan_100_1
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
inet
tcp
test_lan_100_2
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
another_queue
1545574416
inet
tcp
test_lan_100_3
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
1545574416
inet
tcp
test_lan_100_4
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
another_limiter
1545574416
inet
tcp
test_lan_100_5
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
1545574416
inet
tcp
test_rule_3
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
inet
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
fbor
1545907554
inet
tcp
test_rule_floating
1545574416
keep state
1545574416
wan
inet
pass
any
yes
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
GW_LAN
1545574416
tcp
not_rule_dst
1545574416
srv_admin
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
not_rule_src
1545574416
port_ssh
keep state
srv_admin
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
port_ldap_ssl
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1546111216
pass
opt2
inet
tcp
1546111216
admin
1546111216
admin
1546111271
pass
opt2
inet
tcp
1546111271
admin
1546111271
admin
1546111294
pass
opt2
inet
tcp
1546111294
admin
1546111294
admin
opt2
opt2
CBQ
100
Mb
opt2
10000
0
one_queue
50
Mb
on
opt2
1000
1
another_queue
50
Mb
on
on
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
one_limiter
1
-
100
Mb
none
on
none
0
wf2q+
droptail
another_limiter
2
-
1
Mb
none
on
none
0
wf2q+
droptail
disabled_limiter
3
-
1
Kb
none
none
0
wf2q+
droptail
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
GWGroup
- GW_WAN|1|address
- GW_LAN|2|address
down
Failover group
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_openvpn_config.xml
================================================
21.7
normal
pfSense
home.arpa
all
system
1998
0
admins
system
1999
0
page-all
admin
system
admins
$2b$10$13u6qwCOwODv34GyCMgdWub6oQF3RX0rG7c3d3X4JvzuEmAXLYDd2
0
user-shell-access
2000
2000
2.pfsense.pool.ntp.org
https
62083679cd3d4
yes
400000
hadp
hadp
hadp
monthly
re0
dhcp
dhcp6
0
re1
192.168.100.2
24
wan
0
192.168.1.100
192.168.1.199
::1000
::2000
disabled
medium
public
1
automatic
pass
inet
lan
0100000101
lan
pass
inet6
lan
0100000102
lan
-
*/1
*
*
*
*
root
/usr/sbin/newsyslog
-
1
3
*
*
*
root
/etc/rc.periodic daily
-
15
4
*
*
6
root
/etc/rc.periodic weekly
-
30
5
1
*
*
root
/etc/rc.periodic monthly
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
system_information:col1:show,netgate_services_and_support:col2:show,interfaces:col2:show
10
1644705489
62083679cd3d4
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsRENDQTN5Z0F3SUJBZ0lJU2JlZ00zSWZSTEV3RFFZSktvWklodmNOQVFFTEJRQXdXakU0TURZR0ExVUUKQ2hNdmNHWlRaVzV6WlNCM1pXSkRiMjVtYVdkMWNtRjBiM0lnVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaApkR1V4SGpBY0JnTlZCQU1URlhCbVUyVnVjMlV0TmpJd09ETTJOemxqWkROa05EQWVGdzB5TWpBeU1USXlNak0yCk5ERmFGdzB5TXpBek1UY3lNak0yTkRGYU1Gb3hPREEyQmdOVkJBb1RMM0JtVTJWdWMyVWdkMlZpUTI5dVptbG4KZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnBabWxqWVhSbE1SNHdIQVlEVlFRREV4VndabE5sYm5ObApMVFl5TURnek5qYzVZMlF6WkRRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURHCjkzVW5PMm1MbTRFVTRwOUtlMCtmbkZCQTZGV245eTBDcjhaY0dxVFZiN0E3ejY4Y1JBd0l4bEVDYmtWaUx1ejcKSnNUK2gwd1Jiclo3RWI1dkpvZ3JaUHZSV1ByeW9JQWwrT2w1b1dFbE0ycTBQMVJQdjZETndaYjFsUHgzNXdXOApBa1gxQkhLL3FEWWJibjVVRXdqOVdmaVpmcnNJeFBHM21ubmpHMHpiMktOaGJGNDM4Tk1xVTVadVRvQW5PYW9PCmJqb2tveWhNUUh1N0JGV2tQS25lTFJGN3lmN0Q3TVp1QnBBbWVCbnllYkl1ZkpzS1ViWGE3OTNFanVPcjN4disKb2J3QmhtMUN3NWRpc1o2SDZzS2ZudFJ6UWFUZXhZWEo3WGo2MDdtMWc2YkpqR2FweG9WTTlvU1VlUGlzVWNCOQpNbEp1WkpnVmVOYVlFQkJPbEgxWkFnTUJBQUdqZ2dGY01JSUJXREFKQmdOVkhSTUVBakFBTUJFR0NXQ0dTQUdHCitFSUJBUVFFQXdJR1FEQUxCZ05WSFE4RUJBTUNCYUF3TXdZSllJWklBWWI0UWdFTkJDWVdKRTl3Wlc1VFUwd2cKUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRBZEJnTlZIUTRFRmdRVWNHMlpNNHdKRlpQRQoxV1ZndXd0eFRUT0RhOGd3Z1lzR0ExVWRJd1NCZ3pDQmdJQVVjRzJaTTR3SkZaUEUxV1ZndXd0eFRUT0RhOGloClhxUmNNRm94T0RBMkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG4KYm1Wa0lFTmxjblJwWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFl5TURnek5qYzVZMlF6WkRTQwpDRW0zb0ROeUgwU3hNQ2NHQTFVZEpRUWdNQjRHQ0NzR0FRVUZCd01CQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVJCkFnSXdJQVlEVlIwUkJCa3dGNElWY0daVFpXNXpaUzAyTWpBNE16WTNPV05rTTJRME1BMEdDU3FHU0liM0RRRUIKQ3dVQUE0SUJBUUFzazBrTU12dVR0T3c2Ymx5a1U5cWNkRnQvVDlGOFZBZ0taNHgzYXNxNlArRG96N1FGVFpKVwprdmlrQVVUekpMMys4c0NKRDdjV3BZa2ZpdDRBYndhWFIyRzVsczhjL0JRcUdmY1ZOUnJVdWRscG12UUYrYk5iClMxZ2xjS2hYYXZuYnlQdkRMem9CZGVlTmhqYXIzcWc1TTV6T3I0aXYyM0hCZVc2aEY2c0FrV3dpVkU5NEJmZ00KOS9qeW5GalVYTkJheStMODM2TXBpNDhpNnE4OHdlQ25UdDdaTFFjWlZXb0IwcWNQSS96SExTUFlTNlhhcmdvdgpva3E1M3ZQSG9HNnRGUHpFSkpFVmNmOTV1bVcwaUpFR3hCQ3dTeVlnd2xSY0pEeGJ1QklFY0xWb2JKclVveHNLClJXcW13SHdQYkFxRjBOMUZ0cFJ6K3Yvd0lQYWdSQ2lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREc5M1VuTzJtTG00RVUKNHA5S2UwK2ZuRkJBNkZXbjl5MENyOFpjR3FUVmI3QTd6NjhjUkF3SXhsRUNia1ZpTHV6N0pzVCtoMHdSYnJaNwpFYjV2Sm9nclpQdlJXUHJ5b0lBbCtPbDVvV0VsTTJxMFAxUlB2NkROd1piMWxQeDM1d1c4QWtYMUJISy9xRFliCmJuNVVFd2o5V2ZpWmZyc0l4UEczbW5uakcwemIyS05oYkY0MzhOTXFVNVp1VG9Bbk9hb09iam9rb3loTVFIdTcKQkZXa1BLbmVMUkY3eWY3RDdNWnVCcEFtZUJueWViSXVmSnNLVWJYYTc5M0VqdU9yM3h2K29id0JobTFDdzVkaQpzWjZINnNLZm50UnpRYVRleFlYSjdYajYwN20xZzZiSmpHYXB4b1ZNOW9TVWVQaXNVY0I5TWxKdVpKZ1ZlTmFZCkVCQk9sSDFaQWdNQkFBRUNnZ0VCQUxldGVIeVlUMjV2UnpIVnFFSGxKbk50cFhUV1IwVUJYWThPWUN0aytXaUUKYkFnN1NTZnA5Y1lmOW1jdEQyWjlkWTdCa3JoNmhKSFBTQ3pEQzYrbXZheDUxRExHVnh5bmFNWWxUTHhaYThvZwo5aytoNng2WUJFWU9nbU1DZ0RQY2xTR2tZNXEyMll2dktNd1lMQTFIYVZRaHUrdFA0REJQUitvOGRHdGhKNG9ICmlna3Z4OWhQOVAzc1VBZjFQQWtKMzkyYjZ6dGJOYkRWdXhGSTFVbklCKzF1c2s5VUduWlM5bUVXTkxDRTEwYngKT2U1YlVJaTlCWkkzM3pTamdra3ZFQVdJMlBzVWJDaEFqa0RPUStFT1V1dktyQVJoUnBXUDVWUmdxVUJpc0ZuaApmeWFFMUNtTTQ4RWZyVGZ4QklXaTA0cHZ2U0I3Zm4rbXN3RkRCcjcvUGtVQ2dZRUE3L05NcHlZdGw2dng5b043ClNWUmdIY01JZlVZR2xJSVNvTGNJbmRURGQ3K3krR0pvRkFBclhBcVMvR2dKUFgxZkMxbWVYZWJDY0dqeHVGZEgKTlNNbnMxVk1OVHlEdjNMWVhhV0lBSlVvMWtTMlVYaWZHcUN3WEpSckVmVE11U1NlRXpXOGJaNE5Cdmk1R1VsdQpyTnJWNnBqTFQ5eS85ZENUbitHeWthamNMZzhDZ1lFQTFFWmpZTldhTXltbHgydDlpS3hsVTR2SlcxVDMrbW1DCkhWRDM5bDlUSS9BWlQ4Ti9VVGRYVXhjdDN2MnA4OTdPRWJ6cThBRno1dFRrNUV6Y1dJZDcwRmYwRUYxTFpMaXkKME1NZlVLVUlBTExwekh4T294T3YxNVRJTzlXSVF4d2J2TWcxaDh2M2M1MkNCVEpsVzFUektJM3dKMWV2RXBrRwpzcDVqN1NMSUJoY0NnWUVBeTdqTHlkWldPMEhYU3k3U2k2M0JkVU5UZjlqbVdVd2VPS2x0L1dMWkdtQjl1UGtECjJJZFVTTzhKWUplTDBOTVMwUFlqeVNIVXo4K3ArcExQZUVRQ3Z2V2FvRkJpb3pjRWtHMnNES0tYYTJRblR3Q1UKUk8xTkR5MUx3cEVQQjlvWkE4SkoydCtudTlXTWdmV2dxODJZZFhlSWxxT2JyejZKTitOTjB2R0ZEdTBDZ1lCcQp5a1p3anNVV2ZCdEVhZFVyanQ4aTJxNGYzbTBxY3kzY0pjRzVGbGV6T3JUaEpjN0ZRdndSZHhYQ213YUhBMDNVCktxQmV3YnhYSWo5TWcwWk8yMG4wbEdyYVdMVDNKTndBbmtrQXZ5VjVoSWlPTVBNMm8wN1JPNjVJTzdKallKNnIKcUdVVnZnenRBdzVJSXdST29Edjc2UHdxTHJpS3VLVmY4c0wrcDRMTlhRS0JnUUNrbXlIR3dielVJLzN6Z1FNSApxY1RkMUttMDhxTXJjUzZXdU03RGJxTEpIQXdlcFRSYUpuVmVLcnRRS2t4SGRlcjZsK2VWUjNvWXArUE9EbTVECjBGSXMxbXd2RFp3TjI2RVhmZnZkWG9EK1luNnVEeGlvVU9QZ1A2U1hLT3dxcnBuenVFZTFBNFpDQTFnRXJhd2MKTTNmejdiV3d0a1JUUE5uaGxybkVJNXY5Qmc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
6209e3cef1e81
enabled
enabled
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlFQ0RDQ0F2Q2dBd0lCQWdJSUZqRk9oczFuTXpRd0RRWUpLb1pJaHZjTkFRRUxCUUF3WERFVE1CRUdBMVVFDQpBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJBZ1RDRU52Ykc5eVlXUnZNUkF3DQpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExDQpNRGd6TVZvWERUTXlNREl4TWpBMU1EZ3pNVm93WERFVE1CRUdBMVVFQXhNS2IzQmxiblp3YmkxallURUxNQWtHDQpBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJBZ1RDRU52Ykc5eVlXUnZNUkF3RGdZRFZRUUhFd2RDYjNWc1pHVnlNUk13DQpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDDQpBUUVBbXN2aUpNRTFFVGVkNGZPdGJrSHBGM2Q5ZU0rNjQwOFhQbmE4dEpHZEJxM1VBQ3hFem9hQktSdDJ5MWN0DQo2elFEZTVGRjRBQXZ0VjF1Y1pwc2w1bzREUy9JR1NibjZkM1lNaytqOGpBUTNFbXpSOEdPb2huZ2YxUTlBWEM2DQpvaDRyQlA1c1g0WTh1WThrSjNZclg1cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTDQpjTkxaaVZ4Q1FRSzVwOGs5bUE4Ymd4bHFZa2YwbUF5Qk53OU1BZlBVY1VrcUY2UDBnV1BIbElySFovdWhnN2RVDQorMjJhb2NLVUVOaXY5bXFhK0I2Y1VnTFRGVDZzMFZTRXNYL2RBZWg2MllMZ2ZtWEpnNmROSFFJK01nNlNrZWxwDQprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIwR0ExVWREZ1FXQkJSazVvQS8wcWEyDQpLUHdnb1hKcUtNdCtBb0tKZ1RDQmpRWURWUjBqQklHRk1JR0NnQlJrNW9BLzBxYTJLUHdnb1hKcUtNdCtBb0tKDQpnYUZncEY0d1hERVRNQkVHQTFVRUF4TUtiM0JsYm5ad2JpMWpZVEVMTUFrR0ExVUVCaE1DVlZNeEVUQVBCZ05WDQpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wDQpZbXhsZ2dnV01VNkd6V2N6TkRBTUJnTlZIUk1FQlRBREFRSC9NQXNHQTFVZER3UUVBd0lCQmpBTkJna3Foa2lHDQo5dzBCQVFzRkFBT0NBUUVBVUg5S0NkbUpkb0FKbFUwd0JKSFl4akxyS2xsUFk2T05ienI1SmJoQ002OUh4eFlODQpCa2lpbXd1N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCDQpUSXJpSHl0TkRRYW91TmEvS1dzN0ZhdW9iY3RCbDF3OWF0b0hac041b2VoVDNyQVR2MUNDQXRqcGFUSklmSlIzDQowSVFPWWtlNG9ZNkRrSXdIcDJ2UFBtb29HZ0l0YlR3M1UrRTQxWVplN3FDbUUvN3pMVFNaa0lNMmx4NnpENDZqDQpEZjRyZ044TVVMNnhpd09MbzlyQUp5ckRNM2JEeTJ1QjY0QkVzRFFMa2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4DQpoMS9nekRaSURpK1dYQ1lBY2JnTGhaVkJxdG42MnVtRnBNUkl1dz09DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tDQo=
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUNheStJa3dUVVJONTNoDQo4NjF1UWVrWGQzMTR6N3JqVHhjK2RyeTBrWjBHcmRRQUxFVE9ob0VwRzNiTFZ5M3JOQU43a1VYZ0FDKzFYVzV4DQptbXlYbWpnTkw4Z1pKdWZwM2RneVQ2UHlNQkRjU2JOSHdZNmlHZUIvVkQwQmNMcWlIaXNFL214ZmhqeTVqeVFuDQpkaXRmbXBPa1Zqa29jdFRVQ2c5N0pEdkxJeGtjdWlTMzJkejRVVm5EQlR0RUlaSncwdG1KWEVKQkFybW55VDJZDQpEeHVER1dwaVIvU1lESUUzRDB3Qjg5UnhTU29Yby9TQlk4ZVVpc2RuKzZHRHQxVDdiWnFod3BRUTJLLzJhcHI0DQpIcHhTQXRNVlBxelJWSVN4ZjkwQjZIclpndUIrWmNtRHAwMGRBajR5RHBLUjZXbVQxVkZONk9JUk1nUlh3bUIyDQovWTN0RktibkFnTUJBQUVDZ2dFQUNYN0FIR2tOakVUUkZtOFFFRmRTcVBIWGJIV3hqUWZvOFJmdmMxUUxRY0dmDQo0M0xUdGFkaWZOY0dibXFta21yYVc5WUpaemdidFJCS0dnWFM2Mm0yVG5qRDJXY2RpcWJsQUJFS2lXeVJYREhaDQpJV21xQ2g5ME9kczg4cjJyZFE1TXJUMitBQTRINDRuNE9jTngzYWRwcndicThxUTRrZGtjSWYyUy9WN2x4M0U3DQpLWVNyVnNZT3hUS0dwQVl4MzVtU3oyckRadXFhcVhyRGtwSTYxa2tFVDluaGJzQStVZDE5aXZrZ3J1WlU3bVp3DQpmRzQ0V3JDeEhSNjdiYW1LQ3VPei81SnlKNUt5US9VaFkzak9lczkxZXRpeHJXUkRpaXRCVVIwc01kMXZuQ2cyDQpTSkpjQnAvYXRwbWNjMGY1OVZFSDRGOTNqY01jR3V5cE1mQmkwNExFd1FLQmdRRE1BUSsyVk5aQWJ5aVM0cWZqDQpXelE0UWl6MDNSdmUybVhaNHc1aEdKSmk0QnkvdmI4K2xoOE9acGFWSFBjRTVya0tBbExEaFRwUEl0dTFwNWZzDQpic05MTHpJYkIwUmVBdGUxc3JGOU9SRHp0VThKblhrNDEwcnFMbmdBZmhVTGUvSUttNjBtUG4vWWU0UnhVSVI3DQp2TjFNemJXMnlYdHJCSlZ5VG1jbFRkRDZ5UUtCZ1FEQ1FCa1B2NnFpYXZXaG1IRnBiZ3QvMklpSUlBUk5iSUJjDQpmVnVUYVFJbXlOZlVSVzlUVG10UHFiYXRuOGhaWFFaL1ZCbWdaMklPOCtCMXBFYW5zYjZCS2xJMXFUMFdMUzR4DQpaV0hLQ3hEdVFiMXNUYmNRMnd3NW5jbk50c1dlWlkxUlV1dlpYcVQwZFU4WUIwb2FsdGVYWTNVbDJrUWJuWUcvDQpINUpJeWJPOEx3S0JnUURKNE1zQnJoYVBrUERmMm5nMW4wMmYxcXpTYS9SbXBrMWdQemM5a3FsYU8xbDN6WGZ4DQpvWEYrT0xzUE9LaWlLd2cyQlhLTmxjdk1BRHpZR005WTQ0dFRYWk1CK0VFSm4xcURyaC9DUWJTcTEyTXRxcTRKDQpOOVFreG5OdVdWYk9GSXZEUDZjclQzSUljc0x2dDdSREZ2VVFTZ2xtcHlBQkdYb2lzYitVeE5ybk1RS0JnR09QDQpZc2oxbmNsOU5NUk1VK1NMcUkwd09Gbzh2cmZJSXNwRTNnamh5MTZCbGsyUUFRMGJwbGpBVFljVDNDWWhUZEU1DQpFNkZwRzVNNllCTXJ6YUxwc1JDVzFtZjJnLzYzelhNMzJUVXJFdFJyRVdGUE84TUI0blF0Y1Y5a2pFa3hGNHRWDQpDdGp3YjI5MEtNUFNDS00wS08vSTVDUXdpTFAydUtkeTBSRkpnRHUxQW9HQUxqSTdGZDl0ckFrY1dSenMyVHpRDQptaDRTZWxHRDFvdFAxRXpFa1hYVlZwK1lMNXlxOWM3V3hsa09RY1lFVmd1N2huNVFncnBRUFZPTFVmbkJoajdXDQo1MC9IVmR6V21wSXg5NUlqZXFDZklBV1U3N0I5cmJUR2hvWWMzbTdJcEdObzl2WlhHYWgrc2JHY3BEK1phV3UzDQp1Q25pTnJpZEhORGgzWHZQVFZkRTRlVT0NCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0NCg==
1
1
server_tls_user
RADIUS
UDP4
tun
wan
1194
tls-version-min 1.2;
6209e3cef1e81
620c5a733ba1d
2048
none
1
AES-256-CBC
SHA256
none
10.100.0.0/24
no
yes
subnet
none
example.com
10.10.10.10
10.10.10.11
none
0
both
3
AES-256-GCM,AES-128-GCM,AES-256-CBC
keepalive
10
60
10
ping_restart
60
0
2
p2p_tls
UDP4
tun
wan
1195
IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZTc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MTkzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFhOTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3ZjA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K
auth
default
6209e3cef1e81
620c5a733ba1d
2048
none
1
AES-256-CBC
SHA256
none
no
subnet
none
none
0
both
1
AES-256-GCM,AES-128-GCM,CHACHA20-POLY1305
keepalive
10
60
10
ping_restart
60
0
1
ifconfig-push 10.8.0.1 255.255.255.0
yes
0
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_route_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
2001::
64
vmx1
lan
192.168.1.242
24
vmx2
vpn
dhcp
dhcp6
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
10.3.0.0/16
GW_WAN
GW_WAN route
servers
GW_WAN
GW_WAN alias
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
one rule
wan
1544027052
1544027052
any
another rule
wan
1544027052
1544027052
any
third rule
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
one
1544085353
1544085353
wanip
22022
tcp
10.255.1.20
22
wan
two
pass
1544085353
1544085353
wanip
22022
tcp
10.255.1.20
22
wan
last
nat_5e03858acc6b82.92150982
1544085353
1544085353
wanip
22022
tcp
wan
NAT last
nat_5e03858acc6b82.92150982
fr1
test_separator
bg-info
lan
fr0
bg-info
opt2
fr3
bg-info
opt2
1545574416
tcp
test_rule
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_rule_2
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_lan_100_1
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
tcp
test_lan_100_2
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
another_queue
1545574416
tcp
test_lan_100_3
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
1545574416
tcp
test_lan_100_4
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
another_limiter
1545574416
tcp
test_lan_100_5
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
1545574416
tcp
test_rule_3
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
fbor
1545907554
tcp
test_rule_floating
1545574416
keep state
1545574416
wan
inet
pass
any
yes
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
GW_LAN
1545574416
tcp
not_rule_dst
1545574416
srv_admin
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
not_rule_src
1545574416
port_ssh
keep state
srv_admin
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
port_ldap_ssl
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1546111216
pass
opt2
inet
tcp
1546111216
admin
1546111216
admin
1546111271
pass
opt2
inet
tcp
1546111271
admin
1546111271
admin
1546111294
pass
opt2
inet
tcp
1546111294
admin
1546111294
admin
opt2
opt2
CBQ
100
Mb
opt2
10000
0
one_queue
50
Mb
on
opt2
1000
1
another_queue
50
Mb
on
on
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
servers
172.16.2.0/24 lan_voip_poc3 ad_poc2
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
one_limiter
1
-
100
Mb
none
on
none
0
wf2q+
droptail
another_limiter
2
-
1
Mb
none
on
none
0
wf2q+
droptail
disabled_limiter
3
-
1
Kb
none
none
0
wf2q+
droptail
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.240.1
GW_WAN
1
inet
Interface wan Gateway
wan
192.168.240.1
GW_DEFAULT
1
inet
Interface wan Gateway
wan
192.168.240.1
GW_WAN2
1
inet
Interface wan Gateway
lan
192.168.1.1
GW_LAN
1
inet
Interface lan Gateway
8.8.8.8
lan
2002::1
GW_LAN_V6
1
inet6
Interface lan6 Gateway
opt3
dynamic
OPT3_VTIV4
1
inet
GW_DEFAULT
GWGroup
- OPT3_VTIV4|1|address
- GW_LAN|2|address
down
Failover group
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
other
lan
5c0a4b6139b05
network
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_rule_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
fr0
bg-info
opt2
fr3
bg-info
opt2
1545574416
tcp
test_rule
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_rule_sched
1545574416
keep state
1545574419
opt3
inet
pass
workdays
1545574416
tcp
test_rule_2
1545574416
keep state
1545574416
wan
inet
pass
1545574416
tcp
test_lan_100_1
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
tcp
test_lan_100_2
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
another_queue
1545574416
tcp
test_lan_100_3
1545574416
keep state
1545574416
opt3
inet
pass
one_queue
1545574416
tcp
test_lan_100_4
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
another_limiter
1545574416
tcp
test_lan_100_5
1545574416
keep state
1545574416
opt3
inet
pass
one_limiter
1545574416
tcp
test_rule_3
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
test_rule_4
1545574416
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
fbor
1545907554
tcp
test_rule_floating
1545574416
keep state
1545574416
wan
inet
pass
any
yes
fbor
1545907554
tcp
test_rule_floating_quick
1545574416
keep state
1545574416
wan
inet
pass
any
yes
yes
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
GW_LAN
1545574416
tcp
not_rule_dst
1545574416
srv_admin
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
not_rule_src
1545574416
port_ssh
keep state
srv_admin
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
port_ldap_ssl
ad_poc3
1545602758
lan
inet
pass
1545602800
block_all_lan
1545602800
keep state
1545602800
lan
inet
block
1545602801
reject_all_lan
1545602801
keep state
1545602801
lan
inet
reject
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1546111216
pass
opt2
inet
tcp
1546111216
admin
1546111216
admin
1546111271
pass
opt2
inet
tcp
1546111271
admin
1546111271
admin
1546111294
pass
opt2
inet
tcp
1546111294
admin
1546111294
admin
opt2
opt2
CBQ
100
Mb
opt2
10000
0
one_queue
50
Mb
on
opt2
1000
1
another_queue
50
Mb
on
on
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
http://www.acme-corp.com
urltable_ports
10
http://www.acme-corp.com
acme_corp_ports
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
one_limiter
1
-
100
Mb
none
on
none
0
wf2q+
droptail
another_limiter
2
-
1
Mb
none
on
none
0
wf2q+
droptail
disabled_limiter
3
-
1
Kb
none
none
0
wf2q+
droptail
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_BROKE
1
Broken Gateway
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
GWGroup
- GW_WAN|1|address
- GW_LAN|2|address
down
Failover group
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
workdays
1,2,3,4,5
7:30-18:30
workdays
5e5e0fd3ddb8d
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_rule_separator_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
1100
vmx1.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_setup_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
2001::2001:22
64
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_1100
vmx1.1100
172.16.151.210
24
vmx4
vt2
dhcp
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1560930241
match
lan
inet
in
yes
yes
tcp
port_ssh
floating_rule_1
1560930241
match
lan,wan,opt3
inet
in
yes
yes
tcp
port_ssh
floating_rule_2
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
lan
2001::1
GW_LAN6
1
inet6
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx0
100
vmx0.100
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_syslog_config.xml
================================================
18.9
1545602758
aggregated change
vmx0
wan
192.168.240.137
24
vmx1
lan
192.168.1.242
24
2001::2001:22
64
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_1100
vmx1.1100
172.16.151.210
24
vmx4
vt2
dhcp
1
50
ipv4
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_user_config.xml
================================================
19.1
normal
pfSense
localdomain
all
system
1998
0
admins
system
1999
0
page-all
admin
system
admins
$2b$10$13u6qwCOwODv34GyCMgdWub6oQF3RX0rG7c3d3X4JvzuEmAXLYDd2
0
user-shell-access
testdel
all
2000
user
$2b$12$D2jkq4Iut3ODUBN0BCrDk.bV3J5N.MrY5YEnGvTXwxeNBkyxjbbtW
page-dashboard-all
2001
2000
0.pfsense.pool.ntp.org
https
5cf09a99bf5fe
yes
400000
hadp
hadp
hadp
monthly
enabled
re0
dhcp
dhcp6
0
re1
192.168.100.2
24
wan
0
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
automatic
pass
inet
lan
0100000101
lan
pass
inet6
lan
0100000102
lan
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
ICMP
icmp
TCP
tcp
HTTP
http
/
200
HTTPS
https
/
200
SMTP
send
220 *
system_information:col1:show,netgate_services_and_support:col2:show,interfaces:col2:show
10
1559272298
(system)
5cf09a99bf5fe
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVkyWXdPV0U1T1dKbU5XWmxNQjRYRFRFNU1EVXpNVEF6TURnd09Wb1hEVEkwCk1URXlNREF6TURnd09Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV05tTURsaApPVGxpWmpWbVpUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUt6QW91N1I0TW5VCjlWUlhQYkZyK2NRVXhtdGNKdDNpbjAvbFppRGh6ZjNVV0hnTHVRdHBoL2NMMUZIdlFwdzZzazlFRzY5RjREdTUKL2IweElnS1B2YlVFcVpJeit0aEkvZ09LZ29GRTJ1dERxcW8xSnh6SzliVzdVdHVnZWlxUnEyT1hLaGh5ZUNKcQo5OEEwL3l4VitqNkJDbmpNeVVGS2krQjJpcVNnSG1JQVkySHVRcklsb3ZwMGZuTUpkeWRjOGNzVHh0aUJUODh4Cko3K0locDJrSGNDcUtIRGg0bjVYOEdUek54YmxreTJERXBkYkdsUWcyRE1JQm9hTmRnMXpVdUx1cnV2LzVoWkIKQ2JyWjBRNXpRaWVGdU13ZnMrNEdPVW9kS201ZFJJM3YwT0NRd05JUnZVZVpDTUFhTFJVNVdvT0hzMUUzb3RjaAorNlZ3RnVaNndrOENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCUUd2bSs0NWxFUW5oSDVOaHhFRk9YWQpxWVZ1ZnpDQmdnWURWUjBqQkhzd2VZQVVCcjV2dU9aUkVKNFIrVFljUkJUbDJLbUZibitoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqWmpBNVlUazVZbVkxWm1XQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV05tCk1EbGhPVGxpWmpWbVpUQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFiTjJPL2hwcjU5bC9XclJIQlhvTjJDSFYKQi9ra0VwUVVuRXhrSzVNUkJGYTV5ODVLSk5Eb09HL0R5UlVNRFR4VENkVnQrdlpQS0ZveUJXRFdDdGt3YTJScgo1Z1BDb21TUk5mUWhyV1ZtYkhSd0VNTyt6OXkxS1AzU3JhZ0tIeVdTM3lFY25wU1NRY1B1M3hhaERDNERtclJzCjMvNytBMTk2SEJxRjlBc25oUjFDZWpBcm4rSFR5dUdOQk5udGtqUktPN01NcDVxaHlZbkV3SVk5SVQ0TzFIeHYKOEtFTTdBUGRmRS93VUFvZGxET0F5RFRIRFpid2Z1eCtoOURRTGNBRE1sRi9IdzF0Y2UvcHJLek9LT3h6YmdlQQpWZ2pQVmxNbVJqQ2lJa2JNL3BPeWNObENDTzAxczQzbFJzcU5VRzFaLzBjakFzeEw0c0ltUlErQkI2dXdDUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRQ3N3S0x1MGVESjFQVlUKVnoyeGEvbkVGTVpyWENiZDRwOVA1V1lnNGMzOTFGaDRDN2tMYVlmM0M5UlI3MEtjT3JKUFJCdXZSZUE3dWYyOQpNU0lDajcyMUJLbVNNL3JZU1A0RGlvS0JSTnJyUTZxcU5TY2N5dlcxdTFMYm9Ib3FrYXRqbHlvWWNuZ2lhdmZBCk5QOHNWZm8rZ1FwNHpNbEJTb3ZnZG9xa29CNWlBR05oN2tLeUphTDZkSDV6Q1hjblhQSExFOGJZZ1UvUE1TZS8KaUlhZHBCM0FxaWh3NGVKK1YvQms4emNXNVpNdGd4S1hXeHBVSU5nekNBYUdqWFlOYzFMaTdxN3IvK1lXUVFtNgoyZEVPYzBJbmhiak1IN1B1QmpsS0hTcHVYVVNONzlEZ2tNRFNFYjFIbVFqQUdpMFZPVnFEaDdOUk42TFhJZnVsCmNCYm1lc0pQQWdNQkFBRUNnZ0VCQUtzVzFjY0VZVVpDL1AyY3NXTG45eU4xRjlYNEhCNGdkWHRoVERaQXJBdzUKbzZ5d240Rm44TnFCQXJScTYyTmkxbm1la0hTVUZiSFJVRFZ4VmFlSHlIQmd2N2dtZHNhQjgrQjU2eW92a1VqegphVERORjRGeW1NcDFUV2hxbE5OWUZZKzZoRnhWOGhqVUs2NVdUbW9RZEpnMm9MSm16dU0wK3pkQTc2cC9VZGZuCnZkSzZVbDFWSi9WcEhFaEhSS1duSk9uTE1nRU1KS1oxT0NjRTFXOExHU08zY3QvM0VkWi9IRWU2TkZqOXArZTgKTmplTWNpSVduaTE1aTM3ai9LL1FBcFJQcUh5cDBhdUUzR0ZQckhPWjdsUHhlYlZjTExwSEFINWNuc0VyWkJXTApnSWlQcGkxTytrL01wb0h0RklHK25aSXpVdjFzeldrVjg4TjhtcHMrd2FFQ2dZRUExLzZXYVJlVk1GbnFPd0YvCm9UbUJNVTl0TnQ5U1NacTF2RlpNcFVkZk5wMmY0c0ZZNFE2U1gzeFptUEFHQkMyMDBWQWoxbmVlNlBOUTBuclgKSVIvSFJjelNpQXA3R3ZqakRjcGdQaWc2SHdpZHVVM1lvelhKSFdzaHFIVVMrTlo4b0VmUFBld0tvK3hTMmwxdwo2NG5ZR09RcHc0ZTFibURqbEJYOC9CcTBlcGtDZ1lFQXpMKzVDN1EwQmVKZExzOWZOQmFvWFhVRUlHNXhjZmNTCmJHbDFjbzNDN3h2WCtWQXNZYkVWV0hiYk40TVdmVWpSMGhuYStRM2dyaVZvdmZ5K0FqNWovMkFMM2dIY0U1UmMKU2Q0VVA4aG5YdmhST3M5cTNMblZUYVFaZktKY09KTm82VGtMeXZacVJjdmNTWHJDK21tQXc5a0c4SUNFeWZ4VApBV2lmTHk0VzNTY0NnWUVBbWtGRGdnSkpsYUpoV1lxVWI3djF2QldSVmVMZmpabGp1UUdZODJDcGF3UGZMNzROCmo0MHNrK3ptd0FhTEJXanUvWjFTT3RSck5NcXdLZUY0eWpzN3dXbXA0V1k2ek9SNm8xcW9xVHRwWnNoc2UrNVEKalI3WVpwNGdCNEswN2VtZ1Q0ZDVSaXZRM1lqbEV2WXdzc1piQWt2UVY4Z1BscWl6WHdybEJkYThsZUVDZ1lBbgpSVmFlc2crUVdWeDZEL2c0cTJmYmxRZ1htRmRWL29lZ0Y1SVpTS3RzNVRCRmQyVXJ6NlZDZEhtVGFpYzBISFZ5CkVOZDVFWHBZcklBc2dIK0pPcUkvWnhLZm9FZXYwYkxwMEJpZUt6ZjRkVFJQVFYwM3ZNVDJ3VlRLSFBJSFArN04KWE0yd1BoY2dEL3ZPZENkVmxFcklSYVlaRnUxaE9HNUxSTi9UVXNtNzNRS0JnUUNVeFFmY2RyZnZwL0ZSanlkUQpPaUJWWm1JOGFsVXg5SklhZlBjaU5WWlZBZFJnamVEVlVFUTljTklxcDlnd0F4cVVVQ1U3OW9wNWtwbzJoMkZaClBIaFRZaHlpanRxQ09hNWxHdnZTYjg4aWE1Z2xleE1zL2M1bmc0d3RlWHVpSWtqd0lTUmhMWGxCOTU3a2hvSEkKczFnbEp2YTM4MEM0Yk0rcFJhbWdqVlY4blE9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
================================================
FILE: tests/unit/plugins/modules/fixtures/pfsense_vlan_config.xml
================================================
18.9
normal
pfSense
acme.com
on
all
All Users
system
1998
admins
System Administrators
system
1999
0
page-all
system
test
system
groupe1
system
groupe2
admin
System Administrator
system
admins
$2y$10$AMCpA.Z.RNaferLp1yzFq.BvaGgfqaJKtQug7OErbocyNagsEK6xW
0
user-shell-access
2
pfSense.css
2000
2000
0.pfsense.pool.ntp.org
http
5c00e5f9029df
2
yes
400000
hadp
hadp
hadp
monthly
enabled
Etc/UTC
vmx0
wan
192.168.240.137
32
SavedCfg
24
vmx1
lan
192.168.1.242
24
vmx2
vpn
10.0.0.1
24
vmx3
vt1
lan_100
vmx1.1100
172.16.151.210
24
192.168.1.100
192.168.1.199
::1000
::2000
assist
medium
public
1
50
ipv4
advanced
any
wan
1544027052
1544027052
wanip
22022
tcp
10.255.1.20
22
wan
1544085353
1544085353
fr1
test_separator
bg-info
lan
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
wan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
lan
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
lan
inet
pass
1545602758
icmp
ping_from_poc3_1
1545602758
srv_admin
keep state
lan_data_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
udp
ads_to_ads_udp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_1
1545602758
port_dns
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_2
1545602758
port_ldap
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_1_3
1545602758
port_ldap_ssl
ad_poc2
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_1
1545602758
port_dns
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_2
1545602758
port_ldap
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545602758
tcp
ads_to_ads_tcp_2_3
1545602758
port_ldap_ssl
ad_poc1
keep state
ad_poc3
1545602758
lan
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt3
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt3
inet
pass
1545602758
udp
void_conf_tftp_1
1545602758
69
lan_voip_poc2
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545602758
icmp
ping_from_poc3_2
1545602758
srv_admin
keep state
lan_voip_poc3
1545602758
opt3
inet
pass
1545602758
udp
void_conf_tftp_2
1545602758
69
lan_voip_poc1
keep state
ipbx_poc3
1545602758
opt3
inet
pass
1545574416
tcp
antilock_out_1
1545574416
port_ssh
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_2
1545574416
port_http
keep state
1545574416
opt1
inet
pass
1545574416
tcp
antilock_out_3
1545574416
443
keep state
1545574416
opt1
inet
pass
1545602758
udp
void_conf_tftp_3
1545602758
69
lan_voip_poc3
keep state
ipbx_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
ads_to_ads_udp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
udp
void_conf_tftp_4
1545602758
69
lan_voip_poc3
keep state
ipbx_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_1
1545602758
port_dns
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_4_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc1
1545602758
opt1
inet
pass
1545602758
admin_bypass
1545602758
keep state
srv_admin
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_1
1545602758
port_dns
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_2
1545602758
port_ldap
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
1545602758
tcp
ads_to_ads_tcp_3_3
1545602758
port_ldap_ssl
ad_poc3
keep state
ad_poc2
1545602758
opt1
inet
pass
port
port_ssh
22
port
port_http
80
host
srv_admin
192.168.1.165
port
port_dns
51
port
port_ldap
389
port
port_ldap_ssl
636
network
lan_voip_poc2
172.16.2.0/24
network
lan_voip_poc3
172.16.3.0/24
network
lan_voip_poc1
172.16.1.0/24
host
ad_poc3
192.168.3.3
host
ad_poc2
192.168.2.3
host
ad_poc1
192.168.1.3
network
lan_data_poc3
192.168.3.0/24
host
ipbx_poc3
172.16.3.3
host
ipbx_poc2
172.16.2.3
host
ipbx_poc1
172.16.1.3
http://www.acme-corp.com
urltable
10
http://www.acme-corp.com
acme_corp
-
1,31
0-5
*
*
*
root
/usr/bin/nice -n20 adjkerntz -a
-
1
3
1
*
*
root
/usr/bin/nice -n20 /etc/rc.update_bogons.sh
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 sshguard
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 webConfiguratorlockout
-
1
1
*
*
*
root
/usr/bin/nice -n20 /etc/rc.dyndns.update
-
*/60
*
*
*
*
root
/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t 3600 virusprot
-
30
12
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_urltables
-
1
0
*
*
*
root
/usr/bin/nice -n20 /etc/rc.update_pkg_metadata
-
0
0
*
*
*
root
/usr/local/sbin/squid -k rotate -f /usr/local/etc/squid/squid.conf
-
15
0
*
*
*
root
/usr/local/pkg/swapstate_check.php
ICMP
icmp
ICMP
TCP
tcp
Generic TCP
HTTP
http
Generic HTTP
/
200
HTTPS
https
Generic HTTPS
/
200
SMTP
send
Generic SMTP
220 *
system_information:col1:open:0,interfaces:col2:open:0
10
1545602758
aggregated change
5c00e5f9029df
webConfigurator default (5c00e5f9029df)
server
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVlakNDQTJLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJhTVRnd05nWURWUVFLRXk5d1psTmwKYm5ObElIZGxZa052Ym1acFozVnlZWFJ2Y2lCVFpXeG1MVk5wWjI1bFpDQkRaWEowYVdacFkyRjBaVEVlTUJ3RwpBMVVFQXhNVmNHWlRaVzV6WlMwMVl6QXdaVFZtT1RBeU9XUm1NQjRYRFRFNE1URXpNREEzTWpVME5Wb1hEVEkwCk1EVXlNakEzTWpVME5Wb3dXakU0TURZR0ExVUVDaE12Y0daVFpXNXpaU0IzWldKRGIyNW1hV2QxY21GMGIzSWcKVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaGRHVXhIakFjQmdOVkJBTVRGWEJtVTJWdWMyVXROV013TUdVMQpaamt3TWpsa1pqQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9SRHNBV1hlMitrCmtuUFpBUGxBUG1QUUl6RVBDaXBQdXJ4dmE4c0psSCtiNitKRzl4bnpreXptaVR5SFc0M0tmZzVKNzhCdG1jTmQKWlgvZHlyckZ6dlNiQnZpa21lWk9MdGVaejVaaGFOdkxsbjBxRWZibEFwbWdkUS9Nck42eURoTWNZYWN6SEduTwpTaHdFRWZoMHhuOGo5VEFCMG9vT0JyU2dINnpkTjF2YVFIZzBJeDhIa0toUzY5eDRoV3g2WUUrRE9UQk00NUcwCkNVbitUVzNTSytQaU5QaFExT2tyRHlKL21ZLzdmVXpRZkg1bk5ZcEtNZUE2cVZxdUZTa1g0a0RVRlFwOHNoNG4KMXR5QmtJM2gvTFRpTW5uNjhBMXhKOFdrVW8yUWNxNmNuR0hqL0VGbkVKWWRraTVsYUtDWG5wS0Y0c2hrZHJaVgpRN21ybithUHg5OENBd0VBQWFPQ0FVa3dnZ0ZGTUFrR0ExVWRFd1FDTUFBd0VRWUpZSVpJQVliNFFnRUJCQVFECkFnWkFNQXNHQTFVZER3UUVBd0lGb0RBekJnbGdoa2dCaHZoQ0FRMEVKaFlrVDNCbGJsTlRUQ0JIWlc1bGNtRjAKWldRZ1UyVnlkbVZ5SUVObGNuUnBabWxqWVhSbE1CMEdBMVVkRGdRV0JCU3Qra3hud054UlFGT2pXVTljekRwegp4elRYd2pDQmdnWURWUjBqQkhzd2VZQVVyZnBNWjhEY1VVQlRvMWxQWE13NmM4YzAxOEtoWHFSY01Gb3hPREEyCkJnTlZCQW9UTDNCbVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnAKWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFZqTURCbE5XWTVNREk1WkdhQ0FRQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01DQUdBMVVkRVFRWk1CZUNGWEJtVTJWdWMyVXROV013Ck1HVTFaamt3TWpsa1pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEzZHhIRWR5SkpWeC9od0t3WGlwcXYrY04KWFpSQ3Q1NUFHbnNGeXZIcG5Rb3pxYzV1ZFdKSTFUcGF1TGNaZjF6OFM1cXQyUDd3V1BndFhnZ3BIWmgydUlqagpha2ZpV0JIQnorS3Q2Qjl5K2hmVTVPRHUwSXMrcHJyWngyM0ZNNjVCOFNiMGlmUlMvZXFTTFpHTHcvLzdhRVJ2CnVpOUhKOXRuemhRYklMb1VwUUQ5MTRLNERldXdJYXZ4dldMTElwMGsreWNoSTczV2NGR1VZVlJpanFNekhlcUcKY2RLRDVtbXZQTk1OYXdESVhuU3pldVpQMTkvVmhuc0J0Y3hiYzNMQ2RSR1VJS0ZNN2syK0xoaEZrdks1NjhlNQozalpNZjNSVWhQaTlFeEF2Vmw3blVyd1l4UU45TXhHMUJzZmNrYldNd1dUL3lFMHFnRHcrT2M4b21SSW9tZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGtRN0FGbDN0dnBKSnoKMlFENVFENWowQ014RHdvcVQ3cThiMnZMQ1pSL20rdmlSdmNaODVNczVvazhoMXVOeW40T1NlL0FiWm5EWFdWLwozY3E2eGM3MG13YjRwSm5tVGk3WG1jK1dZV2pieTVaOUtoSDI1UUtab0hVUHpLemVzZzRUSEdHbk14eHB6a29jCkJCSDRkTVovSS9Vd0FkS0tEZ2Ewb0IrczNUZGIya0I0TkNNZkI1Q29VdXZjZUlWc2VtQlBnemt3VE9PUnRBbEoKL2sxdDBpdmo0alQ0VU5UcEt3OGlmNW1QKzMxTTBIeCtaeldLU2pIZ09xbGFyaFVwRitKQTFCVUtmTEllSjliYwpnWkNONGZ5MDRqSjUrdkFOY1NmRnBGS05rSEt1bkp4aDQveEJaeENXSFpJdVpXaWdsNTZTaGVMSVpIYTJWVU81CnE1L21qOGZmQWdNQkFBRUNnZ0VCQUpmblI1TmhESjd1T1J2cEZKSEtvL3BPZjZ6ZElsbW45MUFCSWczYkVUeWUKcWFwS3FaUldtT0d2aFRLSHJYQzlpWWUrd2xNZlhNMDdNUm1Yb3BzS21rQURqdThXc3VnbHJWeURHamNzR2RqegpNZkdYZHA4RUplZlFkVGFQY1pjOWpNcEEvK2VCeHUzT2Yxa0hpR0g4YkVBZDBNUWk0bFpISVVWQXJmT2V2NEZhCmlaTEJGdTEzY0pCMEJNbzJUZU9JZDQ1R0p3OXVTQWd3Q1VuZWQ5WG42SlkremI3cVRKUE1qQkdlYWY0L0E5UnMKV29nWW85ZUtYaTF3YmFRcGthdUZINWx2U29jckRsQWRiaERiam00c0lIdzJtQUwwSUk0d1YzVTJLbUhFMEg3Nwp2b1M0dUdHNW1BZFZ0elRDTnZOa09YMC9WVWdNMTJ1eHllZVZ4c3FaTkxFQ2dZRUEvcFhJR2hDNlhkVkNNNDk1CkVsR1RENW4zUXdUOTZXZkNRQ0w2cXlaK1UwUHNla0dDV2pXTWt3R2tHbWl1ZkVKdFN2VFl2d1NzcUZpaTZsdlkKNXRKSkR3dEd4ZW0weEF0SWc3RlNuUVcvdDNWcExXOE9QVmFXQ0lOSXJKSHc3L2tOZ2RpVmNBWXpiN05DUGtjVgptTEs5aSt6bXpKMmkrbE9vaFVKRlE5aVZEQWNDZ1lFQTVZaDFHYWlGMUl6UzRVdE9pZklLcjZ3bW40MEQ3ZEVmClF5SXF5d3JSTVcya1BxVkViaVBCVkM3dUxUeUlKMWdwVDhPakl3WFlrOUlRdW01ZW42TTc2aXlxYmUrY3JhdE8KbEliK3pLL2RBNzZuUjBHOGJ2eVBmMS9FbU5vL0J1ZUZldWQyaWNacWhMd25HUkxUL0FPQktkMTZ4ZGtoSjdkVwo5WWRsbmliL0gya0NnWUJqaC8rQllVVGJab0xKVWJCTjN5S1ZtT1k0aDMwVmdZRktQZUt1Z3hBa1VZdmtpRFFFClBLZEcyZjlrYThTN3crU2l4M0x0eTFBTlNRbjRnZ3djd2QzYVNkaFRpL2ttSkJzbmtXM3hBY3hnVjBEVmlIT1MKdTRWUXNyTDQ5TjJ6bERVWnJLbFA4THl2VU8vM0ZTZDJXWHNOZDRNTVFCejFyVHRYVGY5eWRQeGdoUUtCZ0FzRgptWEhvK0x3ekl2RTBsNG5pT0ljbEFobkpRTFlMdzYrS2xYbDRoSUVXSGJ2d1hhL1lRYkZVSzVXNmdXTTZSWW1TCkFHUnZKRzQ4dVJicXM5cUFrU0Qya2h5RFZBZTI0U2dhcjJPTzBsTWF0WmVqWFhRSzVxMGNJU21oMlVMd25TVkEKdGgxNWpLWGhEeE9TSnVIRUhWQ3dWUFBwTUVGaStXY0M0R3RPdmhONUFvR0FKbXdFSkR2QkZtL0t0SXlZMDhuQgpJSExkc1dSWmRXOXpNbHBsMVk2OTV1b21qS1hVMEwva3VDZ1QwRFVVVG1VK0Q3aGFZL2FQajB6MWpqR0JGcEQ0ClJ3cVA2dVJQTVhRMExLSG5IbmFQclJzanM2Q2xicVQ4ZU93dTRkeVpmYUtTS3VvbVpDZ1pkV2ZxWThVL21tbWIKT21vckhNeWJzWG0wWnJNaTZOemlzam89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
wan
192.168.1.220
GW_WAN
1
inet
Interface wan Gateway
lan
192.168.1.220
GW_LAN
1
inet
Interface lan Gateway
GW_WAN
nmap
NMap is a utility for network exploration or security auditing.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br/&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It supports ping scanning (determine which hosts are up), many port scanning techniques (determine what services the hosts are offering), version detection (determine what application/service is running on a port), and TCP/IP fingerprinting (remote host OS or device identification).
It also offers flexible target and port specification, decoy/stealth scanning, SunRPC scanning, and more.
1.4.4_1
https://doc.pfsense.org/index.php/Nmap_package
nmap.xml
/usr/local/pkg/nmap.inc
iftop
https://forum.pfsense.org/
Realtime interface monitor (console/shell only).
http://www.ex-parrot.com/~pdw/iftop/
0.17_2
iftop.xml
Open-VM-Tools
VMware Tools is a suite of utilities that enhances the performance of the virtual machine's guest operating system and improves management of the virtual machine.
http://open-vm-tools.sourceforge.net/
10.1.0,1
https://doc.pfsense.org/index.php/Open_VM_Tools_package
open-vm-tools.xml
/usr/local/pkg/open-vm-tools.inc
squid3
squid
High performance web proxy cache (3.4 branch). It combines Squid as a proxy server with its capabilities of acting as a HTTP / HTTPS reverse proxy.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
It includes an Exchange-Web-Access (OWA) Assistant, SSL filtering and antivirus integration via C-ICAP.
https://forum.pfsense.org/index.php?board=60.0
http://www.squid-cache.org/
0.4.44_7
squid.xml
squid_generate_rules
General
/pkg_edit.php?xml=squid.xml&id=0
Remote Cache
/pkg.php?xml=squid_upstream.xml
Local Cache
/pkg_edit.php?xml=squid_cache.xml&id=0
Antivirus
/pkg_edit.php?xml=squid_antivirus.xml&id=0
ACLs
/pkg_edit.php?xml=squid_nac.xml&id=0
Traffic Mgmt
/pkg_edit.php?xml=squid_traffic.xml&id=0
Authentication
/pkg_edit.php?xml=squid_auth.xml&id=0
Users
/pkg.php?xml=squid_users.xml
Real Time
/squid_monitor.php
Sync
/pkg_edit.php?xml=squid_sync.xml
/usr/local/pkg/squid.inc
NMap
nmap.xml
Squid Proxy Server
Modify the proxy server settings
/pkg_edit.php?xml=squid.xml&id=0
Squid Reverse Proxy
Modify the reverse proxy server settings
/pkg_edit.php?xml=squid_reverse_general.xml&id=0
vmware-guestd
vmware-guestd.sh
mwexec("/usr/local/etc/rc.d/vmware-guestd status") == 0;
VMware Guest Daemon
vmware-kmod
vmware-kmod.sh
mwexec("/usr/local/etc/rc.d/vmware-kmod status") == 0;
VMware Kernel Modules
squid
squid.sh
squid
Squid Proxy Server Service
clamd
clamd.sh
clamd
ClamAV Antivirus
c-icap
c-icap.sh
c-icap
ICAP Inteface for Squid and ClamAV integration
heap LFUDA
90
95
100
ufs
16
/var/squid/cache
0
4
64
256
heap GDSF
none
on
on
lan
3128
on
lan
splicewhitelist
lan
modern
2048
none
/var/squid/logs
localhost
admin@localhost
en
on
strip
Lg==
ipalias
lan
5c0a4b6139b05
single
24
10.255.2.254
ipalias
lan
5c0a4bd391375
single
24
10.255.3.254
pfSense
acme.com
vmx1
100
vmx1.100
vmx1
1100
vmx1.1100
vmx2
1100
vmx2.1100
================================================
FILE: tests/unit/plugins/modules/pfsense_module.py
================================================
# Copyright: (c) 2018 Red Hat Inc.
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2024, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import errno
import json
import re
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from tempfile import mkstemp
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import fromstring, ElementTree
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
fixture_data = {}
def load_fixture(name):
path = os.path.join(fixture_path, name)
if path in fixture_data:
return fixture_data[path]
with open(path) as f:
data = f.read()
try:
data = json.loads(data)
except ValueError:
pass
fixture_data[path] = data
return data
class TestPFSenseModule(ModuleTestCase):
##############################
# init
#
def __init__(self, *args, **kwargs):
super(TestPFSenseModule, self).__init__(*args, **kwargs)
self.xml_result = None
self.tmp_file = None
self.config_file = None
self.pfmodule = None
def setUp(self):
""" mocking up """
super(TestPFSenseModule, self).setUp()
self.mock_parse = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.ET.parse')
self.parse = self.mock_parse.start()
self.mock_shutil_move = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.shutil.move')
self.shutil_move = self.mock_shutil_move.start()
self.mock_php = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.php')
self.php = self.mock_php.start()
self.php.return_value = ['vmx0', 'vmx1', 'vmx2', 'vmx3']
self.mock_phpshell = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.phpshell')
self.phpshell = self.mock_phpshell.start()
self.phpshell.return_value = (0, '', '')
self.mock_mkstemp = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.mkstemp')
self.mkstemp = self.mock_mkstemp.start()
self.mkstemp.return_value = mkstemp()
self.tmp_file = self.mkstemp.return_value[1]
self.mock_chmod = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.os.chmod')
self.chmod = self.mock_chmod.start()
self.mock_get_version = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.get_version')
self.get_version = self.mock_get_version.start()
self.get_version.return_value = "2.5.2"
self.maxDiff = None
def tearDown(self):
""" mocking down """
super(TestPFSenseModule, self).tearDown()
self.mock_parse.stop()
self.mock_shutil_move.stop()
self.mock_php.stop()
self.mock_phpshell.stop()
self.mock_mkstemp.stop()
self.mock_chmod.stop()
self.mock_get_version.stop()
try:
if self.tmp_file is not None:
os.remove(self.tmp_file)
except OSError as exception:
if exception.errno != errno.ENOENT:
raise
def get_args_fields(self):
""" return params fields """
try:
return self.pfmodule.get_argument_spec().keys()
except AttributeError:
raise NotImplementedError()
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
raise NotImplementedError()
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
raise NotImplementedError()
def check_target_elt_direct(self, target_elt, expected_elt_string):
""" check XML definition of target elt against expected XML """
target_elt_string = ET.tostring(target_elt, encoding="unicode", short_empty_elements=False)
self.assertEqual(target_elt_string, expected_elt_string)
def args_from_var(self, var, state='present', **kwargs):
""" return arguments for module from var """
args = {}
fields = self.get_args_fields()
for field in fields:
if field in var:
args[field] = var[field]
if state is not None:
args['state'] = state
for key, value in kwargs.items():
args[key] = value
return args
def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False, msg=''):
self.load_fixtures()
if failed:
result = self.failed()
self.assertTrue(result['failed'], result)
else:
result = self.changed(changed)
if not failed:
self.assertEqual(result['changed'], changed, result)
else:
self.assertEqual(result['msg'], msg)
if commands is not None:
if sort:
self.assertEqual(sorted(commands), sorted(result['commands']), result['commands'])
else:
self.assertEqual(commands, result['commands'], result['commands'])
return result
def do_module_test(self, obj, command=None, changed=True, failed=False, msg=None, delete=False, state='present', expected_elt_string=None, **kwargs):
""" run test """
if command is not None:
command = self.strip_commands(command)
obj = self.strip_params(obj)
if delete:
state = 'absent'
with set_module_args(self.args_from_var(obj, state=state)):
result = self.execute_module(changed=changed, failed=failed, msg=msg)
if not isinstance(command, list):
command = [command]
if failed:
self.assertFalse(self.load_xml_result())
elif not changed:
self.assertFalse(self.load_xml_result())
self.assertEqual(result['commands'], [], result)
elif delete:
self.assertTrue(self.load_xml_result())
target_elt = self.get_target_elt(obj, absent=True, module_result=result)
self.assertIsNone(target_elt)
self.assertEqual(result['commands'], command, result)
else:
self.assertTrue(self.load_xml_result())
target_elt = self.get_target_elt(obj, module_result=result)
self.assertIsNotNone(target_elt)
self.assertEqual(result['commands'], command, result)
if expected_elt_string is not None:
self.check_target_elt_direct(target_elt, expected_elt_string)
else:
self.check_target_elt(obj, target_elt, **kwargs)
def failed(self):
with self.assertRaises(AnsibleFailJson) as exc:
self.module.main()
result = exc.exception.args[0]
self.assertTrue(result['failed'], result)
return result
def changed(self, changed=False):
with self.assertRaises(AnsibleExitJson) as exc:
self.module.main()
result = exc.exception.args[0]
if 'diff' in result:
changes = dict()
after = dict(result['diff']['after'])
before = dict(result['diff']['before'])
for item in after:
if item in before:
if after[item] != before[item]:
changes[item] = str(before[item]) + ' -> ' + str(after[item])
del before[item]
else:
changes[item] = 'None -> ' + str(after[item])
for item in before:
changes[item] = str(before[item]) + ' -> None'
if changes:
result['changes'] = changes
self.assertEqual(result['changed'], changed, result)
return result
def strip_commands(self, commands):
""" remove old or new parameters """
return commands
def strip_params(self, params):
""" remove old or new parameters """
return params
def get_config_file(self):
""" get config file """
return self.config_file
def load_fixtures(self):
""" loading data """
self.parse.return_value = ElementTree(fromstring(load_fixture(self.get_config_file())))
def load_xml_result(self):
""" load the resulting xml if not already loaded """
if self.xml_result is None and os.path.getsize(self.tmp_file) > 0:
self.xml_result = ET.parse(self.tmp_file)
return self.xml_result is not None
@staticmethod
def find_xml_tag(parent_tag, elt_filter):
""" return alias named name, having type aliastype """
for tag in parent_tag:
found = True
for key, value in elt_filter.items():
elt = tag.find(key)
if elt is not None:
if elt.text is None and value is None:
continue
if elt.text is not None and elt.text == value:
continue
found = False
break
if found:
return tag
return None
def assert_xml_elt_value(self, parent_tag_name, elt_filter, elt_name, elt_value):
""" check the xml elt exist and has the exact value given """
self.load_xml_result()
parent_tag = self.xml_result.find(parent_tag_name)
if parent_tag is None:
self.fail('Unable to find tag ' + parent_tag_name)
tag = self.find_xml_tag(parent_tag, elt_filter)
if tag is None:
self.fail('Tag not found: ' + json.dumps(elt_filter))
self.assert_xml_elt_equal(tag, elt_name, elt_value)
def assert_xml_elt_dict(self, parent_tag_name, elt_filter, elts):
""" check all the xml elt in elts exist and have the exact value given """
self.load_xml_result()
parent_tag = self.xml_result.find(parent_tag_name)
if parent_tag is None:
self.fail('Unable to find tag ' + parent_tag_name)
tag = self.find_xml_tag(parent_tag, elt_filter)
if tag is None:
self.fail('Tag not found: ' + json.dumps(elt_filter))
for elt_name, elt_value in elts.items():
self.assert_xml_elt_equal(tag, elt_name, elt_value)
def assert_has_xml_tag(self, parent_tag_name, elt_filter, absent=False):
""" check the xml elt exist (or not if absent is True) """
self.load_xml_result()
parent_tag = self.xml_result.find(parent_tag_name)
if parent_tag is None:
self.fail('Unable to find tag ' + parent_tag_name)
tag = self.find_xml_tag(parent_tag, elt_filter)
if absent and tag is not None:
self.fail('Tag found: ' + json.dumps(elt_filter))
elif not absent and tag is None:
self.fail('Tag not found: ' + json.dumps(elt_filter))
return tag
def assert_find_xml_elt(self, tag, elt_name):
elt = tag.find(elt_name)
if elt is None:
self.fail('Element not found: ' + elt_name)
return elt
def assert_not_find_xml_elt(self, tag, elt_name):
elt = tag.find(elt_name)
if elt is not None:
self.fail('Element found: ' + elt_name)
return elt
def assert_xml_elt_equal(self, tag, elt_name, elt_value):
elt = tag.find(elt_name)
if elt is None:
self.fail('Element not found: ' + elt_name + ' in tag:' + tag)
if isinstance(elt_value, int):
value = str(elt_value)
else:
value = elt_value
if elt.text != value:
if elt.text is None:
self.fail('Element <' + elt_name + '> differs. Expected: \'' + str(value) + '\' result: None')
else:
self.fail('Element <' + elt_name + '> differs. Expected: \'' + str(value) + '\' result: \'' + elt.text + '\'')
return elt
def assert_xml_elt_match(self, tag, elt_name, elt_regex):
elt = tag.find(elt_name)
if elt is None:
self.fail('Element not found: ' + elt_name)
if re.fullmatch(elt_regex, elt.text) is None:
if elt.text is None:
self.fail('Element <' + elt_name + '> does not match \'' + elt_regex + '\' result: None')
else:
self.fail('Element <' + elt_name + '> does not match \'' + elt_regex + '\' result: \'' + elt.text + '\'')
return elt
def assert_xml_elt_is_none_or_empty(self, tag, elt_name):
elt = tag.find(elt_name)
if elt is None:
return elt
if elt.text is not None and elt.text:
self.fail('Element <' + elt_name + '> differs. Expected: NoneType result: \'' + elt.text + '\'')
return elt
def assert_list_xml_elt_equal(self, tag, elt_name, elt_value):
elts = tag.findall(elt_name)
if elts is None:
self.fail('Element not found: ' + elt_name)
elt_value_copy = list(elt_value)
elt_texts = []
for elt in elts:
if elt.text not in elt_value_copy:
if elt.text is None:
self.fail('Element <' + elt_name + '> differs. Expected: \'' + str(elt_value) + '\' result: None')
else:
self.fail('Element <' + elt_name + '> differs. Expected: \'' + str(elt_value) + '\' result: \'' + elt.text + '\'')
elt_value_copy.remove(elt.text)
elt_texts.append(elt.text)
if len(elt_value_copy):
self.fail('Element <' + elt_name + '> differs. Expected: \'' + str(elt_value) + '\' result: \'' + str(elt_texts) + '\'')
return elts
@staticmethod
def unalias_interface(interface, physical=False):
""" return real alias name if required """
res = []
if physical:
interfaces = dict(lan='vmx1', wan='vmx0', opt1='vmx2', vpn='vmx2', opt2='vmx3', vt1='vmx3', opt3='vmx3.100', lan_100='vmx3.100')
else:
interfaces = dict(lan='lan', wan='wan', vpn='opt1', vt1='opt2', lan_100='opt3')
if interface.startswith('vip:'):
return '_vip602874de0ff00'
for iface in interface.split(','):
if interface in interfaces:
res.append(interfaces[iface])
else:
res.append(iface)
return ','.join(res)
def check_param_equal(self, params, target_elt, param, default=None, xml_field=None, not_find_val=None):
""" if param is defined, check if target_elt has the right value, otherwise that it does not exist in XML """
if xml_field is None:
xml_field = param
value = default
if param in params:
value = params[param]
if value is not None:
if not_find_val is not None and not_find_val == default:
self.assert_not_find_xml_elt(target_elt, xml_field)
else:
self.assert_xml_elt_equal(target_elt, xml_field, value)
else:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
def check_param_bool(self, params, target_elt, param, default=False, value_true=None, value_false=None, xml_field=None):
""" if param is defined, check the elt exist and text equals value_true, otherwise that it does not exist in XML or
is empty if value_true is not None or equals value_false if set """
if xml_field is None:
xml_field = param
if (param in params and params[param]) or default:
if value_true is None:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
else:
self.assert_xml_elt_equal(target_elt, xml_field, value_true)
else:
if value_true is None:
self.assert_not_find_xml_elt(target_elt, xml_field)
else:
if value_false is not None:
self.assert_xml_elt_equal(target_elt, xml_field, value_false)
else:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
def check_value_equal(self, target_elt, xml_field, value, empty=True):
""" if value is defined, check if target_elt has the right value, otherwise that it does not exist in XML """
if value is None:
if empty:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
else:
self.assert_not_find_xml_elt(target_elt, xml_field)
else:
self.assert_xml_elt_equal(target_elt, xml_field, value)
def check_param_equal_or_not_find(self, params, target_elt, param, xml_field=None, not_find_val=None, empty=False):
""" if param is defined, check if target_elt has the right value, otherwise that it does not exist in XML """
if xml_field is None:
xml_field = param
if param in params:
if not_find_val is not None and not_find_val == params[param]:
self.assert_not_find_xml_elt(target_elt, xml_field)
elif empty and params[param]:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
else:
self.assert_xml_elt_equal(target_elt, xml_field, params[param])
else:
self.assert_not_find_xml_elt(target_elt, xml_field)
def check_param_equal_or_present(self, params, target_elt, param, xml_field=None):
""" if param is defined, check if target_elt has the right value, otherwise that it is present in XML """
if xml_field is None:
xml_field = param
if param in params:
self.assert_xml_elt_equal(target_elt, xml_field, params[param])
else:
self.assert_find_xml_elt(target_elt, xml_field)
def check_list_param_equal(self, params, target_elt, param, default=None, xml_field=None, not_find_val=None):
""" if param is defined, check if target_elt has the right value, otherwise that it does not exist in XML """
if xml_field is None:
xml_field = param
value = default
if param in params:
value = params[param]
if value is not None:
if not_find_val is not None and not_find_val == default:
self.assert_not_find_xml_elt(target_elt, xml_field)
else:
self.assert_list_xml_elt_equal(target_elt, xml_field, value)
else:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
def check_list_param_equal_or_not_find(self, params, target_elt, param, xml_field=None, not_find_val=None, empty=False):
""" if param is defined, check if target_elt has the right value, otherwise that it does not exist in XML """
if xml_field is None:
xml_field = param
if param in params:
if not_find_val is not None and not_find_val == params[param]:
self.assert_not_find_xml_elt(target_elt, xml_field)
elif empty and params[param]:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
else:
self.assert_list_xml_elt_equal(target_elt, xml_field, params[param])
else:
self.assert_not_find_xml_elt(target_elt, xml_field)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_aggregate.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from ansible_collections.pfsensible.core.plugins.modules import pfsense_aggregate
from .pfsense_module import TestPFSenseModule
class TestPFSenseAggregateModule(TestPFSenseModule):
module = pfsense_aggregate
def __init__(self, *args, **kwargs):
super(TestPFSenseAggregateModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_aggregate_config.xml'
def assert_find_alias(self, alias):
""" test if an alias exist """
self.load_xml_result()
parent_tag = self.xml_result.find('aliases')
if parent_tag is None:
self.fail('Unable to find tag aliases')
tag = self.find_xml_tag(parent_tag, dict(name=alias))
if tag is None:
self.fail('Alias not found: ' + alias)
def assert_not_find_alias(self, alias):
""" test if an alias does not exist """
self.load_xml_result()
parent_tag = self.xml_result.find('aliases')
if parent_tag is None:
self.fail('Unable to find tag aliases')
tag = self.find_xml_tag(parent_tag, dict(name=alias))
if tag is not None:
self.fail('Alias found: ' + alias)
def assert_find_rule(self, rule, interface):
""" test if a rule exist on interface """
self.load_xml_result()
parent_tag = self.xml_result.find('filter')
if parent_tag is None:
self.fail('Unable to find tag filter')
tag = self.find_xml_tag(parent_tag, dict(descr=rule, interface=interface))
if tag is None:
self.fail('Rule not found: ' + rule)
def assert_not_find_rule(self, rule, interface):
""" test if a rule does not exist on interface """
self.load_xml_result()
parent_tag = self.xml_result.find('filter')
if parent_tag is None:
self.fail('Unable to find tag filter')
tag = self.find_xml_tag(parent_tag, dict(descr=rule, interface=interface))
if tag is not None:
self.fail('Rule found: ' + rule + ' on ' + interface)
def assert_find_rule_separator(self, separator, interface):
""" test if a rule separator exist on interface """
self.load_xml_result()
interface = self.unalias_interface(interface)
parent_tag = self.xml_result.find('filter')
if parent_tag is None:
self.fail('Unable to find tag filter')
separators_elt = parent_tag.find('separator')
if parent_tag is None:
self.fail('Unable to find tag separator')
interface_elt = separators_elt.find(interface)
if parent_tag is None:
self.fail('Unable to find tag ' + interface)
tag = self.find_xml_tag(interface_elt, dict(text=separator))
if tag is None:
self.fail('Separator not found: ' + separator)
def assert_not_find_rule_separator(self, separator, interface):
""" test if a rule separator dost exist on interface """
self.load_xml_result()
interface = self.unalias_interface(interface)
parent_tag = self.xml_result.find('filter')
if parent_tag is None:
self.fail('Unable to find tag filter')
separators_elt = parent_tag.find('separator')
if parent_tag is None:
self.fail('Unable to find tag separator')
interface_elt = separators_elt.find(interface)
if parent_tag is None:
self.fail('Unable to find tag ' + interface)
tag = self.find_xml_tag(interface_elt, dict(text=separator))
if tag is not None:
self.fail('Separator found: ' + separator)
def assert_find_vlan(self, interface, vlan_id):
""" test if a vlan exist """
self.load_xml_result()
parent_tag = self.xml_result.find('vlans')
if parent_tag is None:
self.fail('Unable to find tag vlans')
elt_filter = {}
elt_filter['if'] = interface
elt_filter['tag'] = vlan_id
tag = self.find_xml_tag(parent_tag, elt_filter)
if tag is None:
self.fail('Vlan not found: {0}.{1}'.format(interface, vlan_id))
def assert_not_find_vlan(self, interface, vlan_id):
""" test if an vlan does not exist """
self.load_xml_result()
parent_tag = self.xml_result.find('vlans')
if parent_tag is None:
self.fail('Unable to find tag vlans')
elt_filter = {}
elt_filter['if'] = interface
elt_filter['vlan_id'] = vlan_id
tag = self.find_xml_tag(parent_tag, elt_filter)
if tag is not None:
self.fail('Vlan found: {0}.{1}'.format(interface, vlan_id))
############
# as we rely on sub modules for modifying the xml
# we dont perform extensive checks on the xml modifications
# we just test if elements are created or deleted, and the respective output
def test_aggregate_aliases(self):
""" test creation of a some aliases """
args = dict(
purge_aliases=False,
aggregated_aliases=[
dict(name='one_host', type='host', address='10.9.8.7'),
dict(name='another_host', type='host', address='10.9.8.6'),
dict(name='one_server', type='host', address='192.168.1.165', descr='', detail=''),
dict(name='port_ssh', type='port', address='2222'),
dict(name='port_http', state='absent'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_aliases = []
result_aliases.append("create alias 'one_host', type='host', address='10.9.8.7'")
result_aliases.append("create alias 'another_host', type='host', address='10.9.8.6'")
result_aliases.append("update alias 'port_ssh' set address='2222'")
result_aliases.append("delete alias 'port_http'")
self.assertEqual(result['result_aliases'], result_aliases)
self.assert_find_alias('one_host')
self.assert_find_alias('another_host')
self.assert_find_alias('one_server')
self.assert_find_alias('port_ssh')
self.assert_not_find_alias('port_http')
self.assert_find_alias('port_dns')
def test_aggregate_aliases_checkmode(self):
""" test creation of a some aliases with check_mode """
args = dict(
purge_aliases=False,
aggregated_aliases=[
dict(name='one_host', type='host', address='10.9.8.7'),
dict(name='another_host', type='host', address='10.9.8.6'),
dict(name='one_server', type='host', address='192.168.1.165', descr='', detail=''),
dict(name='port_ssh', type='port', address='2222'),
dict(name='port_http', state='absent'),
],
_ansible_check_mode=True,
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_aliases = []
result_aliases.append("create alias 'one_host', type='host', address='10.9.8.7'")
result_aliases.append("create alias 'another_host', type='host', address='10.9.8.6'")
result_aliases.append("update alias 'port_ssh' set address='2222'")
result_aliases.append("delete alias 'port_http'")
self.assertEqual(result['result_aliases'], result_aliases)
self.assertFalse(self.load_xml_result())
def test_aggregate_aliases_purge(self):
""" test creation of a some aliases with purge """
args = dict(
purge_aliases=True,
purge_rules=False,
aggregated_aliases=[
dict(name='one_host', type='host', address='10.9.8.7'),
dict(name='another_host', type='host', address='10.9.8.6'),
dict(name='one_server', type='host', address='192.168.1.165', descr='', detail=''),
dict(name='port_ssh', type='port', address='2222'),
dict(name='port_http', state='absent'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_aliases = []
result_aliases.append("create alias 'one_host', type='host', address='10.9.8.7'")
result_aliases.append("create alias 'another_host', type='host', address='10.9.8.6'")
result_aliases.append("update alias 'port_ssh' set address='2222'")
result_aliases.append("delete alias 'port_http'")
result_aliases.append("delete alias 'port_dns'")
self.assertEqual(result['result_aliases'], result_aliases)
self.assert_find_alias('one_host')
self.assert_find_alias('another_host')
self.assert_find_alias('one_server')
self.assert_find_alias('port_ssh')
self.assert_not_find_alias('port_http')
self.assert_not_find_alias('port_dns')
def test_aggregate_rules(self):
""" test creation of a some rules """
args = dict(
purge_rules=False,
aggregated_rules=[
dict(name='one_rule', source='any', destination='any', interface='lan'),
dict(name='any2any_ssh', source='any', destination='any:2222', interface='lan', protocol='tcp'),
dict(name='any2any_http', source='any', destination='any:8080', interface='vpn', protocol='tcp'),
dict(name='any2any_ssh', state='absent', interface='vpn'),
]
)
with set_module_args(args):
self.execute_module(changed=True)
self.assert_find_rule('one_rule', 'lan')
self.assert_find_rule('any2any_ssh', 'lan')
self.assert_find_rule('any2any_http', 'lan')
self.assert_find_rule('any2any_https', 'lan')
self.assert_not_find_rule('any2any_ssh', 'opt1')
self.assert_find_rule('any2any_http', 'opt1')
self.assert_find_rule('any2any_https', 'opt1')
def test_aggregate_rules_purge(self):
""" test creation of a some rules with purge """
args = dict(
purge_rules=True,
aggregated_rules=[
dict(name='one_rule', source='any', destination='any', interface='lan'),
dict(name='any2any_ssh', source='any', destination='any:2222', interface='lan', protocol='tcp'),
dict(name='any2any_http', source='any', destination='any:8080', interface='vpn', protocol='tcp'),
dict(name='any2any_ssh', state='absent', interface='vpn'),
]
)
with set_module_args(args):
self.execute_module(changed=True)
self.assert_find_rule('one_rule', 'lan')
self.assert_find_rule('any2any_ssh', 'lan')
self.assert_not_find_rule('any2any_http', 'lan')
self.assert_not_find_rule('any2any_https', 'lan')
self.assert_not_find_rule('any2any_ssh', 'opt1')
self.assert_find_rule('any2any_http', 'opt1')
self.assert_not_find_rule('any2any_https', 'opt1')
def test_aggregate_separators(self):
""" test creation of a some separators """
args = dict(
purge_rule_separators=False,
aggregated_rule_separators=[
dict(name='one_separator', interface='lan'),
dict(name='another_separator', interface='lan_100'),
dict(name='another_test_separator', interface='lan', state='absent'),
dict(name='test_separator', interface='lan', before='bottom', color='warning'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_separators = []
result_separators.append("create rule_separator 'one_separator' on 'lan', color='info'")
result_separators.append("create rule_separator 'another_separator' on 'lan_100', color='info'")
result_separators.append("delete rule_separator 'another_test_separator' on 'lan'")
result_separators.append("update rule_separator 'test_separator' on 'lan' set color='warning', before='bottom'")
self.assertEqual(result['result_rule_separators'], result_separators)
self.assert_find_rule_separator('one_separator', 'lan')
self.assert_find_rule_separator('another_separator', 'lan_100')
self.assert_not_find_rule_separator('another_test_separator', 'lan')
self.assert_find_rule_separator('test_separator', 'lan')
def test_aggregate_separators_purge(self):
""" test creation of a some separators with purge """
args = dict(
purge_rule_separators=True,
aggregated_rule_separators=[
dict(name='one_separator', interface='lan'),
dict(name='another_separator', interface='lan_100'),
dict(name='another_test_separator', interface='lan', state='absent'),
dict(name='test_separator', interface='lan', before='bottom', color='warning'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_separators = []
result_separators.append("create rule_separator 'one_separator' on 'lan', color='info'")
result_separators.append("create rule_separator 'another_separator' on 'lan_100', color='info'")
result_separators.append("delete rule_separator 'another_test_separator' on 'lan'")
result_separators.append("update rule_separator 'test_separator' on 'lan' set color='warning', before='bottom'")
result_separators.append("delete rule_separator 'test_separator' on 'wan'")
result_separators.append("delete rule_separator 'last_test_separator' on 'lan'")
result_separators.append("delete rule_separator 'test_sep_floating' on 'floating'")
self.assertEqual(result['result_rule_separators'], result_separators)
self.assert_find_rule_separator('one_separator', 'lan')
self.assert_find_rule_separator('another_separator', 'lan_100')
self.assert_not_find_rule_separator('another_test_separator', 'lan')
self.assert_find_rule_separator('test_separator', 'lan')
self.assert_not_find_rule_separator('last_test_separator', 'lan')
self.assert_not_find_rule_separator('test_sep_floating', 'floatingrules')
def test_aggregate_nat_outbound(self):
""" test creation of some nat outbound """
args = dict(
purge_nat_outbounds=True,
aggregated_nat_outbounds=[
dict(descr='snat 1', source='192.168.100.0/24', destination='1.1.1.0/24', interface='lan', staticnatport=True),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_nat_outbounds = []
result_nat_outbounds.append("delete nat_outbound 'None'")
result_nat_outbounds.append(
"create nat_outbound 'snat 1', interface='lan', source='192.168.100.0/24', destination='1.1.1.0/24', staticnatport=True")
self.assertEqual(result['result_nat_outbounds'], result_nat_outbounds)
def test_aggregate_vlans(self):
""" test creation of some vlans """
args = dict(
purge_vlans=False,
aggregated_vlans=[
dict(vlan_id=100, interface='vmx0', descr='voice'),
dict(vlan_id=1200, interface='vmx1', state='absent'),
dict(vlan_id=101, interface='vmx1', descr='printers'),
dict(vlan_id=102, interface='vmx2', descr='users'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_aliases = []
result_aliases.append("update vlan 'vmx0.100' set descr='voice'")
result_aliases.append("delete vlan 'vmx1.1200'")
result_aliases.append("create vlan 'vmx1.101', descr='printers', priority=''")
result_aliases.append("create vlan 'vmx2.102', descr='users', priority=''")
self.assertEqual(result['result_vlans'], result_aliases)
self.assert_find_vlan('vmx0', '100')
self.assert_not_find_vlan('vmx1', '1200')
self.assert_find_vlan('vmx1', '101')
self.assert_find_vlan('vmx2', '102')
def test_aggregate_vlans_with_purge(self):
""" test creation of some vlans with purge"""
args = dict(
purge_vlans=True,
aggregated_vlans=[
dict(vlan_id=1100, interface='vmx1'),
dict(vlan_id=1200, interface='vmx1', state='absent'),
dict(vlan_id=101, interface='vmx1', descr='printers'),
dict(vlan_id=102, interface='vmx2', descr='users'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_aliases = []
result_aliases.append("delete vlan 'vmx1.1200'")
result_aliases.append("create vlan 'vmx1.101', descr='printers', priority=''")
result_aliases.append("create vlan 'vmx2.102', descr='users', priority=''")
result_aliases.append("delete vlan 'vmx0.100'")
self.assertEqual(result['result_vlans'], result_aliases)
self.assert_not_find_vlan('vmx1', '1200')
self.assert_find_vlan('vmx1', '101')
self.assert_find_vlan('vmx2', '102')
self.assert_not_find_vlan('vmx0', '100')
================================================
FILE: tests/unit/plugins/modules/test_pfsense_alias.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from copy import copy
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from ansible_collections.pfsensible.core.plugins.modules import pfsense_alias
from ansible_collections.pfsensible.core.plugins.module_utils.alias import PFSenseAliasModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseAliasModule(TestPFSenseModule):
module = pfsense_alias
def __init__(self, *args, **kwargs):
super(TestPFSenseAliasModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_alias_config.xml'
self.pfmodule = PFSenseAliasModule
########################################################
# Generic set of funcs used for testing aliases
# First we run the module
# Then, we check return values
# Finally, we check the xml
def do_alias_creation_test(self, alias, set_after=None, unset_after=None, failed=False, msg='', command=None):
""" test creation of a new alias """
with set_module_args(self.args_from_var(alias)):
result = self.execute_module(changed=True, failed=failed, msg=msg)
if not failed:
diff = dict(before={}, after=alias)
if set_after is not None:
diff['after'].update(set_after)
if unset_after is not None:
for n in unset_after:
del diff['after'][n]
self.assertEqual(result['diff'], diff)
self.assert_xml_elt_dict('aliases', dict(name=alias['name'], type=alias['type']), diff['after'])
self.assertEqual(result['commands'], [command])
else:
self.assertFalse(self.load_xml_result())
def do_alias_deletion_test(self, alias, command=None):
""" test deletion of an alias """
with set_module_args(self.args_from_var(alias, 'absent')):
result = self.execute_module(changed=True)
diff = dict(before=alias, after={})
self.assertEqual(result['diff'], diff)
self.assert_has_xml_tag('aliases', dict(name=alias['name'], type=alias['type']), absent=True)
self.assertEqual(result['commands'], [command])
def do_alias_update_noop_test(self, alias):
""" test not updating an alias """
with set_module_args(self.args_from_var(alias)):
result = self.execute_module(changed=False)
diff = dict(before=alias, after=alias)
self.assertEqual(result['diff'], diff)
self.assertFalse(self.load_xml_result())
self.assertEqual(result['commands'], [])
def do_alias_update_field(self, alias, set_after=None, command=None, **kwargs):
""" test updating field of an host alias """
target = copy(alias)
target.update(kwargs)
with set_module_args(self.args_from_var(target)):
result = self.execute_module(changed=True)
diff = dict(before=alias, after=copy(target))
if set_after is not None:
diff['after'].update(set_after)
self.assertEqual(result['diff'], diff)
if alias['type'] in ['host', 'port', 'network']:
self.assert_xml_elt_value('aliases', dict(name=alias['name'], type=alias['type']), 'address', diff['after']['address'])
else:
self.assert_xml_elt_value('aliases', dict(name=alias['name'], type=alias['type']), 'url', diff['after']['url'])
self.assertEqual(result['commands'], [command])
##############
# hosts
#
def test_host_create(self):
""" test creation of a new host alias """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2', descr='', type='host', detail='')
command = "create alias 'adservers', type='host', address='10.0.0.1 10.0.0.2', descr='', detail=''"
self.do_alias_creation_test(alias, command=command)
def test_host_delete(self):
""" test deletion of an host alias """
alias = dict(name='ad_poc1', address='192.168.1.3', descr='', type='host', detail='')
command = "delete alias 'ad_poc1'"
self.do_alias_deletion_test(alias, command=command)
def test_host_update_noop(self):
""" test not updating an host alias """
alias = dict(name='ad_poc1', address='192.168.1.3', descr='', type='host', detail='')
self.do_alias_update_noop_test(alias)
def test_host_update_ip(self):
""" test updating address of an host alias """
alias = dict(name='ad_poc1', address='192.168.1.3', descr='', type='host', detail='')
command = "update alias 'ad_poc1' set address='192.168.1.4'"
self.do_alias_update_field(alias, address='192.168.1.4', command=command)
def test_host_update_descr(self):
""" test updating descr of an host alias """
alias = dict(name='ad_poc1', address='192.168.1.3', descr='', type='host', detail='')
command = "update alias 'ad_poc1' set descr='ad server'"
self.do_alias_update_field(alias, descr='ad server', command=command)
##############
# ports
#
def test_port_create(self):
""" test creation of a new port alias """
alias = dict(name='port_proxy', address='8080 8443', descr='', type='port', detail='')
command = "create alias 'port_proxy', type='port', address='8080 8443', descr='', detail=''"
self.do_alias_creation_test(alias, command=command)
def test_port_delete(self):
""" test deletion of a port alias """
alias = dict(name='port_ssh', address='22', descr='', type='port', detail='')
command = "delete alias 'port_ssh'"
self.do_alias_deletion_test(alias, command=command)
def test_port_update_noop(self):
""" test not updating a port alias """
alias = dict(name='port_ssh', address='22', descr='', type='port', detail='')
self.do_alias_update_noop_test(alias)
def test_port_update_port(self):
""" test updating port of a port alias """
alias = dict(name='port_ssh', address='22', descr='', type='port', detail='')
command = "update alias 'port_ssh' set address='2222'"
self.do_alias_update_field(alias, address='2222', command=command)
def test_port_update_descr(self):
""" test updating descr of a port alias """
alias = dict(name='port_ssh', address='22', descr='', type='port', detail='')
command = "update alias 'port_ssh' set descr='ssh port'"
self.do_alias_update_field(alias, descr='ssh port', command=command)
##############
# networks
#
def test_network_create(self):
""" test creation of a new network alias """
alias = dict(name='data_networks', address='192.168.1.0/24 192.168.2.0/24', descr='', type='network', detail='')
command = "create alias 'data_networks', type='network', address='192.168.1.0/24 192.168.2.0/24', descr='', detail=''"
self.do_alias_creation_test(alias, command=command)
def test_network_delete(self):
""" test deletion of a network alias """
alias = dict(name='lan_data_poc3', address='192.168.3.0/24', descr='', type='network', detail='')
command = "delete alias 'lan_data_poc3'"
self.do_alias_deletion_test(alias, command=command)
def test_network_update_noop(self):
""" test not updating a network alias """
alias = dict(name='lan_data_poc3', address='192.168.3.0/24', descr='', type='network', detail='')
self.do_alias_update_noop_test(alias)
def test_network_update_network(self):
""" test updating address of a network alias """
alias = dict(name='lan_data_poc3', address='192.168.3.0/24', descr='', type='network', detail='')
command = "update alias 'lan_data_poc3' set address='192.168.2.0/24'"
self.do_alias_update_field(alias, address='192.168.2.0/24', command=command)
def test_network_update_descr(self):
""" test updating descr of a network alias """
alias = dict(name='lan_data_poc3', address='192.168.3.0/24', descr='', type='network', detail='')
command = "update alias 'lan_data_poc3' set descr='data network'"
self.do_alias_update_field(alias, descr='data network', command=command)
##############
# urltables
#
def test_urltable_create(self):
""" test creation of a new urltable alias """
alias = dict(name='acme_table', address='http://www.acme.com', descr='', type='urltable', updatefreq='10', detail='')
command = "create alias 'acme_table', type='urltable', url='http://www.acme.com', descr='', detail='', updatefreq='10'"
self.do_alias_creation_test(alias, command=command, set_after=dict(url='http://www.acme.com'), unset_after=['address'])
def test_urltable_create_url(self):
""" test creation of a new urltable alias """
alias = dict(name='acme_table', url='http://www.acme.com', descr='', type='urltable', updatefreq='10', detail='')
command = "create alias 'acme_table', type='urltable', url='http://www.acme.com', descr='', detail='', updatefreq='10'"
self.do_alias_creation_test(alias, command=command)
def test_urltable_create_exclusive(self):
""" test creattion of a urltable alias with both address and url - fails """
alias = dict(
name='acme_corp', address='http://www.acme-corp.com', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
self.do_alias_creation_test(alias, failed=True, msg='parameters are mutually exclusive: address|url')
def test_urltable_delete(self):
""" test deletion of a urltable alias """
alias = dict(
name='acme_corp', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
command = "delete alias 'acme_corp'"
self.do_alias_deletion_test(alias, command=command)
def test_urltable_update_noop(self):
""" test not updating a urltable alias """
alias = dict(
name='acme_corp', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
self.do_alias_update_noop_test(alias)
def test_urltable_update_url(self):
""" test updating url of a urltable alias """
alias = dict(
name='acme_corp', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
command = "update alias 'acme_corp' set url='http://www.new-acme-corp.com'"
self.do_alias_update_field(alias, url='http://www.new-acme-corp.com', set_after=dict(url='http://www.new-acme-corp.com'), command=command)
def test_urltable_update_descr(self):
""" test updating descr of a urltable alias """
alias = dict(
name='acme_corp', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
command = "update alias 'acme_corp' set descr='acme corp urls'"
self.do_alias_update_field(alias, descr='acme corp urls', command=command)
def test_urltable_update_freq(self):
""" test updating updatefreq of a urltable alias """
alias = dict(
name='acme_corp', url='http://www.acme-corp.com', descr='', type='urltable', updatefreq='10', detail='')
command = "update alias 'acme_corp' set updatefreq='20'"
self.do_alias_update_field(alias, updatefreq='20', command=command)
def test_urltable_ports_create(self):
""" test creation of a new urltable_ports alias """
alias = dict(name='acme_table', url='http://www.acme.com', descr='', type='urltable_ports', updatefreq='10', detail='')
command = "create alias 'acme_table', type='urltable_ports', url='http://www.acme.com', descr='', detail='', updatefreq='10'"
self.do_alias_creation_test(alias, command=command)
##############
# misc
#
def test_create_alias_duplicate(self):
""" test creation of a duplicate alias """
alias = dict(name='port_ssh', address='10.0.0.1 10.0.0.2', type='host')
self.do_alias_creation_test(alias, failed=True, msg="An alias with this name and a different type already exists: 'port_ssh'")
def test_create_alias_invalid_name(self):
""" test creation of a new alias with invalid name """
alias = dict(name='ads-ervers', address='10.0.0.1 10.0.0.2', type='host')
msg = "The alias name 'ads-ervers' must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, "
msg += "and may only contain the following characters: a-z, A-Z, 0-9, _"
self.do_alias_creation_test(alias, failed=True, msg=msg)
def test_create_alias_invalid_name_interface(self):
""" test creation of a new alias with invalid name """
alias = dict(name='lan_100', address='10.0.0.1 10.0.0.2', type='host')
self.do_alias_creation_test(alias, failed=True, msg="An interface description with this name already exists: 'lan_100'")
def test_create_alias_invalid_updatefreq(self):
""" test creation of a new host alias with incoherent params """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2', type='host', updatefreq=10)
self.do_alias_creation_test(alias, failed=True, msg='updatefreq is only valid with type urltable or urltable_ports')
def test_create_alias_without_type(self):
""" test creation of a new host alias without type """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2')
self.do_alias_creation_test(alias, failed=True, msg='state is present but all of the following are missing: type')
def test_create_alias_without_address(self):
""" test creation of a new host alias without address """
alias = dict(name='adservers', type='host')
self.do_alias_creation_test(alias, failed=True, msg='type is host but all of the following are missing: address')
def test_create_alias_invalid_details(self):
""" test creation of a new host alias with invalid details """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2', type='host', detail='ad1||ad2||ad3')
self.do_alias_creation_test(alias, failed=True, msg='Too many details in relation to addresses')
def test_create_alias_invalid_details2(self):
""" test creation of a new host alias with invalid details """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2', type='host', detail='|ad1||ad2')
self.do_alias_creation_test(alias, failed=True, msg='Vertical bars (|) at start or end of descriptions not allowed')
def test_delete_inexistent_alias(self):
""" test deletion of an inexistent alias """
alias = dict(name='ad_poc12', address='192.168.1.3', descr='', type='host', detail='')
with set_module_args(self.args_from_var(alias, 'absent')):
result = self.execute_module(changed=False)
diff = dict(before={}, after={})
self.assertEqual(result['diff'], diff)
self.assertEqual(result['commands'], [])
def test_check_mode(self):
""" test updating an host alias without generating result """
alias = dict(name='ad_poc1', address='192.168.1.3', descr='', type='host', detail='')
with set_module_args(self.args_from_var(alias, address='192.168.1.4', _ansible_check_mode=True)):
result = self.execute_module(changed=True)
diff = dict(before=alias, after=copy(alias))
diff['after']['address'] = '192.168.1.4'
self.assertEqual(result['diff'], diff)
self.assertFalse(self.load_xml_result())
self.assertEqual(result['commands'], ["update alias 'ad_poc1' set address='192.168.1.4'"])
def test_urltable_required_if(self):
""" test creation of a new urltable alias without giving updatefreq (should fail) """
alias = dict(name='acme_table', address='http://www.acme.com', descr='', type='urltable', detail='')
with set_module_args(self.args_from_var(alias)):
self.execute_module(failed=True, msg='type is urltable but all of the following are missing: updatefreq')
def test_urltable_ports_required_if(self):
""" test creation of a new urltable_ports alias without giving updatefreq (should fail) """
alias = dict(name='acme_table', address='http://www.acme.com', descr='', type='urltable_ports', detail='')
with set_module_args(self.args_from_var(alias)):
self.execute_module(failed=True, msg='type is urltable_ports but all of the following are missing: updatefreq')
================================================
FILE: tests/unit/plugins/modules/test_pfsense_alias_null.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from ansible_collections.pfsensible.core.plugins.modules import pfsense_alias
from ansible_collections.pfsensible.core.plugins.module_utils.alias import PFSenseAliasModule
from .pfsense_module import TestPFSenseModule
# Test alias creation starting without an initial element
class TestPFSenseAliasNullModule(TestPFSenseModule):
module = pfsense_alias
def __init__(self, *args, **kwargs):
super(TestPFSenseAliasNullModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_alias_null_config.xml'
self.pfmodule = PFSenseAliasModule
########################################################
# Generic set of funcs used for testing aliases
# First we run the module
# Then, we check return values
# Finally, we check the xml
def do_alias_creation_test(self, alias, failed=False, msg='', command=None):
""" test creation of a new alias """
with set_module_args(self.args_from_var(alias)):
result = self.execute_module(changed=True, failed=failed, msg=msg)
if not failed:
diff = dict(before={}, after=alias)
self.assertEqual(result['diff'], diff)
self.assert_xml_elt_dict('aliases', dict(name=alias['name'], type=alias['type']), diff['after'])
self.assertEqual(result['commands'], [command])
else:
self.assertFalse(self.load_xml_result())
##############
# hosts
#
def test_host_create(self):
""" test creation of a new host alias """
alias = dict(name='adservers', address='10.0.0.1 10.0.0.2', descr='', type='host', detail='')
command = "create alias 'adservers', type='host', address='10.0.0.1 10.0.0.2', descr='', detail=''"
self.do_alias_creation_test(alias, command=command)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_authserver_ldap.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_authserver_ldap
from .pfsense_module import TestPFSenseModule
class TestPFSenseAuthserverLDAPModule(TestPFSenseModule):
module = pfsense_authserver_ldap
def __init__(self, *args, **kwargs):
super(TestPFSenseAuthserverLDAPModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_authserver_config.xml'
self.pfmodule = pfsense_authserver_ldap.PFSenseAuthserverLDAPModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.assert_find_xml_elt(self.xml_result, 'system')
result = root_elt.findall("authserver[name='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple authservers for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
urltype = dict({'tcp': 'Standard TCP', 'starttls': 'STARTTLS Encrypted', 'ssl': 'SSL/TLS Encrypted'})
self.check_param_equal(obj, target_elt, 'name')
self.assert_xml_elt_match(target_elt, 'refid', r'[0-9a-f]{13}')
self.assert_xml_elt_equal(target_elt, 'type', 'ldap')
self.check_param_equal(obj, target_elt, 'ldap_caref', default='global')
self.check_param_equal(obj, target_elt, 'host')
self.check_param_equal(obj, target_elt, 'port', xml_field='ldap_port', default=389)
self.assert_xml_elt_equal(target_elt, 'ldap_urltype', urltype[obj['transport']])
self.check_param_equal(obj, target_elt, 'protover', xml_field='ldap_protver', default=3)
self.check_param_equal(obj, target_elt, 'scope', xml_field='ldap_scope', default='one')
self.check_param_equal(obj, target_elt, 'basedn', xml_field='ldap_basedn', default=None)
self.check_param_equal(obj, target_elt, 'authcn', xml_field='ldap_authcn')
self.check_param_bool(obj, target_elt, 'extended_enabled', xml_field='ldap_extended_enabled', value_true='yes')
self.check_param_equal(obj, target_elt, 'extended_query', xml_field='ldap_extended_query')
self.check_param_equal(obj, target_elt, 'attr_user', xml_field='ldap_attr_user', default='cn')
self.check_param_equal(obj, target_elt, 'attr_group', xml_field='ldap_attr_group', default='cn')
self.check_param_equal(obj, target_elt, 'attr_member', xml_field='ldap_attr_member', default='member')
self.check_param_equal(obj, target_elt, 'attr_groupobj', xml_field='ldap_attr_groupobj', default='posixGroup')
self.check_param_equal(obj, target_elt, 'pam_groupdn', xml_field='ldap_pam_groupdn', default=None)
self.check_param_bool(obj, target_elt, 'ldap_allow_unauthenticated', xml_field='ldap_allow_unauthenticated', default=True)
self.check_param_equal(obj, target_elt, 'timeout', xml_field='ldap_timeout', default=25)
##############
# tests
#
def test_authserver_create(self):
""" test creation of a new authserver """
obj = dict(name='authserver1', host='ldap.example.com', transport='tcp', scope='one', authcn='CN=Users')
self.do_module_test(obj, command="create authserver_ldap 'authserver1', host='ldap.example.com'")
def test_authserver_delete(self):
""" test deletion of a authserver """
obj = dict(name='DELLDAP')
self.do_module_test(obj, command="delete authserver_ldap 'DELLDAP'", delete=True)
def test_authserver_update_noop(self):
""" test not updating a authserver """
obj = dict(name='DELLDAP', host='ldap.example.com', transport='tcp', scope='one', authcn='CN=Users', timeout=25)
self.do_module_test(obj, command="delete authserver_ldap 'DELLDAP'", changed=False)
def test_authserver_update_host(self):
""" test updating host of a authserver """
obj = dict(name='DELLDAP', ldap_timeout=5, host='ldap2.blah.com', transport='tcp', scope='one', authcn='CN=Users')
self.do_module_test(obj, command="update authserver_ldap 'DELLDAP' set host='ldap2.blah.com'")
def test_authserver_disable_allow_unauthenticated(self):
""" test disabling ldap_allow_unauthenticated """
obj = dict(name='DELLDAP', host='ldap.example.com', transport='tcp', scope='one', authcn='CN=Users', ldap_allow_unauthenticated=False)
self.do_module_test(obj, command="update authserver_ldap 'DELLDAP' set ldap_allow_unauthenticated=False")
##############
# misc
#
def test_create_authserver_invalid_timeout(self):
""" test creation of a new authserver with invalid timeout """
obj = dict(name='DELLDAP', host='ldap.example.com', transport='tcp', scope='one', authcn='CN=Users', timeout=0)
self.do_module_test(obj, command="update authserver_ldap 'DELLDAP'", failed=True, msg='timeout 0 must be greater than 1')
def test_delete_inexistent_authserver(self):
""" test deletion of an inexistent authserver """
obj = dict(name='noauthserver')
self.do_module_test(obj, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_authserver_radius.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_authserver_radius
from .pfsense_module import TestPFSenseModule
class TestPFSenseAuthserverRADIUSModule(TestPFSenseModule):
module = pfsense_authserver_radius
def __init__(self, *args, **kwargs):
super(TestPFSenseAuthserverRADIUSModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_authserver_config.xml'
self.pfmodule = pfsense_authserver_radius.PFSenseAuthserverRADIUSModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.assert_find_xml_elt(self.xml_result, 'system')
result = root_elt.findall("authserver[name='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple authservers for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
urltype = dict({'tcp': 'Standard TCP', 'starttls': 'STARTTLS Encrypted', 'ssl': 'SSL/TLS Encrypted'})
self.check_param_equal(obj, target_elt, 'name')
self.assert_xml_elt_match(target_elt, 'refid', r'[0-9a-f]{13}')
self.assert_xml_elt_equal(target_elt, 'type', 'radius')
self.check_param_equal(obj, target_elt, 'host')
self.check_param_equal(obj, target_elt, 'auth_port', xml_field='radius_auth_port', default=1812)
self.check_param_equal(obj, target_elt, 'acct_port', xml_field='radius_acct_port', default=1813)
self.check_param_equal(obj, target_elt, 'protocol', xml_field='radius_protocol', default='MSCHAPv2')
self.check_param_equal(obj, target_elt, 'secret', xml_field='radius_secret')
self.check_param_equal(obj, target_elt, 'timeout', xml_field='radius_timeout', default=5)
self.check_param_equal(obj, target_elt, 'nasip_attribute', xml_field='radius_nasip_attribute', default='lan')
##############
# tests
#
def test_authserver_create(self):
""" test creation of a new authserver """
obj = dict(name='authserver1', host='radius.example.com', secret='password1')
self.do_module_test(obj, command="create authserver_radius 'authserver1'")
def test_authserver_delete(self):
""" test deletion of a authserver """
obj = dict(name='DELRADIUS')
self.do_module_test(obj, command="delete authserver_radius 'DELRADIUS'", delete=True)
def test_authserver_update_noop(self):
""" test not updating a authserver """
obj = dict(name='DELRADIUS', host='radius.example.com', secret='password1', auth_port=1812)
self.do_module_test(obj, changed=False)
def test_authserver_update_host(self):
""" test updating host of a authserver """
obj = dict(name='DELRADIUS', radius_timeout=25, host='radius2.blah.com', secret='password2')
self.do_module_test(obj, command="update authserver_radius 'DELRADIUS' set ")
##############
# misc
#
def test_create_authserver_invalid_timeout(self):
""" test creation of a new authserver with invalid timeout """
obj = dict(name='DELRADIUS', host='radius.example.com', secret='password1', timeout=0)
self.do_module_test(obj, command="update authserver_radius 'DELRADIUS'", failed=True, msg='timeout 0 must be greater than 1')
def test_delete_inexistent_authserver(self):
""" test deletion of an inexistent authserver """
obj = dict(name='noauthserver')
self.do_module_test(obj, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_ca.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_ca
from .pfsense_module import TestPFSenseModule
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
CERTIFICATE = (
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVDRENDQXZDZ0F3SUJBZ0lJRmpGT2hzMW5NelF3RFFZSktvWklodmNOQVFFTEJRQXdYREVUTUJFR0ExVUUKQXhNS2IzQmxiblp3YmkxallURUxN"
"QWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBdwpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExCk1EZ3pN"
"Vm9YRFRNeU1ESXhNakExTURnek1Wb3dYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0cKQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2Iz"
"VnNaR1Z5TVJNdwpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDCkFRRUFtc3ZpSk1FMUVUZWQ0Zk90YmtIcEYzZDllTSs2NDA4WFBu"
"YTh0SkdkQnEzVUFDeEV6b2FCS1J0MnkxY3QKNnpRRGU1RkY0QUF2dFYxdWNacHNsNW80RFMvSUdTYm42ZDNZTWsrajhqQVEzRW16UjhHT29obmdmMVE5QVhDNgpvaDRyQlA1c1g0WTh1WThrSjNZclg1"
"cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTCmNOTFppVnhDUVFLNXA4azltQThiZ3hscVlrZjBtQXlCTnc5TUFmUFVjVWtxRjZQMGdXUEhsSXJIWi91aGc3ZFUKKzIyYW9j"
"S1VFTml2OW1xYStCNmNVZ0xURlQ2czBWU0VzWC9kQWVoNjJZTGdmbVhKZzZkTkhRSStNZzZTa2VscAprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIwR0ExVWREZ1FXQkJS"
"azVvQS8wcWEyCktQd2dvWEpxS010K0FvS0pnVENCalFZRFZSMGpCSUdGTUlHQ2dCUms1b0EvMHFhMktQd2dvWEpxS010K0FvS0oKZ2FGZ3BGNHdYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRF"
"TE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVgpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wCllteGxnZ2dXTVU2R3pXY3pOREFNQmdO"
"VkhSTUVCVEFEQVFIL01Bc0dBMVVkRHdRRUF3SUJCakFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVVIOUtDZG1KZG9BSmxVMHdCSkhZeGpMcktsbFBZNk9OYnpyNUpiaENNNjlIeHhZTgpCa2lpbXd1"
"N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCClRJcmlIeXRORFFhb3VOYS9LV3M3RmF1b2JjdEJsMXc5YXRvSFpzTjVvZWhUM3JBVHYxQ0NBdGpw"
"YVRKSWZKUjMKMElRT1lrZTRvWTZEa0l3SHAydlBQbW9vR2dJdGJUdzNVK0U0MVlaZTdxQ21FLzd6TFRTWmtJTTJseDZ6RDQ2agpEZjRyZ044TVVMNnhpd09MbzlyQUp5ckRNM2JEeTJ1QjY0QkVzRFFM"
"a2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4CmgxL2d6RFpJRGkrV1hDWUFjYmdMaFpWQnF0bjYydW1GcE1SSXV3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=")
CRL1 = (
"LS0tLS1CRUdJTiBYNTA5IENSTC0tLS0tCk1JSUNkRENDQVZ3Q0FRRXdEUVlKS29aSWh2Y05BUUVGQlFBd1hERVRNQkVHQTFVRUF4TUtiM0JsYm5ad2JpMWoKWVRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQ"
"QmdOVkJBZ1RDRU52Ykc5eVlXUnZNUkF3RGdZRFZRUUhFd2RDYjNWcwpaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wWW14bEZ3MHlNakF5TVRrd05UVXhNRFphRncwME9UQTNNRFl3Ck5UVXhNRFph"
"TUNrd0p3SUlMdnhrNzExMkdwUVhEVEl5TURJeE9UQTFOVEV3TWxvd0REQUtCZ05WSFJVRUF3b0IKQmFDQm9EQ0JuVENCalFZRFZSMGpCSUdGTUlHQ2dCUms1b0EvMHFhMktQd2dvWEpxS010K0FvS0pn"
"YUZncEY0dwpYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJBZ1RDRU52CmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURW"
"UVFLRXdwd1psTmxibk5wWW14bGdnZ1cKTVU2R3pXY3pOREFMQmdOVkhSUUVCQUlDSnhFd0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFGbXJ5cFUxU3p5dApNUUZCRWFZZk9waVpqRVhVajE5MVZuWENl"
"b0tNMk83bVUzYW5HVXRZQUJMcG15dmN2YnU2ZkJCVEtYSTFEb0VvClJkV1VDTVMxbk5BTWwyU0N0ZmJ5RHNHNjZHczRiNnRZeXE1SW5LVFJJdldUeU5vS0JiUHc1OHZYV0ljNmVmUXgKSTYvZSt4U3di"
"eE9MSFlRdGd4WTJOdk9xVGVnVE0rTHpIcmNJWmFPS09NbHNodTA4ajgzSnUxR0ttYlBKME1jZwpyVXNiYXRKcURUdWtQMi9VbmI0N1hwN21qUHVTY0Z5MjN2RGl2OHdvcjBYOEFSQW1ibTN4N2ZKeTlt"
"V2d1OVhMCmpNV1lxN1BEaXhwWElqTVdhZzN2bVYxOC9IdDIybW1xS1RPM3prVnJLUDA1TEhCNVloM2ZZcEpWdEhkeENlTzUKdmlvbU53SzA3QUE9Ci0tLS0tRU5EIFg1MDkgQ1JMLS0tLS0=")
CRL2 = (
"-----BEGIN X509 CRL-----\n"
"MIICSDCCATACAQEwDQYJKoZIhvcNAQEFBQAwXDETMBEGA1UEAxMKb3BlbnZwbi1j\n"
"YTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRAwDgYDVQQHEwdCb3Vs\n"
"ZGVyMRMwEQYDVQQKEwpwZlNlbnNpYmxlFw0yMzAxMDcyMzIzMDNaFw01MDA1MjQy\n"
"MzIzMDNaoIGfMIGcMIGNBgNVHSMEgYUwgYKAFGTmgD/SprYo/CChcmooy34CgomB\n"
"oWCkXjBcMRMwEQYDVQQDEwpvcGVudnBuLWNhMQswCQYDVQQGEwJVUzERMA8GA1UE\n"
"CBMIQ29sb3JhZG8xEDAOBgNVBAcTB0JvdWxkZXIxEzARBgNVBAoTCnBmU2Vuc2li\n"
"bGWCCBYxTobNZzM0MAoGA1UdFAQDAgECMA0GCSqGSIb3DQEBBQUAA4IBAQAxhuDn\n"
"A7SJl760tXhQFSWMKTn7VndhiR86GRJzS8H3uyfRqesGrUIcVFlN+z6XqHsJsann\n"
"+/fPvCf5Oo0+R5o4NDpByx5CO0mAy0WReds4bykoSKVUJXEVFXNHl14+Emh6mJtP\n"
"m/Uzzq4cKEtAxZdqd9tbaTwTh4NbH1C7RmbUgRKjWma4CiC1Sofo5mIhx5cCv+ng\n"
"Ny5w9dLF4s+6qFXjvfYmQ0FyeRcltUoF3kTabS1WCdkGjsUSeGHBFLM4NH2mJPMR\n"
"0yfIGdipSonSTF51ICqgoUGAYPqObvlQZDMjFF+GFL3LNQ7gO+1R1OMMKAZ+96nX\n"
"gwt+00UVYhQCCZ3k\n"
"-----END X509 CRL-----\n")
class TestPFSenseCAModule(TestPFSenseModule):
module = pfsense_ca
def __init__(self, *args, **kwargs):
super(TestPFSenseCAModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_ca_config.xml'
self.pfmodule = pfsense_ca.PFSenseCAModule
def setUp(self):
""" mocking up """
super(TestPFSenseCAModule, self).setUp()
self.mock_php = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.php')
self.php = self.mock_php.start()
self.php.return_value = '12000'
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.xml_result.getroot()
result = root_elt.findall("ca[descr='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple CAs for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
self.check_param_equal(obj, target_elt, 'name', xml_field='descr')
if 'trust' in obj:
self.check_param_bool(obj, target_elt, 'trust', value_true='enabled', value_false='disabled')
if 'randomserial' in obj:
self.check_param_bool(obj, target_elt, 'randomserial', value_true='enabled', value_false='disabled')
self.check_param_equal_or_present(obj, target_elt, 'serial')
self.check_param_equal(obj, target_elt, 'certificate', xml_field='crt')
##############
# tests
#
def test_ca_create(self):
""" test creation of a new ca """
obj = dict(name='ca1', certificate=CERTIFICATE)
self.do_module_test(obj, command="create ca 'ca1'")
def test_ca_add_crl(self):
""" test adding a CRL """
obj = dict(name='ca1', certificate=CERTIFICATE, crl=CRL1)
self.do_module_test(obj, command="create ca 'ca1'")
def test_ca_change_crl(self):
""" test adding a CRL """
obj = dict(name='ca1', certificate=CERTIFICATE, crl=CRL2)
self.do_module_test(obj, command="create ca 'ca1'")
def test_ca_delete(self):
""" test deletion of a ca """
obj = dict(name='testdel')
self.do_module_test(obj, command="delete ca 'testdel'", delete=True)
def test_ca_update_noop(self):
""" test not updating a ca """
obj = dict(name='testdel', certificate=CERTIFICATE)
self.do_module_test(obj, changed=False)
def test_ca_update_serial(self):
""" test updating serial of a ca """
obj = dict(name='testdel', certificate=CERTIFICATE, serial=10)
self.do_module_test(obj, command="update ca 'testdel' set serial='10'")
def test_ca_update_trust(self):
""" test updating trust of a ca """
obj = dict(name='testdel', certificate=CERTIFICATE, trust=False)
self.do_module_test(obj, command="update ca 'testdel' set ")
##############
# misc
#
def test_create_ca_invalid_serial(self):
""" test creation of a new ca with invalid serial """
obj = dict(name='ca1', certificate=CERTIFICATE, serial=-1)
self.do_module_test(obj, failed=True, msg='serial must be greater than 0')
def test_delete_nonexistent_ca(self):
""" test deletion of an nonexistent ca """
obj = dict(name='noca')
self.do_module_test(obj, commmand=None, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_cert.py
================================================
# Copyright: (c) 2025, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_cert
from .pfsense_module import TestPFSenseModule
TESTDEL_CRT = (
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVDRENDQXZDZ0F3SUJBZ0lJRmpGT2hzMW5NelF3RFFZSktvWklodmNOQVFFTEJRQXdYREVUTUJFR0ExVUUKQXhNS2IzQmxiblp3YmkxallURUxN"
"QWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBdwpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExCk1EZ3pN"
"Vm9YRFRNeU1ESXhNakExTURnek1Wb3dYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRFTE1Ba0cKQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2Iz"
"VnNaR1Z5TVJNdwpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDCkFRRUFtc3ZpSk1FMUVUZWQ0Zk90YmtIcEYzZDllTSs2NDA4WFBu"
"YTh0SkdkQnEzVUFDeEV6b2FCS1J0MnkxY3QKNnpRRGU1RkY0QUF2dFYxdWNacHNsNW80RFMvSUdTYm42ZDNZTWsrajhqQVEzRW16UjhHT29obmdmMVE5QVhDNgpvaDRyQlA1c1g0WTh1WThrSjNZclg1"
"cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTCmNOTFppVnhDUVFLNXA4azltQThiZ3hscVlrZjBtQXlCTnc5TUFmUFVjVWtxRjZQMGdXUEhsSXJIWi91aGc3ZFUKKzIyYW9j"
"S1VFTml2OW1xYStCNmNVZ0xURlQ2czBWU0VzWC9kQWVoNjJZTGdmbVhKZzZkTkhRSStNZzZTa2VscAprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIwR0ExVWREZ1FXQkJS"
"azVvQS8wcWEyCktQd2dvWEpxS010K0FvS0pnVENCalFZRFZSMGpCSUdGTUlHQ2dCUms1b0EvMHFhMktQd2dvWEpxS010K0FvS0oKZ2FGZ3BGNHdYREVUTUJFR0ExVUVBeE1LYjNCbGJuWndiaTFqWVRF"
"TE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVgpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wCllteGxnZ2dXTVU2R3pXY3pOREFNQmdO"
"VkhSTUVCVEFEQVFIL01Bc0dBMVVkRHdRRUF3SUJCakFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVVIOUtDZG1KZG9BSmxVMHdCSkhZeGpMcktsbFBZNk9OYnpyNUpiaENNNjlIeHhZTgpCa2lpbXd1"
"N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCClRJcmlIeXRORFFhb3VOYS9LV3M3RmF1b2JjdEJsMXc5YXRvSFpzTjVvZWhUM3JBVHYxQ0NBdGpw"
"YVRKSWZKUjMKMElRT1lrZTRvWTZEa0l3SHAydlBQbW9vR2dJdGJUdzNVK0U0MVlaZTdxQ21FLzd6TFRTWmtJTTJseDZ6RDQ2agpEZjRyZ044TVVMNnhpd09MbzlyQUp5ckRNM2JEeTJ1QjY0QkVzRFFM"
"a2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4CmgxL2d6RFpJRGkrV1hDWUFjYmdMaFpWQnF0bjYydW1GcE1SSXV3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=")
TESTDEL_KEY = (
"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUNheStJa3dUVVJONTNoDQo4NjF1UWVrWGQzMTR6N3JqVHhj"
"K2RyeTBrWjBHcmRRQUxFVE9ob0VwRzNiTFZ5M3JOQU43a1VYZ0FDKzFYVzV4DQptbXlYbWpnTkw4Z1pKdWZwM2RneVQ2UHlNQkRjU2JOSHdZNmlHZUIvVkQwQmNMcWlIaXNFL214ZmhqeTVqeVFuDQpk"
"aXRmbXBPa1Zqa29jdFRVQ2c5N0pEdkxJeGtjdWlTMzJkejRVVm5EQlR0RUlaSncwdG1KWEVKQkFybW55VDJZDQpEeHVER1dwaVIvU1lESUUzRDB3Qjg5UnhTU29Yby9TQlk4ZVVpc2RuKzZHRHQxVDdi"
"WnFod3BRUTJLLzJhcHI0DQpIcHhTQXRNVlBxelJWSVN4ZjkwQjZIclpndUIrWmNtRHAwMGRBajR5RHBLUjZXbVQxVkZONk9JUk1nUlh3bUIyDQovWTN0RktibkFnTUJBQUVDZ2dFQUNYN0FIR2tOakVU"
"UkZtOFFFRmRTcVBIWGJIV3hqUWZvOFJmdmMxUUxRY0dmDQo0M0xUdGFkaWZOY0dibXFta21yYVc5WUpaemdidFJCS0dnWFM2Mm0yVG5qRDJXY2RpcWJsQUJFS2lXeVJYREhaDQpJV21xQ2g5ME9kczg4"
"cjJyZFE1TXJUMitBQTRINDRuNE9jTngzYWRwcndicThxUTRrZGtjSWYyUy9WN2x4M0U3DQpLWVNyVnNZT3hUS0dwQVl4MzVtU3oyckRadXFhcVhyRGtwSTYxa2tFVDluaGJzQStVZDE5aXZrZ3J1WlU3"
"bVp3DQpmRzQ0V3JDeEhSNjdiYW1LQ3VPei81SnlKNUt5US9VaFkzak9lczkxZXRpeHJXUkRpaXRCVVIwc01kMXZuQ2cyDQpTSkpjQnAvYXRwbWNjMGY1OVZFSDRGOTNqY01jR3V5cE1mQmkwNExFd1FL"
"QmdRRE1BUSsyVk5aQWJ5aVM0cWZqDQpXelE0UWl6MDNSdmUybVhaNHc1aEdKSmk0QnkvdmI4K2xoOE9acGFWSFBjRTVya0tBbExEaFRwUEl0dTFwNWZzDQpic05MTHpJYkIwUmVBdGUxc3JGOU9SRHp0"
"VThKblhrNDEwcnFMbmdBZmhVTGUvSUttNjBtUG4vWWU0UnhVSVI3DQp2TjFNemJXMnlYdHJCSlZ5VG1jbFRkRDZ5UUtCZ1FEQ1FCa1B2NnFpYXZXaG1IRnBiZ3QvMklpSUlBUk5iSUJjDQpmVnVUYVFJ"
"bXlOZlVSVzlUVG10UHFiYXRuOGhaWFFaL1ZCbWdaMklPOCtCMXBFYW5zYjZCS2xJMXFUMFdMUzR4DQpaV0hLQ3hEdVFiMXNUYmNRMnd3NW5jbk50c1dlWlkxUlV1dlpYcVQwZFU4WUIwb2FsdGVYWTNV"
"bDJrUWJuWUcvDQpINUpJeWJPOEx3S0JnUURKNE1zQnJoYVBrUERmMm5nMW4wMmYxcXpTYS9SbXBrMWdQemM5a3FsYU8xbDN6WGZ4DQpvWEYrT0xzUE9LaWlLd2cyQlhLTmxjdk1BRHpZR005WTQ0dFRY"
"Wk1CK0VFSm4xcURyaC9DUWJTcTEyTXRxcTRKDQpOOVFreG5OdVdWYk9GSXZEUDZjclQzSUljc0x2dDdSREZ2VVFTZ2xtcHlBQkdYb2lzYitVeE5ybk1RS0JnR09QDQpZc2oxbmNsOU5NUk1VK1NMcUkw"
"d09Gbzh2cmZJSXNwRTNnamh5MTZCbGsyUUFRMGJwbGpBVFljVDNDWWhUZEU1DQpFNkZwRzVNNllCTXJ6YUxwc1JDVzFtZjJnLzYzelhNMzJUVXJFdFJyRVdGUE84TUI0blF0Y1Y5a2pFa3hGNHRWDQpD"
"dGp3YjI5MEtNUFNDS00wS08vSTVDUXdpTFAydUtkeTBSRkpnRHUxQW9HQUxqSTdGZDl0ckFrY1dSenMyVHpRDQptaDRTZWxHRDFvdFAxRXpFa1hYVlZwK1lMNXlxOWM3V3hsa09RY1lFVmd1N2huNVFn"
"cnBRUFZPTFVmbkJoajdXDQo1MC9IVmR6V21wSXg5NUlqZXFDZklBV1U3N0I5cmJUR2hvWWMzbTdJcEdObzl2WlhHYWgrc2JHY3BEK1phV3UzDQp1Q25pTnJpZEhORGgzWHZQVFZkRTRlVT0NCi0tLS0t"
"RU5EIFBSSVZBVEUgS0VZLS0tLS0NCg==")
WEB_CRT = (
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsRENDQTN5Z0F3SUJBZ0lJU2JlZ00zSWZSTEV3RFFZSktvWklodmNOQVFFTEJRQXdXakU0TURZR0ExVUUKQ2hNdmNHWlRaVzV6WlNCM1pXSkRi"
"MjVtYVdkMWNtRjBiM0lnVTJWc1ppMVRhV2R1WldRZ1EyVnlkR2xtYVdOaApkR1V4SGpBY0JnTlZCQU1URlhCbVUyVnVjMlV0TmpJd09ETTJOemxqWkROa05EQWVGdzB5TWpBeU1USXlNak0yCk5ERmFG"
"dzB5TXpBek1UY3lNak0yTkRGYU1Gb3hPREEyQmdOVkJBb1RMM0JtVTJWdWMyVWdkMlZpUTI5dVptbG4KZFhKaGRHOXlJRk5sYkdZdFUybG5ibVZrSUVObGNuUnBabWxqWVhSbE1SNHdIQVlEVlFRREV4"
"VndabE5sYm5ObApMVFl5TURnek5qYzVZMlF6WkRRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURHCjkzVW5PMm1MbTRFVTRwOUtlMCtmbkZCQTZGV245eTBDcjhaY0dx"
"VFZiN0E3ejY4Y1JBd0l4bEVDYmtWaUx1ejcKSnNUK2gwd1Jiclo3RWI1dkpvZ3JaUHZSV1ByeW9JQWwrT2w1b1dFbE0ycTBQMVJQdjZETndaYjFsUHgzNXdXOApBa1gxQkhLL3FEWWJibjVVRXdqOVdm"
"aVpmcnNJeFBHM21ubmpHMHpiMktOaGJGNDM4Tk1xVTVadVRvQW5PYW9PCmJqb2tveWhNUUh1N0JGV2tQS25lTFJGN3lmN0Q3TVp1QnBBbWVCbnllYkl1ZkpzS1ViWGE3OTNFanVPcjN4disKb2J3Qmht"
"MUN3NWRpc1o2SDZzS2ZudFJ6UWFUZXhZWEo3WGo2MDdtMWc2YkpqR2FweG9WTTlvU1VlUGlzVWNCOQpNbEp1WkpnVmVOYVlFQkJPbEgxWkFnTUJBQUdqZ2dGY01JSUJXREFKQmdOVkhSTUVBakFBTUJF"
"R0NXQ0dTQUdHCitFSUJBUVFFQXdJR1FEQUxCZ05WSFE4RUJBTUNCYUF3TXdZSllJWklBWWI0UWdFTkJDWVdKRTl3Wlc1VFUwd2cKUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRB"
"ZEJnTlZIUTRFRmdRVWNHMlpNNHdKRlpQRQoxV1ZndXd0eFRUT0RhOGd3Z1lzR0ExVWRJd1NCZ3pDQmdJQVVjRzJaTTR3SkZaUEUxV1ZndXd0eFRUT0RhOGloClhxUmNNRm94T0RBMkJnTlZCQW9UTDNC"
"bVUyVnVjMlVnZDJWaVEyOXVabWxuZFhKaGRHOXlJRk5sYkdZdFUybG4KYm1Wa0lFTmxjblJwWm1sallYUmxNUjR3SEFZRFZRUURFeFZ3WmxObGJuTmxMVFl5TURnek5qYzVZMlF6WkRTQwpDRW0zb0RO"
"eUgwU3hNQ2NHQTFVZEpRUWdNQjRHQ0NzR0FRVUZCd01CQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVJCkFnSXdJQVlEVlIwUkJCa3dGNElWY0daVFpXNXpaUzAyTWpBNE16WTNPV05rTTJRME1BMEdDU3FH"
"U0liM0RRRUIKQ3dVQUE0SUJBUUFzazBrTU12dVR0T3c2Ymx5a1U5cWNkRnQvVDlGOFZBZ0taNHgzYXNxNlArRG96N1FGVFpKVwprdmlrQVVUekpMMys4c0NKRDdjV3BZa2ZpdDRBYndhWFIyRzVsczhj"
"L0JRcUdmY1ZOUnJVdWRscG12UUYrYk5iClMxZ2xjS2hYYXZuYnlQdkRMem9CZGVlTmhqYXIzcWc1TTV6T3I0aXYyM0hCZVc2aEY2c0FrV3dpVkU5NEJmZ00KOS9qeW5GalVYTkJheStMODM2TXBpNDhp"
"NnE4OHdlQ25UdDdaTFFjWlZXb0IwcWNQSS96SExTUFlTNlhhcmdvdgpva3E1M3ZQSG9HNnRGUHpFSkpFVmNmOTV1bVcwaUpFR3hCQ3dTeVlnd2xSY0pEeGJ1QklFY0xWb2JKclVveHNLClJXcW13SHdQ"
"YkFxRjBOMUZ0cFJ6K3Yvd0lQYWdSQ2lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K")
WEB_KEY = (
"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREc5M1VuTzJtTG00RVUKNHA5S2UwK2ZuRkJBNkZXbjl5MENy"
"FpjR3FUVmI3QTd6NjhjUkF3SXhsRUNia1ZpTHV6N0pzVCtoMHdSYnJaNwpFYjV2Sm9nclpQdlJXUHJ5b0lBbCtPbDVvV0VsTTJxMFAxUlB2NkROd1piMWxQeDM1d1c4QWtYMUJISy9xRFliCmJuNVVFd"
"2o5V2ZpWmZyc0l4UEczbW5uakcwemIyS05oYkY0MzhOTXFVNVp1VG9Bbk9hb09iam9rb3loTVFIdTcKQkZXa1BLbmVMUkY3eWY3RDdNWnVCcEFtZUJueWViSXVmSnNLVWJYYTc5M0VqdU9yM3h2K29id"
"0JobTFDdzVkaQpzWjZINnNLZm50UnpRYVRleFlYSjdYajYwN20xZzZiSmpHYXB4b1ZNOW9TVWVQaXNVY0I5TWxKdVpKZ1ZlTmFZCkVCQk9sSDFaQWdNQkFBRUNnZ0VCQUxldGVIeVlUMjV2UnpIVnFFS"
"GxKbk50cFhUV1IwVUJYWThPWUN0aytXaUUKYkFnN1NTZnA5Y1lmOW1jdEQyWjlkWTdCa3JoNmhKSFBTQ3pEQzYrbXZheDUxRExHVnh5bmFNWWxUTHhaYThvZwo5aytoNng2WUJFWU9nbU1DZ0RQY2xTR"
"2tZNXEyMll2dktNd1lMQTFIYVZRaHUrdFA0REJQUitvOGRHdGhKNG9ICmlna3Z4OWhQOVAzc1VBZjFQQWtKMzkyYjZ6dGJOYkRWdXhGSTFVbklCKzF1c2s5VUduWlM5bUVXTkxDRTEwYngKT2U1YlVJa"
"TlCWkkzM3pTamdra3ZFQVdJMlBzVWJDaEFqa0RPUStFT1V1dktyQVJoUnBXUDVWUmdxVUJpc0ZuaApmeWFFMUNtTTQ4RWZyVGZ4QklXaTA0cHZ2U0I3Zm4rbXN3RkRCcjcvUGtVQ2dZRUE3L05NcHlZd"
"Gw2dng5b043ClNWUmdIY01JZlVZR2xJSVNvTGNJbmRURGQ3K3krR0pvRkFBclhBcVMvR2dKUFgxZkMxbWVYZWJDY0dqeHVGZEgKTlNNbnMxVk1OVHlEdjNMWVhhV0lBSlVvMWtTMlVYaWZHcUN3WEpSc"
"kVmVE11U1NlRXpXOGJaNE5Cdmk1R1VsdQpyTnJWNnBqTFQ5eS85ZENUbitHeWthamNMZzhDZ1lFQTFFWmpZTldhTXltbHgydDlpS3hsVTR2SlcxVDMrbW1DCkhWRDM5bDlUSS9BWlQ4Ti9VVGRYVXhjd"
"DN2MnA4OTdPRWJ6cThBRno1dFRrNUV6Y1dJZDcwRmYwRUYxTFpMaXkKME1NZlVLVUlBTExwekh4T294T3YxNVRJTzlXSVF4d2J2TWcxaDh2M2M1MkNCVEpsVzFUektJM3dKMWV2RXBrRwpzcDVqN1NMS"
"UJoY0NnWUVBeTdqTHlkWldPMEhYU3k3U2k2M0JkVU5UZjlqbVdVd2VPS2x0L1dMWkdtQjl1UGtECjJJZFVTTzhKWUplTDBOTVMwUFlqeVNIVXo4K3ArcExQZUVRQ3Z2V2FvRkJpb3pjRWtHMnNES0tYY"
"TJRblR3Q1UKUk8xTkR5MUx3cEVQQjlvWkE4SkoydCtudTlXTWdmV2dxODJZZFhlSWxxT2JyejZKTitOTjB2R0ZEdTBDZ1lCcQp5a1p3anNVV2ZCdEVhZFVyanQ4aTJxNGYzbTBxY3kzY0pjRzVGbGV6T"
"3JUaEpjN0ZRdndSZHhYQ213YUhBMDNVCktxQmV3YnhYSWo5TWcwWk8yMG4wbEdyYVdMVDNKTndBbmtrQXZ5VjVoSWlPTVBNMm8wN1JPNjVJTzdKallKNnIKcUdVVnZnenRBdzVJSXdST29Edjc2UHdxT"
"HJpS3VLVmY4c0wrcDRMTlhRS0JnUUNrbXlIR3dielVJLzN6Z1FNSApxY1RkMUttMDhxTXJjUzZXdU03RGJxTEpIQXdlcFRSYUpuVmVLcnRRS2t4SGRlcjZsK2VWUjNvWXArUE9EbTVECjBGSXMxbXd2R"
"Fp3TjI2RVhmZnZkWG9EK1luNnVEeGlvVU9QZ1A2U1hLT3dxcnBuenVFZTFBNFpDQTFnRXJhd2MKTTNmejdiV3d0a1JUUE5uaGxybkVJNXY5Qmc9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==")
RSA_KEY = (
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUFFJQkFBSkJBTmNObWdtNFlsU1VBcjJ4ZFdlaTVhUlUvRGJXdHNRNDdnamt2MjhFa2plM29iKzZxME0rCkQ1cGh3WURjdjl5Z1ltdUo1"
"d09pMWNQcHJzV2RGV212U3VzQ0F3RUFBUUpCQUtRQ3paM29MNllOaytHVU85UWsKV2p0d1RVS05rcW9vT1BJemN3UjZXZ0YrOXEydlNIaTQxLzZmdjFOaDJQZU91ZDcvZHFxTklLbGxGZXdIYnJsbApp"
"dUVDSVFENHZvZUZqSEdMMzllcGVXVlRpYnF6UWdQTFYzWmlmbHYzMEdkb3ZqTGhVd0loQU4xVGZNOFNxdlBiCjJtelVzL2pITDJQMjl1U1B1bHd3b3lOQ052dFk3a1VKQWlFQTI5YUFMYzYzRjVrSW9G"
"YVM3K2JjNDhyblVaS0cKSlh4cHliWWRmcHdDbWRNQ0lRREhGbnFHcW53c3IrOWpSbEk5enE3S2RUVFJsSmhHcFZtYU5jM1Blc2VhUVFJaApBTzl6UUUralBYK2pXbGhpTWMzZnM5amNiVWJKMWpTUDYv"
"aDBXd3Iyb1dJRwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==")
class TestPFSenseCertModule(TestPFSenseModule):
module = pfsense_cert
def __init__(self, *args, **kwargs):
super(TestPFSenseCertModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_cert_config.xml'
self.pfmodule = pfsense_cert.PFSenseCertModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.xml_result.getroot()
result = root_elt.findall("cert[descr='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple certs for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
self.check_param_equal(obj, target_elt, 'name', xml_field='descr')
self.check_param_equal(obj, target_elt, 'certificate', xml_field='crt')
if 'key' in obj:
self.check_param_equal_or_present(obj, target_elt, 'prv')
##############
# tests
#
def test_cert_create(self):
""" test creation of a new cert """
obj = dict(name='cert1', ca='testdel')
self.do_module_test(obj, command="create cert 'cert1'")
def test_cert_import(self):
""" test import of a new cert """
obj = dict(name='cert1', method='import', certificate=WEB_CRT, key=RSA_KEY)
self.do_module_test(obj, command="create cert 'cert1'")
def test_cert_delete(self):
""" test deletion of a cert """
obj = dict(name='webConfigurator default (62083679cd3d4)')
self.do_module_test(obj, command="delete cert 'webConfigurator default (62083679cd3d4)'", delete=True)
def test_cert_update_noop(self):
""" test not updating a cert """
obj = dict(name='webConfigurator default (62083679cd3d4)', method='import', certificate=WEB_CRT)
self.do_module_test(obj, changed=False)
##############
# misc
#
def test_add_invalid_key(self):
""" test adding an invalid key """
key = 'blah'
obj = dict(name='invalid', method='import', certificate=WEB_CRT, key=key)
msg = 'Could not recognize key format: %s' % (key)
self.do_module_test(obj, failed=True, msg=msg)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_dhcp_server.py
================================================
# Copyright: (c) 2024, David Rosado
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_dhcp_server
from ansible_collections.pfsensible.core.plugins.modules.pfsense_dhcp_server import PFSenseDHCPServerModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseDHCPServerModule(TestPFSenseModule):
module = pfsense_dhcp_server
def __init__(self, *args, **kwargs):
super(TestPFSenseDHCPServerModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_dhcp_server_config.xml'
self.pfmodule = PFSenseDHCPServerModule
def check_target_elt(self, obj, target_elt, target_idx=-1):
""" test the xml definition """
# self.check_param_equal(obj, target_elt, 'interface')
self.check_param_bool(obj, target_elt, 'enable')
self.check_param_equal(obj, target_elt, 'range_from', xml_field='range/from')
self.check_param_equal(obj, target_elt, 'range_to', xml_field='range/to')
self.check_param_equal(obj, target_elt, 'failover_peerip')
self.check_param_equal(obj, target_elt, 'defaultleasetime')
self.check_param_equal(obj, target_elt, 'maxleasetime')
self.check_param_equal(obj, target_elt, 'netmask')
self.check_param_equal(obj, target_elt, 'gateway')
self.check_param_equal(obj, target_elt, 'domain')
self.check_param_equal(obj, target_elt, 'domainsearchlist')
self.check_param_equal(obj, target_elt, 'ddnsdomain')
self.check_param_equal(obj, target_elt, 'ddnsdomainprimary')
self.check_param_equal(obj, target_elt, 'ddnsdomainkeyname')
self.check_param_equal(obj, target_elt, 'ddnsdomainkeyalgorithm', default='hmac-md5')
self.check_param_equal(obj, target_elt, 'ddnsdomainkey')
self.check_param_equal(obj, target_elt, 'mac_allow')
self.check_param_equal(obj, target_elt, 'mac_deny')
self.check_param_equal(obj, target_elt, 'tftp')
self.check_param_equal(obj, target_elt, 'ldap')
self.check_param_equal(obj, target_elt, 'nextserver')
self.check_param_equal(obj, target_elt, 'filename')
self.check_param_equal(obj, target_elt, 'filename32')
self.check_param_equal(obj, target_elt, 'filename64')
self.check_param_equal(obj, target_elt, 'rootpath')
self.check_param_equal(obj, target_elt, 'numberoptions')
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
root_elt = self.assert_find_xml_elt(self.xml_result, 'dhcpd')
return root_elt.find(obj['interface'])
##############
# tests
#
def test_dhcp_server_create(self):
""" test creation of a new DHCP server """
obj = dict(
interface='opt2',
enable=True,
range_from='172.16.0.100',
range_to='172.16.0.199',
defaultleasetime=86400,
maxleasetime=172800,
domain='opt2.example.com'
)
command_as_list = ["create dhcp_server 'opt2', enable=True, range_from='172.16.0.100', ",
"range_to='172.16.0.199', failover_peerip='', defaultleasetime='86400', ",
"maxleasetime='172800', netmask='', gateway='', domain='opt2.example.com', ",
"domainsearchlist='', ddnsdomain='', ddnsdomainprimary='', ddnsdomainkeyname='', ",
"ddnsdomainkeyalgorithm='hmac-md5', ddnsdomainkey='', mac_allow='', mac_deny='', ",
"ddnsclientupdates='allow', tftp='', ldap='', nextserver='', filename='', filename32='', ",
"filename64='', rootpath='', numberoptions=''"]
command = "".join(command_as_list)
self.do_module_test(obj, command=command)
def test_dhcp_server_update(self):
""" test updating an existing DHCP server """
obj = dict(
interface='lan',
enable=True,
range_from='192.168.1.50',
range_to='192.168.1.150',
domain='updated.example.com'
)
command_as_list = ["update dhcp_server 'lan' set , range_from='192.168.1.50', range_to='192.168.1.150', ",
"defaultleasetime='', maxleasetime='', domain='updated.example.com'"]
command = "".join(command_as_list)
self.do_module_test(obj, command=command)
def test_dhcp_server_update_disable_denyunknown(self):
""" test disabling denyunknown from an existing DHCP server """
obj = dict(
interface='opt1',
enable=True,
range_from='10.0.0.100',
range_to='10.0.0.199',
denyunknown='disabled',
)
command_as_list = ["update dhcp_server 'opt1' set , ",
"defaultleasetime='', maxleasetime='', domain='', denyunknown=none"]
command = "".join(command_as_list)
self.do_module_test(obj, command=command)
def test_dhcp_server_delete(self):
""" test deletion of a DHCP server """
obj = dict(interface='opt1', state='absent')
command = "delete dhcp_server 'opt1'"
self.do_module_test(obj, command=command, delete=True)
def test_dhcp_server_create_invalid_interface(self):
""" test creation with an invalid interface """
obj = dict(interface='invalid_interface', enable=True, range_from='192.168.1.100', range_to='192.168.1.200')
self.do_module_test(obj, failed=True, msg='The specified interface invalid_interface is not a valid logical interface or cannot be mapped to one')
def test_dhcp_server_create_invalid_range(self):
""" test creation with an invalid IP range """
interface = 'lan'
obj = dict(interface=interface, enable=True, range_from='192.168.1.200', range_to='192.168.1.100')
self.do_module_test(obj, failed=True, msg=f'The interface {interface} must have a valid IP range pool')
def test_dhcp_server_create_with_options(self):
""" test creation with additional DHCP options """
obj = dict(
interface='opt2',
enable=True,
range_from='172.16.0.50',
range_to='172.16.0.150',
defaultleasetime=43200,
maxleasetime=86400,
domain='opt1.example.com',
ddnsdomain='ddns.example.com',
ddnsdomainprimary='172.16.0.60',
tftp='172.16.0.63',
disablepingcheck=True,
winsserver=['172.16.0.80', '172.16.0.90']
)
command_as_list = ["create dhcp_server 'opt2', enable=True, range_from='172.16.0.50', ",
"range_to='172.16.0.150', failover_peerip='', defaultleasetime='43200', ",
"maxleasetime='86400', netmask='', gateway='', domain='opt1.example.com', ",
"domainsearchlist='', ddnsdomain='ddns.example.com', ddnsdomainprimary='172.16.0.60', ",
"ddnsdomainkeyname='', ddnsdomainkeyalgorithm='hmac-md5', ddnsdomainkey='', ",
"mac_allow='', mac_deny='', ddnsclientupdates='allow', tftp='172.16.0.63', ldap='', ",
"nextserver='', filename='', filename32='', filename64='', rootpath='', numberoptions=''"]
command = "".join(command_as_list)
self.do_module_test(obj, command=command)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_dhcp_static.py
================================================
# Copyright: (c) 2023 Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_dhcp_static
from ansible_collections.pfsensible.core.plugins.modules.pfsense_dhcp_static import PFSenseDHCPStaticModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseDHCPStaticModule(TestPFSenseModule):
module = pfsense_dhcp_static
def __init__(self, *args, **kwargs):
super(TestPFSenseDHCPStaticModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_dhcp_static_config.xml'
self.pfmodule = PFSenseDHCPStaticModule
def check_target_elt(self, obj, target_elt, target_idx=-1):
""" test the xml definition """
# checking destination address and ports
self.check_param_equal(obj, target_elt, 'name', xml_field='cid')
self.check_param_equal(obj, target_elt, 'macaddr', xml_field='mac')
# Forced options
for option in ['ipaddr', 'hostname', 'descr', 'filename',
'rootpath', 'defaultleasetime', 'maxleasetime',
'gateway', 'domain', 'domainsearchlist',
'ddnsdomain', 'ddnsdomainprimary', 'ddnsdomainsecondary',
'ddnsdomainkeyname', 'ddnsdomainkeyalgorithm', 'ddnsdomainkey',
'tftp', 'ldap', 'nextserver', 'filename32', 'filename64',
'filename32arm', 'filename64arm', 'uefihttpboot', 'numberoptions']:
self.check_param_equal_or_present(obj, target_elt, option)
# Non-forced options
for option in ['winsserver', 'dnsserver', 'ntpserver']:
self.check_param_equal(obj, target_elt, option)
# Defaulted options
self.check_param_equal(obj, target_elt, 'ddnsdomainkeyalgorithm', default='hmac-md5')
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
dhcpd_elt = self.assert_find_xml_elt(self.xml_result, 'dhcpd')
root_elt = None
for e in dhcpd_elt:
if 'netif' not in obj or (module_result is not None and e.tag == module_result['netif']):
if e.find('enable') is not None:
root_elt = e
break
result = []
if root_elt is not None:
if 'name' in obj and 'macaddr' in obj:
result = root_elt.findall("staticmap[cid='{0}'][mac='{1}']".format(obj['name'], obj['macaddr']))
elif 'name' in obj:
result = root_elt.findall("staticmap[cid='{0}']".format(obj['name']))
else:
result = root_elt.findall("staticmap[mac='{0}']".format(obj['macaddr']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple static maps for cid {0}.'.format(obj['name']))
else:
return None
##############
# tests
#
def test_dhcp_static_create(self):
""" test """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.0.0.101', netif='opt1')
command = (
"create dhcp_static 'test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.0.0.101'"
)
self.do_module_test(obj, command=command)
def test_dhcp_static_create_empty(self):
""" test """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.10.0.101', netif='opt2')
command = (
"create dhcp_static 'test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.10.0.101'"
)
self.do_module_test(obj, command=command)
def test_dhcp_static_create_display(self):
""" test create with netif display name """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.0.0.101', netif='pub')
command = (
"create dhcp_static 'test_entry', macaddr='ab:ab:ab:ab:ab:ac', ipaddr='10.0.0.101'"
)
self.do_module_test(obj, command=command)
def test_dhcp_static_create_arp_table_static_entry(self):
""" test create with arp_table_static_entry """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ab', ipaddr='10.0.0.101', netif='opt1', arp_table_static_entry=True)
command = (
"create dhcp_static 'test_entry', macaddr='ab:ab:ab:ab:ab:ab', ipaddr='10.0.0.101', arp_table_static_entry=True"
)
self.do_module_test(obj, command=command)
def test_dhcp_static_create_wrong_subnet(self):
""" test create with IP address in the wrong subnet """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ab', ipaddr='1.2.3.4', netif='opt1')
self.do_module_test(obj, failed=True, msg='The IP address must lie in the opt1 subnet.')
def test_dhcp_static_create_no_netif(self):
""" test create with no netif """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ab', ipaddr='1.2.3.4')
self.do_module_test(obj, failed=True, msg='Multiple DHCP servers enabled and no netif specified')
def test_dhcp_static_create_ifgroup(self):
""" test create with interface group """
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ab', ipaddr='1.2.3.4', netif='IFGROUP1')
self.do_module_test(obj, failed=True, msg='DHCP cannot be configured for interface groups')
def test_dhcp_static_create_invalid_macaddr(self):
""" test create with invalid macaddr """
msg = 'A valid MAC address must be specified.'
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:ab:ab', ipaddr='10.10.0.101', netif='opt2')
self.do_module_test(obj, failed=True, msg=msg)
obj = dict(name='test_entry', macaddr='ab:ab:ab:ab:ab:hh', ipaddr='10.10.0.101', netif='opt2')
self.do_module_test(obj, failed=True, msg=msg)
def test_dhcp_static_delete_macaddr(self):
""" test """
obj = dict(macaddr='ab:ab:ab:ab:ab:ab', netif='opt1', state='absent')
command = "delete dhcp_static ''"
def test_dhcp_static_delete_name(self):
""" test """
obj = dict(name='dhcphostid', netif='opt1', state='absent')
command = "delete dhcp_static 'dhcphostid'"
self.do_module_test(obj, command=command, delete=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_dns_resolver.py
================================================
# Copyright: (c) 2024, David Rosado
# Copyright: (c) 2025, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.pfsensible.core.plugins.modules import pfsense_dns_resolver
from ansible_collections.pfsensible.core.plugins.modules.pfsense_dns_resolver import PFSenseDNSResolverModule
from .pfsense_module import TestPFSenseModule
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
class TestPFSenseDNSResolverModule(TestPFSenseModule):
module = pfsense_dns_resolver
def __init__(self, *args, **kwargs):
super(TestPFSenseDNSResolverModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_dns_resolver_config_full.xml'
self.pfmodule = PFSenseDNSResolverModule
def setUp(self):
""" mocking up """
super(TestPFSenseDNSResolverModule, self).setUp()
self.mock_php = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.php')
self.php = self.mock_php.start()
self.php.return_value = {'wan': 'WAN', 'lan': 'LAN', '_llocwan': 'WAN IPv6 Link-Local', '_lloclan': 'LAN IPv6 Link-Local', 'lo0': 'Localhost'}
def check_target_elt(self, obj, target_elt, target_idx=-1):
""" test the xml definition """
self.check_param_equal(obj, target_elt, 'port')
self.check_param_bool(obj, target_elt, 'enablessl')
self.check_param_equal(obj, target_elt, 'sslcert')
self.check_param_equal(obj, target_elt, 'tlsport')
# TODO - figure out how these parameters work
# self.check_param_equal(obj, target_elt, 'active_interface')
# self.check_param_equal(obj, target_elt, 'outgoing_interface')
# self.check_param_equal(obj, target_elt, 'system_domain_local_zone_type')
self.check_param_bool(obj, target_elt, 'dnssec', default=True)
self.check_param_bool(obj, target_elt, 'forwarding')
self.check_param_bool(obj, target_elt, 'forward_tls_upstream')
self.check_param_bool(obj, target_elt, 'regdhcp')
self.check_param_bool(obj, target_elt, 'regdhcpstatic')
self.check_param_bool(obj, target_elt, 'regovpnclients')
self.check_param_equal(obj, target_elt, 'custom_options')
self.check_param_equal(obj, target_elt, 'hosts')
self.check_param_equal(obj, target_elt, 'domainoverrides')
self.check_param_bool(obj, target_elt, 'hideidentity', default=True)
self.check_param_bool(obj, target_elt, 'hideversions', default=True)
self.check_param_bool(obj, target_elt, 'prefetch')
self.check_param_bool(obj, target_elt, 'prefetchkey')
self.check_param_bool(obj, target_elt, 'dnssecstripped', default=True)
self.check_param_equal(obj, target_elt, 'msgcachesize', default=4)
self.check_param_equal(obj, target_elt, 'outgoing_num_tcp', default=10)
self.check_param_equal(obj, target_elt, 'incoming_num_tcp', default=10)
self.check_param_equal(obj, target_elt, 'edns_buffer_size', default="auto")
self.check_param_equal(obj, target_elt, 'num_queries_per_thread', default=512)
self.check_param_equal(obj, target_elt, 'jostle_timeout', default=200)
self.check_param_equal(obj, target_elt, 'cache_max_ttl', default=86400)
self.check_param_equal(obj, target_elt, 'cache_min_ttl', default=0)
self.check_param_equal(obj, target_elt, 'infra_host_ttl', default=900)
self.check_param_equal(obj, target_elt, 'infra_cache_numhosts', default=10000)
self.check_param_equal(obj, target_elt, 'unwanted_reply_threshold', default="disabled")
self.check_param_equal(obj, target_elt, 'log_verbosity', default=1)
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
return self.assert_find_xml_elt(self.xml_result, 'unbound')
##############
# tests
#
def test_dns_resolver_init(self):
""" test init of the DNS Resolver """
obj = dict()
command_as_list = ["update dns_resolver pfsense_dns_resolver set active_interface='all', "
"outgoing_interface='all', system_domain_local_zone_type='transparent', "
"msgcachesize='4', outgoing_num_tcp='10', incoming_num_tcp='10', "
"edns_buffer_size='auto', num_queries_per_thread='512', jostle_timeout='200', "
"cache_max_ttl='86400', cache_min_ttl='0', infra_host_ttl='900', "
"infra_cache_numhosts='10000', unwanted_reply_threshold='disabled', "
"log_verbosity='1'"]
command = "".join(command_as_list)
self.config_file = 'pfsense_dns_resolver_config_init.xml'
self.do_module_test(obj, command=command)
def test_dns_resolver_change(self):
""" test initialization of the DNS Resolver """
obj = dict(
active_interface=['lan', 'lo0'],
outgoing_interface=['wan']
)
command_as_list = ["update dns_resolver pfsense_dns_resolver set active_interface='lan,lo0', outgoing_interface='wan'"]
command = "".join(command_as_list)
self.do_module_test(obj, command=command)
def test_dns_resolver_noop(self):
""" test noop of the DNS Resolver """
obj = dict()
self.do_module_test(obj, changed=False)
def test_dns_resolver_domainoverrides_forward_tls_upstream(self):
""" test initialization of the DNS Resolver """
obj = dict(
domainoverrides=[dict(domain="test.example.com", descr="A description", forward_tls_upstream=False, ip="10.0.0.3", tls_hostname='')]
)
command_as_list = ["update dns_resolver pfsense_dns_resolver set "]
command = "".join(command_as_list)
expected_elt_string = """
all
all
transparent
4
10
10
auto
512
200
86400
0
900
10000
disabled
1
test.example.com
A description
10.0.0.3
""" # noqa: E101,W191
self.do_module_test(obj, command=command, expected_elt_string=expected_elt_string)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_gateway.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_gateway
from ansible_collections.pfsensible.core.plugins.module_utils.gateway import PFSenseGatewayModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseGatewayModule(TestPFSenseModule):
module = pfsense_gateway
def __init__(self, *args, **kwargs):
super(TestPFSenseGatewayModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_gateway_config.xml'
self.pfmodule = PFSenseGatewayModule
def check_target_elt(self, obj, target_elt):
""" test the xml definition """
self.check_param_equal_or_not_find(obj, target_elt, 'monitor')
self.check_param_equal_or_not_find(obj, target_elt, 'disabled', empty=True)
self.check_param_equal_or_not_find(obj, target_elt, 'monitor_disable', empty=True)
self.check_param_equal_or_not_find(obj, target_elt, 'action_disable', empty=True)
self.check_param_equal_or_not_find(obj, target_elt, 'force_down', empty=True)
self.check_param_equal_or_not_find(obj, target_elt, 'nonlocalgateway', empty=True)
self.check_value_equal(target_elt, 'interface', self.unalias_interface(obj['interface']))
self.check_param_equal(obj, target_elt, 'descr')
self.check_param_equal(obj, target_elt, 'weight', '1')
self.check_param_equal(obj, target_elt, 'gateway')
self.check_param_equal(obj, target_elt, 'ipprotocol', 'inet')
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
rules_elt = self.assert_find_xml_elt(self.xml_result, 'gateways')
for item in rules_elt:
name_elt = item.find('name')
if name_elt is not None and name_elt.text == obj['name']:
return item
return None
##############
# tests
#
def test_gateway_create(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='192.168.1.1')
command = "create gateway 'test_gw', interface='lan', gateway='192.168.1.1'"
self.do_module_test(obj, command=command)
def test_gateway_create_with_params(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='192.168.1.1', descr='a test gw', monitor='8.8.8.8', weight=10)
command = "create gateway 'test_gw', interface='lan', gateway='192.168.1.1', descr='a test gw', monitor='8.8.8.8', weight='10'"
self.do_module_test(obj, command=command)
def test_gateway_create_ipv6(self):
""" test """
obj = dict(name='test_gw', interface='wan', ipprotocol='inet6', gateway='2001::1')
command = "create gateway 'test_gw', interface='wan', ipprotocol='inet6', gateway='2001::1'"
self.do_module_test(obj, command=command)
def test_gateway_create_in_vip(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='10.255.2.1')
command = "create gateway 'test_gw', interface='lan', gateway='10.255.2.1'"
self.do_module_test(obj, command=command)
def test_gateway_create_invalid_name(self):
""" test """
obj = dict(name='___', interface='lan', gateway='192.168.1.1')
msg = "The gateway name '___' must be less than 32 characters long, may not consist of only numbers, "
msg += "may not consist of only underscores, and may only contain the following characters: a-z, A-Z, 0-9, _"
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_interface(self):
""" test """
obj = dict(name='test_gw', interface='lan_232', gateway='192.168.1.1')
msg = 'lan_232 is not a valid interface'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_nonlocal(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='1.2.3.4', nonlocalgateway=True)
command = "create gateway 'test_gw', interface='lan', gateway='1.2.3.4', nonlocalgateway=True"
self.do_module_test(obj, command=command)
def test_gateway_create_invalid_ip(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='acme.dyndns.org')
msg = 'gateway must use an IPv4 address'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ip2(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='1.2.3.4')
msg = "The gateway address 1.2.3.4 does not lie within one of the chosen interface's subnets."
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ip3(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='2001::1')
msg = 'gateway must use an IPv4 address'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ip4(self):
""" test """
obj = dict(name='test_gw', interface='vt1', gateway='192.168.1.1')
msg = 'Cannot add IPv4 Gateway Address because no IPv4 address could be found on the interface.'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_monitor(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='192.168.1.1', monitor='2001::1')
msg = 'monitor must use an IPv4 address'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ipv6(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='2001::1', ipprotocol='inet6')
msg = "Cannot add IPv6 Gateway Address because no IPv6 address could be found on the interface."
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ipv6_2(self):
""" test """
obj = dict(name='test_gw', interface='wan', gateway='192.168.1.2', ipprotocol='inet6')
msg = "gateway must use an IPv6 address"
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_ipv6_monitor(self):
""" test """
obj = dict(name='test_gw', interface='wan', ipprotocol='inet6', gateway='2001::1', monitor='192.168.1.1')
msg = 'monitor must use an IPv6 address'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_create_invalid_weight(self):
""" test """
obj = dict(name='test_gw', interface='lan', gateway='192.168.1.1', weight='40')
msg = 'weight must be between 1 and 30'
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_update_noop(self):
""" test """
obj = dict(name='GW_WAN', interface='wan', gateway='192.168.240.1', descr='Interface wan Gateway')
self.do_module_test(obj, changed=False)
def test_gateway_update_dynamic(self):
""" test """
obj = dict(name='OPT3_VTIV4', interface='lan', gateway='dynamic')
msg = "The gateway use 'dynamic' as a target. You can not change the interface"
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_update_dynamic2(self):
""" test """
obj = dict(name='OPT3_VTIV4', interface='lan_100', gateway='1.2.3.4')
msg = "The gateway use 'dynamic' as a target. This is read-only, so you must set gateway as dynamic too"
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_update_dynamic3(self):
""" test """
obj = dict(name='OPT3_VTIV4', interface='lan_100', gateway='dynamic', ipprotocol='inet6')
msg = "The gateway use 'dynamic' as a target. You can not change ipprotocol"
self.do_module_test(obj, msg=msg, failed=True)
def test_gateway_update_dynamic4(self):
""" test """
obj = dict(name='OPT3_VTIV4', interface='lan_100', gateway='dynamic', weight=2)
command = "update gateway 'OPT3_VTIV4' set weight='2'"
self.do_module_test(obj, command=command)
def test_gateway_update_interface(self):
""" test """
obj = dict(name='GW_WAN', interface='lan', gateway='192.168.1.1', descr='Interface wan Gateway')
command = "update gateway 'GW_WAN' set interface='lan', gateway='192.168.1.1'"
self.do_module_test(obj, command=command)
def test_gateway_update_bools_and_monitor(self):
""" test """
obj = dict(name='GW_LAN', interface='lan', gateway='192.168.1.1', descr='Interface lan Gateway')
command = "update gateway 'GW_LAN' set disabled=False, monitor=none, monitor_disable=False, action_disable=False, force_down=False"
self.do_module_test(obj, command=command)
def test_gateway_delete(self):
""" test """
obj = dict(name='GW_WAN2')
command = "delete gateway 'GW_WAN2'"
self.do_module_test(obj, command=command, delete=True)
def test_gateway_delete_static(self):
""" test """
obj = dict(name='OPT3_VTIV4')
msg = "The gateway use 'dynamic' as a target. You can not delete it"
self.do_module_test(obj, msg=msg, delete=True, failed=True)
def test_gateway_delete_default(self):
""" test """
obj = dict(name='GW_DEFAULT')
msg = "The gateway is still in use. You can not delete it"
self.do_module_test(obj, msg=msg, delete=True, failed=True)
def test_gateway_delete_in_group(self):
""" test """
obj = dict(name='GW_LAN')
msg = "The gateway is still in use. You can not delete it"
self.do_module_test(obj, msg=msg, delete=True, failed=True)
def test_gateway_delete_in_route(self):
""" test """
obj = dict(name='GW_WAN')
msg = "The gateway is still in use. You can not delete it"
self.do_module_test(obj, msg=msg, delete=True, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_haproxy_backend.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_haproxy_backend
from ansible_collections.pfsensible.core.plugins.module_utils.haproxy_backend import PFSenseHaproxyBackendModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseHaproxyBackendModule(TestPFSenseModule):
module = pfsense_haproxy_backend
def __init__(self, *args, **kwargs):
super(TestPFSenseHaproxyBackendModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_haproxy_backend_config.xml'
self.pfmodule = PFSenseHaproxyBackendModule
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated backend xml definition """
pkgs_elt = self.assert_find_xml_elt(self.xml_result, 'installedpackages')
hap_elt = self.assert_find_xml_elt(pkgs_elt, 'haproxy')
backends_elt = self.assert_find_xml_elt(hap_elt, 'ha_pools')
for item in backends_elt:
name_elt = item.find('name')
if name_elt is not None and name_elt.text == obj['name']:
return item
if not absent:
self.fail('haproxy_backend ' + obj['name'] + ' not found.')
return None
def check_target_elt(self, obj, target_elt, backend_id=100):
""" test the xml definition of backend """
def _check_elt(name, fname=None, default=None):
if fname is None:
fname = name
if name in obj and obj[name] is not None:
self.assert_xml_elt_equal(target_elt, fname, str(obj[name]))
elif default is not None:
self.assert_xml_elt_equal(target_elt, fname, default)
else:
self.assert_xml_elt_is_none_or_empty(target_elt, fname)
def _check_bool_elt(name, fname=None):
if fname is None:
fname = name
if obj.get(name):
self.assert_xml_elt_equal(target_elt, fname, 'yes')
else:
self.assert_xml_elt_is_none_or_empty(target_elt, fname)
self.assert_xml_elt_equal(target_elt, 'id', str(backend_id))
# checking balance
if 'balance' in obj and obj['balance'] != 'none':
self.assert_xml_elt_equal(target_elt, 'balance', obj['balance'])
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'balance')
# check everything else
_check_elt('balance_urilen')
_check_elt('balance_uridepth')
_check_bool_elt('balance_uriwhole')
_check_elt('connection_timeout')
_check_elt('server_timeout')
_check_elt('check_type', default='none')
_check_elt('check_frequency', 'checkinter')
_check_elt('retries')
_check_bool_elt('log_checks', 'log-health-checks')
_check_elt('httpcheck_method')
_check_elt('monitor_uri')
_check_elt('monitor_httpversion')
_check_elt('monitor_username')
_check_elt('monitor_domain')
##############
# tests
#
def test_haproxy_backend_create(self):
""" test creation of a new backend """
backend = dict(name='exchange')
command = "create haproxy_backend 'exchange', balance='none', check_type='none'"
self.do_module_test(backend, command=command, backend_id=102)
def test_haproxy_backend_create2(self):
""" test creation of a new backend with some parameters"""
backend = dict(name='exchange', balance='roundrobin', check_type='HTTP')
command = "create haproxy_backend 'exchange', balance='roundrobin', check_type='HTTP'"
self.do_module_test(backend, command=command, backend_id=102)
def test_haproxy_backend_create_invalid_name(self):
""" test creation of a new backend """
backend = dict(name='exchange test')
msg = "The field 'name' contains invalid characters."
self.do_module_test(backend, msg=msg, failed=True)
def test_haproxy_backend_delete(self):
""" test deletion of a backend """
backend = dict(name='test-backend')
command = "delete haproxy_backend 'test-backend'"
self.do_module_test(backend, delete=True, command=command)
def test_haproxy_backend_update_noop(self):
""" test not updating a backend """
backend = dict(
name='test-backend', balance='uri', balance_uriwhole=True, log_checks=True, check_type='SSL', check_frequency=123456, httpcheck_method='OPTIONS'
)
self.do_module_test(backend, changed=False)
def test_haproxy_backend_update_bools(self):
""" test updating bools """
backend = dict(name='test-backend', balance='uri', check_type='SSL', check_frequency=123456, httpcheck_method='OPTIONS')
command = "update haproxy_backend 'test-backend' set balance_uriwhole=False, log_checks=False"
self.do_module_test(backend, changed=True, command=command)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_haproxy_backend_server.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_haproxy_backend_server
from ansible_collections.pfsensible.core.plugins.module_utils.haproxy_backend_server import PFSenseHaproxyBackendServerModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseHaproxyBackendServerModule(TestPFSenseModule):
module = pfsense_haproxy_backend_server
def __init__(self, *args, **kwargs):
super(TestPFSenseHaproxyBackendServerModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_haproxy_backend_server_config.xml'
self.pfmodule = PFSenseHaproxyBackendServerModule
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated backend server xml definition """
pkgs_elt = self.assert_find_xml_elt(self.xml_result, 'installedpackages')
hap_elt = self.assert_find_xml_elt(pkgs_elt, 'haproxy')
backends_elt = self.assert_find_xml_elt(hap_elt, 'ha_pools')
for item in backends_elt:
name_elt = item.find('name')
if name_elt is not None and name_elt.text == obj['backend']:
backend_elt = item
break
if backend_elt is None:
self.fail('haproxy backend ' + obj['backend'] + ' not found.')
servers_elt = self.assert_find_xml_elt(backend_elt, 'ha_servers')
for item in servers_elt:
name_elt = item.find('name')
if name_elt is not None and name_elt.text == obj['name']:
return item
if not absent:
self.fail('haproxy backend server ' + obj['name'] + ' not found.')
return None
@staticmethod
def caref(descr):
""" return refid for ca """
if descr == 'test ca':
return '5d85d3071588f'
if descr == 'test ca2':
return '5df5ec5668d9f'
return ''
@staticmethod
def crlref(descr):
""" return refid for crl """
if descr == 'test crl':
return '5df5edf6cae0f'
if descr == 'test crl2':
return '5df5ee048c106'
return ''
@staticmethod
def certref(descr):
""" return refid for cert """
if descr == 'test cert':
return '5df5ec78b3048'
if descr == 'test cert2':
return '5df5ec97dfd07'
return ''
@staticmethod
def idem(descr):
""" return value passed """
return descr
def check_target_elt(self, obj, target_elt, server_id):
""" test the xml definition of server """
def _check_elt(name, fname=None, default=None, fvalue=self.idem):
if fname is None:
fname = name
if name in obj and obj[name] is not None:
self.assert_xml_elt_equal(target_elt, fname, fvalue(str(obj[name])))
elif default is not None:
self.assert_xml_elt_equal(target_elt, fname, fvalue(default))
elif name in obj:
self.assert_xml_elt_is_none_or_empty(target_elt, fname)
else:
self.assert_not_find_xml_elt(target_elt, fname)
def _check_bool_elt(name, fname=None, false_exists=False):
if fname is None:
fname = name
if obj.get(name):
self.assert_xml_elt_equal(target_elt, fname, 'yes')
elif name in obj and false_exists:
self.assert_xml_elt_is_none_or_empty(target_elt, fname)
else:
self.assert_not_find_xml_elt(target_elt, fname)
self.assert_xml_elt_equal(target_elt, 'id', str(server_id))
_check_elt('mode', fname='status', default='active')
_check_elt('forwardto')
_check_elt('address')
_check_elt('port')
_check_elt('weight')
_check_elt('verifyhost')
_check_elt('ca', fname='ssl-server-ca', fvalue=self.caref)
_check_elt('crl', fname='ssl-server-crl', fvalue=self.crlref)
_check_elt('clientcert', fname='ssl-server-clientcert', fvalue=self.certref)
_check_elt('cookie')
_check_elt('maxconn')
_check_elt('advanced')
_check_elt('istemplate')
_check_bool_elt('ssl')
_check_bool_elt('checkssl')
_check_bool_elt('sslserververify')
##############
# tests
#
def test_haproxy_backend_server_create(self):
""" test creation of a new backend server """
server = dict(backend='test-backend', name='exchange', address='exchange.acme.org', port=443)
command = "create haproxy_backend_server 'exchange' on 'test-backend', status='active', address='exchange.acme.org', port=443"
self.do_module_test(server, command=command, server_id=103)
def test_haproxy_backend_server_create2(self):
""" test creation of a new backend server with some parameters"""
server = dict(
backend='test-backend', name='exchange', address='exchange.acme.org', port=443, ssl=True, ca='test ca', clientcert='test cert', crl='test crl'
)
command = (
"create haproxy_backend_server 'exchange' on 'test-backend', status='active', address='exchange.acme.org', port=443, "
"ssl=True, ca='test ca', crl='test crl', clientcert='test cert'"
)
self.do_module_test(server, command=command, server_id=103)
def test_haproxy_backend_server_create_invalid_backend(self):
""" test creation of a new backend server """
server = dict(backend='test.backend', name='exchange', address='exchange.acme.org', port=443)
msg = "The backend named 'test.backend' does not exist"
self.do_module_test(server, msg=msg, failed=True)
def test_haproxy_backend_server_create_invalid_name(self):
""" test creation of a new backend server """
server = dict(backend='test-backend', name='test exchange', address='exchange.acme.org', port=443)
msg = "The field 'name' contains invalid characters"
self.do_module_test(server, msg=msg, failed=True)
def test_haproxy_backend_server_delete(self):
""" test deletion of a backend server """
server = dict(backend='test-backend', name='exchange.acme.org')
command = "delete haproxy_backend_server 'exchange.acme.org' on 'test-backend'"
self.do_module_test(server, delete=True, command=command)
def test_haproxy_backend_server_update_noop(self):
""" test not updating a backend server """
server = dict(backend='test-backend', name='exchange.acme.org', address='exchange.acme.org', port=443)
self.do_module_test(server, changed=False)
def test_haproxy_backend_server_update_frontend(self):
""" test updating a backend server """
server = dict(backend='test-backend', name='exchange.acme.org', forwardto='test-frontend')
command = "update haproxy_backend_server 'exchange.acme.org' on 'test-backend' set forwardto='test-frontend', address=none, port=none"
self.do_module_test(server, changed=True, command=command, server_id=101)
def test_haproxy_backend_server_update_certs(self):
""" test updating certs """
server = dict(
backend='test-backend', name='exchange2.acme.org', address='exchange2.acme.org', port=443, ca='test ca2', clientcert='test cert2', crl='test crl2'
)
command = "update haproxy_backend_server 'exchange2.acme.org' on 'test-backend' set ca='test ca2', crl='test crl2', clientcert='test cert2'"
self.do_module_test(server, changed=True, command=command, server_id=102)
def test_haproxy_backend_server_update_certs2(self):
""" test updating certs """
server = dict(
backend='test-backend', name='exchange2.acme.org', address='exchange2.acme.org', port=443
)
command = "update haproxy_backend_server 'exchange2.acme.org' on 'test-backend' set ca=none, crl=none, clientcert=none"
self.do_module_test(server, changed=True, command=command, server_id=102)
def test_haproxy_backend_server_update_certs3(self):
""" test updating certs """
server = dict(
backend='test-backend', name='exchange.acme.org', address='exchange.acme.org', port=443, ca='test ca2', clientcert='test cert2', crl='test crl2'
)
command = "update haproxy_backend_server 'exchange.acme.org' on 'test-backend' set ca='test ca2', crl='test crl2', clientcert='test cert2'"
self.do_module_test(server, changed=True, command=command, server_id=101)
def test_haproxy_backend_server_invalid_ca(self):
""" test updating certs """
server = dict(backend='test-backend', name='exchange', address='exchange.acme.org', port=443, ca='test ca3')
msg = "test ca3 is not a valid certificate authority"
self.do_module_test(server, msg=msg, failed=True)
def test_haproxy_backend_server_invalid_crl(self):
""" test updating certs """
server = dict(backend='test-backend', name='exchange', address='exchange.acme.org', port=443, crl='test crl3')
msg = "test crl3 is not a valid certificate revocation list"
self.do_module_test(server, msg=msg, failed=True)
def test_haproxy_backend_server_invalid_cert(self):
""" test updating certs """
server = dict(backend='test-backend', name='exchange', address='exchange.acme.org', port=443, clientcert='test cert3')
msg = "test cert3 is not a valid certificate"
self.do_module_test(server, msg=msg, failed=True)
def test_haproxy_backend_server_invalid_frontend(self):
""" test updating certs """
server = dict(backend='test-backend', name='exchange', forwardto='test frontend')
msg = "The frontend named 'test frontend' does not exist"
self.do_module_test(server, msg=msg, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_interface.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_interface
from ansible_collections.pfsensible.core.plugins.module_utils.interface import PFSenseInterfaceModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseInterfaceModule(TestPFSenseModule):
module = pfsense_interface
def __init__(self, *args, **kwargs):
super(TestPFSenseInterfaceModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_interface_config.xml'
self.pfmodule = PFSenseInterfaceModule
def setUp(self):
""" mocking up """
def php_mock(command):
if 'get_interface_list' in command:
interfaces = dict()
interfaces['vmx0'] = dict()
interfaces['vmx1'] = dict(descr='notuniq')
interfaces['vmx2'] = dict(descr='notuniq')
interfaces['vmx3'] = dict()
interfaces['vmx0.100'] = dict(descr='uniq')
interfaces['vmx1.1100'] = dict()
return interfaces
return ['autoselect']
super(TestPFSenseInterfaceModule, self).setUp()
self.php.return_value = None
self.php.side_effect = php_mock
def tearDown(self):
""" mocking down """
super(TestPFSenseInterfaceModule, self).tearDown()
self.php.stop()
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated interface xml definition """
elt_filter = {}
elt_filter['descr'] = obj['descr']
return self.assert_has_xml_tag('interfaces', elt_filter, absent=absent)
def check_target_elt(self, obj, target_elt):
""" test the xml definition of interface """
if 'interface_descr' in obj and obj['interface_descr'] == 'uniq':
obj['interface'] = 'vmx0.100'
self.assert_xml_elt_equal(target_elt, 'if', self.unalias_interface(obj['interface'], physical=True))
# bools
if obj.get('enable'):
self.assert_xml_elt_is_none_or_empty(target_elt, 'enable')
else:
self.assert_not_find_xml_elt(target_elt, 'enable')
if obj.get('blockpriv'):
self.assert_xml_elt_equal(target_elt, 'blockpriv', '')
else:
self.assert_not_find_xml_elt(target_elt, 'blockpriv')
if obj.get('blockbogons'):
self.assert_xml_elt_equal(target_elt, 'blockbogons', '')
else:
self.assert_not_find_xml_elt(target_elt, 'blockbogons')
# ipv4 type related
if obj.get('ipv4_type') is None or obj.get('ipv4_type') == 'none':
self.assert_not_find_xml_elt(target_elt, 'ipaddr')
self.assert_not_find_xml_elt(target_elt, 'subnet')
self.assert_not_find_xml_elt(target_elt, 'gateway')
elif obj.get('ipv4_type') == 'static':
if obj.get('ipv4_address'):
self.assert_xml_elt_equal(target_elt, 'ipaddr', obj['ipv4_address'])
if obj.get('ipv4_prefixlen'):
self.assert_xml_elt_equal(target_elt, 'subnet', str(obj['ipv4_prefixlen']))
if obj.get('ipv4_gateway'):
self.assert_xml_elt_equal(target_elt, 'gateway', obj['ipv4_gateway'])
# ipv6 type related
if obj.get('ipv6_type') is None or obj.get('ipv6_type') in ['none']:
self.assert_not_find_xml_elt(target_elt, 'ipaddrv6')
self.assert_not_find_xml_elt(target_elt, 'subnetv6')
self.assert_not_find_xml_elt(target_elt, 'gatewayv6')
elif obj.get('ipv6_type') == 'slaac':
self.assert_xml_elt_equal(target_elt, 'ipaddrv6', 'slaac')
self.assert_not_find_xml_elt(target_elt, 'subnetv6')
self.assert_not_find_xml_elt(target_elt, 'gatewayv6')
elif obj.get('ipv6_type') == 'static':
if obj.get('ipv6_address'):
self.assert_xml_elt_equal(target_elt, 'ipaddrv6', obj['ipv6_address'])
if obj.get('ipv6_prefixlen'):
self.assert_xml_elt_equal(target_elt, 'subnetv6', str(obj['ipv6_prefixlen']))
if obj.get('ipv6_gateway'):
self.assert_xml_elt_equal(target_elt, 'gatewayv6', obj['ipv6_gateway'])
# mac, mss, mtu
if obj.get('mac'):
self.assert_xml_elt_equal(target_elt, 'spoofmac', obj['mac'])
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'spoofmac')
if obj.get('mtu'):
self.assert_xml_elt_equal(target_elt, 'mtu', str(obj['mtu']))
else:
self.assert_not_find_xml_elt(target_elt, 'mtu')
if obj.get('mss'):
self.assert_xml_elt_equal(target_elt, 'mss', str(obj['mss']))
else:
self.assert_not_find_xml_elt(target_elt, 'mss')
##############
# tests
#
def test_interface_create_no_address(self):
""" test creation of a new interface with no address """
interface = dict(descr='VOICE', interface='vmx0.100')
command = "create interface 'VOICE', port='vmx0.100'"
self.do_module_test(interface, command=command)
def test_interface_create_by_descr(self):
""" test creation of a new interface with interface_descr """
interface = dict(descr='VOICE', interface_descr='uniq')
command = "create interface 'VOICE', port='vmx0.100'"
self.do_module_test(interface, command=command)
def test_interface_create_static(self):
""" test creation of a new interface with a static ip """
interface = dict(descr='VOICE', interface='vmx0.100', ipv4_type='static', ipv4_address='10.20.30.40', ipv4_prefixlen=24)
command = "create interface 'VOICE', port='vmx0.100', ipv4_type='static', ipv4_address='10.20.30.40', ipv4_prefixlen='24'"
self.do_module_test(interface, command=command)
def test_interface_create_static_ipv6(self):
""" test creation of a new interface with a static ipv6 """
interface = dict(descr='VOICE', interface='vmx0.100', ipv6_type='static', ipv6_address='3001::2001:22', ipv6_prefixlen=56)
command = "create interface 'VOICE', port='vmx0.100', ipv6_type='static', ipv6_address='3001::2001:22', ipv6_prefixlen='56'"
self.do_module_test(interface, command=command)
def test_interface_create_slaac(self):
""" test creation of a new interface with slaac """
interface = dict(descr='VOICE', interface='vmx0.100', ipv6_type='slaac')
command = "create interface 'VOICE', port='vmx0.100', ipv6_type='slaac'"
self.do_module_test(interface, command=command)
def test_interface_create_none_mac_mtu_mss(self):
""" test creation of a new interface """
interface = dict(descr='VOICE', interface='vmx0.100', mac='00:11:22:33:44:55', mtu=1500, mss=1100)
command = "create interface 'VOICE', port='vmx0.100', mac='00:11:22:33:44:55', mtu='1500', mss='1100'"
self.do_module_test(interface, command=command)
def test_interface_delete(self):
""" test deletion of an interface """
interface = dict(descr='vt1')
command = "delete interface 'vt1'"
self.do_module_test(interface, delete=True, command=command)
def test_interface_delete_lan(self):
""" test deletion of an interface """
interface = dict(descr='lan')
commands = [
"delete rule_separator 'test_separator', interface='lan'",
"update rule 'floating_rule_2' on 'floating(lan,wan,lan_1100)' set interface='wan,lan_1100'",
"delete rule 'floating_rule_1' on 'floating(lan)'",
"delete rule 'antilock_out_1' on 'lan'",
"delete rule 'antilock_out_2' on 'lan'",
"delete rule 'antilock_out_3' on 'lan'",
"delete interface 'lan'"
]
self.do_module_test(interface, delete=True, command=commands)
def test_interface_delete_fails(self):
""" test deletion of an interface that is part of a group """
interface = dict(descr='lan_1100')
msg = "The interface is part of the group IFGROUP1. Please remove it from the group first."
self.do_module_test(interface, delete=True, failed=True, msg=msg)
def test_interface_update_noop(self):
""" test not updating a interface """
interface = dict(descr='lan_1100', interface='vmx1.1100', enable=True, ipv4_type='static', ipv4_address='172.16.151.210', ipv4_prefixlen=24)
self.do_module_test(interface, changed=False)
def test_interface_update_name(self):
""" test updating interface name """
interface = dict(descr='wlan_1100', interface='vmx1.1100', enable=True, ipv4_type='static', ipv4_address='172.16.151.210', ipv4_prefixlen=24)
command = "update interface 'lan_1100' set interface='wlan_1100'"
self.do_module_test(interface, changed=True, command=command)
def test_interface_update_enable(self):
""" test disabling interface """
interface = dict(descr='lan_1100', interface='vmx1.1100', enable=False, ipv4_type='static', ipv4_address='172.16.151.210', ipv4_prefixlen=24)
command = "update interface 'lan_1100' set enable=False"
self.do_module_test(interface, changed=True, command=command)
def test_interface_update_enable2(self):
""" test enabling interface """
interface = dict(descr='vt1', interface='vmx3', enable=True)
command = "update interface 'vt1' set enable=True"
self.do_module_test(interface, changed=True, command=command)
def test_interface_update_mac(self):
""" test updating mac """
interface = dict(descr='lan_1100', interface='vmx1.1100', enable=True, ipv4_type='static',
ipv4_address='172.16.151.210', ipv4_prefixlen=24, mac='00:11:22:33:44:55', )
command = "update interface 'lan_1100' set mac='00:11:22:33:44:55'"
self.do_module_test(interface, changed=True, command=command)
def test_interface_update_blocks(self):
""" test updating block fields """
interface = dict(descr='lan_1100', interface='vmx1.1100', enable=True, ipv4_type='static',
ipv4_address='172.16.151.210', ipv4_prefixlen=24, blockpriv=True, blockbogons=True)
command = "update interface 'lan_1100' set blockpriv=True, blockbogons=True"
self.do_module_test(interface, changed=True, command=command)
def test_interface_error_used(self):
""" test error already used """
interface = dict(descr='lan_1100', interface='vmx1', enable=True, ipv4_type='static', ipv4_address='172.16.151.210', ipv4_prefixlen=24)
msg = "Port vmx1 is already in use on interface lan"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_gw(self):
""" test error no such gateway """
interface = dict(descr='lan_1100', interface='vmx1.1100', enable=True, ipv4_type='static',
ipv4_address='172.16.151.210', ipv4_prefixlen=24, ipv4_gateway='voice_gw')
msg = "Gateway voice_gw does not exist on lan_1100"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_if(self):
""" test error no such interface """
interface = dict(descr='wlan_1100', interface='vmx1.1200', enable=True, ipv4_type='static',
ipv4_address='172.16.151.210', ipv4_prefixlen=24, ipv4_gateway='voice_gw')
msg = "vmx1.1200 can't be assigned. Interface may only be one the following: ['vmx0', 'vmx1', 'vmx2', 'vmx3', 'vmx0.100', 'vmx1.1100']"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_eq(self):
""" test error same ipv4 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv4_type='static', ipv4_address='192.168.1.242', ipv4_prefixlen=32)
msg = "IP address 192.168.1.242/32 is being used by or overlaps with: lan (192.168.1.242/24)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_overlaps1(self):
""" test error same ipv4 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv4_type='static', ipv4_address='192.168.1.1', ipv4_prefixlen=30)
msg = "IP address 192.168.1.1/30 is being used by or overlaps with: lan (192.168.1.242/24)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_overlaps2(self):
""" test error same ipv4 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv4_type='static', ipv4_address='192.168.1.1', ipv4_prefixlen=22)
msg = "IP address 192.168.1.1/22 is being used by or overlaps with: lan (192.168.1.242/24)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_inet6_eq(self):
""" test error same ipv6 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv6_type='static', ipv6_address='2001::2001:22', ipv6_prefixlen=127)
msg = "IP address 2001::2001:22/127 is being used by or overlaps with: lan (2001::2001:22/64)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_inet6_overlaps1(self):
""" test error same ipv6 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv6_type='static', ipv6_address='2001::2001:1', ipv6_prefixlen=64)
msg = "IP address 2001::2001:1/64 is being used by or overlaps with: lan (2001::2001:22/64)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_error_inet6_overlaps2(self):
""" test error same ipv6 address """
interface = dict(descr='VOICE', interface='vmx0.100', ipv6_type='static', ipv6_address='2001::2001', ipv6_prefixlen=56)
msg = "IP address 2001::2001/56 is being used by or overlaps with: lan (2001::2001:22/64)"
self.do_module_test(interface, failed=True, msg=msg)
def test_interface_delete_sub(self):
""" test delete sub interface """
interface = dict(descr='lan_1200', interface='vmx1.1200')
command = "delete interface 'lan_1200'"
self.do_module_test(interface, delete=True, command=command)
def test_interface_error_not_uniq(self):
""" test creation of a new interface with interface_descr """
interface = dict(descr='VOICE', interface_descr='notuniq')
msg = 'Multiple interfaces found for "notuniq"'
self.do_module_test(interface, failed=True, msg=msg)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_interface_group.py
================================================
# Copyright: (c) 2018, Frederic Bor
# Copyright: (c) 2024, Orioni Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_interface_group
from ansible_collections.pfsensible.core.plugins.module_utils.interface_group import PFSenseInterfaceGroupModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseInterfaceGroupModule(TestPFSenseModule):
module = pfsense_interface_group
def __init__(self, *args, **kwargs):
super(TestPFSenseInterfaceGroupModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_interface_config.xml'
self.pfmodule = PFSenseInterfaceGroupModule
def setUp(self):
""" mocking up """
def php_mock(command):
if 'get_interface_list' in command:
interfaces = dict()
interfaces['vmx0'] = dict()
interfaces['vmx1'] = dict(descr='notuniq')
interfaces['vmx2'] = dict(descr='notuniq')
interfaces['vmx3'] = dict()
interfaces['vmx0.100'] = dict(descr='uniq')
interfaces['vmx1.1100'] = dict()
return interfaces
return ['autoselect']
super(TestPFSenseInterfaceGroupModule, self).setUp()
self.php.return_value = None
self.php.side_effect = php_mock
def tearDown(self):
""" mocking down """
super(TestPFSenseInterfaceGroupModule, self).tearDown()
self.php.stop()
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated interface group xml definition """
elt_filter = {}
elt_filter['ifname'] = obj['name']
return self.assert_has_xml_tag('ifgroups', elt_filter, absent=absent)
def check_target_elt(self, obj, target_elt):
""" test the xml definition of interface group """
# descr, members
if obj.get('descr'):
self.assert_xml_elt_equal(target_elt, 'descr', obj['descr'])
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'descr')
if obj.get('members'):
self.assert_xml_elt_equal(target_elt, 'members', ' '.join(obj['members']))
else:
self.assert_not_find_xml_elt(target_elt, 'members')
##############
# tests
#
def test_interface_group_create(self):
""" test creation of a new interface group """
interface_group = dict(name='IFGROUP2', members=['wan', 'lan'])
command = "create interface_group 'IFGROUP2', members='wan lan'"
self.do_module_test(interface_group, command=command)
def test_interface_group_create_with_descr(self):
""" test creation of a new interface group with a description """
interface_group = dict(name='IFGROUP2', members=['wan', 'lan'], descr='Primary interfaces')
command = "create interface_group 'IFGROUP2', descr='Primary interfaces', members='wan lan'"
self.do_module_test(interface_group, command=command)
def test_interface_group_delete(self):
""" test deletion of an interface group """
interface_group = dict(name='IFGROUP1', state='absent')
command = "delete interface_group 'IFGROUP1'"
self.do_module_test(interface_group, delete=True, command=command)
def test_interface_group_update_noop(self):
""" test not updating a interface group """
interface_group = dict(name='IFGROUP1', members=['opt1', 'opt3'])
self.do_module_test(interface_group, changed=False)
def test_interface_group_update_descr(self):
""" test updating interface group description """
interface_group = dict(name='IFGROUP1', members=['opt1', 'opt3'], descr='Opt Interfaces')
command = "update interface_group 'IFGROUP1' set descr='Opt Interfaces'"
self.do_module_test(interface_group, changed=True, command=command)
def test_interface_group_update_members(self):
""" test updating interface group members """
interface_group = dict(name='IFGROUP1', members=['opt1', 'opt2'])
command = "update interface_group 'IFGROUP1' set members='opt1 opt2'"
self.do_module_test(interface_group, changed=True, command=command)
def test_interface_group_error_no_members(self):
""" test error no members specified """
interface_group = dict(name='IFGROUP2', descr='Primary interfaces')
msg = "state is present but all of the following are missing: members"
self.do_module_test(interface_group, failed=True, msg=msg)
def test_interface_group_error_member_does_not_exist(self):
""" test error member does not exist """
interface_group = dict(name='IFGROUP2', members=['blah'], descr='Primary interfaces')
msg = 'Unknown interface name "blah".'
self.do_module_test(interface_group, failed=True, msg=msg)
def test_interface_group_error_members_not_uniq(self):
""" test error member does not exist """
interface_group = dict(name='IFGROUP2', members=['opt1', 'opt1'], descr='Primary interfaces')
msg = 'List of members is not unique.'
self.do_module_test(interface_group, failed=True, msg=msg)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_ipsec.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_ipsec
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec import PFSenseIpsecModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseIpsecModule(TestPFSenseModule):
module = pfsense_ipsec
def __init__(self, *args, **kwargs):
super(TestPFSenseIpsecModule, self).__init__(*args, **kwargs)
self.pfmodule = PFSenseIpsecModule
def get_config_file(self):
""" get config file """
return 'pfsense_ipsec_config.xml'
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated ipsec xml definition """
elt_filter = {}
elt_filter['descr'] = obj['descr']
return self.assert_has_xml_tag('ipsec', elt_filter, absent=absent)
@staticmethod
def caref(descr):
""" return refid for ca """
if descr == 'test ca':
return '5db509cfed87d'
if descr == 'test ca copy':
return '5db509cfed87e'
return ''
@staticmethod
def certref(descr):
""" return refid for cert """
if descr == 'webConfigurator default (5c00e5f9029df)':
return '5c00e5f9029df'
if descr == 'webConfigurator default copy':
return '5c00e5f9029de'
return ''
def check_target_elt(self, obj, target_elt):
""" test the xml definition of ipsec elt """
# bools
if obj.get('disabled'):
self.assert_xml_elt_is_none_or_empty(target_elt, 'disabled')
else:
self.assert_not_find_xml_elt(target_elt, 'disabled')
self.check_param_bool(obj, target_elt, 'gw_duplicates')
self.check_param_equal_or_not_find(obj, target_elt, 'nattport')
for param in ['rand_time', 'reauth_time', 'rekey_time']:
if obj.get(param):
self.check_param_equal(obj, target_elt, 'rekey_time')
self.check_param_equal(obj, target_elt, 'reauth_time')
self.check_param_equal(obj, target_elt, 'rand_time')
# Added in 2.5.2
if obj.get('startaction'):
self.assert_xml_elt_equal(target_elt, 'startaction', obj['startaction'])
if obj.get('closeaction'):
self.assert_xml_elt_equal(target_elt, 'closeaction', obj['closeaction'])
if obj.get('disable_reauth'):
self.assert_xml_elt_is_none_or_empty(target_elt, 'reauth_enable')
else:
self.assert_not_find_xml_elt(target_elt, 'reauth_enable')
if obj.get('splitconn'):
self.assert_xml_elt_is_none_or_empty(target_elt, 'splitconn')
else:
self.assert_not_find_xml_elt(target_elt, 'splitconn')
if obj.get('enable_dpd') is None or obj.get('enable_dpd'):
if obj.get('dpd_delay') is not None:
self.assert_xml_elt_equal(target_elt, 'dpd_delay', obj['dpd_delay'])
else:
self.assert_xml_elt_equal(target_elt, 'dpd_delay', '10')
if obj.get('dpd_maxfail') is not None:
self.assert_xml_elt_equal(target_elt, 'dpd_maxfail', obj['dpd_maxfail'])
else:
self.assert_xml_elt_equal(target_elt, 'dpd_maxfail', '5')
else:
self.assert_not_find_xml_elt(target_elt, 'dpd_delay')
self.assert_not_find_xml_elt(target_elt, 'dpd_maxfail')
if obj.get('mobike'):
self.assert_xml_elt_equal(target_elt, 'mobike', obj['mobike'])
# iketype & mode
self.assert_xml_elt_equal(target_elt, 'iketype', obj['iketype'])
if obj.get('mode') is not None:
self.assert_xml_elt_equal(target_elt, 'mode', obj['mode'])
if obj.get('nat_traversal') is not None:
self.assert_xml_elt_equal(target_elt, 'nat_traversal', obj['nat_traversal'])
else:
self.assert_xml_elt_equal(target_elt, 'nat_traversal', 'on')
# auth
self.assert_xml_elt_equal(target_elt, 'authentication_method', obj['authentication_method'])
if obj['authentication_method'] == 'rsasig':
self.assert_xml_elt_equal(target_elt, 'certref', self.certref(obj['certificate']))
self.assert_xml_elt_equal(target_elt, 'caref', self.caref(obj['certificate_authority']))
self.assert_xml_elt_is_none_or_empty(target_elt, 'pre-shared-key')
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'certref')
self.assert_xml_elt_is_none_or_empty(target_elt, 'caref')
self.assert_xml_elt_equal(target_elt, 'pre-shared-key', obj['preshared_key'])
# ids
if obj.get('myid_type') is not None:
self.assert_xml_elt_equal(target_elt, 'myid_type', obj['myid_type'])
else:
self.assert_xml_elt_equal(target_elt, 'myid_type', 'myaddress')
if obj.get('myid_data') is not None:
self.assert_xml_elt_equal(target_elt, 'myid_data', obj['myid_data'])
if obj.get('peerid_type') is not None:
self.assert_xml_elt_equal(target_elt, 'peerid_type', obj['peerid_type'])
else:
self.assert_xml_elt_equal(target_elt, 'peerid_type', 'peeraddress')
if obj.get('peerid_data') is not None:
self.assert_xml_elt_equal(target_elt, 'peerid_data', obj['peerid_data'])
# misc
self.assert_xml_elt_equal(target_elt, 'interface', self.unalias_interface(obj['interface']))
if obj.get('protocol') is not None:
self.assert_xml_elt_equal(target_elt, 'protocol', obj['protocol'])
else:
self.assert_xml_elt_equal(target_elt, 'protocol', 'inet')
self.assert_xml_elt_equal(target_elt, 'remote-gateway', obj['remote_gateway'])
if obj.get('lifetime') is not None:
self.assert_xml_elt_equal(target_elt, 'lifetime', obj['lifetime'])
else:
self.assert_xml_elt_equal(target_elt, 'lifetime', '28800')
def strip_commands(self, commands):
commands = commands.replace("margintime='', ", "")
commands = commands.replace("disable_rekey=False, ", "")
return commands
##############
# tests
#
def test_ipsec_create_ikev2(self):
""" test creation of a new ipsec tunnel with 2.5.2 params """
ipsec = dict(
descr='new_tunnel', interface='lan_100', remote_gateway='1.2.3.4', nattport=4501, iketype='ikev2',
authentication_method='pre_shared_key', preshared_key='1234', gw_duplicates=True, rekey_time=2500, reauth_time=2600, rand_time=2700)
command = (
"create ipsec 'new_tunnel', iketype='ikev2', protocol='inet', interface='lan_100', remote_gateway='1.2.3.4', nattport='4501', "
"authentication_method='pre_shared_key', preshared_key='1234', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"rekey_time='2500', reauth_time='2600', rand_time='2700', "
"mobike='off', gw_duplicates=True, startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'")
self.do_module_test(ipsec, command=command)
def test_ipsec_create_ikev1(self):
""" test creation of a new ipsec tunnel """
ipsec = dict(
descr='new_tunnel', interface='lan_100', remote_gateway='1.2.3.4', iketype='ikev1',
authentication_method='pre_shared_key', preshared_key='1234', mode='main', startaction='none', closeaction='none')
command = (
"create ipsec 'new_tunnel', iketype='ikev1', mode='main', protocol='inet', interface='lan_100', remote_gateway='1.2.3.4', "
"authentication_method='pre_shared_key', preshared_key='1234', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', startaction='none', closeaction='none', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'")
self.do_module_test(ipsec, command=command)
def test_ipsec_create_vip_descr(self):
""" test creation of a new ipsec tunnel with vip: interface name """
ipsec = dict(
descr='new_tunnel', interface='vip:WAN CARP', remote_gateway='1.2.3.4', iketype='ikev1',
authentication_method='pre_shared_key', preshared_key='1234', mode='main', startaction='start', closeaction='start')
command = (
"create ipsec 'new_tunnel', iketype='ikev1', mode='main', protocol='inet', interface='vip:WAN CARP', remote_gateway='1.2.3.4', "
"authentication_method='pre_shared_key', preshared_key='1234', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', startaction='start', closeaction='start', "
"nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'")
self.do_module_test(ipsec, command=command)
def test_ipsec_create_vip_subnet(self):
""" test creation of a new ipsec tunnel with vip: interface address """
ipsec = dict(
descr='new_tunnel', interface='vip:151.25.19.11', remote_gateway='1.2.3.4', iketype='ikev1',
authentication_method='pre_shared_key', preshared_key='1234', mode='main', startaction='trap', closeaction='trap')
command = (
"create ipsec 'new_tunnel', iketype='ikev1', mode='main', protocol='inet', interface='vip:151.25.19.11', remote_gateway='1.2.3.4', "
"authentication_method='pre_shared_key', preshared_key='1234', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', startaction='trap', closeaction='trap', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'")
self.do_module_test(ipsec, command=command)
def test_ipsec_create_auto(self):
""" test creation of a new ipsec tunnel """
ipsec = dict(
descr='new_tunnel', interface='lan_100', remote_gateway='1.2.3.4', iketype='auto',
authentication_method='pre_shared_key', preshared_key='1234', mode='main')
command = (
"create ipsec 'new_tunnel', iketype='auto', mode='main', protocol='inet', interface='lan_100', remote_gateway='1.2.3.4', "
"authentication_method='pre_shared_key', preshared_key='1234', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'")
self.do_module_test(ipsec, command=command)
def test_ipsec_delete(self):
""" test deletion of an ipsec """
ipsec = dict(descr='test_tunnel', state='absent')
command = "delete ipsec 'test_tunnel'"
self.do_module_test(ipsec, delete=True, command=command)
def test_ipsec_update_noop(self):
""" test not updating a ipsec """
ipsec = dict(
descr='test_tunnel', interface='lan_100', remote_gateway='1.2.4.8', iketype='ikev2',
authentication_method='pre_shared_key', preshared_key='1234')
self.do_module_test(ipsec, changed=False)
def test_ipsec_update_ike(self):
""" test updating ike """
ipsec = dict(
descr='test_tunnel', interface='lan_100', remote_gateway='1.2.4.8', iketype='ikev1',
authentication_method='pre_shared_key', preshared_key='1234', mode='main')
command = "update ipsec 'test_tunnel' set iketype='ikev1', mode='main'"
self.do_module_test(ipsec, command=command)
def test_ipsec_update_gw(self):
""" test updating gw """
ipsec = dict(
descr='test_tunnel', interface='lan_100', remote_gateway='1.2.3.5', iketype='ikev2',
authentication_method='pre_shared_key', preshared_key='1234')
command = "update ipsec 'test_tunnel' set remote_gateway='1.2.3.5'"
self.do_module_test(ipsec, command=command)
def test_ipsec_update_auth(self):
""" test updating auth """
ipsec = dict(
descr='test_tunnel', interface='lan_100', remote_gateway='1.2.4.8', iketype='ikev2',
authentication_method='rsasig', certificate='webConfigurator default (5c00e5f9029df)', certificate_authority='test ca')
command = (
"update ipsec 'test_tunnel' set authentication_method='rsasig', "
"certificate='webConfigurator default (5c00e5f9029df)', certificate_authority='test ca'")
self.do_module_test(ipsec, command=command)
def test_ipsec_update_cert(self):
""" test updating certificates """
ipsec = dict(
descr='test_tunnel2', interface='lan_100', remote_gateway='1.2.3.6', iketype='ikev2',
authentication_method='rsasig', certificate='webConfigurator default copy', certificate_authority='test ca copy')
command = "update ipsec 'test_tunnel2' set certificate='webConfigurator default copy', certificate_authority='test ca copy'"
self.do_module_test(ipsec, command=command)
def test_ipsec_duplicate_gw(self):
""" test using a duplicate gw """
ipsec = dict(
descr='new_tunnel', interface='lan_100', remote_gateway='1.2.4.8', iketype='ikev1',
authentication_method='pre_shared_key', preshared_key='1234', mode='main')
msg = 'The remote gateway "1.2.4.8" is already used by phase1 "test_tunnel".'
self.do_module_test(ipsec, msg=msg, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_ipsec_aggregate.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from ansible_collections.pfsensible.core.plugins.modules import pfsense_ipsec_aggregate
from parameterized import parameterized
from .pfsense_module import TestPFSenseModule
class TestPFSenseIpsecAggregateModule(TestPFSenseModule):
module = pfsense_ipsec_aggregate
def __init__(self, *args, **kwargs):
super(TestPFSenseIpsecAggregateModule, self).__init__(*args, **kwargs)
def get_config_file(self):
""" get config file """
return 'pfsense_ipsec_aggregate_config.xml'
def assert_find_ipsec(self, ipsec):
""" test if an ipsec tunnel exist """
self.load_xml_result()
parent_tag = self.xml_result.find('ipsec')
if parent_tag is None:
self.fail('Unable to find tag ipsec')
found = False
for ipsec_elt in parent_tag:
if ipsec_elt.tag != 'phase1':
continue
if ipsec_elt.find('descr').text == ipsec:
found = True
break
if not found:
self.fail('Ipsec tunnel not found: ' + ipsec)
def assert_not_find_ipsec(self, ipsec):
""" test if an ipsec tunnel does not exist """
self.load_xml_result()
parent_tag = self.xml_result.find('ipsec')
if parent_tag is None:
self.fail('Unable to find tag ipsec')
found = False
for ipsec_elt in parent_tag:
if ipsec_elt.tag != 'phase1':
continue
if ipsec_elt.find('descr').text == ipsec:
found = True
break
if found:
self.fail('Ipsec tunnel found: ' + ipsec)
def strip_commands(self, commands):
""" remove old or new parameters """
def strip_command(command):
command = command.replace("margintime='', ", "")
command = command.replace("disable_rekey=False, ", "")
return command
if isinstance(commands, str):
return strip_command(commands)
cmds = []
for cmd in commands:
cmd = strip_command(cmd)
cmds.append(cmd)
return cmds
############
# as we rely on sub modules for modifying the xml
# we dont perform checks on the xml modifications
# we just test the output
@parameterized.expand([["2.5.2"]])
def test_ipsec_aggregate_ipsecs(self, pfsense_version):
""" test creation of a some tunnels """
self.get_version.return_value = pfsense_version
args = dict(
purge_ipsecs=False,
aggregated_ipsecs=[
dict(descr='t1', interface='wan', remote_gateway='1.3.3.1', iketype='ikev2', authentication_method='pre_shared_key', preshared_key='azerty123'),
dict(descr='t2', interface='wan', remote_gateway='1.3.3.2', iketype='ikev2', authentication_method='pre_shared_key', preshared_key='qwerty123'),
dict(descr='test_tunnel2', state='absent'),
dict(
descr='test_tunnel', interface='lan_100', remote_gateway='1.2.4.8', iketype='ikev2',
authentication_method='pre_shared_key', preshared_key='0123456789'
),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_ipsecs = []
result_ipsecs.append(
"create ipsec 't1', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.3.3.1', authentication_method='pre_shared_key', "
"preshared_key='azerty123', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', "
"mobike='off', startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'"
)
result_ipsecs.append(
"create ipsec 't2', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.3.3.2', authentication_method='pre_shared_key', "
"preshared_key='qwerty123', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', "
"mobike='off', startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'"
)
result_ipsecs.append("delete ipsec 'test_tunnel2'")
result_ipsecs.append("update ipsec 'test_tunnel' set preshared_key='0123456789'")
result_ipsecs = self.strip_commands(result_ipsecs)
self.assertEqual(result['result_ipsecs'], result_ipsecs)
self.assert_find_ipsec('t1')
self.assert_find_ipsec('t2')
self.assert_not_find_ipsec('test_tunnel2')
self.assert_find_ipsec('test_tunnel')
@parameterized.expand([["2.5.2"]])
def test_ipsec_aggregate_ipsecs_purge(self, pfsense_version):
""" test creation of a some tunnels with purge """
self.get_version.return_value = pfsense_version
args = dict(
purge_ipsecs=True,
aggregated_ipsecs=[
dict(descr='t1', interface='wan', remote_gateway='1.3.3.1', iketype='ikev2', authentication_method='pre_shared_key', preshared_key='azerty123'),
dict(descr='t2', interface='wan', remote_gateway='1.3.3.2', iketype='ikev2', authentication_method='pre_shared_key', preshared_key='qwerty123'),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_ipsecs = []
result_ipsecs.append(
"create ipsec 't1', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.3.3.1', authentication_method='pre_shared_key', "
"preshared_key='azerty123', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', "
"mobike='off', startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'"
)
result_ipsecs.append(
"create ipsec 't2', iketype='ikev2', protocol='inet', interface='wan', remote_gateway='1.3.3.2', authentication_method='pre_shared_key', "
"preshared_key='qwerty123', myid_type='myaddress', peerid_type='peeraddress', lifetime='28800', "
"disable_rekey=False, margintime='', "
"mobike='off', startaction='', closeaction='', nat_traversal='on', enable_dpd=True, dpd_delay='10', dpd_maxfail='5'"
)
result_ipsecs.append("delete ipsec 'test_tunnel'")
result_ipsecs.append("delete ipsec 'test_tunnel2'")
result_ipsecs = self.strip_commands(result_ipsecs)
self.assertEqual(result['result_ipsecs'], result_ipsecs)
self.assert_find_ipsec('t1')
self.assert_find_ipsec('t2')
self.assert_not_find_ipsec('test_tunnel')
self.assert_not_find_ipsec('test_tunnel2')
@parameterized.expand([["2.5.2"]])
def test_ipsec_aggregate_proposals(self, pfsense_version):
""" test creation of a some proposals """
self.get_version.return_value = pfsense_version
args = dict(
purge_ipsec_proposals=False,
aggregated_ipsec_proposals=[
dict(descr='test_tunnel', encryption='aes', key_length=128, hash='md5', dhgroup=14),
dict(descr='test_tunnel2', encryption='cast128', hash='sha512', dhgroup=14),
dict(descr='test_tunnel', encryption='aes', key_length=128, hash='sha256', dhgroup=14, state='absent'),
dict(descr='test_tunnel2', encryption='blowfish', key_length=256, hash='aesxcbc', dhgroup=14, state='absent'),
]
)
with set_module_args(args):
self.execute_module(changed=True)
result = self.execute_module(changed=True)
result_ipsec_proposals = []
result_ipsec_proposals.append("create ipsec_proposal 'test_tunnel', encryption='aes', key_length=128, hash='md5', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("create ipsec_proposal 'test_tunnel2', encryption='cast128', hash='sha512', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("delete ipsec_proposal 'test_tunnel', encryption='aes', key_length=128, hash='sha256', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append(
"delete ipsec_proposal 'test_tunnel2', encryption='blowfish', key_length=256, hash='aesxcbc', dhgroup='14', prf='sha256'"
)
result_ipsec_proposals = self.strip_commands(result_ipsec_proposals)
self.assertEqual(result['result_ipsec_proposals'], result_ipsec_proposals)
@parameterized.expand([["2.5.2"]])
def test_ipsec_aggregate_proposals_purge(self, pfsense_version):
""" test creation of a some proposals with purge """
self.get_version.return_value = pfsense_version
args = dict(
purge_ipsec_proposals=True,
aggregated_ipsec_proposals=[
dict(descr='test_tunnel', encryption='aes', key_length=128, hash='md5', dhgroup=14),
dict(descr='test_tunnel2', encryption='cast128', hash='sha512', dhgroup=14),
]
)
with set_module_args(args):
self.execute_module(changed=True)
result = self.execute_module(changed=True)
result_ipsec_proposals = []
result_ipsec_proposals.append("create ipsec_proposal 'test_tunnel', encryption='aes', key_length=128, hash='md5', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("create ipsec_proposal 'test_tunnel2', encryption='cast128', hash='sha512', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("delete ipsec_proposal 'test_tunnel', encryption='aes', key_length=128, hash='sha256', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("delete ipsec_proposal 'test_tunnel', encryption='aes', key_length=256, hash='sha256', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append(
"delete ipsec_proposal 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14', prf='sha256'"
)
result_ipsec_proposals.append(
"delete ipsec_proposal 'test_tunnel', encryption='blowfish', key_length=256, hash='aesxcbc', dhgroup='14', prf='sha256'"
)
result_ipsec_proposals.append("delete ipsec_proposal 'test_tunnel2', encryption='aes', key_length=128, hash='sha256', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append("delete ipsec_proposal 'test_tunnel2', encryption='aes', key_length=256, hash='sha256', dhgroup='14', prf='sha256'")
result_ipsec_proposals.append(
"delete ipsec_proposal 'test_tunnel2', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14', prf='sha256'"
)
result_ipsec_proposals.append(
"delete ipsec_proposal 'test_tunnel2', encryption='blowfish', key_length=256, hash='aesxcbc', dhgroup='14', prf='sha256'"
)
result_ipsec_proposals = self.strip_commands(result_ipsec_proposals)
self.assertEqual(result['result_ipsec_proposals'], result_ipsec_proposals)
def test_ipsec_aggregate_p2s(self):
""" test creation of a some p2s """
args = dict(
purge_ipsec_p2s=False,
aggregated_ipsec_p2s=[
dict(descr='p2_1', p1_descr='test_tunnel', mode='tunnel', local='1.2.3.4/24', remote='10.20.30.40/24', aes=True, aes_len='auto', sha256=True),
dict(descr='p2_2', p1_descr='test_tunnel', mode='tunnel', local='1.2.3.4/24', remote='10.20.30.50/24', aes=True, aes_len='auto', sha256=True),
dict(
descr='one_p2', p1_descr='test_tunnel', mode='tunnel', local='lan', remote='10.20.30.60/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True'
),
dict(descr='another_p2', p1_descr='test_tunnel', state='absent')
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_ipsec_p2s = []
result_ipsec_p2s.append(
"create ipsec_p2 'p2_1' on 'test_tunnel', disabled=False, mode='tunnel', local='1.2.3.4/24', remote='10.20.30.40/24', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
result_ipsec_p2s.append(
"create ipsec_p2 'p2_2' on 'test_tunnel', disabled=False, mode='tunnel', local='1.2.3.4/24', remote='10.20.30.50/24', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
result_ipsec_p2s.append("update ipsec_p2 'one_p2' on 'test_tunnel' set remote='10.20.30.60/24'")
result_ipsec_p2s.append("delete ipsec_p2 'another_p2' on 'test_tunnel'")
self.assertEqual(result['result_ipsec_p2s'], result_ipsec_p2s)
def test_ipsec_aggregate_p2s_purge(self):
""" test creation of a some p2s with purge """
args = dict(
purge_ipsec_p2s=True,
aggregated_ipsec_p2s=[
dict(descr='p2_1', p1_descr='test_tunnel', mode='tunnel', local='1.2.3.4/24', remote='10.20.30.40/24', aes=True, aes_len='auto', sha256=True),
dict(descr='p2_2', p1_descr='test_tunnel', mode='tunnel', local='1.2.3.4/24', remote='10.20.30.50/24', aes=True, aes_len='auto', sha256=True),
]
)
with set_module_args(args):
result = self.execute_module(changed=True)
result_ipsec_p2s = []
result_ipsec_p2s.append(
"create ipsec_p2 'p2_1' on 'test_tunnel', disabled=False, mode='tunnel', local='1.2.3.4/24', remote='10.20.30.40/24', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
result_ipsec_p2s.append(
"create ipsec_p2 'p2_2' on 'test_tunnel', disabled=False, mode='tunnel', local='1.2.3.4/24', remote='10.20.30.50/24', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
result_ipsec_p2s.append("delete ipsec_p2 'one_p2' on 'test_tunnel'")
result_ipsec_p2s.append("delete ipsec_p2 'another_p2' on 'test_tunnel'")
result_ipsec_p2s.append("delete ipsec_p2 'third_p2' on 'test_tunnel'")
result_ipsec_p2s.append("delete ipsec_p2 'nat_p2' on 'test_tunnel'")
self.assertEqual(result['result_ipsec_p2s'], result_ipsec_p2s)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_ipsec_p2.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_ipsec_p2
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_p2 import PFSenseIpsecP2Module
from .pfsense_module import TestPFSenseModule
class TestPFSenseIpsecP2Module(TestPFSenseModule):
module = pfsense_ipsec_p2
def __init__(self, *args, **kwargs):
super(TestPFSenseIpsecP2Module, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_ipsec_p2_config.xml'
self.pfmodule = PFSenseIpsecP2Module
##############
# tests utils
#
def get_phase1_elt(self, descr, absent=False):
""" get phase1 """
elt_filter = {}
elt_filter['descr'] = descr
return self.assert_has_xml_tag('ipsec', elt_filter, absent=absent)
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated phase2 xml definition """
phase1_elt = self.get_phase1_elt(obj['p1_descr'])
elt_filter = {}
elt_filter['descr'] = obj['descr']
elt_filter['ikeid'] = phase1_elt.find('ikeid').text
return self.assert_has_xml_tag('ipsec', elt_filter, absent=absent)
@staticmethod
def get_enc_elt(phase2_elt, enc_name):
""" get encryption """
for elt in phase2_elt:
if elt.tag != 'encryption-algorithm-option':
continue
if elt.find('name').text == enc_name:
return elt
return None
def check_enc(self, phase2, phase2_elt, enc_name, param_name):
""" check encryption """
enc_elt = self.get_enc_elt(phase2_elt, enc_name)
if phase2.get(param_name):
if enc_elt is None:
self.fail('Encryption named {0} not found'.format(enc_name))
if phase2.get(param_name + '_len') is not None:
keylen_elt = enc_elt.find('keylen')
if keylen_elt is None:
self.fail('Key length not found for encryption named {0}'.format(enc_name))
self.assertEqual(keylen_elt.text, phase2[param_name + '_len'])
else:
if enc_elt is not None:
self.fail('Encryption named {0} found'.format(enc_name))
@staticmethod
def get_hash_elt(phase2_elt, hash_name):
""" get hash """
for elt in phase2_elt:
if elt.tag != 'hash-algorithm-option':
continue
if elt.text == hash_name:
return elt
return None
def check_hash(self, phase2, phase2_elt, hash_name, param_name):
""" check hash """
hash_elt = self.get_hash_elt(phase2_elt, hash_name)
if phase2.get(param_name):
if hash_elt is None:
self.fail('Hash algorithm named {0} not found'.format(hash_name))
else:
if hash_elt is not None:
self.fail('Hash algorithm named {0} found'.format(hash_name))
def param_to_address(self, address):
""" hardcoded addresses """
ret = dict()
if address in ['1.2.3.1', '1.2.3.2']:
ret['type'] = 'address'
ret['address'] = address
ret['type'] = 'address'
ret['address'] = address
elif address == '1.2.3.4/24':
ret['type'] = 'network'
ret['address'] = '1.2.3.4'
ret['netbits'] = '24'
elif address == '10.20.30.40/24':
ret['type'] = 'network'
ret['address'] = '10.20.30.40'
ret['netbits'] = '24'
elif address == '10.20.30.50/24':
ret['type'] = 'network'
ret['address'] = '10.20.30.50'
ret['netbits'] = '24'
elif address in ['lan_100', 'lan']:
ret['type'] = self.unalias_interface(address)
else:
self.fail('Please add address {0} to param_to_address'.format(address))
return ret
def check_address(self, phase2, phase2_elt, elt_name, param_name):
""" check address """
if phase2.get(param_name) is None:
if phase2_elt.find(elt_name) is not None:
self.fail('Address type {0} found'.format(elt_name))
else:
addr_elt = phase2_elt.find(elt_name)
if addr_elt is None:
self.fail('Address type {0} not found'.format(elt_name))
address = self.param_to_address(phase2[param_name])
for param in address.keys():
elt = addr_elt.find(param)
if elt is None:
self.fail('Address param {0} not found'.format(param))
self.assertEqual(elt.text, address[param])
params = address.keys()
for elt in addr_elt:
if elt.tag not in params:
self.fail('Address param{0} found'.format(elt.tag))
def check_target_elt(self, obj, target_elt):
""" test the xml definition of phase2 elt """
# bools
if obj.get('disabled'):
self.assert_xml_elt_is_none_or_empty(target_elt, 'disabled')
else:
self.assert_not_find_xml_elt(target_elt, 'disabled')
self.assert_xml_elt_equal(target_elt, 'mode', obj['mode'])
if obj.get('procotol') is not None:
self.assert_xml_elt_equal(target_elt, 'protocol', obj['protocol'])
else:
self.assert_xml_elt_equal(target_elt, 'protocol', 'esp')
if obj.get('pfsgroup') is not None:
self.assert_xml_elt_equal(target_elt, 'pfsgroup', obj['pfsgroup'])
else:
self.assert_xml_elt_equal(target_elt, 'pfsgroup', '14')
if obj.get('lifetime') is not None:
if obj['lifetime'] == 0:
self.assert_xml_elt_is_none_or_empty(target_elt, 'lifetime')
else:
self.assert_xml_elt_equal(target_elt, 'lifetime', str(obj['lifetime']))
else:
self.assert_xml_elt_equal(target_elt, 'lifetime', '3600')
if obj.get('pinghost') is not None:
self.assert_xml_elt_equal(target_elt, 'pinghost', str(obj['pinghost']))
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'pinghost')
# encryptions
self.check_enc(obj, target_elt, 'aes', 'aes')
self.check_enc(obj, target_elt, 'aes128gcm', 'aes128gcm')
self.check_enc(obj, target_elt, 'aes192gcm', 'aes192gcm')
self.check_enc(obj, target_elt, 'aes256gcm', 'aes256gcm')
self.check_enc(obj, target_elt, 'blowfish', 'blowfish')
self.check_enc(obj, target_elt, '3des', 'des')
self.check_enc(obj, target_elt, 'cast128', 'cast128')
# hashes
self.check_hash(obj, target_elt, 'hmac_sha1', 'sha1')
self.check_hash(obj, target_elt, 'hmac_sha256', 'sha256')
self.check_hash(obj, target_elt, 'hmac_sha384', 'sha384')
self.check_hash(obj, target_elt, 'hmac_sha512', 'sha512')
self.check_hash(obj, target_elt, 'aesxcbc', 'aesxcbc')
self.check_address(obj, target_elt, 'localid', 'local')
self.check_address(obj, target_elt, 'remoteid', 'remote')
self.check_address(obj, target_elt, 'natlocalid', 'nat')
##############
# tests
#
def test_phase2_create_vti(self):
""" test creation of a new phase2 in vti mode """
phase2 = dict(p1_descr='test_tunnel', descr='test_p2', mode='vti', local='1.2.3.1', remote='1.2.3.2', aes='True', aes_len='auto', sha256='True')
command = (
"create ipsec_p2 'test_p2' on 'test_tunnel', disabled=False, mode='vti', local='1.2.3.1', remote='1.2.3.2', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
self.do_module_test(phase2, command=command)
def test_phase2_create_tunnel(self):
""" test creation of a new phase2 in tunnel mode """
phase2 = dict(p1_descr='test_tunnel', descr='test_p2', mode='tunnel', local='lan_100', remote='1.2.3.4/24', aes='True', aes_len='auto', sha256='True')
command = (
"create ipsec_p2 'test_p2' on 'test_tunnel', disabled=False, mode='tunnel', local='lan_100', remote='1.2.3.4/24', "
"aes=True, aes_len='auto', sha256=True, pfsgroup='14', lifetime=3600"
)
self.do_module_test(phase2, command=command)
def test_phase2_delete(self):
""" test deletion of a phase2 """
phase2 = dict(p1_descr='test_tunnel', descr='one_p2', state='absent')
command = "delete ipsec_p2 'one_p2' on 'test_tunnel'"
self.do_module_test(phase2, delete=True, command=command)
def test_phase2_update_noop(self):
""" test not updating a phase2 """
phase2 = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
self.do_module_test(phase2, changed=False)
def test_phase2_update_aes_len(self):
""" test update aes """
phase2 = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='10.20.30.40/24',
aes='True', aes_len='auto', aes128gcm=True, aes128gcm_len='128', sha256='True')
command = "update ipsec_p2 'one_p2' on 'test_tunnel' set aes_len='auto'"
self.do_module_test(phase2, command=command)
def test_phase2_update_disable_aes(self):
""" test removing aes """
phase2 = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='10.20.30.40/24',
aes128gcm=True, aes128gcm_len='128', sha256='True')
command = "update ipsec_p2 'one_p2' on 'test_tunnel' set aes=False, aes_len=none"
self.do_module_test(phase2, command=command)
def test_phase2_update_set_3des(self):
""" test enabling 3des """
phase2 = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', des=True, sha256='True')
command = "update ipsec_p2 'one_p2' on 'test_tunnel' set des=True"
self.do_module_test(phase2, command=command)
def test_phase2_update_remove_3des(self):
""" test disabling 3des """
phase2 = dict(
p1_descr='test_tunnel', descr='another_p2', mode='tunnel', local='lan', remote='10.20.30.50/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', des=False, sha256='True')
command = "update ipsec_p2 'another_p2' on 'test_tunnel' set des=False"
self.do_module_test(phase2, command=command)
def test_phase2_update_remove_sha256(self):
""" test disabling sha256 """
phase2 = dict(
p1_descr='test_tunnel', descr='another_p2', mode='tunnel', local='lan', remote='10.20.30.50/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', des=True, sha512='True')
command = "update ipsec_p2 'another_p2' on 'test_tunnel' set sha256=False, sha512=True"
self.do_module_test(phase2, command=command)
def test_phase2_update_change_address(self):
""" test changing address """
phase2 = dict(
p1_descr='test_tunnel', descr='third_p2', mode='tunnel', local='lan_100', remote='10.20.30.50/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', des=True, sha256='True')
command = "update ipsec_p2 'third_p2' on 'test_tunnel' set local='lan_100'"
self.do_module_test(phase2, command=command)
def test_phase2_update_set_nat(self):
""" test setting nat """
phase2 = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='10.20.30.40/24', nat='1.2.3.4/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
command = "update ipsec_p2 'one_p2' on 'test_tunnel' set nat='1.2.3.4/24'"
self.do_module_test(phase2, command=command)
def test_phase2_update_remove_nat(self):
""" test removing nat """
phase2 = dict(
p1_descr='test_tunnel', descr='nat_p2', mode='tunnel', local='lan', remote='1.2.3.4/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
command = "update ipsec_p2 'nat_p2' on 'test_tunnel' set nat=none"
self.do_module_test(phase2, command=command)
def test_phase2_inexistent_tunnel(self):
""" test error with inexistent tunnel """
ipsec = dict(
p1_descr='inexistent_tunnel', descr='nat_p2', mode='tunnel', local='lan', remote='1.2.3.4/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'No ipsec tunnel named inexistent_tunnel'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_no_encryption(self):
""" test error with no encryption """
ipsec = dict(
p1_descr='test_tunnel', descr='nat_p2', mode='tunnel', local='lan', remote='1.2.3.4/24', sha256='True')
msg = 'At least one encryption algorithm must be selected.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_no_hash(self):
""" test error with no hash """
ipsec = dict(
p1_descr='test_tunnel', descr='nat_p2', mode='tunnel', local='lan', remote='1.2.3.4/24', cast128='True')
msg = 'At least one hashing algorithm needs to be selected.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_vti_lan(self):
""" test error on vti address """
ipsec = dict(
p1_descr='test_tunnel', descr='nat_p2', mode='vti', local='lan', remote='1.2.3.4', cast128='True', sha256='True')
msg = 'VTI requires a valid local network or IP address for its endpoint address.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_vti_lan2(self):
""" test error on vti address """
ipsec = dict(
p1_descr='test_tunnel', descr='nat_p2', mode='vti', local='1.2.3.4', remote='lan', cast128='True', sha256='True')
msg = 'VTI requires a valid remote IP address for its endpoint address.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel6_remote(self):
""" test error on tunnel6 address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel6', local='lan', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv6 address or network must be specified in remote with tunnel6.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel6_remote2(self):
""" test error on tunnel6 address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel6', local='lan', remote='1.2.3.4',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv6 address or network must be specified in remote with tunnel6.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel6_local(self):
""" test error on tunnel6 address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel6', local='1.2.3.4/24', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv6 address or network must be specified in local with tunnel6.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel_remote(self):
""" test error on tunnel address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='fd69:81a5:a5:7396:0:0:0:0',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv4 address or network must be specified in remote with tunnel.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel_remote2(self):
""" test error on tunnel address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='lan', remote='fd69:81a5:a5:7396:0:0:0:0/64',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv4 address or network must be specified in remote with tunnel.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_tunnel_local(self):
""" test error on tunnel address """
ipsec = dict(
p1_descr='test_tunnel', descr='one_p2', mode='tunnel', local='fd69:81a5:a5:7396:0:0:0:0', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'A valid IPv4 address or network must be specified in local with tunnel.'
self.do_module_test(ipsec, msg=msg, failed=True)
def test_phase2_duplicate(self):
""" test error duplicate local/remote definition """
phase2 = dict(
p1_descr='test_tunnel', descr='duplicate_p2', mode='tunnel', local='lan', remote='10.20.30.40/24',
aes='True', aes_len='128', aes128gcm=True, aes128gcm_len='128', sha256='True')
msg = 'Phase2 with this Local/Remote networks combination is already defined for this Phase1.'
self.do_module_test(phase2, msg=msg, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_ipsec_proposal.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_ipsec_proposal
from ansible_collections.pfsensible.core.plugins.module_utils.ipsec_proposal import PFSenseIpsecProposalModule
from .pfsense_module import TestPFSenseModule
from parameterized import parameterized
class TestPFSenseIpsecProposalModule(TestPFSenseModule):
module = pfsense_ipsec_proposal
def __init__(self, *args, **kwargs):
super(TestPFSenseIpsecProposalModule, self).__init__(*args, **kwargs)
self.pfmodule = PFSenseIpsecProposalModule
def get_config_file(self):
""" get config file """
if self.get_version.return_value.startswith("2.4."):
return '2.4/pfsense_ipsec_proposal_config.xml'
return 'pfsense_ipsec_proposal_config.xml'
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated proposal xml definition """
elt_filter = {}
elt_filter['descr'] = obj['descr']
ipsec_elt = self.assert_has_xml_tag('ipsec', elt_filter)
if ipsec_elt is None:
return None
encryption_elt = ipsec_elt.find('encryption')
if encryption_elt is None:
return None
for item_elt in encryption_elt:
elt = item_elt.find('dhgroup')
if elt is None or elt.text != str(obj['dhgroup']):
continue
elt = item_elt.find('hash-algorithm')
if elt is None or elt.text != obj['hash']:
continue
if not self.get_version.return_value.startswith("2.4."):
elt = item_elt.find('prf-algorithm')
if elt is None or 'prf' not in obj and elt.text != 'sha256' and elt.text != obj['prf']:
continue
encalg_elt = item_elt.find('encryption-algorithm')
if encalg_elt is None:
continue
elt = encalg_elt.find('name')
if elt is None or elt.text != obj['encryption']:
continue
elt = encalg_elt.find('keylen')
if (elt is None or elt.text == '') and obj.get('key_length') is None:
return item_elt
if elt is not None and elt.text == str(obj.get('key_length')):
return item_elt
return None
def check_target_elt(self, obj, target_elt):
""" test the xml definition of proposal elt """
if target_elt is None:
self.fail('Unable to find proposal on ' + obj['descr'])
def strip_commands(self, commands):
""" remove old or new parameters """
if self.get_version.return_value.startswith("2.4."):
commands = commands.replace(", prf='sha256'", "")
return commands
##############
# tests
#
@parameterized.expand([["2.4.4"], ["2.5.0"], ["2.5.2"]])
def test_ipsec_proposal_create(self, pfsense_version):
""" test creation of a new proposal """
self.get_version.return_value = pfsense_version
proposal = dict(descr='test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup=21)
command = "create ipsec_proposal 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='21', prf='sha256'"
self.do_module_test(proposal, command=command)
@parameterized.expand([["2.4.4"], ["2.5.0"], ["2.5.2"]])
def test_ipsec_proposal_create_nokeylen(self, pfsense_version):
""" test creation of a new proposal """
self.get_version.return_value = pfsense_version
proposal = dict(descr='test_tunnel2', encryption='cast128', hash='sha256', dhgroup=21)
command = "create ipsec_proposal 'test_tunnel2', encryption='cast128', hash='sha256', dhgroup='21', prf='sha256'"
self.do_module_test(proposal, command=command)
@parameterized.expand([["2.4.4"], ["2.5.0"], ["2.5.2"]])
def test_ipsec_proposal_delete(self, pfsense_version):
""" test deletion of an ipsec proposal """
self.get_version.return_value = pfsense_version
proposal = dict(descr='test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup=14, state='absent')
command = "delete ipsec_proposal 'test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup='14', prf='sha256'"
self.do_module_test(proposal, delete=True, command=command)
@parameterized.expand([["2.4.4"], ["2.5.0"], ["2.5.2"]])
def test_ipsec_proposal_update_noop(self, pfsense_version):
""" test not updating a ipsec proposal """
self.get_version.return_value = pfsense_version
proposal = dict(descr='test_tunnel', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup=14)
self.do_module_test(proposal, changed=False)
def test_ipsec_proposal_wrong_keylen(self):
""" test using a wrong key_length """
proposal = dict(descr='test_tunnel', encryption='aes128gcm', key_length=256, hash='sha256', dhgroup=14)
msg = 'key_length for encryption aes128gcm must be one of: 64, 96, 128.'
self.do_module_test(proposal, msg=msg, failed=True)
def test_ipsec_proposal_wrong_tunnel(self):
""" test using a wrong tunnel """
proposal = dict(descr='test_tunnel3', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup=14)
msg = 'No ipsec tunnel named test_tunnel3'
self.do_module_test(proposal, msg=msg, failed=True)
def test_ipsec_proposal_wrong_encryption(self):
""" test using a wrong encryption """
proposal = dict(descr='test_tunnel2', encryption='aes128gcm', key_length=128, hash='sha256', dhgroup=14)
msg = 'Encryption Algorithm AES-GCM can only be used with IKEv2'
self.do_module_test(proposal, msg=msg, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_log_settings.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_log_settings
from .pfsense_module import TestPFSenseModule
class TestPFSenseLogSettingsModule(TestPFSenseModule):
module = pfsense_log_settings
def __init__(self, *args, **kwargs):
super(TestPFSenseLogSettingsModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_syslog_config.xml'
self.pfmodule = pfsense_log_settings.PFSenseLogSettingsModule
self.defaults = {
'filterdescriptions': 1,
'reverse': True,
'nentries': 50,
'sourceip': None,
'ipproto': 'ipv4',
}
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
return self.assert_find_xml_elt(self.xml_result, 'syslog')
def check_target_elt(self, obj, target_elt):
""" test the xml definition of target elt """
def check_param(param, xml_field=None):
if obj is not None:
if xml_field is None:
xml_field = param
if param in obj:
# Special handling for sourceip
# Given as ip or descr but set as internal interface id
interface_map = {
'192.168.240.137': 'wan',
'wan': 'wan',
'192.168.1.242': 'lan',
'10.255.2.254': '_vip5c0a4b6139b05',
'127.0.0.1': 'lo0',
'Localhost': 'lo0',
}
if param == 'sourceip':
self.assert_xml_elt_equal(target_elt, xml_field, interface_map.get(obj[param], obj[param]))
else:
self.assert_xml_elt_equal(target_elt, xml_field, obj[param])
else:
if param in self.defaults:
self.assert_xml_elt_equal(target_elt, xml_field, self.defaults[param])
else:
self.assert_not_find_xml_elt(target_elt, xml_field)
def check_bool_param(param, xml_field=None):
if obj is not None:
if xml_field is None:
xml_field = param
if param in obj:
# Special handling for inverted field
# When nologdefaultpass is present in xml, value is False
if param == 'nologdefaultpass':
if obj[param]:
self.assert_not_find_xml_elt(target_elt, param)
else:
self.assert_xml_elt_equal(target_elt, xml_field, '')
else:
self.check_param_bool(obj, target_elt, param, xml_field=xml_field)
else:
if param in self.defaults:
if self.defaults[param]:
self.assert_xml_elt_equal(target_elt, xml_field, None)
else:
self.assert_xml_elt_is_none_or_empty(target_elt, xml_field)
else:
self.assert_not_find_xml_elt(target_elt, xml_field)
check_param('logformat', xml_field='format')
check_bool_param('reverse')
check_param('nentries')
check_bool_param('nologdefaultblock')
check_bool_param('nologdefaultpass')
check_bool_param('nologbogons')
check_bool_param('nologprivatenets')
check_bool_param('nolognginx')
check_bool_param('rawfilter')
check_param('filterdescriptions')
check_bool_param('disablelocallogging')
check_param('logfilesize')
check_param('logcompressiontype')
check_param('rotatecount')
check_bool_param('enable')
check_param('sourceip')
check_param('ipproto')
check_param('remoteserver')
check_param('remoteserver2')
check_param('remoteserver3')
check_bool_param('logall')
check_bool_param('system')
check_bool_param('logfilter', xml_field='filter')
check_bool_param('resolver')
check_bool_param('dhcp')
check_bool_param('ppp')
check_bool_param('auth')
check_bool_param('portalauth')
check_bool_param('vpn')
check_bool_param('dpinger')
check_bool_param('routing')
check_bool_param('ntpd')
check_bool_param('hostapd')
def test_syslog_logformat_rfc5424(self):
""" test syslog format rfc5424 """
syslog = dict(logformat='rfc5424')
command = "update log_settings syslog set format='rfc5424'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logformat_rfc3164(self):
""" test syslog format rfc3164 """
syslog = dict(logformat='rfc3164')
command = "update log_settings syslog set format='rfc3164'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logformat_invalid(self):
""" test syslog format invalid """
syslog = dict(logformat='rfc1149')
msg = 'value of logformat must be one of: rfc3164, rfc5424, got: rfc1149'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_reverse(self):
""" test log_settings reverse=False """
syslog = dict(reverse=False)
command = "update log_settings syslog set reverse=False"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_reverse_true(self):
""" test log_settings reverse=True """
syslog = dict(reverse=True)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nentries_valid(self):
""" test log_settings nentries """
syslog = dict(nentries='5')
command = "update log_settings syslog set nentries='5'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nentries_valid2(self):
""" test log_settings nentries """
syslog = dict(nentries='500')
command = "update log_settings syslog set nentries='500'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nentries_valid3(self):
""" test log_settings nentries """
syslog = dict(nentries='200000')
command = "update log_settings syslog set nentries='200000'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nentries_invalid1(self):
""" test log_settings nentries """
syslog = dict(nentries='-1')
msg = 'nentries must be an integer from 5 to 200000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_nentries_invalid2(self):
""" test log_settings nentries """
syslog = dict(nentries='4')
msg = 'nentries must be an integer from 5 to 200000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_nentries_invalid3(self):
""" test log_settings nentries """
syslog = dict(nentries='200001')
msg = 'nentries must be an integer from 5 to 200000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_nologdefaultblock_false(self):
""" test log_settings nologdefaultblock=False """
syslog = dict(nologdefaultblock=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nologdefaultblock_true(self):
""" test log_settings nologdefaultblock=True """
syslog = dict(nologdefaultblock=True)
command = "update log_settings syslog set nologdefaultblock=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nologdefaultpass_false(self):
""" test log_settings nologdefaultpass=False """
syslog = dict(nologdefaultpass=False)
# different bool values are correct, logic is inverted
command = "update log_settings syslog set nologdefaultpass=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nologdefaultpass_true(self):
""" test log_settings nologdefaultpass=True """
syslog = dict(nologdefaultpass=True)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nologbogons_false(self):
""" test log_settings nologbogons=False """
syslog = dict(nologbogons=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nologbogons_true(self):
""" test log_settings nologbogons=True """
syslog = dict(nologbogons=True)
command = "update log_settings syslog set nologbogons=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nologprivatenets_false(self):
""" test log_settings nologprivatenets=False """
syslog = dict(nologprivatenets=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nologprivatenets_true(self):
""" test log_settings nologprivatenets=True """
syslog = dict(nologprivatenets=True)
command = "update log_settings syslog set nologprivatenets=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_nolognginx_false(self):
""" test log_settings nolognginx=False """
syslog = dict(nolognginx=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_nolognginx_true(self):
""" test log_settings nolognginx=True """
syslog = dict(nolognginx=True)
command = "update log_settings syslog set nolognginx=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rawfilter_false(self):
""" test log_settings rawfilter=False """
syslog = dict(rawfilter=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_rawfilter_true(self):
""" test log_settings rawfilter=True """
syslog = dict(rawfilter=True)
command = "update log_settings syslog set rawfilter=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_filterdescriptions_valid0(self):
""" test log_settings filterdescriptions = 0 """
syslog = dict(filterdescriptions='0')
command = "update log_settings syslog set filterdescriptions='0'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_filterdescriptions_valid1(self):
""" test log_settings filterdescriptions = 1 """
syslog = dict(filterdescriptions='1')
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_filterdescriptions_valid2(self):
""" test log_settings filterdescriptions = 2 """
syslog = dict(filterdescriptions='2')
command = "update log_settings syslog set filterdescriptions='2'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_filterdescriptions_invalid3(self):
""" test log_settings filterdescriptions = 3 """
syslog = dict(filterdescriptions='3')
msg = "value of filterdescriptions must be one of: 0, 1, 2, got: 3"
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_disablelocallogging_false(self):
""" test log_settings disablelocallogging=False """
syslog = dict(disablelocallogging=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_disablelocallogging_true(self):
""" test log_settings disablelocallogging=True """
syslog = dict(disablelocallogging=True)
command = "update log_settings syslog set disablelocallogging=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilesize_valid1(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize='512000')
command = "update log_settings syslog set logfilesize='512000'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilesize_valid2(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize='100000')
command = "update log_settings syslog set logfilesize='100000'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilesize_valid3(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize=int((2**32) / 2) - 1)
command = "update log_settings syslog set logfilesize='2147483647'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilesize_invalid1(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize='-1')
msg = 'logfilesize must be an integer greater or equal than 100000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logfilesize_invalid2(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize='99999')
msg = 'logfilesize must be an integer greater or equal than 100000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logfilesize_invalid3(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize='0')
msg = 'logfilesize must be an integer greater or equal than 100000'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logfilesize_invalid4(self):
""" test log_settings logfilesize """
syslog = dict(logfilesize=int(((2**32) / 2) + 1))
msg = 'logfilesize is too large: 2147483649'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logcompressiontype_valid_xz(self):
""" test syslog logcompression = xz """
syslog = dict(logcompressiontype='xz')
command = "update log_settings syslog set logcompressiontype='xz'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logcompressiontype_valid_gzip(self):
""" test syslog logcompression = gzip """
syslog = dict(logcompressiontype='gzip')
command = "update log_settings syslog set logcompressiontype='gzip'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rotatecount_valid0(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='0')
command = "update log_settings syslog set rotatecount='0'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rotatecount_valid1(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='7')
command = "update log_settings syslog set rotatecount='7'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rotatecount_valid2(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='31')
command = "update log_settings syslog set rotatecount='31'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rotatecount_valid3(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='99')
command = "update log_settings syslog set rotatecount='99'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_rotatecount_invalid1(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='-1')
msg = 'rotatecount must be an integer from 0 to 99'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_rotatecount_invalid2(self):
""" test log_settings rotatecount """
syslog = dict(rotatecount='100')
msg = 'rotatecount must be an integer from 0 to 99'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_enable_true(self):
""" test syslog format enable=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_enable_false(self):
""" test syslog format logall=false """
syslog = dict(enable=False)
self.do_module_test(syslog, changed=False, state=None)
def test_syslog_ipproto_ipv4(self):
""" test syslog ipproto ipv4 """
syslog = dict(ipproto='ipv4')
command = "update log_settings syslog set ipproto='ipv4'"
self.do_module_test(syslog, command=command, state=None, changed=False)
def test_syslog_ipproto_ipv6(self):
""" test syslog ipproto ipv6 """
syslog = dict(ipproto='ipv6')
command = "update log_settings syslog set ipproto='ipv6'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_wan_ip(self):
""" test log_settings sourceip=wan """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='192.168.240.137')
command = "update log_settings syslog set enable=True, sourceip='wan', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_wan_descr(self):
""" test log_settings sourceip=wan """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='wan')
command = "update log_settings syslog set enable=True, sourceip='wan', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_lan(self):
""" test log_settings sourceip=lan """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='192.168.1.242')
command = "update log_settings syslog set enable=True, sourceip='lan', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_lo0(self):
""" test log_settings sourceip=lan """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='127.0.0.1')
command = "update log_settings syslog set enable=True, sourceip='lo0', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_descr(self):
""" test log_settings sourceip=lan """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='Localhost')
command = "update log_settings syslog set enable=True, sourceip='lo0', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_valid_empty(self):
""" test log_settings sourceip='' """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip=None)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_valid_vip_ip(self):
""" test log_settings sourceip=_vip5c0a4b6139b05 """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='10.255.2.254')
command = "update log_settings syslog set enable=True, sourceip='_vip5c0a4b6139b05', remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_sourceip_invalid_vip(self):
""" test log_settings sourceip=_vip5c0a4b6139b06 """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='_vip5c0a4b6139b05')
msg = "sourceip: Invalid address _vip5c0a4b6139b05!"
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_sourceip_invalid_opt4(self):
""" test log_settings sourceip=opt4 """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, sourceip='opt4')
msg = "sourceip: Invalid address opt4!"
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_remoteserver_hostname(self):
""" test log_settings remoteserver_hostname """
syslog = dict(enable=True, remoteserver='2001:0db8:cafe:affe:0000:0000:0000:0001', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='2001:0db8:cafe:affe:0000:0000:0000:0001', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_remoteserver_fqdn(self):
""" test log_settings remoteserver_fqdn """
syslog = dict(enable=True, remoteserver='logserver.example.com', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='logserver.example.com', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_remoteserver_fqdn_port(self):
""" test log_settings remoteserver_fqdn_port """
syslog = dict(enable=True, remoteserver='logserver.example.com:514', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='logserver.example.com:514', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_remoteserver_ipv6(self):
""" test log_settings remoteserver_ipv6 """
syslog = dict(enable=True, remoteserver='2001:0db8:cafe:affe:0000:0000:0000:0001', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='2001:0db8:cafe:affe:0000:0000:0000:0001', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_remoteserver_ipv6_port(self):
""" test log_settings remoteserver_ipv6 """
syslog = dict(enable=True, remoteserver='[2001:0db8:cafe:affe:0000:0000:0000:0001]:514', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='[2001:0db8:cafe:affe:0000:0000:0000:0001]:514', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_remoteserver_ipv4_invalid_port1(self):
""" test log_settings remoteserver_ipv4_invalid_port1 """
syslog = dict(enable=True, remoteserver='1234:0', logall=True)
msg = "Invalid port 0"
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_remoteserver_ipv4_invalid_port2(self):
""" test log_settings remoteserver_ipv4_invalid_port1 """
syslog = dict(enable=True, remoteserver='1234:65536', logall=True)
msg = "Invalid port 65536"
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_ipproto_invalid(self):
""" test syslog ipproto invalid """
syslog = dict(ipproto='ipv5')
msg = 'value of ipproto must be one of: ipv4, ipv6, got: ipv5'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logall_true(self):
""" test syslog format logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', logall=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logall_false(self):
""" test syslog format logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_system_true(self):
""" test syslog format system=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', system=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', system=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_system_false(self):
""" test syslog format system=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', system=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_system_invalid_with_logall(self):
""" test syslog format system=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, system=True)
msg = 'system = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_system_valid_with_logall(self):
""" test syslog format system=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, system=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', system=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilter_true(self):
""" test syslog format logfilter=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logfilter=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', filter=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilter_false(self):
""" test syslog format logfilter=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logfilter=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_logfilter_invalid_with_logall(self):
""" test syslog format logfilter=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, logfilter=True)
msg = 'logfilter = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_logfilter_valid_with_logall(self):
""" test syslog format logfilter=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, logfilter=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', filter=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_resolver_true(self):
""" test syslog format resolver=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', resolver=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', resolver=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_resolver_false(self):
""" test syslog format resolver=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', resolver=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_resolver_invalid_with_logall(self):
""" test syslog format resolver=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, resolver=True)
msg = 'resolver = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_resolver_valid_with_logall(self):
""" test syslog format resolver=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, resolver=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', resolver=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dhcp_true(self):
""" test syslog format dhcp=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', dhcp=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', dhcp=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dhcp_false(self):
""" test syslog format dhcp=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', dhcp=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dhcp_invalid_with_logall(self):
""" test syslog format dhcp=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, dhcp=True)
msg = 'dhcp = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_dhcp_valid_with_logall(self):
""" test syslog format dhcp=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, dhcp=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', dhcp=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ppp_true(self):
""" test syslog format ppp=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', ppp=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', ppp=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ppp_false(self):
""" test syslog format ppp=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', ppp=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ppp_invalid_with_logall(self):
""" test syslog format ppp=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, ppp=True)
msg = 'ppp = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_ppp_valid_with_logall(self):
""" test syslog format ppp=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, ppp=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', ppp=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_auth_true(self):
""" test syslog format auth=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', auth=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', auth=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_auth_false(self):
""" test syslog format auth=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', auth=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_auth_invalid_with_logall(self):
""" test syslog format auth=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, auth=True)
msg = 'auth = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_auth_valid_with_logall(self):
""" test syslog format auth=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, auth=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', auth=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_portalauth_true(self):
""" test syslog format portalauth=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', portalauth=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', portalauth=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_portalauth_false(self):
""" test syslog format portalauth=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', portalauth=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_portalauth_invalid_with_logall(self):
""" test syslog format portalauth=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, portalauth=True)
msg = 'portalauth = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_portalauth_valid_with_logall(self):
""" test syslog format portalauth=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, portalauth=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', portalauth=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_vpn_true(self):
""" test syslog format vpn=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', vpn=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', vpn=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_vpn_false(self):
""" test syslog format vpn=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', vpn=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_vpn_invalid_with_logall(self):
""" test syslog format vpn=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, vpn=True)
msg = 'vpn = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_vpn_valid_with_logall(self):
""" test syslog format vpn=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, vpn=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', vpn=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dpinger_true(self):
""" test syslog format dpinger=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', dpinger=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', dpinger=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dpinger_false(self):
""" test syslog format dpinger=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', dpinger=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_dpinger_invalid_with_logall(self):
""" test syslog format dpinger=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, dpinger=True)
msg = 'dpinger = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_dpinger_valid_with_logall(self):
""" test syslog format dpinger=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, dpinger=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', dpinger=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_routing_true(self):
""" test syslog format routing=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', routing=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', routing=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_routing_false(self):
""" test syslog format routing=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', routing=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_routing_invalid_with_logall(self):
""" test syslog format routing=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, routing=True)
msg = 'routing = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_routing_valid_with_logall(self):
""" test syslog format routing=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, routing=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', routing=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ntpd_true(self):
""" test syslog format ntpd=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', ntpd=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', ntpd=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ntpd_false(self):
""" test syslog format ntpd=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', ntpd=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_ntpd_invalid_with_logall(self):
""" test syslog format ntpd=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, ntpd=True)
msg = 'ntpd = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_ntpd_valid_with_logall(self):
""" test syslog format ntpd=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, ntpd=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', ntpd=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_hostapd_true(self):
""" test syslog format hostapd=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', hostapd=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', hostapd=True"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_hostapd_false(self):
""" test syslog format hostapd=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', hostapd=False)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4'"
self.do_module_test(syslog, command=command, state=None)
def test_syslog_hostapd_invalid_with_logall(self):
""" test syslog format hostapd=true, logall=true """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=True, hostapd=True)
msg = 'hostapd = True is invalid when logall is True'
self.do_module_test(syslog, msg=msg, state=None, failed=True)
def test_syslog_hostapd_valid_with_logall(self):
""" test syslog format hostapd=true, logall=false """
syslog = dict(enable=True, remoteserver='1.2.3.4', logall=False, hostapd=True)
command = "update log_settings syslog set enable=True, remoteserver='1.2.3.4', hostapd=True"
self.do_module_test(syslog, command=command, state=None)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_nat_outbound.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_nat_outbound
from ansible_collections.pfsensible.core.plugins.module_utils.nat_outbound import PFSenseNatOutboundModule
from .pfsense_module import TestPFSenseModule
from ipaddress import ip_address, IPv4Address
class TestPFSenseNatOutboundModule(TestPFSenseModule):
module = pfsense_nat_outbound
def __init__(self, *args, **kwargs):
super(TestPFSenseNatOutboundModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_nat_outbound.xml'
self.pfmodule = PFSenseNatOutboundModule
@staticmethod
def is_ipv4_address(address):
""" test if address is a valid ipv4 address """
try:
addr = ip_address(u'{0}'.format(address))
return isinstance(addr, IPv4Address)
except ValueError:
pass
return False
def parse_address(self, name, addr, field, invert=False):
""" return address parsed in dict """
parts = addr.split(':')
res = {}
port = None
if parts[0] == 'NET':
res[field] = parts[1]
if len(parts) > 2:
port = parts[2].replace('-', ':')
else:
if parts[0] == 'any':
if name == 'source':
res[field] = 'any'
else:
res['any'] = None
elif parts[0] == '(self)':
res[field] = '(self)'
elif parts[0] in ['lan', 'vpn', 'vt1', 'lan_100']:
res[field] = self.unalias_interface(parts[0])
else:
res[field] = parts[0]
if field in res and self.is_ipv4_address(res[field]) and res[field].find('/') == -1:
res[field] += '/32'
if len(parts) > 1:
port = parts[1].replace('-', ':')
if invert:
res['not'] = None
return (res, port)
@staticmethod
def reparse_network(value):
if value == '1.2.3.4/24':
return '1.2.3.0/24'
elif value == '2.3.4.5/24':
return '2.3.4.0/24'
return value
def check_addr(self, params, target_elt, addr, field, port, invert=False):
""" test the addresses definition """
(addr_dict, port_value) = self.parse_address(addr, params[addr], field, invert=invert)
addr_elt = self.assert_find_xml_elt(target_elt, addr)
for key, value in addr_dict.items():
self.check_value_equal(addr_elt, key, self.reparse_network(value))
# self.assert_xml_elt_equal(addr_elt, key, value)
for item_elt in addr_elt:
self.assertTrue(item_elt.tag in addr_dict)
self.check_value_equal(target_elt, port, port_value, port == 'sourceport')
def check_target_addr(self, params, target_elt):
""" test the addresses definition """
if 'address' not in params or params['address'] == '':
self.assert_xml_elt_is_none_or_empty(target_elt, 'target')
self.assert_xml_elt_is_none_or_empty(target_elt, 'target_subnet')
self.assert_not_find_xml_elt(target_elt, 'natport')
elif params['address'] == '4.5.6.7:888-999':
self.assert_xml_elt_equal(target_elt, 'target', '4.5.6.7')
self.assert_xml_elt_equal(target_elt, 'target_subnet', '32')
self.assert_xml_elt_equal(target_elt, 'natport', '888:999')
elif params['address'] == '4.5.6.7/24:888-999':
self.assert_xml_elt_equal(target_elt, 'target', '4.5.6.0')
self.assert_xml_elt_equal(target_elt, 'target_subnet', '24')
self.assert_xml_elt_equal(target_elt, 'natport', '888:999')
@staticmethod
def md5(value):
if value == 'acme_key':
return '0xfdc529cc680c4e8c74efbf114ec436fb'
return value
def check_target_elt(self, obj, target_elt, target_idx=-1):
""" test the xml definition """
self.check_addr(obj, target_elt, 'source', 'network', 'sourceport')
self.check_addr(obj, target_elt, 'destination', 'network', 'dstport', invert=obj.get('invert'))
self.check_target_addr(obj, target_elt)
self.check_param_equal_or_not_find(obj, target_elt, 'disabled')
self.check_param_equal_or_not_find(obj, target_elt, 'nonat')
self.check_param_equal_or_not_find(obj, target_elt, 'staticnatport')
self.check_param_equal_or_not_find(obj, target_elt, 'nosync')
self.check_param_equal_or_not_find(obj, target_elt, 'nonat')
self.check_value_equal(target_elt, 'interface', self.unalias_interface(obj['interface']))
self.check_param_equal(obj, target_elt, 'ipprotocol', not_find_val='inet46')
self.check_param_equal(obj, target_elt, 'protocol', not_find_val='any')
self.check_param_equal(obj, target_elt, 'poolopts')
self.check_value_equal(target_elt, 'source_hash_key', self.md5(obj.get('source_hash_key')))
self.check_rule_idx(obj, target_idx)
def check_rule_idx(self, params, target_idx):
""" test the xml position """
nat_elt = self.assert_find_xml_elt(self.xml_result, 'nat')
rules_elt = self.assert_find_xml_elt(nat_elt, 'outbound')
idx = -1
for rule_elt in rules_elt:
if rule_elt.tag != 'rule':
continue
idx += 1
descr_elt = rule_elt.find('descr')
self.assertIsNotNone(descr_elt)
self.assertIsNotNone(descr_elt.text)
if descr_elt.text == params['descr']:
self.assertEqual(idx, target_idx)
return
self.fail('rule not found ' + str(idx))
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
nat_elt = self.assert_find_xml_elt(self.xml_result, 'nat')
outbount_elt = self.assert_find_xml_elt(nat_elt, 'outbound')
for item in outbount_elt:
descr_elt = item.find('descr')
if descr_elt is not None and descr_elt.text == obj['descr']:
return item
return None
##############
# tests
#
def test_nat_outbound_create(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_aliases(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='srv_admin:port_ssh', destination='srv_admin:port_ssh', address='srv_admin:port_ssh')
command = (
"create nat_outbound 'https-source-rewriting', interface='lan', source='srv_admin:port_ssh', "
"destination='srv_admin:port_ssh', address='srv_admin:port_ssh'"
)
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_address(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', address='4.5.6.7:888-999')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', address='4.5.6.7/32:888-999'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_address_net(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', address='4.5.6.7/24:888-999')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', address='4.5.6.0/24:888-999'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_networks(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='2.3.4.5/24:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='2.3.4.5/24:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_ipprotocol(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', ipprotocol='inet', source='any', destination='1.2.3.4:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', ipprotocol='inet', source='any', destination='1.2.3.4:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_protocol(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', protocol='tcp', source='any', destination='1.2.3.4:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', protocol='tcp', source='any', destination='1.2.3.4:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_networks_invert(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='2.3.4.5/24:443', invert=True)
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='2.3.4.5/24:443', invert=True"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_interface_destination_network(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='NET:lan:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='1.2.3.4/24', destination='NET:lan:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_interface_source_network(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='NET:lan', destination='2.3.4.5/24:443')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='NET:lan', destination='2.3.4.5/24:443'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_top(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', after='top')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', after='top'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_outbound_create_after(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', after='one rule')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', after='one rule'"
self.do_module_test(obj, command=command, target_idx=1)
def test_nat_outbound_create_before(self):
""" test """
obj = dict(descr='https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', before='another rule')
command = "create nat_outbound 'https-source-rewriting', interface='lan', source='any', destination='1.2.3.4:443', before='another rule'"
self.do_module_test(obj, command=command, target_idx=1)
def test_nat_outbound_create_with_sourcehashkey(self):
""" test """
obj = dict(descr='valid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='0x12345678901234567890123456789012')
command = "create nat_outbound 'valid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='0x12345678901234567890123456789012'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_create_with_sourcehashkey_str(self):
""" test """
obj = dict(descr='valid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='acme_key')
command = "create nat_outbound 'valid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='0xfdc529cc680c4e8c74efbf114ec436fb'"
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_outbound_update_noop(self):
""" test """
obj = dict(descr='one rule', interface='wan', source='any', destination='any')
self.do_module_test(obj, target_idx=0, changed=False)
def test_nat_outbound_update_bottom(self):
""" test """
obj = dict(descr='one rule', interface='wan', source='any', destination='any', before='bottom')
command = "update nat_outbound 'one rule' set before='bottom'"
self.do_module_test(obj, command=command, target_idx=2)
def test_nat_outbound_update_top(self):
""" test """
obj = dict(descr='another rule', interface='wan', source='any', destination='any', after='top')
command = "update nat_outbound 'another rule' set after='top'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_outbound_update_source(self):
""" test """
obj = dict(descr='one rule', interface='wan', source='(self):123', destination='any')
command = "update nat_outbound 'one rule' set source='(self):123'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_outbound_update_destination(self):
""" test """
obj = dict(descr='one rule', interface='wan', source='any', destination='1.2.3.4:555')
command = "update nat_outbound 'one rule' set destination='1.2.3.4/32:555'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_outbound_update_interface(self):
""" test """
obj = dict(descr='one rule', interface='lan_100', source='any', destination='any')
command = "update nat_outbound 'one rule' set interface='lan_100'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_outbound_delete(self):
""" test """
obj = dict(descr='one rule')
command = "delete nat_outbound 'one rule'"
self.do_module_test(obj, command=command, delete=True)
def test_nat_outbound_invalid_sourcehashkey_hex(self):
""" test """
obj = dict(descr='invalid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='0xg2345678901234567890123456789012')
msg = 'Incorrect format for source-hash key, "0x" must be followed by exactly 32 hexadecimal characters.'
self.do_module_test(obj, msg=msg, failed=True)
def test_nat_outbound_invalid_sourcehashkey_len(self):
""" test """
obj = dict(descr='invalid', interface='lan', source='any', destination='1.2.3.4:443', source_hash_key='0x1234567890123456789012345678901')
msg = 'Incorrect format for source-hash key, "0x" must be followed by exactly 32 hexadecimal characters.'
self.do_module_test(obj, msg=msg, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_nat_port_forward.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_nat_port_forward
from ansible_collections.pfsensible.core.plugins.module_utils.nat_port_forward import PFSenseNatPortForwardModule
from .pfsense_module import TestPFSenseModule
from .test_pfsense_rule import TestPFSenseRuleModule
class TestPFSenseNatPortForwardModule(TestPFSenseModule):
module = pfsense_nat_port_forward
def __init__(self, *args, **kwargs):
super(TestPFSenseNatPortForwardModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_nat_port_forward_config.xml'
self.pfmodule = PFSenseNatPortForwardModule
def check_target_addr(self, params, target_elt):
""" test the addresses definition """
if params['target'] == '2.3.4.5:443':
self.assert_xml_elt_equal(target_elt, 'target', '2.3.4.5')
self.assert_xml_elt_equal(target_elt, 'local-port', '443')
def get_associated_rule_elt(self, params, ruleid):
""" check the associated rule """
filters = dict()
filters['interface'] = self.unalias_interface(params['interface'])
filters['associated-rule-id'] = ruleid
return self.assert_has_xml_tag('filter', filters)
def check_target_elt(self, obj, target_elt, target_idx=-1):
""" test the xml definition """
rules_tester = TestPFSenseRuleModule()
rules_tester.check_rule_elt_addr(obj, target_elt, 'source')
# checking destination address and ports
rules_tester.check_rule_elt_addr(obj, target_elt, 'destination')
self.check_target_addr(obj, target_elt)
self.check_param_equal_or_not_find(obj, target_elt, 'disabled')
self.check_param_equal_or_not_find(obj, target_elt, 'nordr')
self.check_param_equal_or_not_find(obj, target_elt, 'nosync')
self.check_param_equal_or_not_find(obj, target_elt, 'natreflection', not_find_val='system-default')
self.check_value_equal(target_elt, 'interface', self.unalias_interface(obj['interface']))
self.check_param_equal(obj, target_elt, 'ipprotocol', 'inet')
self.check_param_equal(obj, target_elt, 'protocol', 'tcp')
self.check_param_equal_or_present(obj, target_elt, 'local-port')
self.check_rule_idx(obj, target_idx)
if 'associated_rule' not in obj:
obj['associated_rule'] = 'associated'
if obj['associated_rule'] == 'none' or obj['associated_rule'] == 'unassociated':
self.assert_xml_elt_is_none_or_empty(target_elt, 'associated-rule-id')
elif obj['associated_rule'] == 'pass':
self.check_value_equal(target_elt, 'associated-rule-id', 'pass')
else:
ruleid_elt = self.assert_find_xml_elt(target_elt, 'associated-rule-id')
self.assertTrue(ruleid_elt.text.startswith('nat_'))
rule_elt = self.get_associated_rule_elt(obj, ruleid_elt.text)
self.assertEqual(rule_elt.find('descr').text, 'NAT ' + obj['descr'])
def check_rule_idx(self, params, target_idx):
""" test the xml position """
rules_elt = self.assert_find_xml_elt(self.xml_result, 'nat')
idx = -1
for rule_elt in rules_elt:
if rule_elt.tag != 'rule':
continue
idx += 1
descr_elt = rule_elt.find('descr')
self.assertIsNotNone(descr_elt)
self.assertIsNotNone(descr_elt.text)
if descr_elt.text == params['descr']:
self.assertEqual(idx, target_idx)
return
self.fail('rule not found ' + str(idx))
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
rules_elt = self.assert_find_xml_elt(self.xml_result, 'nat')
for item in rules_elt:
descr_elt = item.find('descr')
if descr_elt is not None and descr_elt.text == obj['descr']:
return item
return None
##############
# tests
#
def test_nat_port_forward_create(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='pass')
command = (
"create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='pass'"
)
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_port_forward_create_range(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:9000-10000', destination='1.2.3.4:9000-10000', target='2.3.4.5:9000', associated_rule='none')
command = (
"create nat_port_forward 'test_pf', interface='lan', source='any:9000-10000', destination='1.2.3.4:9000-10000', "
"target='2.3.4.5:9000', associated_rule='none'"
)
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_port_forward_create_associated(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='associated')
cmd1 = "create rule 'NAT test_pf' on 'lan', source='any:443', destination='2.3.4.5:443', protocol='tcp'"
cmd2 = "create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443'"
self.do_module_test(obj, command=[cmd1, cmd2], target_idx=3)
def test_nat_port_forward_create_unassociated(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='unassociated')
cmd1 = "create rule 'NAT test_pf' on 'lan', source='any:443', destination='2.3.4.5:443', protocol='tcp'"
cmd2 = (
"create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', "
"associated_rule='unassociated'"
)
self.do_module_test(obj, command=[cmd1, cmd2], target_idx=3)
def test_nat_port_forward_create_top(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='pass', after='top')
command = (
"create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', "
"associated_rule='pass', after='top'"
)
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_port_forward_create_after(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='pass', after='one')
command = (
"create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', "
"associated_rule='pass', after='one'"
)
self.do_module_test(obj, command=command, target_idx=1)
def test_nat_port_forward_create_before(self):
""" test """
obj = dict(descr='test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', associated_rule='pass', before='two')
command = (
"create nat_port_forward 'test_pf', interface='lan', source='any:443', destination='1.2.3.4:443', target='2.3.4.5:443', "
"associated_rule='pass', before='two'"
)
self.do_module_test(obj, command=command, target_idx=1)
def test_nat_port_forward_create_icmp(self):
""" test """
obj = dict(descr='test_pf', interface='wan', protocol='icmp', source='any', destination='1.2.3.4', target='2.3.4.5', associated_rule='associated')
command = [
"create rule 'NAT test_pf' on 'wan', source='any', destination='2.3.4.5', protocol='icmp'",
"create nat_port_forward 'test_pf', interface='wan', protocol='icmp', source='any', destination='1.2.3.4', target='2.3.4.5'"
]
self.do_module_test(obj, command=command, target_idx=3)
def test_nat_port_forward_create_tcp_fail_no_port(self):
""" test """
obj = dict(descr='test_pf', interface='wan', source='any', destination='1.2.3.4', target='2.3.4.5', associated_rule='associated')
msg = 'Must specify a target port with protocol "tcp".'
self.do_module_test(obj, failed=True, msg=msg)
def test_nat_port_forward_create_icmp_fail_port(self):
""" test """
obj = dict(descr='test_pf', interface='wan', protocol='icmp', source='any', destination='1.2.3.4', target='2.3.4.5:443', associated_rule='associated')
msg = 'Cannot specify a target port with protocol "icmp".'
self.do_module_test(obj, failed=True, msg=msg)
def test_nat_port_forward_update_noop(self):
""" test """
obj = dict(descr='one', interface='wan', source='any', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='none')
self.do_module_test(obj, target_idx=0, changed=False)
def test_nat_port_forward_update_bottom(self):
""" test """
obj = dict(descr='one', interface='wan', source='any', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='none', before='bottom')
command = "update nat_port_forward 'one' set before='bottom'"
self.do_module_test(obj, command=command, target_idx=2)
def test_nat_port_forward_update_top(self):
""" test """
obj = dict(descr='last', interface='wan', source='any', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='associated', after='top')
command = "update nat_port_forward 'last' set after='top'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_port_forward_update_source(self):
""" test """
obj = dict(descr='one', interface='wan', source='1.2.3.4', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='none')
command = "update nat_port_forward 'one' set source='1.2.3.4'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_port_forward_update_destination(self):
""" test """
obj = dict(descr='one', interface='wan', source='any', destination='1.2.3.4:22022', target='10.255.1.20:22', associated_rule='none')
command = "update nat_port_forward 'one' set destination='1.2.3.4:22022'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_port_forward_update_interface(self):
""" test """
obj = dict(descr='one', interface='vpn', source='any', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='none')
command = "update nat_port_forward 'one' set interface='vpn'"
self.do_module_test(obj, command=command, target_idx=0)
def test_nat_port_forward_update_interface_associated(self):
""" test """
obj = dict(descr='last', interface='lan_100', source='any', destination='IP:wan:22022', target='10.255.1.20:22', associated_rule='associated')
cmd1 = "delete rule 'NAT last' on 'wan'"
cmd2 = "create rule 'NAT last' on 'lan_100', source='any', destination='10.255.1.20:22', protocol='tcp'"
cmd3 = "update nat_port_forward 'last' set interface='lan_100'"
self.do_module_test(obj, command=[cmd1, cmd2, cmd3], target_idx=2)
def test_nat_port_forward_delete(self):
""" test """
obj = dict(descr='one')
command = "delete nat_port_forward 'one'"
self.do_module_test(obj, command=command, delete=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_openvpn_override.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_openvpn_override
from .pfsense_module import TestPFSenseModule
class TestPFSenseOpenVPNOverrideModule(TestPFSenseModule):
module = pfsense_openvpn_override
def __init__(self, *args, **kwargs):
super(TestPFSenseOpenVPNOverrideModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_openvpn_config.xml'
self.pfmodule = pfsense_openvpn_override.PFSenseOpenVPNOverrideModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.xml_result.getroot().find('openvpn')
result = root_elt.findall("openvpn-csc[common_name='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple OpenVPN overrides for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
self.check_param_equal(obj, target_elt, 'name', xml_field='common_name')
self.check_param_bool(obj, target_elt, 'disable')
self.check_param_bool(obj, target_elt, 'block', default=False, value_true='yes')
self.check_param_equal(obj, target_elt, 'tunnel_network')
self.check_param_equal(obj, target_elt, 'tunnel_networkv6')
self.check_param_equal(obj, target_elt, 'local_network')
self.check_param_equal(obj, target_elt, 'local_networkv6')
self.check_param_equal(obj, target_elt, 'remote_network')
self.check_param_equal(obj, target_elt, 'remote_networkv6')
self.check_param_bool(obj, target_elt, 'gwredir', default=False, value_true='yes')
self.check_param_bool(obj, target_elt, 'push_reset', default=False, value_true='yes')
##############
# tests
#
def test_openvpn_override_create(self):
""" test creation of a new OpenVPN override """
obj = dict(name='vpnuser1', block=True)
self.do_module_test(obj, command="create openvpn_override 'vpnuser1', common_name='vpnuser1'")
def test_openvpn_override_delete(self):
""" test deletion of a OpenVPN override """
obj = dict(name='delvpnuser')
self.do_module_test(obj, command="delete openvpn_override 'delvpnuser'", delete=True)
def test_openvpn_override_update_noop(self):
""" test not updating a OpenVPN override """
obj = dict(name='delvpnuser', gwredir=True, server_list=1, custom_options='ifconfig-push 10.8.0.1 255.255.255.0')
self.do_module_test(obj, changed=False)
def test_openvpn_override_update_network(self):
""" test updating network of a OpenVPN override """
obj = dict(name='delvpnuser', gwredir=True, server_list=1, custom_options='ifconfig-push 10.8.0.1 255.255.255.0', tunnel_network='10.10.10.10/24')
self.do_module_test(obj, command="update openvpn_override 'delvpnuser' set ")
##############
# misc
#
def test_create_openvpn_override_invalid_network(self):
""" test creation of a new OpenVPN override with invalid network """
obj = dict(name='delvpnuser', remote_network='30.4.3.3/24')
self.do_module_test(obj, failed=True, msg='A valid IPv4 network must be specified for remote_network.')
def test_delete_nonexistent_openvpn_override(self):
""" test deletion of an nonexistent OpenVPN override """
obj = dict(name='novpnuser')
self.do_module_test(obj, commmand=None, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_openvpn_server.py
================================================
# Copyright: (c) 2022, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import base64
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_openvpn_server
from .pfsense_module import TestPFSenseModule
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
CERTIFICATE = (
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlFQ0RDQ0F2Q2dBd0lCQWdJSUZqRk9oczFuTXpRd0RRWUpLb1pJaHZjTkFRRUxCUUF3WERFVE1CRUdBMVVFDQpBeE1LYjNCbGJuWndiaTFqWVRF"
"TE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJBZ1RDRU52Ykc5eVlXUnZNUkF3DQpEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1STXdFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNQjRYRFRJeU1ESXhOREExDQpN"
"RGd6TVZvWERUTXlNREl4TWpBMU1EZ3pNVm93WERFVE1CRUdBMVVFQXhNS2IzQmxiblp3YmkxallURUxNQWtHDQpBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJBZ1RDRU52Ykc5eVlXUnZNUkF3RGdZRFZRUUhF"
"d2RDYjNWc1pHVnlNUk13DQpFUVlEVlFRS0V3cHdabE5sYm5OcFlteGxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDDQpBUUVBbXN2aUpNRTFFVGVkNGZPdGJrSHBGM2Q5ZU0r"
"NjQwOFhQbmE4dEpHZEJxM1VBQ3hFem9hQktSdDJ5MWN0DQo2elFEZTVGRjRBQXZ0VjF1Y1pwc2w1bzREUy9JR1NibjZkM1lNaytqOGpBUTNFbXpSOEdPb2huZ2YxUTlBWEM2DQpvaDRyQlA1c1g0WTh1"
"WThrSjNZclg1cVRwRlk1S0hMVTFBb1BleVE3eXlNWkhMb2t0OW5jK0ZGWnd3VTdSQ0dTDQpjTkxaaVZ4Q1FRSzVwOGs5bUE4Ymd4bHFZa2YwbUF5Qk53OU1BZlBVY1VrcUY2UDBnV1BIbElySFovdWhn"
"N2RVDQorMjJhb2NLVUVOaXY5bXFhK0I2Y1VnTFRGVDZzMFZTRXNYL2RBZWg2MllMZ2ZtWEpnNmROSFFJK01nNlNrZWxwDQprOVZSVGVqaUVUSUVWOEpnZHYyTjdSU201d0lEQVFBQm80SE5NSUhLTUIw"
"R0ExVWREZ1FXQkJSazVvQS8wcWEyDQpLUHdnb1hKcUtNdCtBb0tKZ1RDQmpRWURWUjBqQklHRk1JR0NnQlJrNW9BLzBxYTJLUHdnb1hKcUtNdCtBb0tKDQpnYUZncEY0d1hERVRNQkVHQTFVRUF4TUti"
"M0JsYm5ad2JpMWpZVEVMTUFrR0ExVUVCaE1DVlZNeEVUQVBCZ05WDQpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJNd0VRWURWUVFLRXdwd1psTmxibk5wDQpZbXhsZ2dn"
"V01VNkd6V2N6TkRBTUJnTlZIUk1FQlRBREFRSC9NQXNHQTFVZER3UUVBd0lCQmpBTkJna3Foa2lHDQo5dzBCQVFzRkFBT0NBUUVBVUg5S0NkbUpkb0FKbFUwd0JKSFl4akxyS2xsUFk2T05ienI1SmJo"
"Q002OUh4eFlODQpCa2lpbXd1N09mRmFGZkZDT25NSjhvcStKVGxjMG9vREoxM2xCdHRONkdybnZrUTNQMXdZYkNFTmJuaWxPYVVCDQpUSXJpSHl0TkRRYW91TmEvS1dzN0ZhdW9iY3RCbDF3OWF0b0ha"
"c041b2VoVDNyQVR2MUNDQXRqcGFUSklmSlIzDQowSVFPWWtlNG9ZNkRrSXdIcDJ2UFBtb29HZ0l0YlR3M1UrRTQxWVplN3FDbUUvN3pMVFNaa0lNMmx4NnpENDZqDQpEZjRyZ044TVVMNnhpd09Mbzly"
"QUp5ckRNM2JEeTJ1QjY0QkVzRFFMa2huUE92ZWtETjQ1NnV6TmpYS0E3VnE4DQpoMS9nekRaSURpK1dYQ1lBY2JnTGhaVkJxdG42MnVtRnBNUkl1dz09DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t"
"DQo=")
TLSKEY = (
"IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KNjFiY2E4MDk0ZmM4YjA3ZTZlMjE3NzRmNTI0YTIyOWYKNGMzZGZhMDVjZ"
"Tc2ODVlN2NkNDc1N2I0OGM3ZmMzZDcKYzQzMjhjYzBmMWQ4Yjc2OTk2MjVjNzAwYmVkNzNhNWYKY2RjMjYzMTY2YThlMzVmYTk4NGU0OWVkZDg5MDNkZmMKMDc1ZTQyY2ZlOTM5NzUwYzhmMjc1YTY3MT"
"kzMGRmMzEKMDY2Mzk1MjM2ZWRkYWQ3NDc3YmVjZjJmNDgyNzBlMjUKODM1N2JlMGE1MGUzY2Y0ZjllZTEyZTdkMmM4YTY2YzEKODUwNjBlODM5ZWUyMzdjNTZkZmUzNjA4NjU0NDhhYzgKNjhmM2JhYWQ"
"4ODNjNDU3NTdlZTVjMWQ4ZDk5ZjM4ZjcKZGNiZDAwZmI3Nzc2ZWFlYjQ1ZmQwOTBjNGNlYTNmMGMKMzgzNDE0ZTJlYmU4MWNiZGIxZmNlN2M2YmFhMDlkMWYKMTU4OGUzNGRkYzUxY2NjOTE5NDNjNTFh"
"OTI2OTE3NWQKNzZiZjdhOWI1ZmM3NDAyNmE3MTVkNGVmODVkYzY2Y2UKMWE5MWQwNjNhODIwZDY4MTc0ODlmYjJkZjNmYzY2MmMKMmU2OWZiMzNiMzM5MjdjYjUyNThkZDQ4M2NkNDE0Y2QKMDJhZWE3Z"
"jA3MmNhZmEwOTY5Yjg5NWVjYzNiYmExNGQKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K")
class TestPFSenseOpenVPNServerModule(TestPFSenseModule):
module = pfsense_openvpn_server
def __init__(self, *args, **kwargs):
super(TestPFSenseOpenVPNServerModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_openvpn_config.xml'
self.pfmodule = pfsense_openvpn_server.PFSenseOpenVPNServerModule
def setUp(self):
""" mocking up """
super(TestPFSenseOpenVPNServerModule, self).setUp()
self.mock_run_command = patch('ansible.module_utils.basic.AnsibleModule.run_command')
self.run_command = self.mock_run_command.start()
self.run_command.return_value = (0, base64.b64decode(TLSKEY.encode()).decode(), '')
self.mock_php = patch('ansible_collections.pfsensible.core.plugins.module_utils.pfsense.PFSenseModule.php')
self.php = self.mock_php.start()
self.php.return_value = {'SHA256': 'SHA256 (256-bit)'}
def tearDown(self):
""" mocking down """
super(TestPFSenseOpenVPNServerModule, self).tearDown()
self.run_command.stop()
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.xml_result.getroot().find('openvpn')
result = root_elt.findall("openvpn-server[description='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple OpenVPN servers for name {0}.'.format(obj['name']))
else:
return None
@staticmethod
def caref(descr):
""" return refid for ca """
if descr == 'OpenVPN CA':
return '6209e3cef1e81'
return ''
@staticmethod
def crlref(descr):
""" return refid for crl """
if descr == 'OpenVPN CRL':
return '6209e3cef1e81'
return None
@staticmethod
def certref(descr):
""" return refid for cert """
if descr == 'OpenVPN CERT':
return '6209e3cef1e81'
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
# Use "generated" key
if 'shared_key' in obj and obj['shared_key'] == 'generate':
obj['shared_key'] = TLSKEY
if 'tls' in obj and obj['tls'] == 'generate':
obj['tls'] = TLSKEY
obj['tls_type'] = 'auth'
self.check_param_equal(obj, target_elt, 'name', xml_field='description')
self.check_param_equal(obj, target_elt, 'custom_options')
self.check_param_equal(obj, target_elt, 'mode', default='ptp_tls')
if obj['mode'] == 'server_tls_user':
self.check_list_param_equal(obj, target_elt, 'authmode')
if obj['mode'] == 'p2p_shared_key':
self.check_param_equal(obj, target_elt, 'shared_key')
self.check_param_equal(obj, target_elt, 'dev_mode', default='tun')
self.check_param_bool(obj, target_elt, 'disabled')
self.check_param_equal(obj, target_elt, 'interface', default='wan')
self.check_param_equal(obj, target_elt, 'local_port', default=1194)
self.check_param_equal(obj, target_elt, 'protocol', default='UDP4')
if 'tls' in obj['mode']:
self.check_param_equal(obj, target_elt, 'tls')
self.check_param_equal(obj, target_elt, 'tls')
self.check_param_equal(obj, target_elt, 'tls_type')
self.assert_xml_elt_equal(target_elt, 'caref', self.caref(obj['ca']))
if 'crl' in obj:
self.assert_xml_elt_equal(target_elt, 'crlref', self.crlref(obj['crl']))
if 'cert' in obj:
self.assert_xml_elt_equal(target_elt, 'certref', self.certref(obj['cert']))
self.check_param_equal(obj, target_elt, 'cert_depth', default=1)
else:
self.assert_not_find_xml_elt('tls')
self.assert_not_find_xml_elt('tls_type')
self.check_param_bool(obj, target_elt, 'strictusercn')
self.check_param_equal(obj, target_elt, 'dh_length', default=2048)
self.check_param_equal(obj, target_elt, 'ecdh_curve', default='none')
self.check_param_equal(obj, target_elt, 'data_ciphers_fallback', default='AES-256-CBC')
self.check_param_equal(obj, target_elt, 'data_ciphers', default='AES-256-GCM,AES-128-GCM,CHACHA20-POLY1305')
self.check_param_equal(obj, target_elt, 'digest', default='SHA256')
self.check_param_equal(obj, target_elt, 'ecdh_curve', default='none')
self.check_param_equal(obj, target_elt, 'allow_compression', default='no')
self.check_param_equal(obj, target_elt, 'compression', default=None)
self.check_param_bool(obj, target_elt, 'compression_push', default=False, value_true='yes')
self.check_param_equal(obj, target_elt, 'ecdh_curve', default='none')
self.check_param_equal(obj, target_elt, 'tunnel_network')
self.check_param_equal(obj, target_elt, 'tunnel_networkv6')
self.check_param_equal(obj, target_elt, 'local_network')
self.check_param_equal(obj, target_elt, 'local_networkv6')
self.check_param_equal(obj, target_elt, 'remote_network')
self.check_param_equal(obj, target_elt, 'remote_networkv6')
self.check_param_bool(obj, target_elt, 'gwredir', default=False, value_true='yes')
self.check_param_bool(obj, target_elt, 'gwredir6', default=False, value_true='yes')
self.check_param_equal(obj, target_elt, 'maxclients')
##############
# tests
#
def test_openvpn_server_create(self):
""" test creation of a new OpenVPN server """
obj = dict(name='ovpns3', mode='p2p_tls', ca='OpenVPN CA', local_port=1196)
self.do_module_test(obj, command="create openvpn_server 'ovpns3', description='ovpns3'")
def test_openvpn_server_create_generate(self):
""" test creation of a new OpenVPN server """
obj = dict(name='ovpns3', mode='p2p_tls', ca='OpenVPN CA', local_port=1196, tls='generate')
self.do_module_test(obj, command="create openvpn_server 'ovpns3', description='ovpns3'")
def test_openvpn_server_delete(self):
""" test deletion of a OpenVPN server """
obj = dict(name='ovpns2')
self.do_module_test(obj, command="delete openvpn_server 'ovpns2'", delete=True)
def test_openvpn_server_update_noop(self):
""" test not updating a OpenVPN server """
obj = dict(name='ovpns2', mode='p2p_tls', ca='OpenVPN CA', local_port=1195, tls=TLSKEY, tls_type='auth')
self.do_module_test(obj, changed=False)
def test_openvpn_server_update_network(self):
""" test updating network of a OpenVPN server """
obj = dict(name='ovpns2', mode='p2p_tls', ca='OpenVPN CA', local_port=1195, tls=TLSKEY, tls_type='auth', tunnel_network='10.10.10.10/24')
self.do_module_test(obj, command="update openvpn_server 'ovpns2' set ")
##############
# misc
#
def test_create_openvpn_server_duplicate_port(self):
""" test creation of a new OpenVPN server with duplicate port """
obj = dict(name='ovpns3', mode='p2p_tls', ca='OpenVPN CA')
self.do_module_test(obj, failed=True, msg='The specified local_port (1194) is in use by vpn ID 1')
def test_create_openvpn_server_invalid_certificate(self):
""" test creation of a new OpenVPN server with invalid certificate """
obj = dict(name='ovpns2', mode='p2p_tls', ca='OpenVPN CA', cert='blah')
self.do_module_test(obj, failed=True, msg='blah is not a valid certificate')
def test_delete_nonexistent_openvpn_server(self):
""" test deletion of an nonexistent OpenVPN server """
obj = dict(name='novpn')
self.do_module_test(obj, commmand=None, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_route.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_route
from ansible_collections.pfsensible.core.plugins.module_utils.route import PFSenseRouteModule
from .pfsense_module import TestPFSenseModule
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
class TestPFSenseRouteModule(TestPFSenseModule):
module = pfsense_route
def __init__(self, *args, **kwargs):
super(TestPFSenseRouteModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_route_config.xml'
self.pfmodule = PFSenseRouteModule
def setUp(self):
""" mocking up """
super(TestPFSenseRouteModule, self).setUp()
self.mock_run_command = patch('ansible.module_utils.basic.AnsibleModule.run_command')
self.run_command = self.mock_run_command.start()
self.run_command.return_value = (0, '', '')
def tearDown(self):
""" mocking down """
super(TestPFSenseRouteModule, self).tearDown()
self.run_command.stop()
def check_target_elt(self, obj, target_elt):
""" test the xml definition """
self.check_param_equal_or_not_find(obj, target_elt, 'disabled')
self.check_param_equal(obj, target_elt, 'gateway')
self.check_param_equal(obj, target_elt, 'network')
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
root_elt = self.assert_find_xml_elt(self.xml_result, 'staticroutes')
for item in root_elt:
name_elt = item.find('descr')
if name_elt is not None and name_elt.text == obj['descr']:
return item
return None
##############
# tests
#
def test_route_create(self):
""" test """
obj = dict(descr='test_route', network='1.2.3.4/24', gateway='GW_LAN')
command = "create route 'test_route', network='1.2.3.4/24', gateway='GW_LAN'"
self.do_module_test(obj, command=command)
def test_route_create_invalid_gw(self):
""" test """
obj = dict(descr='test_route', network='1.2.3.4/24', gateway='GW_INVALID')
msg = "The gateway GW_INVALID does not exist"
self.do_module_test(obj, msg=msg, failed=True)
def test_route_create_invalid_ip(self):
""" test """
obj = dict(descr='test_route', network='2001::1', gateway='GW_LAN')
msg = 'The gateway "192.168.1.1" is a different Address Family than network "2001::1".'
self.do_module_test(obj, msg=msg, failed=True)
def test_route_create_invalid_ip2(self):
""" test """
obj = dict(descr='test_route', network='1.2.3.4', gateway='GW_LAN_V6')
msg = 'The gateway "2002::1" is a different Address Family than network "1.2.3.4".'
self.do_module_test(obj, msg=msg, failed=True)
def test_route_create_invalid_alias(self):
""" test """
obj = dict(descr='test_route', network='invalid_alias', gateway='GW_LAN')
msg = 'A valid IPv4 or IPv6 destination network or alias must be specified.'
self.do_module_test(obj, msg=msg, failed=True)
def test_route_update_noop(self):
""" test """
obj = dict(descr='GW_WAN route', network='10.3.0.0/16', gateway='GW_WAN')
self.do_module_test(obj, changed=False)
def test_route_update_network(self):
""" test """
obj = dict(descr='GW_WAN route', network='10.4.0.0/16', gateway='GW_WAN')
command = "update route 'GW_WAN route' set network='10.4.0.0/16'"
self.do_module_test(obj, command=command)
def test_route_update_gateway(self):
""" test """
obj = dict(descr='GW_WAN route', network='10.3.0.0/16', gateway='GW_LAN')
command = "update route 'GW_WAN route' set gateway='GW_LAN'"
self.do_module_test(obj, command=command)
def test_route_delete(self):
""" test """
obj = dict(descr='GW_WAN route')
command = "delete route 'GW_WAN route'"
self.do_module_test(obj, command=command, delete=True)
def test_route_delete_alias(self):
""" test """
obj = dict(descr='GW_WAN alias')
command = "delete route 'GW_WAN alias'"
self.do_module_test(obj, command=command, delete=True)
def test_route_create_dhcp(self):
""" test """
obj = dict(descr='test_route', network='1.2.3.4/24', gateway='VPN_DHCP')
command = "create route 'test_route', network='1.2.3.4/24', gateway='VPN_DHCP'"
self.do_module_test(obj, command=command)
def test_route_create_dhcp6(self):
""" test """
obj = dict(descr='test_route', network='2001::/56', gateway='VPN_DHCP6')
command = "create route 'test_route', network='2001::/56', gateway='VPN_DHCP6'"
self.do_module_test(obj, command=command)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_rule
from ansible_collections.pfsensible.core.plugins.module_utils.rule import PFSenseRuleModule
from ansible_collections.pfsensible.core.plugins.module_utils.pfsense import PFSenseModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseRuleModule(TestPFSenseModule):
module = pfsense_rule
def __init__(self, *args, **kwargs):
super(TestPFSenseRuleModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_rule_config.xml'
self.pfmodule = PFSenseRuleModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def parse_address(self, addr):
""" return address parsed in dict """
if PFSenseModule.is_ipv6_address(addr) or PFSenseModule.is_ipv6_network(addr):
parts = [addr]
else:
parts = addr.split(':')
res = {}
if parts[0][0] == '!':
res['not'] = None
parts[0] = parts[0][1:]
if parts[0] == 'any':
res['any'] = None
elif parts[0] == '(self)':
res['network'] = '(self)'
elif parts[0] == 'NET':
res['network'] = self.unalias_interface(parts[1])
del parts[1]
elif parts[0] == 'IP':
res['network'] = self.unalias_interface(parts[1]) + 'ip'
del parts[1]
elif parts[0] in ['lan', 'lan', 'vpn', 'vt1', 'lan_100']:
res['network'] = self.unalias_interface(parts[0])
else:
res['address'] = parts[0]
if len(parts) > 1:
res['port'] = parts[1]
return res
def check_rule_elt_addr(self, rule, rule_elt, addr):
""" test the addresses definition of rule """
addr_dict = self.parse_address(rule[addr])
addr_elt = self.assert_find_xml_elt(rule_elt, addr)
for key, value in addr_dict.items():
self.assert_xml_elt_equal(addr_elt, key, value)
if 'any' in addr_dict:
self.assert_not_find_xml_elt(addr_elt, 'address')
self.assert_not_find_xml_elt(addr_elt, 'network')
if 'network' in addr_dict:
self.assert_not_find_xml_elt(addr_elt, 'address')
self.assert_not_find_xml_elt(addr_elt, 'any')
if 'address' in addr_dict:
self.assert_not_find_xml_elt(addr_elt, 'network')
self.assert_not_find_xml_elt(addr_elt, 'any')
if 'not' not in addr_dict:
self.assert_not_find_xml_elt(addr_elt, 'not')
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
obj['interface'] = self.unalias_interface(obj['interface'])
if 'floating' in obj and obj['floating'] == 'yes':
return self.assert_has_xml_tag('filter', dict(descr=obj['name'], floating='yes'), absent=absent)
return self.assert_has_xml_tag('filter', dict(descr=obj['name'], interface=obj['interface']), absent=absent)
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
# checking source address and ports
self.check_rule_elt_addr(obj, target_elt, 'source')
# checking destination address and ports
self.check_rule_elt_addr(obj, target_elt, 'destination')
# checking log option
if 'log' in obj and obj['log'] == 'yes':
self.assert_xml_elt_is_none_or_empty(target_elt, 'log')
elif 'log' not in obj or obj['log'] == 'no':
self.assert_not_find_xml_elt(target_elt, 'log')
# checking action option
if 'action' in obj:
action = obj['action']
else:
action = 'pass'
self.assert_xml_elt_equal(target_elt, 'type', action)
# checking floating option
if 'floating' in obj and obj['floating'] == 'yes':
self.assert_xml_elt_equal(target_elt, 'floating', 'yes')
if 'quick' in obj and obj['quick'] == 'yes':
self.assert_xml_elt_equal(target_elt, 'quick', 'yes')
else:
self.assert_not_find_xml_elt(target_elt, 'quick')
elif 'floating' not in obj or obj['floating'] == 'no':
self.assert_not_find_xml_elt(target_elt, 'floating')
self.assert_not_find_xml_elt(target_elt, 'quick')
# checking direction option
self.check_param_equal_or_not_find(obj, target_elt, 'direction')
# checking default queue option
self.check_param_equal_or_not_find(obj, target_elt, 'queue', 'defaultqueue')
# checking acknowledge queue option
self.check_param_equal_or_not_find(obj, target_elt, 'ackqueue')
# limiters
self.check_param_equal_or_not_find(obj, target_elt, 'in_queue', 'dnpipe')
self.check_param_equal_or_not_find(obj, target_elt, 'out_queue', 'pdnpipe')
# schedule
self.check_param_equal_or_not_find(obj, target_elt, 'sched')
# checking ipprotocol option
if 'ipprotocol' in obj:
action = obj['ipprotocol']
else:
action = 'inet'
self.assert_xml_elt_equal(target_elt, 'ipprotocol', action)
# checking protocol option
if 'protocol' in obj and obj['protocol'] != 'any':
self.assert_xml_elt_equal(target_elt, 'protocol', obj['protocol'])
else:
self.assert_not_find_xml_elt(target_elt, 'protocol')
# checking tcpflags_any option
if 'tcpflags_any' in obj and obj['tcpflags_any'] == 'yes':
self.assert_xml_elt_is_none_or_empty(target_elt, 'tcpflags_any')
elif 'tcpflags_any' not in obj or obj['tcpflags_any'] == 'no':
self.assert_not_find_xml_elt(target_elt, 'tcpflags_any')
# checking statetype option
if 'statetype' in obj and obj['statetype'] != 'keep state':
statetype = obj['statetype']
else:
statetype = 'keep state'
self.assert_xml_elt_equal(target_elt, 'statetype', statetype)
# checking disabled option
if 'disabled' in obj and obj['disabled'] == 'yes':
self.assert_xml_elt_is_none_or_empty(target_elt, 'disabled')
elif 'disabled' not in obj or obj['disabled'] == 'no':
self.assert_not_find_xml_elt(target_elt, 'disabled')
# checking gateway option
if 'gateway' in obj and obj['gateway'] != 'default':
self.assert_xml_elt_equal(target_elt, 'gateway', obj['gateway'])
else:
self.assert_not_find_xml_elt(target_elt, 'gateway')
# checking tracker
if 'tracker' in obj:
self.assert_xml_elt_equal(target_elt, 'tracker', obj['tracker'])
# checking icmptype
if 'icmptype' in obj:
self.assert_xml_elt_equal(target_elt, 'icmptype', obj['icmptype'])
def check_rule_idx(self, rule, target_idx):
""" test the xml position of rule """
floating = 'floating' in rule and rule['floating'] == 'yes'
rule['interface'] = self.unalias_interface(rule['interface'])
rules_elt = self.assert_find_xml_elt(self.xml_result, 'filter')
idx = -1
for rule_elt in rules_elt:
interface_elt = rule_elt.find('interface')
floating_elt = rule_elt.find('floating')
floating_rule = floating_elt is not None and floating_elt.text == 'yes'
if floating and not floating_rule:
continue
if not floating:
if floating_rule or interface_elt is None or interface_elt.text is None or interface_elt.text != rule['interface']:
continue
idx += 1
descr_elt = rule_elt.find('descr')
self.assertIsNotNone(descr_elt)
self.assertIsNotNone(descr_elt.text)
if descr_elt.text == rule['name']:
self.assertEqual(idx, target_idx)
return
self.fail('rule not found ' + str(idx))
def check_separator_idx(self, interface, sep_name, expected_idx):
""" test the logical position of separator """
filter_elt = self.assert_find_xml_elt(self.xml_result, 'filter')
separator_elt = self.assert_find_xml_elt(filter_elt, 'separator')
iface_elt = self.assert_find_xml_elt(separator_elt, interface)
for separator in iface_elt:
text_elt = separator.find('text')
if text_elt is not None and text_elt.text == sep_name:
row_elt = self.assert_find_xml_elt(separator, 'row')
idx = int(row_elt.text.replace('fr', ''))
if idx != expected_idx:
self.fail('Idx of separator ' + sep_name + ' if wrong: ' + str(idx) + ', expected: ' + str(expected_idx))
return
self.fail('Separator ' + sep_name + 'not found on interface ' + interface)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule_create.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from .test_pfsense_rule import TestPFSenseRuleModule
class TestPFSenseRuleCreateModule(TestPFSenseRuleModule):
############################
# rule creation tests
#
def test_rule_create_one_rule(self):
""" test creation of a new rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_log(self):
""" test creation of a new rule with logging """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', log='yes')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', log=True"
self.do_module_test(obj, command=command)
def test_rule_create_nolog(self):
""" test creation of a new rule without logging """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', log='no')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_pass(self):
""" test creation of a new rule explictly passing """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', action='pass')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_block(self):
""" test creation of a new rule blocking """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', action='block')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', action='block'"
self.do_module_test(obj, command=command)
def test_rule_create_reject(self):
""" test creation of a new rule rejecting """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', action='reject')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', action='reject'"
self.do_module_test(obj, command=command)
def test_rule_create_disabled(self):
""" test creation of a new disabled rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', disabled=True)
command = "create rule 'one_rule' on 'lan', source='any', destination='any', disabled=True"
self.do_module_test(obj, command=command)
def test_rule_create_floating(self):
""" test creation of a new floating rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', floating='yes', direction='any')
command = "create rule 'one_rule' on 'floating(lan)', source='any', destination='any', direction='any'"
self.do_module_test(obj, command=command)
def test_rule_create_floating_any(self):
""" test creation of a new floating rule with any interface """
obj = dict(name='one_rule', source='any', destination='any', interface='any', floating='yes', direction='any')
command = "create rule 'one_rule' on 'floating(any)', source='any', destination='any', direction='any'"
def test_rule_create_non_floating_any(self):
""" test creation of a new rule with any interface """
obj = dict(name='one_rule', source='any', destination='any', interface='any', floating='no', direction='any')
msg = "any is not a valid interface"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_floating_quick(self):
""" test creation of a new floating rule with quick match """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', floating='yes', direction='any', quick='yes')
command = "create rule 'one_rule' on 'floating(lan)', source='any', destination='any', direction='any', quick=True"
self.do_module_test(obj, command=command)
def test_rule_create_nofloating(self):
""" test creation of a new non-floating rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', floating='no')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_floating_interfaces(self):
""" test creation of a floating rule on three interfaces """
obj = dict(name='one_rule', source='any', destination='any', interface='lan,wan,vt1', floating='yes', direction='any')
command = "create rule 'one_rule' on 'floating(lan,wan,vt1)', source='any', destination='any', direction='any'"
self.do_module_test(obj, command=command)
def test_rule_create_inet46(self):
""" test creation of a new rule using ipv4 and ipv6 """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', ipprotocol='inet46')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', ipprotocol='inet46'"
self.do_module_test(obj, command=command)
def test_rule_create_inet6(self):
""" test creation of a new rule using ipv6 """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', ipprotocol='inet6')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', ipprotocol='inet6'"
self.do_module_test(obj, command=command)
def test_rule_create_tcp(self):
""" test creation of a new rule for tcp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_udp(self):
""" test creation of a new rule for udp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='udp')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='udp'"
self.do_module_test(obj, command=command)
def test_rule_create_tcp_udp(self):
""" test creation of a new rule for tcp/udp protocols """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='tcp/udp')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='tcp/udp'"
self.do_module_test(obj, command=command)
def test_rule_create_icmp(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='icmp'"
self.do_module_test(obj, command=command)
def test_rule_create_icmp_redir(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp', icmptype='redir', action='block')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='icmp', icmptype='redir', action='block'"
self.do_module_test(obj, command=command)
def test_rule_create_icmp_invalid_inet(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp', icmptype='neighbradv')
msg = 'ICMP types neighbradv are invalid with IP type inet'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_icmp_invalid_inet6(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp', ipprotocol='inet6', icmptype='trace')
msg = 'ICMP types trace are invalid with IP type inet6'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_icmp_invalid_inet46(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp', ipprotocol='inet46', icmptype='trace')
msg = 'ICMP types trace are invalid with IP type inet46'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_icmp_invalid_empty(self):
""" test creation of a new rule for icmp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='icmp', icmptype='')
msg = 'You must specify at least one icmptype or any for all of them'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_esp(self):
""" test creation of a new rule for esp protocol """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='esp')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', protocol='esp'"
self.do_module_test(obj, command=command)
def test_rule_create_protocol_any(self):
""" test creation of a new rule for (self) """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', protocol='any')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_tcpflags_any(self):
""" test creation of a new rule with tcpflags_any """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', tcpflags_any='yes')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', tcpflags_any=True"
self.do_module_test(obj, command=command)
def test_rule_create_state_keep(self):
""" test creation of a new rule with explicit keep state """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', statetype='keep state')
command = "create rule 'one_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_state_sloppy(self):
""" test creation of a new rule with sloppy state """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', statetype='sloppy state')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', statetype='sloppy state'"
self.do_module_test(obj, command=command)
def test_rule_create_state_synproxy(self):
""" test creation of a new rule with synproxy state """
# todo: synproxy is only valid with tcp
obj = dict(name='one_rule', source='any', destination='any', interface='lan', statetype='synproxy state')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', statetype='synproxy state'"
self.do_module_test(obj, command=command)
def test_rule_create_state_none(self):
""" test creation of a new rule with no state """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', statetype='none')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', statetype='none'"
self.do_module_test(obj, command=command)
def test_rule_create_state_invalid(self):
""" test creation of a new rule with invalid state """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', statetype='acme state')
msg = "value of statetype must be one of: keep state, sloppy state, synproxy state, none, got: acme state"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_after(self):
""" test creation of a new rule after another """
obj = dict(name='one_rule', source='any', destination='any', interface='vpn', after='admin_bypass')
command = "create rule 'one_rule' on 'vpn', source='any', destination='any', after='admin_bypass'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 13)
def test_rule_create_after_top(self):
""" test creation of a new rule at top """
obj = dict(name='one_rule', source='any', destination='any', interface='wan', after='top')
command = "create rule 'one_rule' on 'wan', source='any', destination='any', after='top'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
def test_rule_create_after_invalid(self):
""" test creation of a new rule after an invalid rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', after='admin_bypass')
msg = "Failed to insert after rule=admin_bypass interface=lan"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_before(self):
""" test creation of a new rule before another """
obj = dict(name='one_rule', source='any', destination='any', interface='vpn', before='admin_bypass')
command = "create rule 'one_rule' on 'vpn', source='any', destination='any', before='admin_bypass'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 12)
def test_rule_create_before_bottom(self):
""" test creation of a new rule at bottom """
obj = dict(name='one_rule', source='any', destination='any', interface='wan', before='bottom')
command = "create rule 'one_rule' on 'wan', source='any', destination='any', before='bottom'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 4)
def test_rule_create_before_bottom_default(self):
""" test creation of a new rule at bottom (default) """
obj = dict(name='one_rule', source='any', destination='any', interface='wan', action='pass')
command = "create rule 'one_rule' on 'wan', source='any', destination='any'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 4)
def test_rule_create_before_invalid(self):
""" test creation of a new rule before an invalid rule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', before='admin_bypass')
msg = "Failed to insert before rule=admin_bypass interface=lan"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_source_alias(self):
""" test creation of a new rule with a valid source alias """
obj = dict(name='one_rule', source='srv_admin', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='srv_admin', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_source_urltable_alias(self):
""" test creation of a new rule with a valid source urltable alias """
obj = dict(name='one_rule', source='acme_corp', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='acme_corp', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_source_alias_invalid(self):
""" test creation of a new rule with an invalid source alias """
obj = dict(name='one_rule', source='acme', destination='any', interface='lan')
msg = "Cannot parse address acme, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_invalid_ports(self):
""" test creation of a new rule with an invalid use of ports """
obj = dict(name='one_rule', source='192.193.194.195', destination='any:22', interface='lan', protocol='icmp')
msg = "'one_rule' on 'lan': you can't use ports on protocols other than tcp, udp, tcp/udp or any"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_source_ip_invalid(self):
""" test creation of a new rule with an invalid source ip """
obj = dict(name='one_rule', source='192.193.194.195.196', destination='any', interface='lan')
msg = "Cannot parse address 192.193.194.195.196, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_source_net_invalid(self):
""" test creation of a new rule with an invalid source network """
obj = dict(name='one_rule', source='192.193.194.195/256', destination='any', interface='lan')
msg = "Cannot parse address 192.193.194.195/256, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_destination_alias(self):
""" test creation of a new rule with a valid destination alias """
obj = dict(name='one_rule', source='any', destination='srv_admin', interface='lan')
command = "create rule 'one_rule' on 'lan', source='any', destination='srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_create_destination_alias_invalid(self):
""" test creation of a new rule with an invalid destination alias """
obj = dict(name='one_rule', source='any', destination='acme', interface='lan')
msg = "Cannot parse address acme, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_destination_ip_invalid(self):
""" test creation of a new rule with an invalid destination ip """
obj = dict(name='one_rule', source='any', destination='192.193.194.195.196', interface='lan')
msg = "Cannot parse address 192.193.194.195.196, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_destination_net_invalid(self):
""" test creation of a new rule with an invalid destination network """
obj = dict(name='one_rule', source='any', destination='192.193.194.195/256', interface='lan')
msg = "Cannot parse address 192.193.194.195/256, not IP or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_source_self_lan(self):
""" test creation of a new rule with self"""
obj = dict(name='one_rule', source='(self)', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='(self)', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_ip_to_ip(self):
""" test creation of a new rule with valid ips """
obj = dict(name='one_rule', source='10.10.1.1', destination='10.10.10.1', interface='lan')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1', destination='10.10.10.1'"
self.do_module_test(obj, command=command)
def test_rule_create_ip6_to_ip6(self):
""" test creation of a new rule with valid ips """
obj = dict(name='one_rule', source='2001:db8:1::1', destination='2001:db8:2::2', ipprotocol='inet6', interface='lan')
command = "create rule 'one_rule' on 'lan', source='2001:db8:1::1', destination='2001:db8:2::2', ipprotocol='inet6'"
self.do_module_test(obj, command=command)
def test_rule_create_net_to_net(self):
""" test creation of a new rule valid networks """
obj = dict(name='one_rule', source='10.10.1.0/24', destination='10.10.10.0/24', interface='lan')
command = "create rule 'one_rule' on 'lan', source='10.10.1.0/24', destination='10.10.10.0/24'"
self.do_module_test(obj, command=command)
def test_rule_create_net6_to_net6(self):
""" test creation of a new rule valid networks """
obj = dict(name='one_rule', source='2001:db8:1::/64', destination='2001:db8:2::/64', ipprotocol='inet6', interface='lan')
command = "create rule 'one_rule' on 'lan', source='2001:db8:1::/64', destination='2001:db8:2::/64', ipprotocol='inet6'"
self.do_module_test(obj, command=command)
def test_rule_create_net_interface(self):
""" test creation of a new rule with valid interface """
obj = dict(name='one_rule', source='NET:lan', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='NET:lan', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_net_interface_invalid(self):
""" test creation of a new rule with invalid interface """
obj = dict(name='one_rule', source='NET:invalid_lan', destination='any', interface='lan')
msg = "invalid_lan is not a valid interface"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_net_interface_invalid2(self):
""" test creation of a new rule with invalid interface """
obj = dict(name='one_rule', source='NET:', destination='any', interface='lan')
msg = "Cannot parse address NET:"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_ip_interface(self):
""" test creation of a new rule with valid interface """
obj = dict(name='one_rule', source='IP:vt1', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='IP:vt1', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_ip_interface_with_port(self):
""" test creation of a new rule with valid interface """
obj = dict(name='one_rule', source='IP:vt1:22', destination='any', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='IP:vt1:22', destination='any', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_ip_interface_invalid(self):
""" test creation of a new rule with invalid interface """
obj = dict(name='one_rule', source='IP:invalid_lan', destination='any', interface='lan')
msg = "invalid_lan is not a valid interface"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_interface(self):
""" test creation of a new rule with valid interface """
obj = dict(name='one_rule', source='vpn', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='vpn', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_port_number(self):
""" test creation of a new rule with port """
obj = dict(name='one_rule', source='10.10.1.1', destination='10.10.10.1:80', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1', destination='10.10.10.1:80', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_port_alias(self):
""" test creation of a new rule with port alias """
obj = dict(name='one_rule', source='10.10.1.1', destination='10.10.10.1:port_http', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1', destination='10.10.10.1:port_http', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_urltable_port_alias(self):
""" test creation of a new rule with urltable port alias """
obj = dict(name='one_rule', source='10.10.1.1', destination='10.10.10.1:acme_corp_ports', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1', destination='10.10.10.1:acme_corp_ports', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_port_range(self):
""" test creation of a new rule with range of ports """
obj = dict(name='one_rule', source='10.10.1.1:30000-40000', destination='10.10.10.1', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1:30000-40000', destination='10.10.10.1', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_port_alias_range(self):
""" test creation of a new rule with range of alias ports """
obj = dict(name='one_rule', source='10.10.1.1:port_ssh-port_http', destination='10.10.10.1', interface='lan', protocol='tcp')
command = "create rule 'one_rule' on 'lan', source='10.10.1.1:port_ssh-port_http', destination='10.10.10.1', protocol='tcp'"
self.do_module_test(obj, command=command)
def test_rule_create_port_alias_range_invalid_1(self):
""" test creation of a new rule with range of invalid alias ports """
obj = dict(name='one_rule', source='10.10.1.1:port_ssh-openvpn_port', destination='10.10.10.1', interface='lan')
msg = "Cannot parse port openvpn_port, not port number or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_port_alias_range_invalid_2(self):
""" test creation of a new rule with range of invalid alias ports """
obj = dict(name='one_rule', source='10.10.1.1:-openvpn_port', destination='10.10.10.1', interface='lan')
msg = "Cannot parse port -openvpn_port"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_port_alias_range_invalid_3(self):
""" test creation of a new rule with range of invalid alias ports """
obj = dict(name='one_rule', source='10.10.1.1:port_ssh-65537', destination='10.10.10.1', interface='lan')
msg = "Cannot parse port 65537, not port number or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_port_number_invalid(self):
""" test creation of a new rule with invalid port number """
obj = dict(name='one_rule', source='10.10.1.1:65536', destination='10.10.10.1', interface='lan', protocol='tcp')
msg = "Cannot parse port 65536, not port number or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_port_alias_invalid(self):
""" test creation of a new rule with invalid port alias """
obj = dict(name='one_rule', source='10.10.1.1:openvpn_port', destination='10.10.10.1', interface='lan')
msg = "Cannot parse port openvpn_port, not port number or alias"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_negate_source(self):
""" test creation of a new rule with a not source """
obj = dict(name='one_rule', source='!srv_admin', destination='any', interface='lan')
command = "create rule 'one_rule' on 'lan', source='!srv_admin', destination='any'"
self.do_module_test(obj, command=command)
def test_rule_create_negate_destination(self):
""" test creation of a new rule with a not destination """
obj = dict(name='one_rule', source='any', destination='!srv_admin', interface='lan')
command = "create rule 'one_rule' on 'lan', source='any', destination='!srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_create_separator_top(self):
""" test creation of a new rule at top """
obj = dict(name='one_rule', source='any', destination='any', interface='vt1', after='top')
command = "create rule 'one_rule' on 'vt1', source='any', destination='any', after='top'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
self.check_separator_idx(obj['interface'], 'test_sep1', 1)
self.check_separator_idx(obj['interface'], 'test_sep2', 4)
def test_rule_create_separator_bottom(self):
""" test creation of a new rule at bottom """
obj = dict(name='one_rule', source='any', destination='any', interface='vt1', before='bottom')
command = "create rule 'one_rule' on 'vt1', source='any', destination='any', before='bottom'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 3)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 3)
def test_rule_create_separator_before_first(self):
""" test creation of a new rule before first rule """
obj = dict(name='one_rule', source='any', destination='any', interface='vt1', before='r1')
command = "create rule 'one_rule' on 'vt1', source='any', destination='any', before='r1'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 4)
def test_rule_create_separator_after_third(self):
""" test creation of a new rule after third rule """
obj = dict(name='one_rule', source='any', destination='any', interface='vt1', after='r3')
command = "create rule 'one_rule' on 'vt1', source='any', destination='any', after='r3'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 3)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 4)
def test_rule_create_queue(self):
""" test creation of a new rule with default queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', queue='one_queue')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', queue='one_queue'"
self.do_module_test(obj, command=command)
def test_rule_create_queue_ack(self):
""" test creation of a new rule with default queue and ack queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', queue='one_queue', ackqueue='another_queue')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', queue='one_queue', ackqueue='another_queue'"
self.do_module_test(obj, command=command)
def test_rule_create_queue_ack_without_default(self):
""" test creation of a new rule with ack queue and without default queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', ackqueue='another_queue')
msg = "A default queue must be selected when an acknowledge queue is also selected"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_queue_same(self):
""" test creation of a new rule with same default queue and ack queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', queue='one_queue', ackqueue='one_queue')
msg = "Acknowledge queue and default queue cannot be the same"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_queue_invalid(self):
""" test creation of a new rule with invalid default queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', queue='acme_queue')
msg = "Failed to find enabled queue=acme_queue"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_queue_invalid_ack(self):
""" test creation of a new rule with default queue and invalid ack queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', queue='one_queue', ackqueue='acme_queue')
msg = "Failed to find enabled ackqueue=acme_queue"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter(self):
""" test creation of a new rule with in_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='one_limiter')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', in_queue='one_limiter'"
self.do_module_test(obj, command=command)
def test_rule_create_limiter_out(self):
""" test creation of a new rule with in_queue and out_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='one_limiter', out_queue='another_limiter')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', in_queue='one_limiter', out_queue='another_limiter'"
self.do_module_test(obj, command=command)
def test_rule_create_limiter_disabled(self):
""" test creation of a new rule with disabled in_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='disabled_limiter')
msg = "Failed to find enabled in_queue=disabled_limiter"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter_out_without_in(self):
""" test creation of a new rule with out_queue and without in_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', out_queue='another_limiter')
msg = "A queue must be selected for the In direction before selecting one for Out too"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter_same(self):
""" test creation of a new rule with same in_queue and out_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='one_limiter', out_queue='one_limiter')
msg = "In and Out Queue cannot be the same"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter_invalid(self):
""" test creation of a new rule with invalid in_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='acme_queue')
msg = "Failed to find enabled in_queue=acme_queue"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter_invalid_out(self):
""" test creation of a new rule with in_queue and invalid out_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='one_limiter', out_queue='acme_queue')
msg = "Failed to find enabled out_queue=acme_queue"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_limiter_floating_any(self):
""" test creation of a new rule with in_queue and invalid out_queue """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', in_queue='one_limiter', floating='yes', direction='any')
msg = "Limiters can not be used in Floating rules without choosing a direction"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_gateway(self):
""" test creation of a new rule with gateway """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', gateway='GW_LAN')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', gateway='GW_LAN'"
self.do_module_test(obj, command=command)
def test_rule_create_gateway_invalid(self):
""" test creation of a new rule with invalid gateway """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', gateway='GW_WLAN')
msg = 'Gateway "GW_WLAN" does not exist or does not match target rule ip protocol.'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_gateway_invalid_ipprotocol(self):
""" test creation of a new rule with gateway """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', ipprotocol='inet6', gateway='GW_LAN')
msg = 'Gateway "GW_LAN" does not exist or does not match target rule ip protocol.'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_gateway_floating(self):
""" test creation of a new floating rule with gateway """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', floating='yes', direction='in', gateway='GW_LAN')
command = "create rule 'one_rule' on 'floating(lan)', source='any', destination='any', direction='in', gateway='GW_LAN'"
self.do_module_test(obj, command=command)
def test_rule_create_gateway_floating_any(self):
""" test creation of a new floating rule with gateway """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', floating='yes', direction='any', gateway='GW_LAN')
msg = "Gateways can not be used in Floating rules without choosing a direction"
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_gateway_group(self):
""" test creation of a new rule with gateway group """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', gateway='GWGroup')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', gateway='GWGroup'"
self.do_module_test(obj, command=command)
def test_rule_create_gateway_group_invalid_ipprotocol(self):
""" test creation of a new rule with gateway group """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', ipprotocol='inet6', gateway='GWGroup')
msg = 'Gateway "GWGroup" does not exist or does not match target rule ip protocol.'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_tracker(self):
""" test creation of a new rule with tracker """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', tracker='1234')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', tracker='1234'"
self.do_module_test(obj, command=command)
def test_rule_create_tracker_leading0(self):
""" test creation of a new rule with tracker with a leading 0 """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', tracker='0100000101')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', tracker='0100000101'"
self.do_module_test(obj, command=command)
def test_rule_create_tracker_invalid(self):
""" test creation of a new rule with invalid tracker """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', tracker='-1234')
msg = 'tracker -1234 must be a positive integer'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_create_schedule(self):
""" test creation of a new rule with schedule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', sched='workdays')
command = "create rule 'one_rule' on 'lan', source='any', destination='any', sched='workdays'"
self.do_module_test(obj, command=command)
def test_rule_create_schedule_invalid(self):
""" test creation of a new rule with invalid schedule """
obj = dict(name='one_rule', source='any', destination='any', interface='lan', sched='acme')
msg = 'Schedule acme does not exist'
self.do_module_test(obj, failed=True, msg=msg)
##################################
# protocol any with ports
#
def test_rule_create_protocol_any_with_dst_port(self):
""" test creation of a rule with protocol any and destination port """
obj = dict(name='one_rule', source='any', destination='any:443', interface='lan', protocol='any')
command = "create rule 'one_rule' on 'lan', source='any', destination='any:443'"
self.do_module_test(obj, command=command)
def test_rule_create_protocol_icmp_with_dst_port(self):
""" test creation of a rule with protocol icmp and destination port should fail """
obj = dict(name='one_rule', source='any', destination='any:443', interface='lan', protocol='icmp')
msg = "'one_rule' on 'lan': you can't use ports on protocols other than tcp, udp, tcp/udp or any"
self.do_module_test(obj, failed=True, msg=msg)
##################################
# pass rule ordering (insert before first block/reject)
#
def test_rule_create_pass_before_block(self):
""" test that a new pass rule is inserted before the first block rule on the same interface """
obj = dict(name='new_pass_rule', source='any', destination='any', interface='lan')
command = "create rule 'new_pass_rule' on 'lan', source='any', destination='any'"
self.do_module_test(obj, command=command)
# Verify the new pass rule appears before block_all_lan in the XML
self.load_xml_result()
filter_elt = self.xml_result.find('filter')
rules = []
for rule_elt in filter_elt.findall('rule'):
iface_elt = rule_elt.find('interface')
descr_elt = rule_elt.find('descr')
if iface_elt is not None and iface_elt.text == 'lan' and descr_elt is not None:
rules.append(descr_elt.text)
self.assertIn('new_pass_rule', rules)
self.assertIn('block_all_lan', rules)
pass_idx = rules.index('new_pass_rule')
block_idx = rules.index('block_all_lan')
self.assertLess(pass_idx, block_idx, "pass rule should be positioned before block rule")
def test_rule_create_block_appends_to_end(self):
""" test that a new block rule appends to the end (after existing block rules) """
obj = dict(name='new_block_rule', source='any', destination='any', interface='lan', action='block')
command = "create rule 'new_block_rule' on 'lan', source='any', destination='any', action='block'"
self.do_module_test(obj, command=command)
# Verify the new block rule appears after block_all_lan
self.load_xml_result()
filter_elt = self.xml_result.find('filter')
rules = []
for rule_elt in filter_elt.findall('rule'):
iface_elt = rule_elt.find('interface')
descr_elt = rule_elt.find('descr')
if iface_elt is not None and iface_elt.text == 'lan' and descr_elt is not None:
rules.append(descr_elt.text)
self.assertIn('new_block_rule', rules)
self.assertIn('block_all_lan', rules)
new_idx = rules.index('new_block_rule')
existing_idx = rules.index('block_all_lan')
self.assertGreater(new_idx, existing_idx, "new block rule should append after existing block rule")
def test_rule_create_pass_before_reject(self):
""" test that a new pass rule is inserted before a reject rule, not just block """
# Fixture has both block_all_lan and reject_all_lan on lan.
# A new pass rule should be inserted before both.
obj = dict(name='new_pass_above_reject', source='any', destination='any:80', interface='lan', protocol='tcp')
command = "create rule 'new_pass_above_reject' on 'lan', source='any', destination='any:80', protocol='tcp'"
self.do_module_test(obj, command=command)
self.load_xml_result()
filter_elt = self.xml_result.find('filter')
rules = []
for rule_elt in filter_elt.findall('rule'):
iface_elt = rule_elt.find('interface')
descr_elt = rule_elt.find('descr')
if iface_elt is not None and iface_elt.text == 'lan' and descr_elt is not None:
rules.append(descr_elt.text)
self.assertIn('new_pass_above_reject', rules)
self.assertIn('block_all_lan', rules)
self.assertIn('reject_all_lan', rules)
pass_idx = rules.index('new_pass_above_reject')
block_idx = rules.index('block_all_lan')
reject_idx = rules.index('reject_all_lan')
self.assertLess(pass_idx, block_idx, "pass rule should be before block rule")
self.assertLess(pass_idx, reject_idx, "pass rule should be before reject rule")
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule_misc.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import set_module_args
from .test_pfsense_rule import TestPFSenseRuleModule
class TestPFSenseRuleMiscModule(TestPFSenseRuleModule):
##############
# delete
#
def test_rule_delete(self):
""" test deleting a rule """
obj = dict(name='test_rule_3', source='any', destination='any', interface='wan', protocol='tcp')
command = "delete rule 'test_rule_3' on 'wan'"
self.do_module_test(obj, command=command, delete=True)
##############
# misc
#
def test_check_mode(self):
""" test check mode """
obj = dict(name='one_rule', source='any', destination='any', interface='lan')
with set_module_args(self.args_from_var(obj, _ansible_check_mode=True)):
self.execute_module(changed=True)
self.assertFalse(self.load_xml_result())
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule_noop.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from .test_pfsense_rule import TestPFSenseRuleModule
class TestPFSenseRuleNoopModule(TestPFSenseRuleModule):
############################
# rule noop tests
#
def test_rule_noop_action(self):
""" test not updating action of a rule to block """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', action='pass', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_disabled(self):
""" test not updating disabled of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', disabled='False', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_enabled(self):
""" test not updating disabled of a rule """
obj = dict(name='test_lan_100_1', source='any', destination='any', interface='lan_100', disabled='True', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_disabled_default(self):
""" test not updating disabled of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_floating_interface(self):
""" test not updating interface of a floating rule """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_floating_direction(self):
""" test not updating direction of a rule to out """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_inet(self):
""" test not updating ippprotocol of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', ipprotocol='inet', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_protocol(self):
""" test not updating protocol of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_log_no(self):
""" test not updating log of a rule to no """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', log='no', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_log_yes(self):
""" test not updating log of a rule to no """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', log='yes', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_log_default(self):
""" test not updating log of a rule to default """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', log='no', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_source_and_destination(self):
""" test not updating source and destination of a rule """
obj = dict(name='ads_to_ads_tcp_2_3', source='ad_poc3:port_ldap_ssl', destination='ad_poc1:port_ldap_ssl', interface='lan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_negate_source(self):
""" test creation of a new rule with a not source """
obj = dict(name='not_rule_src', source='!srv_admin', destination='any:port_ssh', interface='lan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_negate_destination(self):
""" test creation of a new rule with a not destination """
obj = dict(name='not_rule_dst', source='any', destination='!srv_admin:port_ssh', interface='lan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_before(self):
""" test not updating position of a rule to before another """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', log='yes', protocol='tcp', before='test_rule_3')
self.do_module_test(obj, changed=False)
def test_rule_noop_before_bottom(self):
""" test not updating position of a rule to bottom """
obj = dict(name='antilock_out_3', source='any', destination='any:443', interface='wan', protocol='tcp', before='bottom')
self.do_module_test(obj, changed=False)
def test_rule_noop_position_bottom(self):
""" test not updating position of a rule to bottom """
obj = dict(name='antilock_out_3', source='any', destination='any:443', interface='wan', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_position_middle(self):
""" test not updating position of a rule to before another """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', log='yes', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_after(self):
""" test not updating position of a rule to after another rule """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', log='yes', protocol='tcp', after='test_rule')
self.do_module_test(obj, changed=False)
def test_rule_noop_after_top(self):
""" test not updating position of a rule to top """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', log='no', protocol='tcp', after='top')
self.do_module_test(obj, changed=False)
def test_rule_noop_separator_top(self):
""" test not updating position of a rule to top """
obj = dict(name='r1', source='any', destination='any', interface='vt1', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_separator_bottom(self):
""" test not updating position of a rule to bottom """
obj = dict(name='r3', source='any', destination='any', interface='vt1', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_queue_ack(self):
""" test updating queue of a rule """
obj = dict(name='test_lan_100_2', source='any', destination='any', interface='lan_100', queue='one_queue', ackqueue='another_queue', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_queue(self):
""" test updating queue and ackqueue of a rule """
obj = dict(name='test_lan_100_3', source='any', destination='any', interface='lan_100', queue='one_queue', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_limiter_out(self):
""" test updating queue of a rule """
obj = dict(
name='test_lan_100_4', source='any', destination='any', interface='lan_100', in_queue='one_limiter', out_queue='another_limiter', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_limiter_in(self):
""" test updating queue and ackqueue of a rule """
obj = dict(name='test_lan_100_5', source='any', destination='any', interface='lan_100', in_queue='one_limiter', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_tracker(self):
""" test updating tracker of a rule """
obj = dict(name='test_lan_100_5', source='any', destination='any', interface='lan_100', in_queue='one_limiter', protocol='tcp', tracker=1545574416)
self.do_module_test(obj, changed=False)
def test_rule_noop_tracker(self):
""" test updating tracker of a rule """
obj = dict(name='test_lan_100_5', source='any', destination='any', interface='lan_100', in_queue='one_limiter', protocol='tcp')
self.do_module_test(obj, changed=False)
def test_rule_noop_schedule(self):
""" test updating scheduling of a rule """
obj = dict(name='test_rule_sched', source='any', destination='any', interface='lan_100', action='pass', protocol='tcp', sched='workdays')
self.do_module_test(obj, changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule_separator.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import sys
import pytest
from ansible_collections.pfsensible.core.plugins.modules import pfsense_rule_separator
from ansible_collections.pfsensible.core.plugins.module_utils.rule_separator import PFSenseRuleSeparatorModule
from .pfsense_module import TestPFSenseModule
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
class TestPFSenseRuleSeparatorModule(TestPFSenseModule):
module = pfsense_rule_separator
def __init__(self, *args, **kwargs):
super(TestPFSenseRuleSeparatorModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_rule_separator_config.xml'
self.pfmodule = PFSenseRuleSeparatorModule
def get_target_elt(self, obj, absent=False, module_result=None):
""" get separator from XML """
if obj.get('floating'):
interface = 'floatingrules'
else:
interface = self.unalias_interface(obj['interface'])
filter_elt = self.assert_find_xml_elt(self.xml_result, 'filter')
separator_elt = self.assert_find_xml_elt(filter_elt, 'separator')
iface_elt = self.assert_find_xml_elt(separator_elt, interface)
for separator_elt in iface_elt:
text_elt = separator_elt.find('text')
if text_elt is not None and text_elt.text == obj['name']:
if absent:
self.fail('Separator ' + obj['name'] + ' found on interface ' + interface)
return separator_elt
if not absent:
self.fail('Separator ' + obj['name'] + ' not found on interface ' + interface)
return None
def check_target_elt(self, obj, target_elt):
""" check XML separator definition """
if obj.get('floating'):
interface = 'floatingrules'
else:
interface = self.unalias_interface(obj['interface'])
self.assert_xml_elt_equal(target_elt, 'if', interface)
if 'color' not in obj:
self.assert_xml_elt_equal(target_elt, 'color', 'bg-info')
else:
self.assert_xml_elt_equal(target_elt, 'color', 'bg-' + obj['color'])
def check_separator_idx(self, separator, expected_idx):
""" test the logical position of separator """
separator_elt = self.get_target_elt(separator)
row_elt = self.assert_find_xml_elt(separator_elt, 'row')
idx = int(row_elt.text.replace('fr', ''))
if idx != expected_idx:
self.fail('Idx of separator ' + separator['name'] + ' if wrong: ' + str(idx) + ', expected: ' + str(expected_idx))
##############
# hosts
#
def test_separator_create(self):
""" test creation of a new separator """
separator = dict(name='voip', interface='lan_100')
command = "create rule_separator 'voip' on 'lan_100', color='info'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 6)
def test_separator_create_floating(self):
""" test creation of a new separator """
separator = dict(name='voip', floating=True)
command = "create rule_separator 'voip' on 'floating', color='info'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 0)
def test_separator_create_top(self):
""" test creation of a new separator at top """
separator = dict(name='voip', interface='lan_100', after='top')
command = "create rule_separator 'voip' on 'lan_100', color='info', after='top'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 0)
def test_separator_create_bottom(self):
""" test creation of a new separator at bottom """
separator = dict(name='voip', interface='lan', before='bottom')
command = "create rule_separator 'voip' on 'lan', color='info', before='bottom'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 14)
def test_separator_create_after(self):
""" test creation of a new separator at bottom """
separator = dict(name='voip', interface='lan', after='antilock_out_1')
command = "create rule_separator 'voip' on 'lan', color='info', after='antilock_out_1'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 1)
def test_separator_create_before(self):
""" test creation of a new separator at bottom """
separator = dict(name='voip', interface='lan', before='antilock_out_2')
command = "create rule_separator 'voip' on 'lan', color='info', before='antilock_out_2'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 1)
def test_separator_delete(self):
""" test deletion of a separator """
separator = dict(name='test_separator', interface='lan')
command = "delete rule_separator 'test_separator' on 'lan'"
self.do_module_test(separator, command=command, delete=True)
def test_separator_delete_inexistent(self):
""" test deletion of an inexistent separator """
separator = dict(name='test_separator', interface='wan')
self.do_module_test(separator, command='', changed=False, delete=True)
def test_separator_update_noop(self):
""" test changing nothing to a separator """
separator = dict(name='test_separator', interface='lan', color='info')
self.do_module_test(separator, changed=False)
def test_separator_update_color(self):
""" test updating color of a separator """
separator = dict(name='test_separator', interface='lan', color='warning')
command = "update rule_separator 'test_separator' on 'lan' set color='warning'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 1)
def test_separator_update_position(self):
""" test updating position of a separator """
separator = dict(name='test_separator', interface='lan', after='top')
command = "update rule_separator 'test_separator' on 'lan' set color='info', after='top'"
self.do_module_test(separator, command=command)
self.check_separator_idx(separator, 0)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_rule_update.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from .test_pfsense_rule import TestPFSenseRuleModule
class TestPFSenseRuleUpdateModule(TestPFSenseRuleModule):
############################
# rule update tests
#
def test_rule_update_action(self):
""" test updating action of a rule to block """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', action='block', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set action='block'"
self.do_module_test(obj, command=command)
def test_rule_update_disabled(self):
""" test updating disabled of a rule to True """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', disabled='True', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set disabled=True"
self.do_module_test(obj, command=command)
def test_rule_update_enabled(self):
""" test updating disabled of a rule to False """
obj = dict(name='test_lan_100_1', source='any', destination='any', interface='lan_100', disabled='False', protocol='tcp')
command = "update rule 'test_lan_100_1' on 'lan_100' set disabled=False"
self.do_module_test(obj, command=command)
def test_rule_update_enabled_default(self):
""" test updating disabled of a rule to default """
obj = dict(name='test_lan_100_1', source='any', destination='any', interface='lan_100', protocol='tcp')
command = "update rule 'test_lan_100_1' on 'lan_100' set disabled=False"
self.do_module_test(obj, command=command)
def test_rule_update_floating_interface(self):
""" test updating interface of a floating rule """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='lan', floating='yes', direction='any', protocol='tcp')
command = "update rule 'test_rule_floating' on 'floating(wan)' set interface='lan'"
self.do_module_test(obj, command=command)
def test_rule_update_floating_interfaces(self):
""" test updating interfaces of a floating rule """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='lan,lan_100', floating='yes', direction='any', protocol='tcp')
command = "update rule 'test_rule_floating' on 'floating(wan)' set interface='lan,lan_100'"
self.do_module_test(obj, command=command)
def test_rule_update_floating_direction(self):
""" test updating direction of a rule to out """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='out', protocol='tcp')
command = "update rule 'test_rule_floating' on 'floating(wan)' set direction='out'"
self.do_module_test(obj, command=command)
def test_rule_update_floating_quick(self):
""" test updating quick match of a floating rule """
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp', quick='yes')
command = "update rule 'test_rule_floating' on 'floating(wan)' set quick=True"
self.do_module_test(obj, command=command)
def test_rule_update_floating_remove_quick(self):
""" test updating quick match of a floating rule """
obj = dict(name='test_rule_floating_quick', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
command = "update rule 'test_rule_floating_quick' on 'floating(wan)' set quick=False"
self.do_module_test(obj, command=command)
def test_rule_update_floating_yes(self):
""" test updating floating of a rule to yes
Since you can't change the floating mode of a rule, it should create a new rule
"""
obj = dict(name='test_rule', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
command = "create rule 'test_rule' on 'floating(wan)', source='any', destination='any', protocol='tcp', direction='any'"
self.do_module_test(obj, command=command)
other_rule = dict(name='test_rule', source='any', destination='any', interface='wan', floating='no', protocol='tcp')
other_rule_elt = self.get_target_elt(other_rule)
self.check_target_elt(other_rule, other_rule_elt)
def test_rule_update_floating_no(self):
""" test updating floating of a rule to no
Since you can't change the floating mode of a rule, it should create a new rule
"""
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='no', direction='any', protocol='tcp')
command = "create rule 'test_rule_floating' on 'wan', source='any', destination='any', protocol='tcp', direction='any'"
self.do_module_test(obj, command=command)
other_rule = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
other_rule_elt = self.get_target_elt(other_rule)
self.check_target_elt(other_rule, other_rule_elt)
def test_rule_update_floating_default(self):
""" test updating floating of a rule to default (no)
Since you can't change the floating mode of a rule, it should create a new rule
"""
obj = dict(name='test_rule_floating', source='any', destination='any', interface='wan', protocol='tcp')
command = "create rule 'test_rule_floating' on 'wan', source='any', destination='any', protocol='tcp'"
self.do_module_test(obj, command=command)
other_rule = dict(name='test_rule_floating', source='any', destination='any', interface='wan', floating='yes', direction='any', protocol='tcp')
other_rule_elt = self.get_target_elt(other_rule)
self.check_target_elt(other_rule, other_rule_elt)
def test_rule_update_inet(self):
""" test updating ippprotocol of a rule to ipv4 and ipv6 """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', ipprotocol='inet46', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set ipprotocol='inet46'"
self.do_module_test(obj, command=command)
def test_rule_update_protocol_udp(self):
""" test updating protocol of a rule to udp """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', protocol='udp')
command = "update rule 'test_rule' on 'wan' set protocol='udp'"
self.do_module_test(obj, command=command)
def test_rule_update_protocol_any(self):
""" test updating protocol of a rule to udp """
obj = dict(name='r2', source='any', destination='any', interface='vt1', protocol='any')
command = "update rule 'r2' on 'vt1' set protocol='any'"
self.do_module_test(obj, command=command)
def test_rule_update_protocol_tcp_udp(self):
""" test updating protocol of a rule to tcp/udp """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', protocol='tcp/udp')
command = "update rule 'test_rule' on 'wan' set protocol='tcp/udp'"
self.do_module_test(obj, command=command)
def test_rule_update_log_yes(self):
""" test updating log of a rule to yes """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', log='yes', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set log=True"
self.do_module_test(obj, command=command)
def test_rule_update_log_no(self):
""" test updating log of a rule to no """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', log='no', protocol='tcp')
command = "update rule 'test_rule_2' on 'wan' set log=False"
self.do_module_test(obj, command=command)
def test_rule_update_tcpflags_any_yes(self):
""" test updating log of a rule to yes """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', protocol='tcp', tcpflags_any='yes')
command = "update rule 'test_rule' on 'wan' set tcpflags_any=True"
self.do_module_test(obj, command=command)
def test_rule_update_tcpflags_any_no(self):
""" test updating log of a rule to no """
obj = dict(name='test_rule_4', source='any', destination='any', interface='lan_100', tcpflags_any='no')
command = "update rule 'test_rule_4' on 'lan_100' set tcpflags_any=False"
self.do_module_test(obj, command=command)
def test_rule_update_log_default(self):
""" test updating log of a rule to default """
obj = dict(name='test_rule_2', source='any', destination='any', interface='wan', protocol='tcp')
command = "update rule 'test_rule_2' on 'wan' set log=False"
self.do_module_test(obj, command=command)
def test_rule_update_negate_add_source(self):
""" test updating source of a rule with a not """
obj = dict(name='test_rule_2', source='!srv_admin', destination='any', interface='wan', protocol='tcp', log=True)
command = "update rule 'test_rule_2' on 'wan' set source='!srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_update_negate_add_destination(self):
""" test updating destination of a rule with a not """
obj = dict(name='test_rule_2', source='any', destination='!srv_admin', interface='wan', protocol='tcp', log=True)
command = "update rule 'test_rule_2' on 'wan' set destination='!srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_update_negate_remove_source(self):
""" test updating source of a rule remove the not """
obj = dict(name='not_rule_src', source='srv_admin', destination='any:port_ssh', interface='lan', protocol='tcp')
command = "update rule 'not_rule_src' on 'lan' set source='srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_update_negate_remove_destination(self):
""" test updating destination of a rule remove the not """
obj = dict(name='not_rule_dst', source='any', destination='srv_admin:port_ssh', interface='lan', protocol='tcp')
command = "update rule 'not_rule_dst' on 'lan' set destination='srv_admin'"
self.do_module_test(obj, command=command)
def test_rule_update_before(self):
""" test updating position of a rule to before another """
obj = dict(name='test_rule_3', source='any', destination='any:port_http', interface='wan', protocol='tcp', before='test_rule')
command = "update rule 'test_rule_3' on 'wan' set before='test_rule'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
def test_rule_update_before_bottom(self):
""" test updating position of a rule to bottom """
obj = dict(name='test_rule_3', source='any', destination='any:port_http', interface='wan', protocol='tcp', before='bottom')
command = "update rule 'test_rule_3' on 'wan' set before='bottom'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 3)
def test_rule_update_after(self):
""" test updating position of a rule to after another rule """
obj = dict(name='test_rule_3', source='any', destination='any:port_http', interface='wan', protocol='tcp', after='antilock_out_3')
command = "update rule 'test_rule_3' on 'wan' set after='antilock_out_3'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 3)
def test_rule_update_after_self(self):
""" test updating position of a rule to after same rule """
obj = dict(name='test_rule_3', source='any', destination='any', interface='wan', protocol='tcp', after='test_rule_3')
msg = 'Cannot specify the current rule in after'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_update_before_self(self):
""" test updating position of a rule to before same rule """
obj = dict(name='test_rule_3', source='any', destination='any', interface='wan', protocol='tcp', before='test_rule_3')
msg = 'Cannot specify the current rule in before'
self.do_module_test(obj, failed=True, msg=msg)
def test_rule_update_after_top(self):
""" test updating position of a rule to top """
obj = dict(name='test_rule_3', source='any', destination='any:port_http', interface='wan', protocol='tcp', after='top')
command = "update rule 'test_rule_3' on 'wan' set after='top'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
def test_rule_update_separator_top(self):
""" test updating position of a rule to top """
obj = dict(name='r2', source='any', destination='any', interface='vt1', protocol='tcp', after='top')
command = "update rule 'r2' on 'vt1' set after='top'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
self.check_separator_idx(obj['interface'], 'test_sep1', 1)
self.check_separator_idx(obj['interface'], 'test_sep2', 3)
def test_rule_update_separator_bottom(self):
""" test updating position of a rule to bottom """
obj = dict(name='r1', source='any', destination='any', interface='vt1', protocol='tcp', before='bottom')
command = "update rule 'r1' on 'vt1' set before='bottom'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 2)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 2)
def test_rule_update_separator_before_first(self):
""" test creation of a new rule at bottom """
obj = dict(name='r3', source='any', destination='any', interface='vt1', protocol='tcp', before='r1')
command = "update rule 'r3' on 'vt1' set before='r1'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 0)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 3)
def test_rule_update_separator_after_third(self):
""" test creation of a new rule at bottom """
obj = dict(name='r1', source='any', destination='any', interface='vt1', protocol='tcp', after='r3')
command = "update rule 'r1' on 'vt1' set after='r3'"
self.do_module_test(obj, command=command)
self.check_rule_idx(obj, 2)
self.check_separator_idx(obj['interface'], 'test_sep1', 0)
self.check_separator_idx(obj['interface'], 'test_sep2', 3)
def test_rule_update_queue_set(self):
""" test updating queue of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', queue='one_queue', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set queue='one_queue'"
self.do_module_test(obj, command=command)
def test_rule_update_queue_set_ack(self):
""" test updating queue and ackqueue of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', queue='one_queue', ackqueue='another_queue', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set queue='one_queue', ackqueue='another_queue'"
self.do_module_test(obj, command=command)
def test_rule_update_queue_unset_ack(self):
""" test updating ackqueue of a rule """
obj = dict(name='test_lan_100_2', source='any', destination='any', interface='lan_100', queue='one_queue', protocol='tcp')
command = "update rule 'test_lan_100_2' on 'lan_100' set ackqueue=none"
self.do_module_test(obj, command=command)
def test_rule_update_queue_unset(self):
""" test updating queue of a rule """
obj = dict(name='test_lan_100_3', source='any', destination='any', interface='lan_100', protocol='tcp')
command = "update rule 'test_lan_100_3' on 'lan_100' set queue=none"
self.do_module_test(obj, command=command)
def test_rule_update_limiter_set(self):
""" test updating limiter of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', in_queue='one_limiter', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set in_queue='one_limiter'"
self.do_module_test(obj, command=command)
def test_rule_update_limiter_set_out(self):
""" test updating limiter in and out of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', in_queue='one_limiter', out_queue='another_limiter', protocol='tcp')
command = "update rule 'test_rule' on 'wan' set in_queue='one_limiter', out_queue='another_limiter'"
self.do_module_test(obj, command=command)
def test_rule_update_limiter_unset_out(self):
""" test updating limiter out of a rule """
obj = dict(name='test_lan_100_4', source='any', destination='any', interface='lan_100', in_queue='one_limiter', protocol='tcp')
command = "update rule 'test_lan_100_4' on 'lan_100' set out_queue=none"
self.do_module_test(obj, command=command)
def test_rule_update_limiter_unset(self):
""" test updating limiter of a rule """
obj = dict(name='test_lan_100_5', source='any', destination='any', interface='lan_100', protocol='tcp')
command = "update rule 'test_lan_100_5' on 'lan_100' set in_queue=none"
self.do_module_test(obj, command=command)
def test_rule_update_gateway_set(self):
""" test updating gateway of a rule """
obj = dict(name='test_rule_3', source='any', destination='any:port_http', interface='wan', protocol='tcp', gateway='GW_WAN')
command = "update rule 'test_rule_3' on 'wan' set gateway='GW_WAN'"
self.do_module_test(obj, command=command)
def test_rule_update_gateway_unset(self):
""" test updating gateway of a rule """
obj = dict(name='antilock_out_1', source='any', destination='any:port_ssh', interface='lan', protocol='tcp', log=True)
command = "update rule 'antilock_out_1' on 'lan' set gateway=none"
self.do_module_test(obj, command=command)
def test_rule_update_tracker(self):
""" test updating tracker of a rule """
obj = dict(name='test_lan_100_5', source='any', destination='any', interface='lan_100', in_queue='one_limiter', protocol='tcp', tracker='1234')
command = "update rule 'test_lan_100_5' on 'lan_100' set tracker='1234'"
self.do_module_test(obj, command=command)
def test_rule_update_icmp(self):
""" test updating ipprotocol to icmptype """
obj = dict(name='r1', source='any', destination='any', interface='vt1', protocol='icmp', icmptype='echorep,echoreq')
command = "update rule 'r1' on 'vt1' set protocol='icmp', icmptype='echorep,echoreq'"
self.do_module_test(obj, command=command)
def test_rule_update_port_old_syntax(self):
""" test updating gateway of a rule """
obj = dict(name='test_rule_3', source='any', destination='any:port_ssh', interface='wan', protocol='tcp')
command = "update rule 'test_rule_3' on 'wan' set destination_port='port_ssh'"
self.do_module_test(obj, command=command)
def test_rule_update_port_new_syntax(self):
""" test updating gateway of a rule """
obj = dict(name='test_rule_3', source='any', destination='any', destination_port='port_ssh', interface='wan', protocol='tcp')
command = "update rule 'test_rule_3' on 'wan' set destination_port='port_ssh'"
self.do_module_test(obj, command=command)
def test_rule_update_schedule(self):
""" test updating scheduling of a rule """
obj = dict(name='test_rule', source='any', destination='any', interface='wan', action='pass', protocol='tcp', sched='workdays')
command = "update rule 'test_rule' on 'wan' set sched='workdays'"
self.do_module_test(obj, command=command)
def test_rule_update_remove_schedule(self):
""" test updating scheduling of a rule """
obj = dict(name='test_rule_sched', source='any', destination='any', interface='lan_100', action='pass', protocol='tcp')
command = "update rule 'test_rule_sched' on 'lan_100' set sched=none"
self.do_module_test(obj, command=command)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_setup.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_setup
from .pfsense_module import TestPFSenseModule
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
class TestPFSenseSetupModule(TestPFSenseModule):
module = pfsense_setup
def __init__(self, *args, **kwargs):
super(TestPFSenseSetupModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_setup_config.xml'
@staticmethod
def get_args_fields():
""" return params fields """
fields = ['hostname', 'domain', 'dns_addresses', 'dns_hostnames', 'dns_gateways', 'dnsallowoverride', 'dnslocalhost', 'timezone']
fields += ['timeservers', 'language', 'webguicss', 'webguifixedmenu', 'webguihostnamemenu', 'dashboardcolumns', 'interfacessort']
fields += ['dashboardavailablewidgetspanel', 'systemlogsfilterpanel', 'systemlogsmanagelogpanel', 'statusmonitoringsettingspanel']
fields += ['requirestatefilter', 'webguileftcolumnhyper', 'disablealiaspopupdetail', 'roworderdragging', 'logincss', 'loginshowhost']
return fields
def setUp(self):
""" mocking up """
super(TestPFSenseSetupModule, self).setUp()
# Remove validate command for webguicss which references files on the pfSense instance
self.mock_validate_webguicss = patch.dict('ansible_collections.pfsensible.core.plugins.modules.pfsense_setup.SETUP_ARG_ROUTE',
dict(webguicss=dict(parse=pfsense_setup.p2o_webguicss)))
self.mock_validate_webguicss.start()
self.mock_run_command = patch('ansible.module_utils.basic.AnsibleModule.run_command')
self.run_command = self.mock_run_command.start()
self.run_command.return_value = (0, '', '')
def tearDown(self):
""" mocking down """
super(TestPFSenseSetupModule, self).tearDown()
self.mock_validate_webguicss.stop()
self.run_command.stop()
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated xml definition """
return self.assert_find_xml_elt(self.xml_result, 'system')
def check_target_elt(self, obj, target_elt):
""" test the xml definition of setup elt """
webgui_elt = self.assert_find_xml_elt(target_elt, 'webgui')
def check_param(param, elt):
if obj.get(param) is not None:
self.assert_xml_elt_equal(elt, param, obj[param])
def check_bool_param(param, elt):
if obj.get(param) is not None:
if obj[param]:
self.assert_xml_elt_is_none_or_empty(elt, param)
else:
self.assert_not_find_xml_elt(elt, param)
check_param('hostname', target_elt)
check_param('domain', target_elt)
check_bool_param('dnsallowoverride', target_elt)
check_param('dnslocalhost', target_elt)
check_param('timezone', target_elt)
check_param('timeservers', target_elt)
check_param('language', target_elt)
if obj.get('webguicss') is not None:
self.assert_xml_elt_equal(webgui_elt, 'webguicss', obj['webguicss'] + '.css')
self.check_param_bool(obj, webgui_elt, 'webguifixedmenu', value_true='fixed')
check_param('webguihostnamemenu', webgui_elt)
check_param('dashboardcolumns', webgui_elt)
check_bool_param('interfacessort', webgui_elt)
check_bool_param('dashboardavailablewidgetspanel', webgui_elt)
check_bool_param('systemlogsfilterpanel', webgui_elt)
check_bool_param('systemlogsmanagelogpanel', webgui_elt)
check_bool_param('statusmonitoringsettingspanel', webgui_elt)
check_bool_param('requirestatefilter', webgui_elt)
check_bool_param('webguileftcolumnhyper', webgui_elt)
check_bool_param('disablealiaspopupdetail', webgui_elt)
check_bool_param('roworderdragging', webgui_elt)
check_bool_param('loginshowhost', webgui_elt)
check_param('logincss', webgui_elt)
# TODO: check dns_addresses, dns_hostnames, dns_gateways
##############
# tests
#
def test_setup_hostname(self):
""" test setup hostname """
setup = dict(hostname='acme')
command = "update setup general set hostname='acme'"
self.do_module_test(setup, command=command, state=None)
def test_setup_hostname_invalid(self):
""" test setup hostname """
setup = dict(hostname='acme.corp.com')
msg = "A valid hostname is specified, but the domain name part should be omitted"
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_hostname_invalid2(self):
""" test setup hostname """
setup = dict(hostname='(invalid)')
msg = "The hostname can only contain the characters A-Z, 0-9 and '-'. It may not start or end with '-'"
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_domain(self):
""" test setup domain """
setup = dict(domain='corp.com')
command = "update setup general set domain='corp.com'"
self.do_module_test(setup, command=command, state=None)
def test_setup_domain_invalid(self):
""" test setup domain """
setup = dict(domain='@invalid.com')
msg = "The domain may only contain the characters a-z, 0-9, '-' and '.'"
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dnsallowoverride(self):
""" test setup general """
setup = dict(dnsallowoverride=False)
command = "update setup general set dnsallowoverride=False"
self.do_module_test(setup, command=command, state=None)
def test_setup_dnslocalhost(self):
""" test setup dnslocalhost """
setup = dict(dnslocalhost='remote')
command = "update setup general set dnslocalhost='remote'"
self.do_module_test(setup, command=command, state=None)
def test_setup_webguifixedmenu(self):
""" test setup webguifixedmenu """
setup = dict(webguifixedmenu=True)
command = "update setup general set webguifixedmenu=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_interfacessort(self):
""" test setup interfacessort """
setup = dict(interfacessort=True)
command = "update setup general set interfacessort=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_dashboardavailablewidgetspanel(self):
""" test setup dashboardavailablewidgetspanel """
setup = dict(dashboardavailablewidgetspanel=True)
command = "update setup general set dashboardavailablewidgetspanel=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_systemlogsfilterpanel(self):
""" test setup systemlogsfilterpanel """
setup = dict(systemlogsfilterpanel=True)
command = "update setup general set systemlogsfilterpanel=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_systemlogsmanagelogpanel(self):
""" test setup systemlogsmanagelogpanel """
setup = dict(systemlogsmanagelogpanel=True)
command = "update setup general set systemlogsmanagelogpanel=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_statusmonitoringsettingspanel(self):
""" test setup statusmonitoringsettingspanel """
setup = dict(statusmonitoringsettingspanel=True)
command = "update setup general set statusmonitoringsettingspanel=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_requirestatefilter(self):
""" test setup requirestatefilter """
setup = dict(requirestatefilter=True)
command = "update setup general set requirestatefilter=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_webguileftcolumnhyper(self):
""" test setup webguileftcolumnhyper """
setup = dict(webguileftcolumnhyper=True)
command = "update setup general set webguileftcolumnhyper=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_disablealiaspopupdetail(self):
""" test setup disablealiaspopupdetail """
setup = dict(disablealiaspopupdetail=True)
command = "update setup general set disablealiaspopupdetail=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_roworderdragging(self):
""" test setup roworderdragging """
setup = dict(roworderdragging=True)
command = "update setup general set roworderdragging=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_loginshowhost(self):
""" test setup loginshowhost """
setup = dict(loginshowhost=True)
command = "update setup general set loginshowhost=True"
self.do_module_test(setup, command=command, state=None)
def test_setup_language(self):
""" test setup language """
setup = dict(language='fr_FR')
command = "update setup general set language='fr_FR'"
self.do_module_test(setup, command=command, state=None)
def test_setup_timeservers(self):
""" test setup timeservers """
setup = dict(timeservers='1.2.3.4 0.pool.ntp.org')
command = "update setup general set timeservers='1.2.3.4 0.pool.ntp.org'"
self.do_module_test(setup, command=command, state=None)
def test_setup_timezone(self):
""" test setup timezone """
setup = dict(timezone='Europe/Paris')
command = "update setup general set timezone='Europe/Paris'"
self.do_module_test(setup, command=command, state=None)
def test_setup_webguicss(self):
""" test setup webguicss """
setup = dict(webguicss='pfSense-dark')
command = "update setup general set webguicss='pfSense-dark'"
self.do_module_test(setup, command=command, state=None)
def test_setup_webguihostnamemenu(self):
""" test setup webguihostnamemenu """
setup = dict(webguihostnamemenu='fqdn')
command = "update setup general set webguihostnamemenu='fqdn'"
self.do_module_test(setup, command=command, state=None)
def test_setup_dashboardcolumns(self):
""" test setup dashboardcolumns """
setup = dict(dashboardcolumns='3')
command = "update setup general set dashboardcolumns='3'"
self.do_module_test(setup, command=command, state=None)
def test_setup_dashboardcolumns_invalid(self):
""" test setup dashboardcolumns """
setup = dict(dashboardcolumns='0')
msg = "The submitted Dashboard Columns value is invalid."
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_logincss(self):
""" test setup logincss """
setup = dict(logincss='ff0000')
command = "update setup general set logincss='ff0000'"
self.do_module_test(setup, command=command, state=None)
def test_setup_logincss_invalid(self):
""" test setup logincss """
setup = dict(logincss='gg0000')
msg = "logincss must be a six digits hexadecimal string."
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 8.8.8.8', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
command = "update setup general set dns_addresses='8.8.4.4 8.8.8.8', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN'"
self.do_module_test(setup, command=command, state=None)
def test_setup_dns_addresses_invalid(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 8.8.8.8 256.255.254.253', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
msg = 'A valid IP address must be specified for DNS server 256.255.254.253.'
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses_ipv6(self):
""" test setup dns """
setup = dict(dns_addresses='2001::8 8.8.4.4', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
command = "update setup general set dns_addresses='2001::8 8.8.4.4', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN'"
self.do_module_test(setup, command=command, state=None)
def test_setup_dns_addresses_invalid_ipv4(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 8.8.8.8', dns_hostnames='acme1 acme2', dns_gateways='none GW_LAN6')
msg = 'The IPv6 gateway "GW_LAN6" can not be specified for IPv4 DNS server "8.8.8.8".'
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses_invalid_ipv6(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 2001::8', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
msg = 'The IPv4 gateway "GW_WAN" can not be specified for IPv6 DNS server "2001::8".'
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses_invalid_gw(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 8.8.8.8', dns_hostnames='acme1 acme2', dns_gateways='none GW_ACME')
msg = 'The gateway "GW_ACME" does not exist.'
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses_invalid_gw2(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.4.4 192.168.1.1', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
msg = "A gateway can not be assigned to DNS '192.168.1.1' server which is on a directly connected network."
self.do_module_test(setup, msg=msg, state=None, failed=True)
def test_setup_dns_addresses_duplicates(self):
""" test setup dns """
setup = dict(dns_addresses='8.8.8.8 8.8.8.8', dns_hostnames='acme1 acme2', dns_gateways='none GW_WAN')
msg = "Each configured DNS server must have a unique IP address. Remove the duplicated IP."
self.do_module_test(setup, msg=msg, state=None, failed=True)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_user.py
================================================
# Copyright: (c) 2020, Orion Poplawski
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_user
from .pfsense_module import TestPFSenseModule
class TestPFSenseUserModule(TestPFSenseModule):
module = pfsense_user
def __init__(self, *args, **kwargs):
super(TestPFSenseUserModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_user_config.xml'
self.pfmodule = pfsense_user.PFSenseUserModule
@staticmethod
def runTest():
""" dummy function needed to instantiate this test module from another in python 2.7 """
pass
def get_target_elt(self, obj, absent=False, module_result=None):
""" return target elt from XML """
root_elt = self.assert_find_xml_elt(self.xml_result, 'system')
result = root_elt.findall("user[name='{0}']".format(obj['name']))
if len(result) == 1:
return result[0]
elif len(result) > 1:
self.fail('Found multiple users for name {0}.'.format(obj['name']))
else:
return None
def check_target_elt(self, obj, target_elt):
""" check XML definition of target elt """
self.check_param_equal(obj, target_elt, 'name')
self.check_param_equal(obj, target_elt, 'descr')
self.check_param_equal(obj, target_elt, 'scope', default='user')
self.check_param_equal(obj, target_elt, 'uid', default='2001')
# TODO - need to load groups
# self.check_param_equal(obj, target_elt, 'groups')
self.check_param_equal(obj, target_elt, 'password', xml_field='bcrypt-hash')
self.check_list_param_equal_or_not_find(obj, target_elt, 'priv')
self.check_param_equal_or_not_find(obj, target_elt, 'authorizedkeys')
##############
# tests
#
def test_user_create(self):
""" test creation of a new user """
obj = dict(name='user1', descr='User One', password='$2b$12$D2jkq4Iut3ODUBN0BCrDk.bV3J5N.MrY5YEnGvTXwxeNBkyxjbbtW')
self.do_module_test(obj, command="create user 'user1', descr='User One', uid='2001'")
def test_user_delete(self):
""" test deletion of a user """
obj = dict(name='testdel')
self.do_module_test(obj, command="delete user 'testdel'", delete=True)
def test_user_update_noop(self):
""" test not updating a user """
obj = dict(name='testdel', descr='Delete Me', uid='2000')
self.do_module_test(obj, command="delete user 'testdel'", changed=False)
def test_user_update_descr(self):
""" test updating descr of a user """
obj = dict(name='testdel', descr='Keep Me', uid='2000', password='$2b$12$D2jkq4Iut3ODUBN0BCrDk.bV3J5N.MrY5YEnGvTXwxeNBkyxjbbtW',
priv=['page-dashboard-all'])
self.do_module_test(obj, command="update user 'testdel' set descr='Keep Me'")
##############
# misc
#
def test_create_user_invalid_password(self):
""" test creation of a new user with invalid password """
obj = dict(name='user1', descr='User One', password='password')
self.do_module_test(obj, command="update user 'testdel'", failed=True, msg='Password (password) does not appear to be a bcrypt hash')
def test_delete_inexistent_user(self):
""" test deletion of an inexistent user """
obj = dict(name='nouser')
self.do_module_test(obj, state='absent', changed=False)
================================================
FILE: tests/unit/plugins/modules/test_pfsense_vlan.py
================================================
# Copyright: (c) 2018, Frederic Bor
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import sys
if sys.version_info < (2, 7):
pytestmark = pytest.mark.skip("pfSense Ansible modules require Python >= 2.7")
from ansible_collections.pfsensible.core.plugins.modules import pfsense_vlan
from ansible_collections.pfsensible.core.plugins.module_utils.vlan import PFSenseVlanModule
from .pfsense_module import TestPFSenseModule
class TestPFSenseVlanModule(TestPFSenseModule):
module = pfsense_vlan
def __init__(self, *args, **kwargs):
super(TestPFSenseVlanModule, self).__init__(*args, **kwargs)
self.config_file = 'pfsense_vlan_config.xml'
self.pfmodule = PFSenseVlanModule
##############
# tests utils
#
def get_target_elt(self, obj, absent=False, module_result=None):
""" get the generated vlan xml definition """
elt_filter = {}
elt_filter['if'] = self.unalias_interface(obj['interface'], physical=True)
elt_filter['tag'] = str(obj['vlan_id'])
return self.assert_has_xml_tag('vlans', elt_filter, absent=absent)
def check_target_elt(self, obj, target_elt):
""" test the xml definition of vlan """
# checking vlanif
self.assert_xml_elt_equal(target_elt, 'vlanif', '{0}.{1}'.format(self.unalias_interface(obj['interface'], physical=True), obj['vlan_id']))
# checking descr
if 'descr' in obj:
self.assert_xml_elt_equal(target_elt, 'descr', obj['descr'])
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'descr')
# checking priority
if 'priority' in obj and obj['priority'] is not None:
self.assert_xml_elt_equal(target_elt, 'pcp', str(obj['priority']))
else:
self.assert_xml_elt_is_none_or_empty(target_elt, 'pcp')
##############
# tests
#
def test_vlan_create(self):
""" test creation of a new vlan """
vlan = dict(vlan_id=100, interface='vmx0')
command = "create vlan 'vmx0.100', descr='', priority=''"
self.do_module_test(vlan, command=command)
def test_vlan_create_with_assigned_name(self):
""" test creation of a new vlan using assigned name """
vlan = dict(vlan_id=100, interface='vpn')
command = "create vlan 'vmx2.100', descr='', priority=''"
self.do_module_test(vlan, command=command)
def test_vlan_create_with_friendly_name(self):
""" test creation of a new vlan using friendly name """
vlan = dict(vlan_id=100, interface='opt2')
command = "create vlan 'vmx3.100', descr='', priority=''"
self.do_module_test(vlan, command=command)
def test_vlan_create_with_wrong_inteface(self):
""" test creation of a new vlan using wrong interface """
vlan = dict(vlan_id=100, interface='opt3')
msg = "Vlans can't be set on interface opt3"
self.do_module_test(vlan, failed=True, msg=msg)
def test_vlan_create_with_wrong_vlan(self):
""" test creation of a new vlan using wrong vlan_id """
vlan = dict(vlan_id=0, interface='opt2')
msg = "vlan_id must be between 1 and 4094 on interface opt2"
self.do_module_test(vlan, failed=True, msg=msg)
def test_vlan_create_with_wrong_prioriy(self):
""" test creation of a new vlan using wrong priority """
vlan = dict(vlan_id=100, interface='opt2', priority=8)
msg = "priority must be between 0 and 7 on interface opt2"
self.do_module_test(vlan, failed=True, msg=msg)
def test_vlan_create_with_priority(self):
""" test creation of a new vlan """
vlan = dict(vlan_id=100, interface='vmx0', descr='voice')
command = "create vlan 'vmx0.100', descr='voice', priority=''"
self.do_module_test(vlan, command=command)
def test_vlan_create_with_descr(self):
""" test creation of a new vlan """
vlan = dict(vlan_id=100, interface='vmx0', priority=5)
command = "create vlan 'vmx0.100', descr='', priority='5'"
self.do_module_test(vlan, command=command)
def test_vlan_delete(self):
""" test deletion of a vlan """
vlan = dict(vlan_id=100, interface='vmx1')
command = "delete vlan 'vmx1.100'"
self.do_module_test(vlan, delete=True, command=command)
def test_vlan_delete_used(self):
""" test deletion of a still used vlan """
vlan = dict(vlan_id=1100, interface='vmx1')
self.do_module_test(vlan, delete=True, failed=True, msg='vlan 1100 on vmx1 cannot be deleted because it is still being used as an interface')
def test_vlan_delete_unexistent(self):
""" test deletion of a vlan """
vlan = dict(vlan_id=1200, interface='vmx1')
self.do_module_test(vlan, delete=True, changed=False)
def test_vlan_update_noop(self):
""" test not updating a vlan """
vlan = dict(vlan_id=1100, interface='vmx1')
self.do_module_test(vlan, changed=False)
def test_vlan_update_priority(self):
""" test updating priority """
vlan = dict(vlan_id=1100, interface='vmx1', priority=1)
command = "update vlan 'vmx1.1100' set priority='1'"
self.do_module_test(vlan, changed=True, command=command)
def test_vlan_update_descr(self):
""" test updating descr """
vlan = dict(vlan_id=1100, interface='vmx1', descr='test')
command = "update vlan 'vmx1.1100' set descr='test'"
self.do_module_test(vlan, changed=True, command=command)