From 1d97b5ef58f603e3fff5f3af5365ded224820305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 16:00:43 -0300 Subject: [PATCH 01/24] add support to create L2TP and PPTP VPN connection --- plugins/modules/net_tools/nmcli.py | 105 ++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 3c59b7efea0..b50e5f6e4b1 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -45,8 +45,8 @@ - The interface to bind the connection to. - The connection will only be applicable to this interface name. - A special value of C('*') can be used for interface-independent connections. - - The ifname argument is mandatory for all connection types except bond, team, bridge and vlan. - - This parameter defaults to C(conn_name) when left unset. + - The ifname argument is mandatory for all connection types except bond, team, bridge, vlan and vpn. + - This parameter defaults to C(conn_name) when left unset for all connection types except vpn that removes it. type: str type: description: @@ -58,7 +58,7 @@ - Type C(wireguard) is added in community.general 4.3.0 type: str choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi, gsm, - wireguard ] + wireguard, vpn ] mode: description: - This is the type of device or network connection that you wish to create for a bond or bridge. @@ -905,6 +905,54 @@ description: C(NMSettingSecretFlags) indicating how to handle the I(wireguard.private-key) property. type: int choices: [ 0, 1, 2 ] + vpn: + description: + - The configuration to a VPN connection (PPTP and L2TP). + - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host + type: dict + suboptions: + permissions: + description: User that will have persmission to use the connection + type: str + required: true + service-type: + description: This defines the service type of connection. + type: str + required: true + choices: [ pptp, l2tp ] + gateway: + description: The gateway to connection. It can be an IP (for example C(192.0.2.1)) or a FQDN address (for example C(vpn.example.com)) + type: str + required: true + password-flags: + description: + - NMSettingSecretFlags indicating how to handle the I(password) property. + - 'Following choices are allowed: + C(0) B(NONE): The system is responsible for providing and storing this secret (default), + C(1) B(AGENT_OWNED): A user secret agent is responsible for providing and storing this secret; when it is required agents will be + asked to retrieve it + C(2) B(NOT_SAVED): This secret should not be saved, but should be requested from the user each time it is needed + C(4) B(NOT_REQUIRED): In situations where it cannot be automatically determined that the secret is required + (some VPNs and PPP providers do not require all secrets) this flag indicates that the specific secret is not required.' + type: int + choices: [ 0, 1, 2 , 4 ] + default: 0 + user: + description: Username provided by VPN administrator + type: str + required: true + ipsec-enabled: + description: + - Enable or disable IPSec tunnel to L2TP host. + - This option is need when C(service-type) is C(l2tp) + type: bool + choices: [ yes, no ] + ipsec-psk: + description: + - The Pre-shared key encoded. + - You can encode using this linux command: C(echo "0s"$(base64 <<<'[YOUR PRE-SHARED KEY]' | rev | cut -c5- | rev)) + - This is only used when C(ipsec-enabled = yes) + type: str ''' EXAMPLES = r''' @@ -1288,6 +1336,27 @@ autoconnect: true state: present +- name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' + block: + - name: Encript pre-shared key + shell: echo "0s"$(base64 <<<'Brittany123' | rev | cut -c5- | rev) + register: psk + + - name: Create the connection + community.general.nmcli: + type: vpn + conn_name: my-vpn-connection + vpn: + permissions: {{ ansible_user }}, + service-type: l2tp, + gateway: vpn.example.com, + password-flags: 2, + user: brittany, + ipsec-enabled: true, + ipsec-psk: {{ psk.stdout }} + autoconnect: false + state: present + ''' RETURN = r"""# @@ -1404,6 +1473,7 @@ def __init__(self, module): self.wifi_sec = module.params['wifi_sec'] self.gsm = module.params['gsm'] self.wireguard = module.params['wireguard'] + self.vpn = module.params['vpn'] if self.method4: self.ipv4_method = self.method4 @@ -1592,6 +1662,29 @@ def connection_options(self, detect_change=False): options.update({ 'wireguard.%s' % name: value, }) + elif self.type == 'vpn': + if self.vpn: + vpn_data_values = '' + for name, value in self.vpn.items(): + if name == 'service-type': + options.update({ + 'vpn-type': value, + }) + elif name == 'permissions': + options.update({ + 'connection.permissions': value, + }) + else: + if vpn_data_values != '': + vpn_data_values += ', ' + + if isinstance(value, bool): + value = self.bool_to_string(value) + + vpn_data_values += '%s=%s' % (name, value) + options.update({ + 'vpn.data': vpn_data_values, + }) # Convert settings values based on the situation. for setting, value in options.items(): setting_type = self.settings_type(setting) @@ -1832,6 +1925,10 @@ def connection_update(self, nmcli_command): 'connection.interface-name': ifname, } + # VPN doesn't need an interface but if sended it must be a valid interface. + if self.type == 'vpn' and self.ifname is None: + options.__delitem__('connection.interface-name') + options.update(self.connection_options()) # Constructing the command. @@ -2064,6 +2161,7 @@ def main(): 'wifi', 'gsm', 'wireguard', + 'vpn', ]), ip4=dict(type='list', elements='str'), gw4=dict(type='str'), @@ -2163,6 +2261,7 @@ def main(): wifi_sec=dict(type='dict', no_log=True), gsm=dict(type='dict'), wireguard=dict(type='dict'), + vpn=dict(type='dict'), ), mutually_exclusive=[['never_default4', 'gw4'], ['routes4_extended', 'routes4'], From c013a9eed4fb63dfdc42c780f0b50f37aaeb4fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:51:28 -0300 Subject: [PATCH 02/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index b50e5f6e4b1..58990e4c0ee 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -951,7 +951,7 @@ description: - The Pre-shared key encoded. - You can encode using this linux command: C(echo "0s"$(base64 <<<'[YOUR PRE-SHARED KEY]' | rev | cut -c5- | rev)) - - This is only used when C(ipsec-enabled = yes) + - This is only used when I(ipsec-enabled=true). type: str ''' From 69019aa299119d4a81698024e71edf1852511014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:51:59 -0300 Subject: [PATCH 03/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 58990e4c0ee..7c65f8d1e07 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -910,6 +910,7 @@ - The configuration to a VPN connection (PPTP and L2TP). - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host type: dict + version_added: 5.1.0 suboptions: permissions: description: User that will have persmission to use the connection From b92fcf5b8c6f6e7f17d7bb6ac69135aa43de73f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:08 -0300 Subject: [PATCH 04/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 7c65f8d1e07..f07bbb62ce6 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -908,7 +908,7 @@ vpn: description: - The configuration to a VPN connection (PPTP and L2TP). - - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host + - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host. type: dict version_added: 5.1.0 suboptions: From 51ad4409a0c0cb2c9823a9dd4b1acbf606d51365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:14 -0300 Subject: [PATCH 05/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index f07bbb62ce6..0ca53542c67 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -907,7 +907,7 @@ choices: [ 0, 1, 2 ] vpn: description: - - The configuration to a VPN connection (PPTP and L2TP). + - Configuration of a VPN connection (PPTP and L2TP). - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host. type: dict version_added: 5.1.0 From b59d9247b867b3604a6f5d4855339028600f5a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:22 -0300 Subject: [PATCH 06/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 0ca53542c67..0a07dfc219d 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -913,7 +913,7 @@ version_added: 5.1.0 suboptions: permissions: - description: User that will have persmission to use the connection + description: User that will have permission to use the connection. type: str required: true service-type: From 254de2fa83f5953efb55912babbbeeceace04824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:28 -0300 Subject: [PATCH 07/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 0a07dfc219d..d7eb8e6d54e 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -922,7 +922,7 @@ required: true choices: [ pptp, l2tp ] gateway: - description: The gateway to connection. It can be an IP (for example C(192.0.2.1)) or a FQDN address (for example C(vpn.example.com)) + description: The gateway to connection. It can be an IP address (for example C(192.0.2.1)) or a FQDN address (for example C(vpn.example.com)). type: str required: true password-flags: From 72593ab6b8e5a54a9c81cdbe09545dd9026b5c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:34 -0300 Subject: [PATCH 08/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index d7eb8e6d54e..755403387c1 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -939,7 +939,7 @@ choices: [ 0, 1, 2 , 4 ] default: 0 user: - description: Username provided by VPN administrator + description: Username provided by VPN administrator. type: str required: true ipsec-enabled: From 11f82620c7b68329708cd9e3ce1c4d6715d68747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 17:52:38 -0300 Subject: [PATCH 09/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 755403387c1..db16604305d 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -945,7 +945,7 @@ ipsec-enabled: description: - Enable or disable IPSec tunnel to L2TP host. - - This option is need when C(service-type) is C(l2tp) + - This option is need when C(service-type) is C(l2tp). type: bool choices: [ yes, no ] ipsec-psk: From 1b6736c661760cd758e674c383113b411112dda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 19:40:56 -0300 Subject: [PATCH 10/24] apply changes pointed on tests and review - add changelog fragment - change example code to use jinja2 in place of shell command --- .../fragments/4746-add-vpn-support-nmcli.yaml | 2 ++ plugins/modules/net_tools/nmcli.py | 33 ++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 changelogs/fragments/4746-add-vpn-support-nmcli.yaml diff --git a/changelogs/fragments/4746-add-vpn-support-nmcli.yaml b/changelogs/fragments/4746-add-vpn-support-nmcli.yaml new file mode 100644 index 00000000000..1ab7e8a20a7 --- /dev/null +++ b/changelogs/fragments/4746-add-vpn-support-nmcli.yaml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - adds ``vpn`` type and parameter for supporting VPN with service type L2TP and PPTP (https://github.com/ansible-collections/community.general/pull/4746). \ No newline at end of file diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index db16604305d..57ba2f79811 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -908,7 +908,8 @@ vpn: description: - Configuration of a VPN connection (PPTP and L2TP). - - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host. + - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) + if host has UI - are installed on the host. type: dict version_added: 5.1.0 suboptions: @@ -922,7 +923,8 @@ required: true choices: [ pptp, l2tp ] gateway: - description: The gateway to connection. It can be an IP address (for example C(192.0.2.1)) or a FQDN address (for example C(vpn.example.com)). + description: The gateway to connection. It can be an IP address (for example C(192.0.2.1)) + or a FQDN address (for example C(vpn.example.com)). type: str required: true password-flags: @@ -950,8 +952,10 @@ choices: [ yes, no ] ipsec-psk: description: - - The Pre-shared key encoded. - - You can encode using this linux command: C(echo "0s"$(base64 <<<'[YOUR PRE-SHARED KEY]' | rev | cut -c5- | rev)) + - The pre-shared key in base64 encoding. + - > + You can encode using this linux command: C(echo "0s"$(base64 <<<'[YOUR PRE-SHARED KEY]' | rev | cut -c5- | rev)) + or just using this Ansible jinja2 expression: C("0s{{ ('[YOUR PRE-SHARED KEY]' | b64encode) }}"). - This is only used when I(ipsec-enabled=true). type: str ''' @@ -1339,22 +1343,21 @@ - name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' block: - - name: Encript pre-shared key - shell: echo "0s"$(base64 <<<'Brittany123' | rev | cut -c5- | rev) - register: psk + - ansible.builtin.set_fact: + psk: "0s{{ ('Brittany123' | b64encode) }}" - name: Create the connection community.general.nmcli: type: vpn conn_name: my-vpn-connection vpn: - permissions: {{ ansible_user }}, - service-type: l2tp, - gateway: vpn.example.com, - password-flags: 2, - user: brittany, - ipsec-enabled: true, - ipsec-psk: {{ psk.stdout }} + permissions: "{{ ansible_user }}" + service-type: l2tp + gateway: vpn.example.com + password-flags: 2 + user: brittany + ipsec-enabled: true + ipsec-psk: "{{ psk }}" autoconnect: false state: present @@ -1681,7 +1684,7 @@ def connection_options(self, detect_change=False): if isinstance(value, bool): value = self.bool_to_string(value) - + vpn_data_values += '%s=%s' % (name, value) options.update({ 'vpn.data': vpn_data_values, From 6db65525502a3864a393df257d13d88c7390d347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 29 May 2022 20:36:04 -0300 Subject: [PATCH 11/24] removes trailing whitespace --- plugins/modules/net_tools/nmcli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 57ba2f79811..9af4d26cfee 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -908,7 +908,7 @@ vpn: description: - Configuration of a VPN connection (PPTP and L2TP). - - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) + - In order to use L2TP you need to be sure that C(network-manager-l2tp) - and C(network-manager-l2tp-gnome) if host has UI - are installed on the host. type: dict version_added: 5.1.0 @@ -923,7 +923,7 @@ required: true choices: [ pptp, l2tp ] gateway: - description: The gateway to connection. It can be an IP address (for example C(192.0.2.1)) + description: The gateway to connection. It can be an IP address (for example C(192.0.2.1)) or a FQDN address (for example C(vpn.example.com)). type: str required: true From ed5fa87eafdaa25a5679fba9a975145fe93c30a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Mon, 30 May 2022 10:30:32 -0300 Subject: [PATCH 12/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 9af4d26cfee..7ad65c672a5 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1931,7 +1931,7 @@ def connection_update(self, nmcli_command): # VPN doesn't need an interface but if sended it must be a valid interface. if self.type == 'vpn' and self.ifname is None: - options.__delitem__('connection.interface-name') + del options['connection.interface-name'] options.update(self.connection_options()) From ceeb8eb28b7c2da548b80e6a7f9987a0152e22fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Mon, 30 May 2022 10:30:48 -0300 Subject: [PATCH 13/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 7ad65c672a5..72329df818c 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1344,7 +1344,7 @@ - name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' block: - ansible.builtin.set_fact: - psk: "0s{{ ('Brittany123' | b64encode) }}" + psk: "0s{{ ('Brittany123' | ansible.builtin.b64encode) }}" - name: Create the connection community.general.nmcli: From e4ce2388d9accad32dbb3cc4cc8c92b50d7ca6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Mon, 30 May 2022 21:05:33 -0300 Subject: [PATCH 14/24] removes linux command from examples --- plugins/modules/net_tools/nmcli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 72329df818c..53c7a626c16 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -954,8 +954,7 @@ description: - The pre-shared key in base64 encoding. - > - You can encode using this linux command: C(echo "0s"$(base64 <<<'[YOUR PRE-SHARED KEY]' | rev | cut -c5- | rev)) - or just using this Ansible jinja2 expression: C("0s{{ ('[YOUR PRE-SHARED KEY]' | b64encode) }}"). + You can encode using this Ansible jinja2 expression: C("0s{{ ('[YOUR PRE-SHARED KEY]' | b64encode) }}"). - This is only used when I(ipsec-enabled=true). type: str ''' From c18f2af57ddf78c49b78b1755108526c6d6b2452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Thu, 2 Jun 2022 10:11:52 -0300 Subject: [PATCH 15/24] remove unnecessary brakets Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 53c7a626c16..ffce055953c 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -954,7 +954,7 @@ description: - The pre-shared key in base64 encoding. - > - You can encode using this Ansible jinja2 expression: C("0s{{ ('[YOUR PRE-SHARED KEY]' | b64encode) }}"). + You can encode using this Ansible jinja2 expression: C("0s{{ '[YOUR PRE-SHARED KEY]' | b64encode }}"). - This is only used when I(ipsec-enabled=true). type: str ''' From 46049c8742de00d232c9c1e9a00d07aaa2b2d4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Thu, 2 Jun 2022 10:12:12 -0300 Subject: [PATCH 16/24] remove unnecessary brakets Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index ffce055953c..1899d9db021 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1343,7 +1343,7 @@ - name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' block: - ansible.builtin.set_fact: - psk: "0s{{ ('Brittany123' | ansible.builtin.b64encode) }}" + psk: "0s{{ 'Brittany123' | ansible.builtin.b64encode }}" - name: Create the connection community.general.nmcli: From 72f90dcb4cc25f0827f4764b03d2d9a41edc5570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Thu, 2 Jun 2022 10:13:21 -0300 Subject: [PATCH 17/24] simplify psk encoding on example Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 1899d9db021..d12d9206cbc 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1356,7 +1356,7 @@ password-flags: 2 user: brittany ipsec-enabled: true - ipsec-psk: "{{ psk }}" + ipsec-psk: "0s{{ 'Brittany123' | ansible.builtin.b64encode }}" autoconnect: false state: present From 7cf041ee30babf2de3b23d31ede870c9acc95a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Thu, 2 Jun 2022 10:13:36 -0300 Subject: [PATCH 18/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index d12d9206cbc..775dbdc660f 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -931,10 +931,10 @@ description: - NMSettingSecretFlags indicating how to handle the I(password) property. - 'Following choices are allowed: - C(0) B(NONE): The system is responsible for providing and storing this secret (default), + C(0) B(NONE): The system is responsible for providing and storing this secret (default); C(1) B(AGENT_OWNED): A user secret agent is responsible for providing and storing this secret; when it is required agents will be - asked to retrieve it - C(2) B(NOT_SAVED): This secret should not be saved, but should be requested from the user each time it is needed + asked to retrieve it; + C(2) B(NOT_SAVED): This secret should not be saved, but should be requested from the user each time it is needed; C(4) B(NOT_REQUIRED): In situations where it cannot be automatically determined that the secret is required (some VPNs and PPP providers do not require all secrets) this flag indicates that the specific secret is not required.' type: int From 6521bb889f58863955bcf01bffc28bbf8806eaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Thu, 2 Jun 2022 10:13:45 -0300 Subject: [PATCH 19/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 775dbdc660f..5ba30f49383 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -55,7 +55,8 @@ - Type C(generic) is added in Ansible 2.5. - Type C(infiniband) is added in community.general 2.0.0. - Type C(gsm) is added in community.general 3.7.0. - - Type C(wireguard) is added in community.general 4.3.0 + - Type C(wireguard) is added in community.general 4.3.0. + - Type C(vpn) is added in community.general 5.1.0. type: str choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi, gsm, wireguard, vpn ] From f3ac6648342990f2cebc315f8098cee3b1d93825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sat, 4 Jun 2022 23:25:02 -0300 Subject: [PATCH 20/24] add unit tests - test unchenged l2tp and pptp vpn connections - test create l2tp and pptp vpn connections - fix is_connection_changed to remove default ifname attribuition --- plugins/modules/net_tools/nmcli.py | 4 + .../plugins/modules/net_tools/test_nmcli.py | 177 ++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 5ba30f49383..281122652aa 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -2126,6 +2126,10 @@ def is_connection_changed(self): 'connection.interface-name': self.ifname, } + # VPN doesn't need an interface but if sended it must be a valid interface. + if self.type == 'vpn' and self.ifname is None: + del options['connection.interface-name'] + if not self.type: current_con_type = self.show_connection().get('connection.type') if current_con_type: diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py index 546cae20e89..a34008fb288 100644 --- a/tests/unit/plugins/modules/net_tools/test_nmcli.py +++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py @@ -98,6 +98,12 @@ 'state': 'absent', '_ansible_check_mode': True, }, + { + 'type': 'vpn', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, ] TESTCASE_GENERIC = [ @@ -1177,6 +1183,68 @@ wireguard.ip6-auto-default-route: -1 (default) """ +TESTCASE_VPN_L2TP = [ + { + 'type': 'vpn', + 'conn_name': 'vpn_l2tp', + 'vpn': { + 'permissions': 'brittany', + 'service-type': 'l2tp', + 'gateway': 'vpn.example.com', + 'password-flags': '2', + 'user': 'brittany', + 'ipsec-enabled': 'true', + 'ipsec-psk': 'QnJpdHRhbnkxMjM=', + }, + 'autoconnect': 'false', + 'state': 'present', + '_ansible_check_mode': False, + }, +] + +TESTCASE_VPN_L2TP_SHOW_OUTPUT = """\ +connection.id: vpn_l2tp +connection.type: vpn +connection.autoconnect: no +connection.permissions: brittany +ipv4.method: auto +ipv6.method: auto +vpn-type: l2tp +vpn.service-type: org.freedesktop.NetworkManager.l2tp +vpn.data: gateway=vpn.example.com, password-flags=2, user=brittany, ipsec-enabled=true, ipsec-psk=QnJpdHRhbnkxMjM= +vpn.secrets: ipsec-psk = QnJpdHRhbnkxMjM= +vpn.persistent: no +vpn.timeout: 0 +""" + +TESTCASE_VPN_PPTP = [ + { + 'type': 'vpn', + 'conn_name': 'vpn_pptp', + 'vpn': { + 'permissions': 'brittany', + 'service-type': 'pptp', + 'gateway': 'vpn.example.com', + 'password-flags': '2', + 'user': 'brittany', + }, + 'autoconnect': 'false', + 'state': 'present', + '_ansible_check_mode': False, + }, +] + +TESTCASE_VPN_PPTP_SHOW_OUTPUT = """\ +connection.id: vpn_pptp +connection.type: vpn +connection.autoconnect: no +connection.permissions: brittany +ipv4.method: auto +ipv6.method: auto +vpn-type: pptp +vpn.service-type: org.freedesktop.NetworkManager.pptp +vpn.data: gateway=vpn.example.com, password-flags=2, user=brittany +""" def mocker_set(mocker, connection_exists=False, @@ -1547,6 +1615,19 @@ def mocked_wireguard_connection_unchanged(mocker): execute_return=(0, TESTCASE_WIREGUARD_SHOW_OUTPUT, "")) +@pytest.fixture +def mocked_vpn_l2tp_connection_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_VPN_L2TP_SHOW_OUTPUT, "")) + +@pytest.fixture +def mocked_vpn_pptp_connection_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_VPN_PPTP_SHOW_OUTPUT, "")) + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module']) def test_bond_connection_create(mocked_generic_connection_create, capfd): """ @@ -3456,3 +3537,99 @@ def test_wireguard_mod(mocked_generic_connection_modify, capfd): results = json.loads(out) assert not results.get('failed') assert results['changed'] + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_L2TP, indirect=['patch_ansible_module']) +def test_vpn_l2tp_connection_unchanged(mocked_vpn_l2tp_connection_unchanged, capfd): + """ + Test : L2TP VPN connection unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_PPTP, indirect=['patch_ansible_module']) +def test_vpn_pptp_connection_unchanged(mocked_vpn_pptp_connection_unchanged, capfd): + """ + Test : L2TP VPN connection unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_L2TP, indirect=['patch_ansible_module']) +def test_create_vpn_l2tp(mocked_generic_connection_create, capfd): + """ + Test : Create L2TP VPN connection + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[0] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'add' + assert add_args[0][3] == 'type' + assert add_args[0][4] == 'vpn' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'vpn_l2tp' + + add_args_text = list(map(to_text, add_args[0])) + + for param in ['connection.autoconnect', 'no', + 'connection.permissions', 'brittany', + 'vpn.data', 'gateway=vpn.example.com, password-flags=2, user=brittany, ipsec-enabled=true, ipsec-psk=QnJpdHRhbnkxMjM=', + 'vpn-type', 'l2tp', + ]: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_PPTP, indirect=['patch_ansible_module']) +def test_create_vpn_pptp(mocked_generic_connection_create, capfd): + """ + Test : Create PPTP VPN connection + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[0] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'add' + assert add_args[0][3] == 'type' + assert add_args[0][4] == 'vpn' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'vpn_pptp' + + add_args_text = list(map(to_text, add_args[0])) + + for param in ['connection.autoconnect', 'no', + 'connection.permissions', 'brittany', + 'vpn.data', 'gateway=vpn.example.com, password-flags=2, user=brittany', + 'vpn-type', 'pptp', + ]: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] From e0fd4d627c39e6b7305e71d5c8963343f1b8b09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 5 Jun 2022 00:49:24 -0300 Subject: [PATCH 21/24] improve tests on vpn.data param - fix _compare_conn_params to handle vpn.data as lists --- plugins/modules/net_tools/nmcli.py | 3 ++ .../plugins/modules/net_tools/test_nmcli.py | 30 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 281122652aa..5500aadf485 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -2098,6 +2098,9 @@ def _compare_conn_params(self, conn_info, options): current_value = current_value.strip('"') if key == self.mtu_setting and self.mtu is None: self.mtu = 0 + if key == 'vpn.data': + current_value = list(map(str.strip, current_value.split(','))) + value = list(map(str.strip, value.split(','))) else: # parameter does not exist current_value = None diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py index a34008fb288..39cb3e05e9e 100644 --- a/tests/unit/plugins/modules/net_tools/test_nmcli.py +++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py @@ -1243,9 +1243,10 @@ ipv6.method: auto vpn-type: pptp vpn.service-type: org.freedesktop.NetworkManager.pptp -vpn.data: gateway=vpn.example.com, password-flags=2, user=brittany +vpn.data: password-flags=2, gateway=vpn.example.com, user=brittany """ + def mocker_set(mocker, connection_exists=False, execute_return=(0, "", ""), @@ -1621,6 +1622,7 @@ def mocked_vpn_l2tp_connection_unchanged(mocker): connection_exists=True, execute_return=(0, TESTCASE_VPN_L2TP_SHOW_OUTPUT, "")) + @pytest.fixture def mocked_vpn_pptp_connection_unchanged(mocker): mocker_set(mocker, @@ -3538,6 +3540,7 @@ def test_wireguard_mod(mocked_generic_connection_modify, capfd): assert not results.get('failed') assert results['changed'] + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_L2TP, indirect=['patch_ansible_module']) def test_vpn_l2tp_connection_unchanged(mocked_vpn_l2tp_connection_unchanged, capfd): """ @@ -3545,25 +3548,27 @@ def test_vpn_l2tp_connection_unchanged(mocked_vpn_l2tp_connection_unchanged, cap """ with pytest.raises(SystemExit): nmcli.main() - + out, err = capfd.readouterr() results = json.loads(out) assert not results.get('failed') assert not results['changed'] + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_PPTP, indirect=['patch_ansible_module']) def test_vpn_pptp_connection_unchanged(mocked_vpn_pptp_connection_unchanged, capfd): """ - Test : L2TP VPN connection unchanged + Test : PPTP VPN connection unchanged """ with pytest.raises(SystemExit): nmcli.main() - + out, err = capfd.readouterr() results = json.loads(out) assert not results.get('failed') assert not results['changed'] + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_L2TP, indirect=['patch_ansible_module']) def test_create_vpn_l2tp(mocked_generic_connection_create, capfd): """ @@ -3589,16 +3594,21 @@ def test_create_vpn_l2tp(mocked_generic_connection_create, capfd): for param in ['connection.autoconnect', 'no', 'connection.permissions', 'brittany', - 'vpn.data', 'gateway=vpn.example.com, password-flags=2, user=brittany, ipsec-enabled=true, ipsec-psk=QnJpdHRhbnkxMjM=', - 'vpn-type', 'l2tp', + 'vpn.data', 'vpn-type', 'l2tp', ]: assert param in add_args_text + vpn_data_index = add_args_text.index('vpn.data') + 1 + args_vpn_data = add_args_text[vpn_data_index] + for vpn_data in ['gateway=vpn.example.com', 'password-flags=2', 'user=brittany', 'ipsec-enabled=true', 'ipsec-psk=QnJpdHRhbnkxMjM=']: + assert vpn_data in args_vpn_data + out, err = capfd.readouterr() results = json.loads(out) assert not results.get('failed') assert results['changed'] + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_VPN_PPTP, indirect=['patch_ansible_module']) def test_create_vpn_pptp(mocked_generic_connection_create, capfd): """ @@ -3624,11 +3634,15 @@ def test_create_vpn_pptp(mocked_generic_connection_create, capfd): for param in ['connection.autoconnect', 'no', 'connection.permissions', 'brittany', - 'vpn.data', 'gateway=vpn.example.com, password-flags=2, user=brittany', - 'vpn-type', 'pptp', + 'vpn.data', 'vpn-type', 'pptp', ]: assert param in add_args_text + vpn_data_index = add_args_text.index('vpn.data') + 1 + args_vpn_data = add_args_text[vpn_data_index] + for vpn_data in ['password-flags=2', 'gateway=vpn.example.com', 'user=brittany']: + assert vpn_data in args_vpn_data + out, err = capfd.readouterr() results = json.loads(out) assert not results.get('failed') From abdf08fc40d50f923afd995367ea47f9aca98b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 5 Jun 2022 09:57:08 -0300 Subject: [PATCH 22/24] removes block and set_fact from example Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 31 +++++++++++++----------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 5500aadf485..cccb03bb01b 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1342,24 +1342,19 @@ state: present - name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' - block: - - ansible.builtin.set_fact: - psk: "0s{{ 'Brittany123' | ansible.builtin.b64encode }}" - - - name: Create the connection - community.general.nmcli: - type: vpn - conn_name: my-vpn-connection - vpn: - permissions: "{{ ansible_user }}" - service-type: l2tp - gateway: vpn.example.com - password-flags: 2 - user: brittany - ipsec-enabled: true - ipsec-psk: "0s{{ 'Brittany123' | ansible.builtin.b64encode }}" - autoconnect: false - state: present + community.general.nmcli: + type: vpn + conn_name: my-vpn-connection + vpn: + permissions: "{{ ansible_user }}" + service-type: l2tp + gateway: vpn.example.com + password-flags: 2 + user: brittany + ipsec-enabled: true + ipsec-psk: "0s{{ 'Brittany123' | ansible.builtin.b64encode }}" + autoconnect: false + state: present ''' From 9c4065df83f2dd9b415be565feb7456720c31956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 5 Jun 2022 09:58:15 -0300 Subject: [PATCH 23/24] makes line shortter to better reading Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index cccb03bb01b..26dc1eeb3c7 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -1341,7 +1341,9 @@ autoconnect: true state: present -- name: Create a VPN L2TP connection for ansible_user to connect on vpn.example.com authenticating with user 'brittany' and pre-shared key as 'Brittany123' +- name: >- + Create a VPN L2TP connection for ansible_user to connect on vpn.example.com + authenticating with user 'brittany' and pre-shared key as 'Brittany123' community.general.nmcli: type: vpn conn_name: my-vpn-connection From 1edfe4bc853ab05a0ef29025f778c159e696f3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Roberto=20Emerich=20Junior?= Date: Sun, 5 Jun 2022 09:58:32 -0300 Subject: [PATCH 24/24] Update plugins/modules/net_tools/nmcli.py Co-authored-by: Felix Fontein --- plugins/modules/net_tools/nmcli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 26dc1eeb3c7..6d68603784a 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -955,7 +955,7 @@ description: - The pre-shared key in base64 encoding. - > - You can encode using this Ansible jinja2 expression: C("0s{{ '[YOUR PRE-SHARED KEY]' | b64encode }}"). + You can encode using this Ansible jinja2 expression: C("0s{{ '[YOUR PRE-SHARED KEY]' | ansible.builtin.b64encode }}"). - This is only used when I(ipsec-enabled=true). type: str '''