Skip to content

Commit

Permalink
Merge branch 'pfsensible:master' into nat_outbound_parse
Browse files Browse the repository at this point in the history
  • Loading branch information
opoplawski authored Jan 6, 2024
2 parents a799cc2 + 7ea3d86 commit dccc8b1
Show file tree
Hide file tree
Showing 30 changed files with 422 additions and 56 deletions.
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace: pfsensible
name: core

# The version of the collection. Must be compatible with semantic versioning
version: 0.5.3
version: 0.6.0

# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
Expand Down
19 changes: 19 additions & 0 deletions plugins/module_utils/__impl/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ def check_ip_address(self, address, ipprotocol, objtype, allow_networks=False, f
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() """

Expand Down
20 changes: 14 additions & 6 deletions plugins/module_utils/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,6 @@ def _params_to_obj(self):
else:
self.target_elt = self._get_interface_elt_by_display_name(self.obj['descr'])

if self.target_elt is not None:
self.result['ifname'] = self.target_elt.tag

return obj

def _validate_params(self):
Expand Down Expand Up @@ -214,6 +211,7 @@ def _copy_and_add_target(self):
""" create the XML target_elt """
self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
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 """
Expand All @@ -229,6 +227,7 @@ def _copy_and_update_target(self):
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):
Expand Down Expand Up @@ -322,10 +321,19 @@ def _pre_remove_target_elt(self):
""" processing before removing elt """
self.obj['if'] = self.target_elt.find('if').text

self._remove_all_separators(self.target_elt.tag)
self._remove_all_rules(self.target_elt.tag)
ifname = self.target_elt.tag
if self.pfsense.ifgroups is not None:
for ifgroup_elt in self.pfsense.ifgroups.findall("ifgroupentry"):
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(self.target_elt.tag)
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 """
Expand Down
19 changes: 9 additions & 10 deletions plugins/module_utils/interface_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True, type='str'),
descr=dict(type='str'),
members=dict(required=True, type='list', elements='str'),
members=dict(type='list', elements='str'),
)

INTERFACE_GROUP_REQUIRED_IF = [
['state', 'present', ['members']],
]

INTERFACE_GROUP_PHP_COMMAND = '''
require_once("interfaces.inc");
{0}
Expand Down Expand Up @@ -78,8 +82,9 @@ def _validate_params(self):
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 len(params['members']) > len(set(params['members'])):
self.module.fail_json(msg='List of members is not unique.')
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

##############################
Expand Down Expand Up @@ -183,12 +188,6 @@ def _log_fields(self, before=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))
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

def _log_update(self, before):
""" generate pseudo-CLI command to update an interface """
log = "update {0}".format(self._get_module_name(True))
values = self._log_fields(before)
self.result['commands'].append(log + ' set ' + values)
8 changes: 8 additions & 0 deletions plugins/module_utils/openvpn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ def _validate_params(self):
# check name
self.pfsense.validate_string(params['name'], 'openvpn')

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')

# Check auth clients
if len(params['authmode']) > 0:
system = self.pfsense.get_element('system')
Expand Down Expand Up @@ -250,6 +257,7 @@ def _find_target(self):
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
Expand Down
13 changes: 9 additions & 4 deletions plugins/module_utils/openvpn_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
class PFSenseOpenVPNOverrideModule(PFSenseModuleBase):
""" module managing pfSense OpenVPN Client Specific Overrides """

from ansible_collections.pfsensible.core.plugins.module_utils.__impl.checks import validate_openvpn_tunnel_network

@staticmethod
def get_argument_spec():
""" return argument spec """
Expand Down Expand Up @@ -121,10 +123,13 @@ def _validate_params(self):
# check name
self.pfsense.validate_string(params['name'], 'openvpn_override')

if params.get('tunnel_network') and not self.pfsense.is_ipv4_network(params['tunnel_network']):
self.module.fail_json(msg='A valid IPv4 network must be specified for tunnel_network.')
if params.get('tunnel_network6') and not self.pfsense.is_ipv6_network(params['tunnel_networkv6']):
self.module.fail_json(msg='A valid IPv6 network must be specified for tunnel_network6.')
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']):
Expand Down
13 changes: 13 additions & 0 deletions plugins/module_utils/openvpn_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ def _validate_params(self):
# check name
self.pfsense.validate_string(params['name'], 'openvpn')

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')

# Check auth servers
if len(params['authmode']) > 0:
system = self.pfsense.get_element('system')
Expand Down Expand Up @@ -321,6 +328,7 @@ def _find_target(self):
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
Expand All @@ -331,6 +339,11 @@ def _find_target(self):
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))

Expand Down
32 changes: 25 additions & 7 deletions plugins/module_utils/pfsense.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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:
Expand Down Expand Up @@ -55,7 +56,12 @@ class PFSenseModule(object):
parse_ip_network,
parse_port,
)
from ansible_collections.pfsensible.core.plugins.module_utils.__impl.checks import check_name, check_ip_address, validate_string
from ansible_collections.pfsensible.core.plugins.module_utils.__impl.checks import (
check_name,
check_ip_address,
validate_string,
validate_openvpn_tunnel_network,
)

def __init__(self, module, config='/cf/conf/config.xml'):
self.module = module
Expand Down Expand Up @@ -342,17 +348,29 @@ def element_to_dict(src_elt):
res[elt.tag] = value
return res

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 for added CAs
cas = self.get_elements('ca')
for elt in cas:
if xml_find(elt, 'descr').text == name:
return xml_find(elt, 'refid').text
return None
# 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():
Expand Down
8 changes: 6 additions & 2 deletions plugins/modules/pfsense_interface_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
members:
description: The members of the interface group.
type: list
required: yes
elements: str
"""

Expand Down Expand Up @@ -76,12 +75,17 @@
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.interface_group import PFSenseInterfaceGroupModule, INTERFACE_GROUP_ARGUMENT_SPEC
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)
Expand Down
22 changes: 22 additions & 0 deletions plugins/modules/pfsense_openvpn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@
'''

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
Expand Down
22 changes: 22 additions & 0 deletions plugins/modules/pfsense_openvpn_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,28 @@
"""

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
Expand Down
11 changes: 11 additions & 0 deletions tests/plays/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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`.

Update `host_vars/pfsense-test.yml` with IP addresses of your test pfSense install.
7 changes: 7 additions & 0 deletions tests/plays/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# config file for ansible -- https://ansible.com/
# ===============================================

[defaults]
inventory = hosts
collections_paths = ../../../..
remote_user = root
4 changes: 4 additions & 0 deletions tests/plays/host_vars/pfsense-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
# IP address of the interfaces
interface_ips:
wan: 192.168.122.228
2 changes: 2 additions & 0 deletions tests/plays/hosts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pfsense]
pfsense-test
Loading

0 comments on commit dccc8b1

Please sign in to comment.