Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenVPN fixes #215

Merged
merged 9 commits into from
Nov 9, 2023
Merged
67 changes: 48 additions & 19 deletions packages/ns-api/files/ns.ovpntunnel
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ def get_public_addresses():
pass
return ret

def setup_firewall(u, name, device, link):
ovpn_interface = firewall.add_vpn_interface(u, name, device, link=link)
if not firewall.zone_exists(u, 'openvpn'):
firewall.add_trusted_zone(u, "openvpn", [ovpn_interface])
else:
firewall.add_device_to_zone(u, device, 'openvpn')

# APIs

def import_client(tunnel):
Expand All @@ -146,6 +153,7 @@ def import_client(tunnel):
u.set("openvpn", iname, "enabled", 1)
u.set("openvpn", iname, "nobind", "1")
u.set("openvpn", iname, "dev", tun)
u.set("openvpn", iname, "ns_client", "1")

if tunnel['Topology'] == 'p2p':
psk = f"{cert_dir}psk.key"
Expand Down Expand Up @@ -174,19 +182,21 @@ def import_client(tunnel):
u.set("openvpn", iname, "compress", tunnel['Compression'])

if tunnel.get('Digest',''):
u.set("openvpn", iname, "digest", tunnel['Digest'])
u.set("openvpn", iname, "auth", tunnel['Digest'])

if tunnel.get('Cipher',''):
u.set("openvpn", iname, "digest", tunnel['Cipher'])
u.set("openvpn", iname, "cipher", tunnel['Cipher'])

if tunnel.get('TlsVersionMin', ''):
u.set("openvpn", iname, "tls_version_min", tunnel['TlsVersionMin'])

# Add management socket
u.set("openvpn", iname, 'management', f'/var/run/openvpn_{iname}.socket unix')

u.save('openvpn')
# Add interface to LAN
olink = f"openvpn/{iname}"
ovpn_interface = firewall.add_vpn_interface(u, 'openvpn', tun, link=olink)
ovpn_zone = firewall.add_trusted_zone(u, "openvpn", [ovpn_interface], link=olink)

setup_firewall(u, name, tun, f"openvpn/{iname}")

return {"id": iname}

def edit_server(args):
Expand Down Expand Up @@ -234,8 +244,8 @@ def setup_server(u, iname, tunnel):
# generate only on create
if not os.path.exists(f"{cert_dir}issued/server.crt"):
try:
subprocess.run(["/usr/sbin/ns-openvpnrw-init-pki", iname])
subprocess.run(["/usr/sbin/ns-openvpntunnel-add-client", iname])
subprocess.run(["/usr/sbin/ns-openvpnrw-init-pki", iname], check=True)
subprocess.run(["/usr/sbin/ns-openvpntunnel-add-client", iname], check=True)
except:
utils.generic_error("pki_not_initialized")
u.set("openvpn", iname, "dh", f"{cert_dir}dh.pem")
Expand Down Expand Up @@ -281,16 +291,18 @@ def setup_server(u, iname, tunnel):
u.set("openvpn", iname, 'client_connect', f'"/usr/libexec/ns-openvpn/openvpn-connect {iname}"')
u.set("openvpn", iname, 'client_disconnect', f'"/usr/libexec/ns-openvpn/openvpn-disconnect {iname}"')

# Add management socket
u.set("openvpn", iname, 'management', f'/var/run/openvpn_{iname}.socket unix')

u.save('openvpn')

# Open OpenVPN port
proto = tunnel['proto'].removesuffix('-server')
olink = f"openvpn/{iname}"
firewall.add_service(u, f'ovpn{name}', tunnel['port'], proto, link=olink)

# Add interface to LAN
ovpn_interface = firewall.add_vpn_interface(u, 'openvpn', tun, link=olink)
ovpn_zone = firewall.add_trusted_zone(u, "openvpn", [ovpn_interface], link=olink)
setup_firewall(u, name, tun, olink)

return {"id": iname}

def export_client(name):
Expand All @@ -306,7 +318,7 @@ def export_client(name):
"status": "enabled",
"Compression": u.get("openvpn", name, "compress", default=""),
"RemotePort": u.get("openvpn", name, "lport", default=""),
"RemoteHost": u.get("openvpn", name, "public_ip", default=""),
"RemoteHost": u.get("openvpn", name, "ns_public_ip", default=""),
"Digest": u.get("openvpn", name, "digest", default=""),
"Cipher": u.get("openvpn", name, "cipher", default=""),
"Topology": u.get("openvpn", name, "topology", default=""),
Expand Down Expand Up @@ -361,9 +373,11 @@ def list_tunnels():
remote = []
if vpn.get("ifconfig", "") != "":
record["topology"] = "p2p"
if record["topology"] == "p2p":
try:
for r in u.get_all("openvpn", section, "route"):
remote.append(opt2cidr(r))
remote.append(opt2cidr(r))
except:
pass
client = record | {
"port": vpn.get("port", ""),
"remote_host": vpn.get("remote", ""),
Expand Down Expand Up @@ -404,8 +418,11 @@ def delete_tunnel(name):
except:
return utils.validation_error("tunnel_not_found")
try:
dev = u.get("openvpn", name, 'dev')
u.delete('openvpn', name)
u.save('openvpn')
firewall.delete_linked_sections(u, f'openvpn/{name}')
firewall.remove_device_from_zone(u, dev, 'openvpn')
base_dir = f"/etc/openvpn/{name}/"
# cleanup certs and secretes
if os.path.exists(base_dir):
Expand All @@ -423,6 +440,12 @@ def disable_tunnel(name):
try:
u.set('openvpn', name, 'enabled', '0')
u.save('openvpn')
# disable rule
if u.get("openvpn", name, "server", default=None) or u.get("openvpn", name, "ifconfig", default=None):
for r in utils.get_all_by_type(u, 'firewall', 'rule'):
if u.get('firewall', r, 'ns_link', default='') == f'openvpn/{name}':
u.set('firewall', r, 'enabled', '0')
u.save('firewall')
return {"result": "success"}
except:
return utils.generic_error("tunnel_not_disabled")
Expand All @@ -436,6 +459,12 @@ def enable_tunnel(name):
try:
u.set('openvpn', name, 'enabled', '1')
u.save('openvpn')
# enable rule
if u.get("openvpn", name, "server", default=None) or u.get("openvpn", name, "ifconfig", default=None):
for r in utils.get_all_by_type(u, 'firewall', 'rule'):
if u.get('firewall', r, 'ns_link', default='') == f'openvpn/{name}':
u.set('firewall', r, 'enabled', '1')
u.save('firewall')
return {"result": "success"}
except:
return utils.generic_error("tunnel_not_enabled")
Expand Down Expand Up @@ -655,12 +684,12 @@ def setup_client(u, iname, args):
routes.append(f"route {ip} {to_netmask(prefix)}")
u.set('openvpn', iname, 'route', routes)

# Add interface to LAN
olink = f"openvpn/{iname}"
ovpn_interface = firewall.add_vpn_interface(u, 'openvpn', dev_name, link=olink)
ovpn_zone = firewall.add_trusted_zone(u, "openvpn", [ovpn_interface], link=olink)

# Add management socket
u.set("openvpn", iname, 'management', f'/var/run/openvpn_{iname}.socket unix')

u.save('openvpn')

setup_firewall(u, args['ns_name'], dev_name, olink)


def edit_client(args):
Expand Down
11 changes: 8 additions & 3 deletions packages/ns-migration/files/scripts/openvpn
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ u.set("openvpn", iname, 'crl_verify', os.path.join(cert_dir, 'crl.pem'))

u.set("openvpn", iname, 'client_connect', f'"/usr/libexec/ns-openvpn/openvpn-connect {iname}"')
u.set("openvpn", iname, 'client_disconnect', f'"/usr/libexec/ns-openvpn/openvpn-disconnect {iname}"')
u.set("openvpn", iname, 'management', f'/var/run/openvpn_{iname}.socket unix')

if data['rw']['ldap']:
auth_script = '/usr/libexec/ns-openvpn/openvpn-remote-auth'
Expand Down Expand Up @@ -100,10 +101,14 @@ for user in data["users"]:
save_cert(os.path.join(cert_dir, f'issued/{sname}.crt'), user['crt'])

# Add interface to LAN
ovpn_interface = firewall.add_vpn_interface(u, 'rwopenvpn', data['rw']['options']['dev'])
ovpn_zone = firewall.add_trusted_zone(u, "rwopenvpn", [ovpn_interface])
ovpn_interface = firewall.add_vpn_interface(u, 'rwopenvpn', data['rw']['options']['dev'], link=f'openvpn/{sname}')
if not firewall.zone_exists(u, 'rwopenvpn'):
firewall.add_trusted_zone(u, "rwopenvpn", [ovpn_interface])
else:
firewall.add_device_to_zone(u, data['rw']['options']['dev'], 'rwopenvpn')

# Open OpenVPN port
firewall.add_service(u, 'OpenVPNRW', data['rw']['options']['port'], data['rw']['options']['proto'])
firewall.add_service(u, 'OpenVPNRW', data['rw']['options']['port'], data['rw']['options']['proto'], link=f'openvpn/{sname}')

# Save configuration
u.commit("openvpn")
Expand Down
2 changes: 2 additions & 0 deletions packages/ns-openvpn/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ endef

define Package/ns-openvpn/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_DIR) $(1)/usr/libexec/ns-openvpn/connect-scripts
$(INSTALL_DIR) $(1)/usr/libexec/ns-openvpn/disconnect-scripts
$(INSTALL_DIR) $(1)/etc/uci-defaults
Expand All @@ -55,6 +56,7 @@ define Package/ns-openvpn/install
$(INSTALL_BIN) ./files/80-save-connection $(1)/usr/libexec/ns-openvpn/connect-scripts
$(INSTALL_BIN) ./files/80-save-disconnection $(1)/usr/libexec/ns-openvpn/disconnect-scripts
$(INSTALL_BIN) ./files/99_ns-openvpn $(1)/etc/uci-defaults
$(INSTALL_BIN) ./files/openvpn-status $(1)/usr/bin
endef

define Package/ns-openvpn/postinst
Expand Down
1 change: 1 addition & 0 deletions packages/ns-openvpn/files/ns-openvpnrw-setup
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ except:
u.set('openvpn', instance, 'crl_verify', f'/etc/openvpn/{instance}/pki/crl.pem')
u.set('openvpn', instance, 'key', f'/etc/openvpn/{instance}/pki/private/server.key')
u.set('openvpn', instance, 'ns_description', "Default OpenVPN RoadWarrior instance")
u.set("openvpn", instance, 'management', f'/var/run/openvpn_{iname}.socket unix')
u.set('openvpn', instance, 'ns_tag', ["automated"])
u.save('openvpn')

Expand Down
88 changes: 88 additions & 0 deletions packages/ns-openvpn/files/openvpn-status
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/python

#
# Copyright (C) 2023 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

# Read OpenVPN status from management socket

import os
import sys
import json
import time
import socket
import argparse
import datetime
import subprocess

def since2ts(since):
return int(time.mktime(datetime.datetime.strptime(since, "%Y-%m-%d %H:%M:%S").timetuple()))

parser = argparse.ArgumentParser(prog='openvpn-status', description='Read OpenVPN status from management socket')
parser.add_argument("socket_path")
parser.add_argument("-d", "--debug", action="store_true")
parser.add_argument("-c", "--client", action="store_true")
parser.add_argument("-t", "--topology", choices=["subnet", "p2p"])
args = parser.parse_args()

if not os.path.exists(args.socket_path):
print(f"File not found: {socket_path}", file=sys.stderr)
exit(1)

try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as tsock:
tsock.connect(args.socket_path)
fp = tsock.makefile(mode='r', newline='\n')

results = {}
greetings = fp.readline()
if greetings:
tsock.sendall(b"status 3\n")
else:
exit(1)

while True:
msg = fp.readline()
if args.debug:
print(msg, file=sys.stderr)
if "ERROR" in msg:
exit(1)
if "END" in msg:
break

if args.topology == 'subnet':
if msg.startswith("CLIENT_LIST"):
_, cn, real_address, virtual_ipv4, _, bytes_received, bytes_sent, since, _, _, _, _, _ = msg.split('\t')
if cn == 'UNDEF':
continue

ip_addr, port_addr = real_address.split(':')

results[cn] = {
'real_address': ip_addr,
'virtual_address': virtual_ipv4,
'bytes_received': bytes_received,
'bytes_sent': bytes_sent,
'since': since2ts(since)
}

if args.topology == 'p2p' or args.client:
if not 'stats' in results:
results['stats'] = {"since": 0, 'bytes_received': 0, 'bytes_sent': 0}
if msg.startswith("Updated,"):
results['stats']['since'] = since2ts(msg.split(',')[1].rstrip())
elif "read bytes," in msg:
results['stats']['bytes_received'] = results['stats'].get('bytes_received', 0) + int(msg.split(',')[1])
elif "write bytes," in msg:
results['stats']['bytes_sent'] = results['stats'].get('bytes_sent', 0) + int(msg.split(',')[1])

json_results = json.dumps(results)
print(json_results)
exit(0)

except Exception as e:
print("An error occurred:", e)
import traceback
traceback.print_exc()
exit(1)
2 changes: 1 addition & 1 deletion packages/python3-nethsec/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=python3-nethsec
PKG_VERSION:=0.0.14
PKG_VERSION:=0.0.15
PKG_RELEASE:=1

PKG_MAINTAINER:=Giacomo Sanchietti <[email protected]>
Expand Down