Skip to content

Commit eecbba4

Browse files
authored
[feature] Added support for OpenVPN tls-auth option #174
The OpenVPN backend will automatically create a file for the key present in "tls_auth" field and update the value of the "tls-auth" parameter. Closes #174
1 parent f2e692c commit eecbba4

5 files changed

Lines changed: 198 additions & 0 deletions

File tree

docs/source/backends/openvpn.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ Required properties:
114114
+--------------------------+---------+--------------+-------------------------------------------------------------+
115115
| ``pkcs12`` | string | | any non whitespace character |
116116
+--------------------------+---------+--------------+-------------------------------------------------------------+
117+
| ``tls_auth`` | string | | string containing TLS Auth key |
118+
+--------------------------+---------+--------------+-------------------------------------------------------------+
117119
| ``ns_cert_type`` | string | | ``client``, ``server`` or empty string |
118120
+--------------------------+---------+--------------+-------------------------------------------------------------+
119121
| ``mtu_disc`` | string | ``no`` | ``no``, ``maybe`` or ``yes`` |

netjsonconfig/backends/openvpn/converters.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from copy import deepcopy
22

3+
from ...schema import X509_FILE_MODE
34
from ..base.converter import BaseConverter
45
from .schema import schema
56

@@ -46,8 +47,41 @@ def __intermediate_vpn(self, config, remove=[False, 0, '']):
4647
# do not display status-version if status directive not present
4748
if 'status' not in config and 'status_version' in config:
4849
del config['status_version']
50+
config = self.__add_tls_auth_key(config)
4951
return self.sorted_dict(config)
5052

53+
def __add_tls_auth_key(self, config):
54+
tls_auth = config.get('tls_auth', None)
55+
if not tls_auth:
56+
return config
57+
tls_auth = tls_auth.strip()
58+
if len(tls_auth.split(' ')) == 2:
59+
# The field already contains path to auth key
60+
# and TLS Auth direction. No operation is required.
61+
pass
62+
else:
63+
# The TLS Auth key is present in the field.
64+
# Determine TLS Auth key file path from CA's file path.
65+
ca_path = config.get('ca', '')
66+
dev = config.get('dev', '')
67+
tls_auth_path = '/'.join(ca_path.split('/')[:-1] + [f'{dev}_tls_auth.key'])
68+
if config.get('mode') == 'server':
69+
tls_auth_direction = 0
70+
else:
71+
tls_auth_direction = 1
72+
config['tls_auth'] = f'{tls_auth_path} {tls_auth_direction}'
73+
# Add TLS Auth key file
74+
file_data = {
75+
'path': tls_auth_path,
76+
'mode': X509_FILE_MODE,
77+
'contents': tls_auth,
78+
}
79+
try:
80+
self.netjson['files'].append(file_data)
81+
except KeyError:
82+
self.netjson['files'] = [file_data]
83+
return config
84+
5185
def to_netjson_loop(self, block, result, index):
5286
vpn = self.__netjson_vpn(block)
5387
result.setdefault('openvpn', [])

netjsonconfig/backends/openvpn/openvpn.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ def auto_client(
7676
# remote_cert_tls
7777
remote_cert_tls = {None: '', '': '', 'client': 'server'}
7878
client['remote_cert_tls'] = remote_cert_tls[server.get('remote_cert_tls')]
79+
tls_auth = server.get('tls_auth')
80+
if tls_auth:
81+
if len(tls_auth.strip().split(' ')) == 2:
82+
# The field contains path to auth key and direction.
83+
# auto_client does not support such format.
84+
pass
85+
else:
86+
# The TLS Auth key is present in the field.
87+
# Copy the TLS Auth key. Convertor will handle
88+
# parsing it into file.
89+
client['tls_auth'] = tls_auth
7990
copy_keys = [
8091
'name',
8192
'dev_type',

netjsonconfig/backends/openvpn/schema.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@
190190
"pattern": "^(\\S*)$",
191191
"propertyOrder": 17,
192192
},
193+
"tls_auth": {
194+
"title": "TLS Auth",
195+
"description": (
196+
"Adds an additional layer of HMAC authentication on top of "
197+
"the TLS control channel to mitigate DoS attacks and "
198+
"attacks on the TLS stack"
199+
),
200+
"type": "string",
201+
"format": "textarea",
202+
"propertyOrder": 18,
203+
},
193204
"ns_cert_type": {
194205
"title": "NS cert type",
195206
"type": "string",

tests/openvpn/test_backend.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def test_server_mode(self):
5858
"status": "/var/log/openvpn.status 10",
5959
"status_version": 1,
6060
"tls_server": True,
61+
"tls_auth": "tls_auth.key 0",
6162
"tun_ipv6": False,
6263
"up": "",
6364
"up_delay": 0,
@@ -98,6 +99,7 @@ def test_server_mode(self):
9899
script-security 0
99100
status /var/log/openvpn.status 10
100101
status-version 1
102+
tls-auth tls_auth.key 0
101103
tls-server
102104
user nobody
103105
verb 3
@@ -151,6 +153,7 @@ def test_client_mode(self):
151153
"status": "/var/log/openvpn.status 30",
152154
"status_version": 1,
153155
"tls_client": True,
156+
"tls_auth": "tls_auth.key 1",
154157
"topology": "p2p",
155158
"tun_ipv6": True,
156159
"up": "/home/user/up-command.sh",
@@ -193,6 +196,7 @@ def test_client_mode(self):
193196
script-security 1
194197
status /var/log/openvpn.status 30
195198
status-version 1
199+
tls-auth tls_auth.key 1
196200
tls-client
197201
topology p2p
198202
tun-ipv6
@@ -742,3 +746,139 @@ def test_override(self):
742746
o = OpenVpn(self._simple_conf, templates=[template])
743747
# ensure dummy values in template have been overridden
744748
self.assertDictEqual(o.config, self._simple_conf)
749+
750+
_openvpn_server_tls_auth_config = {
751+
"openvpn": [
752+
{
753+
"name": "test",
754+
"ca": "/etc/openvpn/ca.pem",
755+
"cert": "/etc/openvpn/cert.pem",
756+
"dev": "tap0",
757+
"dev_type": "tap",
758+
"dh": "/etc/openvpn/dh.pem",
759+
"key": "/etc/openvpn/key.pem",
760+
"mode": "server",
761+
"proto": "udp",
762+
"status": "",
763+
"status_version": 1,
764+
"tls_server": True,
765+
"tls_auth": (
766+
"#\n"
767+
"# 2048 bit OpenVPN static key\n"
768+
"#\n-----BEGIN OpenVPN Static key V1-----\n"
769+
"tls-auth-key\n"
770+
"-----END OpenVPN Static key V1-----"
771+
),
772+
},
773+
{
774+
"name": "test2",
775+
"ca": "/etc/openvpn/ca2.pem",
776+
"cert": "/etc/openvpn/cert2.pem",
777+
"dev": "tap1",
778+
"dev_type": "tap",
779+
"dh": "/etc/openvpn/dh2.pem",
780+
"key": "/etc/openvpn/key2.pem",
781+
"mode": "server",
782+
"proto": "udp",
783+
"status": "",
784+
"status_version": 1,
785+
"tls_server": True,
786+
"tls_auth": (
787+
"#\n"
788+
"# 2048 bit OpenVPN static key\n"
789+
"#\n-----BEGIN OpenVPN Static key V1-----\n"
790+
"tls-auth-key2\n"
791+
"-----END OpenVPN Static key V1-----"
792+
),
793+
},
794+
],
795+
}
796+
797+
_openvpn_server_tls_auth_render = """# openvpn config: test
798+
799+
ca /etc/openvpn/ca.pem
800+
cert /etc/openvpn/cert.pem
801+
dev tap0
802+
dev-type tap
803+
dh /etc/openvpn/dh.pem
804+
key /etc/openvpn/key.pem
805+
mode server
806+
proto udp
807+
tls-auth /etc/openvpn/tap0_tls_auth.key 0
808+
tls-server
809+
810+
# openvpn config: test2
811+
812+
ca /etc/openvpn/ca2.pem
813+
cert /etc/openvpn/cert2.pem
814+
dev tap1
815+
dev-type tap
816+
dh /etc/openvpn/dh2.pem
817+
key /etc/openvpn/key2.pem
818+
mode server
819+
proto udp
820+
tls-auth /etc/openvpn/tap1_tls_auth.key 0
821+
tls-server
822+
823+
# ---------- files ---------- #
824+
825+
# path: /etc/openvpn/tap0_tls_auth.key
826+
# mode: 0600
827+
828+
#
829+
# 2048 bit OpenVPN static key
830+
#
831+
-----BEGIN OpenVPN Static key V1-----
832+
tls-auth-key
833+
-----END OpenVPN Static key V1-----
834+
835+
# path: /etc/openvpn/tap1_tls_auth.key
836+
# mode: 0600
837+
838+
#
839+
# 2048 bit OpenVPN static key
840+
#
841+
-----BEGIN OpenVPN Static key V1-----
842+
tls-auth-key2
843+
-----END OpenVPN Static key V1-----
844+
845+
"""
846+
847+
_openvpn_client_tls_auth_render = """# openvpn config: test
848+
849+
ca /etc/openvpn/ca.pem
850+
cert /etc/openvpn/cert.pem
851+
dev tap0
852+
dev-type tap
853+
key /etc/openvpn/key.pem
854+
mode p2p
855+
nobind
856+
proto udp
857+
remote vpn1.test.com 1195
858+
resolv-retry infinite
859+
tls-auth /etc/openvpn/tap0_tls_auth.key 1
860+
tls-client
861+
862+
# ---------- files ---------- #
863+
864+
# path: /etc/openvpn/tap0_tls_auth.key
865+
# mode: 0600
866+
867+
#
868+
# 2048 bit OpenVPN static key
869+
#
870+
-----BEGIN OpenVPN Static key V1-----
871+
tls-auth-key
872+
-----END OpenVPN Static key V1-----
873+
874+
"""
875+
876+
def test_tls_auth_key_present(self):
877+
server = OpenVpn(self._openvpn_server_tls_auth_config)
878+
self.assertEqual(server.render(), self._openvpn_server_tls_auth_render)
879+
client_config = OpenVpn.auto_client(
880+
'vpn1.test.com',
881+
self._openvpn_server_tls_auth_config['openvpn'][0],
882+
)
883+
client = OpenVpn(client_config)
884+
self.assertEqual(client.render(), self._openvpn_client_tls_auth_render)

0 commit comments

Comments
 (0)