Skip to content

Commit

Permalink
implemented openvpn_client_override module (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed Mar 17, 2024
1 parent 5f12267 commit 75b1dae
Show file tree
Hide file tree
Showing 17 changed files with 525 additions and 15 deletions.
93 changes: 93 additions & 0 deletions docs/source/modules/openvpn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ OpenVPN
**TESTS**: `ansibleguy.opnsense.openvpn_client <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/openvpn_client.yml>`_ |
`ansibleguy.opnsense.openvpn_server <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/openvpn_server.yml>`_ |
`ansibleguy.opnsense.openvpn_static_key <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/openvpn_static_key.yml>`_ |
`ansibleguy.opnsense.openvpn_client_override <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/openvpn_client_override.yml>`_ |
`ansibleguy.opnsense.openvpn_status <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/openvpn_status.yml>`_

**API Docs**: `OpenVPN <https://docs.opnsense.org/development/api/core/openvpn.html>`_
Expand Down Expand Up @@ -122,6 +123,33 @@ ansibleguy.opnsense.openvpn_static_key
"name","string","true","\-","description, desc","The name used to match this config to existing entries"
"mode","string","false","crypt","type","One of: 'auth', 'crypt'. Define the use of this key, authentication (--tls-auth) or authentication and encryption (--tls-crypt)"
"key","string","false","\-","\-","OpenVPN Static key. If empty - it will be auto-generated."
"reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst

ansibleguy.opnsense.openvpn_client_override
===========================================

.. csv-table:: Definition
:header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment"
:widths: 15 10 10 10 10 45

"name","string","true","\-","description, desc","The client's X.509 common-name used to match these override to"
"servers","list","true","\-","instances","Select the OpenVPN servers where this override applies to, leave empty for all"
"description","string","false","\-","desc","You may enter a description here for your reference (not parsed)."
"block","boolean","false","false","block_connection, block_client","Block this client connection based on its common name. Don't use this option to permanently disable a client due to a compromised key or password. Use a CRL (certificate revocation list) instead."
"push_reset","boolean","false","false","reset","Don't inherit the global push list for a specific client instance. NOTE: --push-reset is very thorough: it will remove almost all options from the list of to-be-pushed options. In many cases, some of these options will need to be re-configured afterwards - specifically, --topology subnet and --route-gateway will get lost and this will break client configs in many cases."
"network_tunnel_ip4","string","false","\-","tun_ip4, tunnel_ip4","Push virtual IP endpoints for client tunnel, overriding dynamic allocation."
"network_tunnel_ip6","string","false","\-","tun_ip6, tunnel_ip6","Push virtual IP endpoints for client tunnel, overriding dynamic allocation."
"network_local","list","false","\-","net_local, push_route","These are the networks accessible by the client, these are pushed via route{-ipv6} clauses in OpenVPN to the client."
"network_remote","list","false","\-","net_remote, route","Remote networks for the server, these are configured via iroute{-ipv6} clauses in OpenVPN and inform the server to send these networks to this specific client."
"route_gateway","string","false","\-","route_gw, rt_gw","Specify a default gateway to use for the connected client. Without one set the first address in the netblock is being offered. When segmenting the tunnel (server) network, this one might not be accessible from the client."
"redirect_gateway","list","false","\-","redirect_gw, redir_gw","Automatically execute routing commands to cause all outgoing IP traffic to be redirected over the VPN."
"register_dns","boolean","false","false","\-","Run ipconfig /flushdns and ipconfig /registerdns on connection initiation. This is known to kick Windows into recognizing pushed DNS servers."
"domain","string","false","\-","dns_domain","Set Connection-specific DNS Suffix."
"domain_list","list","false","\-","dns_domain_search","Add name to the domain search list. Repeat this option to add more entries. Up to 10 domains are supported"
"dns_servers","list","false","\-","dns","Set primary domain name server IPv4 or IPv6 address. Repeat this option to set secondary DNS server addresses."
"ntp_servers","list","false","\-","ntp","Set primary NTP server address (Network Time Protocol). Repeat this option to set secondary NTP server addresses."
"wins_servers","list","false","\-","wins","Set primary WINS server address (NetBIOS over TCP/IP Name Server). Repeat this option to set secondary WINS server addresses."
"reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst

ansibleguy.opnsense.openvpn_status
==================================
Expand Down Expand Up @@ -446,6 +474,71 @@ ansibleguy.opnsense.openvpn_static_key
----

ansibleguy.opnsense.openvpn_client_override
===========================================

.. code-block:: yaml
- hosts: localhost
gather_facts: no
module_defaults:
group/ansibleguy.opnsense.all:
firewall: 'opnsense.template.ansibleguy.net'
api_credential_file: '/home/guy/.secret/opn.key'
tasks:
- name: Example
ansibleguy.opnsense.openvpn_client_override:
name: 'example'
# servers: []
# description: ''
# block: false
# push_reset: false
# network_tunnel_ip4: ''
# network_tunnel_ip6: ''
# network_local: []
# network_remote: []
# route_gateway: ''
# redirect_gateway: []
# register_dns: false
# domain: ''
# domain_list: []
# dns_servers: []
# ntp_servers: []
# wins_servers: []
# reload: true
# enabled: true
- name: Adding
ansibleguy.opnsense.openvpn_client_override:
name: 'test1'
servers: 'test-server'
network_tunnel_ip4: '192.168.77.3/29'
network_local: ['192.168.78.128/27']
domain: 'test.vpn'
dns_servers: ['1.1.1.1', '8.8.8.8']
- name: Blocking client
ansibleguy.opnsense.openvpn_client_override:
name: 'test2'
block: true
- name: Listing
ansibleguy.opnsense.list:
# target: 'openvpn_client_override'
register: existing_entries
- name: Printing tests
ansible.builtin.debug:
var: existing_entries.data
- name: Removing
ansibleguy.opnsense.openvpn_client_override:
name: 'test1'
state: 'absent'
----

ansibleguy.opnsense.openvpn_status
==================================

Expand Down
2 changes: 2 additions & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,5 @@ plugin_routing:
redirect: ansibleguy.opnsense.webproxy_pac_match
proxy_pac_rule:
redirect: ansibleguy.opnsense.webproxy_pac_rule
openvpn_client_overwrite:
redirect: ansibleguy.opnsense.openvpn_client_override
12 changes: 8 additions & 4 deletions plugins/module_utils/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,15 @@ def _search_path_handling(self, data: dict, ak_path: str = None) -> dict:
elif hasattr(self.i, self.ATTR_AK_PATH):
ak_path = getattr(self.i, self.ATTR_AK_PATH)

if ak_path is not None:
for k in ak_path.split(self.ATTR_AK_PATH_SPLIT_CHAR):
data = data[k]
try:
if ak_path is not None:
for k in ak_path.split(self.ATTR_AK_PATH_SPLIT_CHAR):
data = data[k]

return data
return data

except KeyError as err:
exit_bug(f"Got invalid API_KEY_PATH: '{ak_path}' not matching data '{data}'")

def get_existing(self, diff_filter: bool = False) -> list:
if diff_filter:
Expand Down
10 changes: 10 additions & 0 deletions plugins/module_utils/helper/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,16 @@ def get_key_by_value_end_from_selection(selection: dict, value: str) -> (str, No

return None


def get_key_by_value_beg_from_selection(selection: dict, value: str) -> (str, None):
if isinstance(selection, dict):
for key, values in selection.items():
if 'value' in values and values['value'].startswith(value):
return key

return None


def to_digit(data: bool) -> int:
return 1 if data else 0

Expand Down
1 change: 0 additions & 1 deletion plugins/module_utils/main/_tmpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class TMPL(BaseModule):
FIELDS_TRANSLATE = {
'field1': 'apifield1',
}
FIELDS_BOOL_INVERT = []
FIELDS_TYPING = {
'bool': ['enabled'],
'list': [],
Expand Down
4 changes: 1 addition & 3 deletions plugins/module_utils/main/ipsec_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ def __init__(self, module: AnsibleModule, result: dict, session: Session = None)
def check(self) -> None:
if self.p['state'] == 'present':
if is_unset(self.p['network']):
self.m.fail_json(
"You need to provide a 'network' to create an IPSec-Pool!"
)
self.m.fail_json("You need to provide a 'network' to create an IPSec-Pool!")

self._base_check()
1 change: 1 addition & 0 deletions plugins/module_utils/main/ipsec_psk.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PreSharedKey(BaseModule):
}
EXIST_ATTR = 'psk'
TIMEOUT = 30.0 # ipsec reload
FIELDS_DIFF_NO_LOG = ['psk']

def __init__(self, module: AnsibleModule, result: dict, session: Session = None):
BaseModule.__init__(self=self, m=module, r=result, s=session)
Expand Down
1 change: 0 additions & 1 deletion plugins/module_utils/main/openvpn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ class Client(BaseModule):
'mss_fix': 'mssfix',
'key': 'tls_key',
}
FIELDS_BOOL_INVERT = []
FIELDS_TYPING = {
'bool': ['enabled', 'mss_fix'],
'list': ['network_local', 'network_remote', 'options', 'remote'],
Expand Down
72 changes: 72 additions & 0 deletions plugins/module_utils/main/openvpn_client_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from ansible.module_utils.basic import AnsibleModule

from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \
Session
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \
is_unset, get_key_by_value_beg_from_selection
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule


class Override(BaseModule):
FIELD_ID = 'name'
CMDS = {
'add': 'add',
'del': 'del',
'set': 'set',
'toggle': 'toggle',
'search': 'search',
'detail': 'get',
}
API_KEY_PATH = 'cso'
API_MOD = 'openvpn'
API_CONT = 'client_overwrites'
API_CONT_REL = 'service'
API_CMD_REL = 'reconfigure'
FIELDS_CHANGE = [
'servers', 'description', 'block', 'push_reset', 'network_tunnel_ip4', 'network_tunnel_ip6',
'network_local', 'network_remote', 'route_gateway', 'redirect_gateway', 'register_dns',
'domain', 'domain_list', 'dns_servers', 'ntp_servers', 'wins_servers',
]
FIELDS_ALL = ['enabled', FIELD_ID]
FIELDS_ALL.extend(FIELDS_CHANGE)
FIELDS_TRANSLATE = {
'name': 'common_name',
'network_tunnel_ip4': 'tunnel_network',
'network_tunnel_ip6': 'tunnel_networkv6',
'network_local': 'local_networks',
'network_remote': 'remote_networks',
'domain': 'dns_domain',
'domain_list': 'dns_domain_search',
}
FIELDS_TYPING = {
'bool': ['enabled', 'block', 'push_reset', 'register_dns'],
'list': [
'servers', 'redirect_gateway', 'network_local', 'network_remote', 'domain_list',
'dns_servers', 'ntp_servers', 'wins_servers',
],
}
EXIST_ATTR = 'override'

def __init__(self, module: AnsibleModule, result: dict, session: Session = None):
BaseModule.__init__(self=self, m=module, r=result, s=session)
self.override = {}

def check(self) -> None:
if self.p['state'] == 'present':
if is_unset(self.p['servers']):
self.m.fail_json("You need to provide at least one 'servers' to create an Client-Overwrite!")

self._base_check()

if not is_unset(self.p['servers']):
servers = []
for server in self.p['servers']:
servers.append(get_key_by_value_beg_from_selection(
selection=self.b.raw['servers'],
value=server,
))

self.p['servers'] = servers

if self.p['state'] == 'present':
self.r['diff']['after'] = self.b.build_diff(data=self.p)
1 change: 0 additions & 1 deletion plugins/module_utils/main/openvpn_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class Server(BaseModule):
'data_ciphers': 'data-ciphers',
'data_cipher_fallback': 'data-ciphers-fallback',
}
FIELDS_BOOL_INVERT = []
FIELDS_TYPING = {
'bool': ['enabled', 'mss_fix', 'ocsp', 'user_as_cn', 'user_cn_strict', 'register_dns'],
'list': [
Expand Down
1 change: 0 additions & 1 deletion plugins/module_utils/main/openvpn_static_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class Key(BaseModule):
FIELDS_TRANSLATE = {
'name': 'description',
}
FIELDS_BOOL_INVERT = []
FIELDS_TYPING = {
'select': ['mode'],
}
Expand Down
8 changes: 6 additions & 2 deletions plugins/modules/list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Copyright: (C) 2023, AnsibleGuy <guy@ansibleguy.net>
# Copyright: (C) 2024, AnsibleGuy <guy@ansibleguy.net>
# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt)

# module to query running config
Expand Down Expand Up @@ -34,7 +34,7 @@
'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'ipsec_connection', 'ipsec_pool',
'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general',
'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule',
'openvpn_instance', 'openvpn_static_key',
'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override',
]


Expand Down Expand Up @@ -361,6 +361,10 @@ def run_module():
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.openvpn_static_key import \
Key as Target_Obj

elif target == 'openvpn_client_override':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.openvpn_client_override import \
Override as Target_Obj

except AttributeError:
module_dependency_error()

Expand Down
Loading

0 comments on commit 75b1dae

Please sign in to comment.