From f51d3b127063e1988cb31b7da6e3da7799f95c85 Mon Sep 17 00:00:00 2001 From: Jaymin Patel Date: Tue, 9 Aug 2022 15:28:44 +0530 Subject: [PATCH] luci-app-libreswan: Add LuCI for Libreswan LuCI Support for IPSec VPN (Libreswan) Signed-off-by: Jaymin Patel --- applications/luci-app-libreswan/Makefile | 19 ++ .../resources/view/libreswan/globals.js | 65 ++++++ .../resources/view/libreswan/overview.js | 77 +++++++ .../resources/view/libreswan/proposals.js | 55 +++++ .../resources/view/libreswan/tunnels.js | 209 ++++++++++++++++++ .../etc/uci-defaults/802-luci-app-libreswan | 8 + .../root/usr/libexec/libreswan/rpc/luci.sh | 55 +++++ .../share/luci/menu.d/luci-app-libreswan.json | 46 ++++ .../share/rpcd/acl.d/luci-app-libreswan.json | 17 ++ 9 files changed, 551 insertions(+) create mode 100644 applications/luci-app-libreswan/Makefile create mode 100644 applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/globals.js create mode 100644 applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/overview.js create mode 100644 applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/proposals.js create mode 100644 applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/tunnels.js create mode 100644 applications/luci-app-libreswan/root/etc/uci-defaults/802-luci-app-libreswan create mode 100755 applications/luci-app-libreswan/root/usr/libexec/libreswan/rpc/luci.sh create mode 100644 applications/luci-app-libreswan/root/usr/share/luci/menu.d/luci-app-libreswan.json create mode 100644 applications/luci-app-libreswan/root/usr/share/rpcd/acl.d/luci-app-libreswan.json diff --git a/applications/luci-app-libreswan/Makefile b/applications/luci-app-libreswan/Makefile new file mode 100644 index 000000000000..206579f2afdd --- /dev/null +++ b/applications/luci-app-libreswan/Makefile @@ -0,0 +1,19 @@ +# +# Copyright (C) 2022 Jaymin Patel +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE=Luci Application for IPSec VPN (Libreswan) +LUCI_DEPENDS:=+luci-base +luci +libreswan +LUCI_PKGARCH:=all +PKG_LICENSE:=GPL-2.0 + +PKG_MAINTAINER:=Jaymin Patel + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature + diff --git a/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/globals.js b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/globals.js new file mode 100644 index 000000000000..1d6913d2cb6d --- /dev/null +++ b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/globals.js @@ -0,0 +1,65 @@ +'use strict'; +'require view'; +'require ui'; +'require form'; +'require rpc'; +'require tools.widgets as widgets'; + +return view.extend({ + callLocalLeftIPs: rpc.declare({ + object: 'libreswan', + method: 'get_local_leftips', + expect: { '': {} } + }), + + load: function() { + return Promise.all([ + this.callLocalLeftIPs(), + ]); + }, + + render: function(data) { + var local_addresses = data[0]['leftip']; + var m, s, o, listen_interface; + + m = new form.Map('libreswan', _('IPSec Global Settings')); + + s = m.section(form.NamedSection, 'globals', 'libreswan'); + s.anonymous = false; + s.addremove = false; + + o = s.option(form.Flag, 'debug', _('Debug Logs')); + o.default = false; + o.rmempty = false; + + o = s.option(form.Flag, 'uniqueids', _('Uniquely Identify Remotes')); + o.default = false; + o.rmempty = false; + + listen_interface = s.option(widgets.NetworkSelect, 'listen_interface', _('Listen Interface')); + listen_interface.datatype = 'string'; + listen_interface.multiple = false; + listen_interface.optional = true; + + o = s.option(form.Value, 'listen', _('Listen Address')); + o.datatype = 'ip4addr'; + for (var i = 0; i < local_addresses.length; i++) { + o.value(local_addresses[i]); + } + o.optional = true; + o.depends({ listen_interface : '' }); + + o = s.option(form.Value, 'nflog_all', _('Enable nflog on nfgroup')); + o.datatype = 'uinteger'; + o.default = 0; + o.rmempty = true; + o.optional = true; + + o = s.option(form.DynamicList, 'virtual_private', _('Allowed Virtual Private')); + o.datatype = 'neg(ip4addr)'; + o.multiple = true; + o.optional = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/overview.js b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/overview.js new file mode 100644 index 000000000000..61a2e4cbccce --- /dev/null +++ b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/overview.js @@ -0,0 +1,77 @@ +'use strict'; +'require view'; +'require rpc'; +'require form'; +'require poll'; + +var callLibreswanStatus = rpc.declare({ + object: 'libreswan', + method: 'status', + expect: { }, +}); + +function secondsToString(seconds) { + var str = ''; + var numdays = Math.floor(seconds / 86400); + var numhours = Math.floor((seconds % 86400) / 3600); + var numminutes = Math.floor(((seconds % 86400) % 3600) / 60); + var numseconds = ((seconds % 86400) % 3600) % 60; + + str = (numdays ? numdays + 'd ' : '') + (numhours ? numhours + 'h ' : '') + (numminutes ? numminutes + 'm ' : '') + numseconds + 's'; + return str; +} + +return view.extend({ + render: function() { + var table = + E('table', { 'class': 'table lases' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Name')), + E('th', { 'class': 'th' }, _('Remote')), + E('th', { 'class': 'th' }, _('Local Subnet')), + E('th', { 'class': 'th' }, _('Remote Subnet')), + E('th', { 'class': 'th' }, _('Tx')), + E('th', { 'class': 'th' }, _('Rx')), + E('th', { 'class': 'th' }, _('Phase1')), + E('th', { 'class': 'th' }, _('Phase2')), + E('th', { 'class': 'th' }, _('Status')), + E('th', { 'class': 'th' }, _('Uptime')), + E([]) + ]) + ]); + + poll.add(function() { + return callLibreswanStatus().then(function(tunnelsInfo) { + var tunnels = Array.isArray(tunnelsInfo.tunnels) ? tunnelsInfo.tunnels : []; + + cbi_update_table(table, + tunnels.map(function(tunnel) { + return [ + tunnel.name, + tunnel.right, + tunnel.leftsubnet, + tunnel.rightsubnet, + tunnel.tx, + tunnel.rx, + tunnel.phase1 ? _('Up') : _('Down'), + tunnel.phase2 ? _('Up') : _('Down'), + tunnel.connected ? _('Up') : _('Down'), + secondsToString(tunnel.uptime), + ]; + }), + E('em', _('There are no active Tunnels')) + ); + }); + }); + + return E([ + E('h3', _('IPSec Tunnels Summary')), + E('br'), + table + ]); + }, + + handleSave: null, + handleSaveApply:null, + handleReset: null +}); diff --git a/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/proposals.js b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/proposals.js new file mode 100644 index 000000000000..27cb77509a8c --- /dev/null +++ b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/proposals.js @@ -0,0 +1,55 @@ +'use strict'; +'require view'; +'require ui'; +'require form'; + +return view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('libreswan', _('IPSec Proposals')); + + s = m.section(form.GridSection, 'crypto_proposal'); + s.anonymous = false; + s.addremove = true; + s.nodescriptions = false; + s.addbtntitle = _('Add Proposal'); + + o = s.tab('general', _('General')); + + o = s.taboption('general', form.ListValue, 'hash_algorithm', _('Hash Algorithm')); + o.default = 'md5'; + o.value('md5', _('MD5')); + o.value('sha1', _('SHA1')); + o.value('sha256', _('SHA256')); + o.value('sha384', _('SHA384')); + o.value('sha512', _('SHA512')); + + o = s.taboption('general', form.ListValue, 'encryption_algorithm', _('Encryption Method')); + o.default = '3des'; + o.value('3des', _('3DES')) + o.value('aes', _('AES')) + o.value('aes_ctr', _('AES_CTR')); + o.value('aes_cbc', _('AES_CBC')); + o.value('aes128', _('AES128')); + o.value('aes192', _('AES192')); + o.value('aes256', _('AES256')); + o.value('camellia_cbc', _('CAMELLIA_CBC')); + + o = s.taboption('general', form.MultiValue, 'dh_group', _('DH Group')); + o.default = 'modp1536'; + o.value('modp1536', _('DH Group 5')); + o.value('modp2048', _('DH Group 14')); + o.value('modp3072', _('DH Group 15')); + o.value('modp4096', _('DH Group 16')); + o.value('modp6144', _('DH Group 17')); + o.value('modp8192', _('DH Group 18')); + o.value('dh19', _('DH Group 19')); + o.value('dh20', _('DH Group 20')); + o.value('dh21', _('DH Group 21')); + o.value('dh22', _('DH Group 22')); + o.value('dh31', _('DH Group 31')); + + return m.render(); + } +}); diff --git a/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/tunnels.js b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/tunnels.js new file mode 100644 index 000000000000..5603d728f8a9 --- /dev/null +++ b/applications/luci-app-libreswan/htdocs/luci-static/resources/view/libreswan/tunnels.js @@ -0,0 +1,209 @@ +'use strict'; +'require view'; +'require form'; +'require ui'; +'require uci'; +'require rpc'; + +return view.extend({ + callLocalSubnets: rpc.declare({ + object: 'libreswan', + method: 'get_local_subnets', + expect: { '': {} } + }), + + callLocalLeftIPs: rpc.declare({ + object: 'libreswan', + method: 'get_local_leftips', + expect: { '': {} } + }), + + callLocalInterfaces: rpc.declare({ + object: 'libreswan', + method: 'get_local_interfaces', + expect: { '': {} } + }), + + load: function() { + return Promise.all([ + uci.load('libreswan'), + this.callLocalSubnets(), + this.callLocalLeftIPs(), + this.callLocalInterfaces(), + ]); + }, + + render: function(data) { + var local_subnets = data[1]['subnet']; + var local_leftips = data[2]['leftip']; + var local_interfaces = data[3]['interfaces']; + var proposals = uci.sections('libreswan', 'crypto_proposal'); + + var m, s, o, phase2; + + if (proposals == '') { + ui.addNotification(null, E('p', _('Proposals must be configured for Tunnels'))); + return; + } + + m = new form.Map('libreswan', 'IPSec Tunnels'); + + s = m.section(form.GridSection, 'tunnel'); + s.anonymous = false; + s.addremove = true; + s.nodedescription = false; + s.addbtntitle = _('Add Tunnel'); + + o = s.tab('general', _('General')); + o = s.tab('authentication', _('Authentication')); + o = s.tab('interface', _('Interface')); + o = s.tab('advanced', _('Advanced')); + + o = s.taboption('general', form.ListValue, 'auto', _('Mode')); + o.default = 'start'; + o.value('add', _('Listen')); + o.value('start', _('Initiate')); + + o = s.taboption('general', form.Value, 'left', _('Local IP/Interface')); + o.datatype = 'or(string, ipaddr)'; + for (var i = 0; i < local_leftips.length; i++) { + o.value(local_leftips[i]); + } + for (var i = 0; i < local_interfaces.length; i++) { + o.value(local_interfaces[i]); + } + o.value('%defaultroute'); + o.optional = false; + + o = s.taboption('general', form.Value, 'leftid', _('Left ID')); + o.datatype = 'string'; + o.value('%any'); + o.modalonly = true; + + o = s.taboption('general', form.Value, 'right', _('Remote IP')); + o.datatype = 'or(string, ipaddr)'; + o.value('0.0.0.0'); + o.value('%any'); + o.optional = false; + + o = s.taboption('general', form.Value, 'rightid', _('Right ID')); + o.datatype = 'string'; + o.value('%any'); + o.modalonly = true; + + o = s.taboption('general', form.Value, 'leftsourceip', _('Local Source IP')); + o.datatype = 'ipaddr'; + for (var i = 0; i < local_leftips.length; i++) { + o.value(local_leftips[i]); + } + o.optional = false; + o.modalonly = true; + + o = s.taboption('general', form.Value, 'rightsourceip', _('Remote Source IP')); + o.datatype = 'ipaddr'; + o.optional = false; + o.modalonly = true; + + o = s.taboption('general', form.DynamicList, 'leftsubnets', _('Local Subnets')); + o.datatype = 'ipaddr'; + for (var i = 0; i < local_subnets.length; i++) { + o.value(local_subnets[i]); + } + o.value('0.0.0.0/0'); + + o = s.taboption('general', form.DynamicList, 'rightsubnets', _('Remote Subnets')); + o.datatype = 'ipaddr'; + o.value('0.0.0.0/0'); + + o = s.taboption('authentication', form.ListValue, 'authby', _('Auth Method')); + o.default = 'secret' + o.value('secret', 'Preshare Key'); + o.modalonly = true; + o.optional = false; + + o = s.taboption('authentication', form.Value, 'psk', _('Preshare Key')); + o.datatype = 'and(string, minlenght(8))' + o.password = true; + o.modalonly = true; + o.optional = false; + + o = s.taboption('advanced', form.ListValue, 'ikev2', _('IKE V2')); + o.default = 'yes'; + o.value('yes', _('IKE Version 2')); + o.value('no', _('IKE Version 1')); + o.modalonly = true; + + o = s.taboption('advanced', form.MultiValue, 'ike', _('Phase1 Proposals')); + for (var i = 0; i < proposals.length; i++) { + o.value(proposals[i]['.name']); + } + o.modalonly = true; + + o = s.taboption('advanced', form.Value, 'ikelifetime', _('IKE Life Time')); + o.datatype = 'uinteger'; + o.default = 10800; + o.modalonly = false; + o.modalonly = true; + + o = s.taboption('advanced', form.Flag, 'rekey', _('Rekey')); + o.default = false; + o.modalonly = false; + o.modalonly = true; + + o = s.taboption('advanced', form.Value, 'rekeymargin', _('Rekey Margin Time')); + o.datatype = 'uinteger'; + o.default = 540; + o.modalonly = false; + o.modalonly = true; + + o = s.taboption('advanced', form.ListValue, 'dpdaction', _('DPD Action')); + o.default = 'restart'; + o.value('none', _('None')); + o.value('clear', _('Clear')); + o.value('hold', _('Hold')); + o.value('restart', _('Restart')); + o.modalonly = true; + + o = s.taboption('advanced', form.Value, 'dpddelay', _('DPD Delay')); + o.datatype = 'uinteger'; + o.default = 30; + o.modalonly = true; + + o = s.taboption('advanced', form.Value, 'dpdtimeout', _('DPD Timeout')); + o.datatype = 'uinteger'; + o.default = 150; + o.modalonly = true; + + o = s.taboption('advanced', form.ListValue, 'phase2', _('Phase2 Protocol')); + o.default = 'esp'; + o.value('esp', 'ESP'); + o.modalonly = true; + o.optional = false; + + o = s.taboption('advanced', form.MultiValue, 'phase2alg', _('Phase2 Proposals')); + for (var i = 0; i < proposals.length; i++) { + o.value(proposals[i]['.name']); + } + o.modalonly = true; + + o = s.taboption('advanced', form.Value, 'nflog', _('Enable nflog on nfgroup')); + o.default = 0; + o.datatype = 'uinteger'; + o.rmempty = true; + o.optional = true; + o.modalonly = true; + + o = s.taboption('interface', form.Value, 'vti_interface', _('VTI Interface')); + o.datatype = 'string'; + o.modalonly = true; + + o = s.taboption('interface', form.Value, 'leftvti', _('Address')); + o.datatype = 'ipaddr'; + o.modalonly = true; + + o = s.taboption('interface', form.Value, 'mark', _('Traffic Mark')); + o.modalonly = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-libreswan/root/etc/uci-defaults/802-luci-app-libreswan b/applications/luci-app-libreswan/root/etc/uci-defaults/802-luci-app-libreswan new file mode 100644 index 000000000000..638333566692 --- /dev/null +++ b/applications/luci-app-libreswan/root/etc/uci-defaults/802-luci-app-libreswan @@ -0,0 +1,8 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@libreswan[-1] + add ucitrack libreswan + set ucitrack.@libreswan[-1].init=ipsec + commit ucitrack +EOF diff --git a/applications/luci-app-libreswan/root/usr/libexec/libreswan/rpc/luci.sh b/applications/luci-app-libreswan/root/usr/libexec/libreswan/rpc/luci.sh new file mode 100755 index 000000000000..83355b632730 --- /dev/null +++ b/applications/luci-app-libreswan/root/usr/libexec/libreswan/rpc/luci.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +. /lib/functions.sh + +get_local_leftips() { + json_init + json_add_array leftip + for addr in $(ip -4 addr show | awk -F'[ /]' '/inet.*scope global/{printf("%s ", $6)}'); do + json_add_string leftip "$addr" + done + json_dump +} + +get_local_interfaces() { + json_init + config_load network + + add_interface() { + json_add_string interfaces "%$1" + } + + json_init + json_add_array interfaces + config_foreach add_interface interface + json_dump +} + +get_local_subnets() { + json_init + json_add_array subnet + for addr in $(ip ro ls table all | grep '^[1-9]' | awk '{print $1}' | sort -u); do + json_add_string subnet "$addr" + done + json_dump +} + +get_local_gateways() { + json_init + json_add_array gateway + for addr in $(ip ro ls table all | grep ^default | sed -e 's/.*via //g' -e 's/ .*//g' | sort -u); do + json_add_string gateway "$addr" + done + json_dump +} + +luci_help() { + json_add_object get_local_leftips + json_close_object + json_add_object get_local_interfaces + json_close_object + json_add_object get_local_subnets + json_close_object + json_add_object get_local_gateways + json_close_object +} diff --git a/applications/luci-app-libreswan/root/usr/share/luci/menu.d/luci-app-libreswan.json b/applications/luci-app-libreswan/root/usr/share/luci/menu.d/luci-app-libreswan.json new file mode 100644 index 000000000000..f324950fab98 --- /dev/null +++ b/applications/luci-app-libreswan/root/usr/share/luci/menu.d/luci-app-libreswan.json @@ -0,0 +1,46 @@ +{ + "admin/vpn/libreswan": { + "title": "Libreswan IPSec", + "order": 90, + "action": { + "type": "alias", + "path": "admin/vpn/libreswan/overview" + } + }, + + "admin/vpn/libreswan/overview": { + "title": "Overview", + "order": 10, + "action": { + "type": "view", + "path": "libreswan/overview" + } + }, + + "admin/vpn/libreswan/globals": { + "title": "IPSec Globals", + "order": 20, + "action": { + "type": "view", + "path": "libreswan/globals" + } + }, + + "admin/vpn/libreswan/proposals": { + "title": "IPSec Proposals", + "order": 30, + "action": { + "type": "view", + "path": "libreswan/proposals" + } + }, + + "admin/vpn/libreswan/tunnels": { + "title": "IPSec Tunnels", + "order": 40, + "action": { + "type": "view", + "path": "libreswan/tunnels" + } + } +} diff --git a/applications/luci-app-libreswan/root/usr/share/rpcd/acl.d/luci-app-libreswan.json b/applications/luci-app-libreswan/root/usr/share/rpcd/acl.d/luci-app-libreswan.json new file mode 100644 index 000000000000..e20a452e5f1b --- /dev/null +++ b/applications/luci-app-libreswan/root/usr/share/rpcd/acl.d/luci-app-libreswan.json @@ -0,0 +1,17 @@ +{ + "luci-app-libreswan" : { + "description" : "Grant access to LuCI app Libreswan IPSec", + "read" : { + "ubus" : { + "libreswan" : [ "*" ] + }, + "uci": [ "libreswan" ] + }, + "write" : { + "ubus" : { + "libreswan" : [ "*" ] + }, + "uci": [ "libreswan" ] + } + } +}