From: George Iv <57254463+zhoreeq@users.noreply.github.com> Date: Sun, 1 Dec 2019 05:32:50 +0000 (-0500) Subject: luci-app-yggdrasil: Add application for Yggdrasil X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=277d4e3268dd47f2fa94046aa24bb990ea4d2c59;p=oweals%2Fluci.git luci-app-yggdrasil: Add application for Yggdrasil Signed-off-by: George Iv <57254463+zhoreeq@users.noreply.github.com> --- diff --git a/applications/luci-app-yggdrasil/Makefile b/applications/luci-app-yggdrasil/Makefile new file mode 100644 index 000000000..747d8c3d6 --- /dev/null +++ b/applications/luci-app-yggdrasil/Makefile @@ -0,0 +1,14 @@ +# +# Copyright (C) 2008-2019 The LuCI Team +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI support for Yggdrasil +LUCI_DEPENDS:=+yggdrasil + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/keys.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/keys.js new file mode 100644 index 000000000..8bc79321f --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/keys.js @@ -0,0 +1,22 @@ +'use strict'; +'require form'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('yggdrasil', 'Yggdrasil'); + + s = m.section(form.TypedSection, "yggdrasil", _("Encryption keys")); + s.anonymous = true; + + s.option(form.Value, "EncryptionPublicKey", _("Encryption public key")); + s.option(form.Value, "EncryptionPrivateKey", _("Encryption private key"), + _("Keep this private. When compromised, generate a new keypair and IPv6.")); + s.option(form.Value, "SigningPublicKey", _("Signing public key")); + s.option(form.Value, "SigningPrivateKey", _("Signing private key"), + _("Keep this private. When compromised, generate a new keypair and IPv6.")); + + return m.render(); + } +}); diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/peers.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/peers.js new file mode 100644 index 000000000..62336a270 --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/peers.js @@ -0,0 +1,31 @@ +'use strict'; +'require form'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('yggdrasil', 'Yggdrasil'); + + o = m.section(form.TableSection, "peer", _("Peers"), + _("List of connection strings for outbound peer connections in URI format, " + + "e.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections " + + "will obey the operating system routing table, therefore you should " + + "use this section when you may connect via different interfaces.")); + o.option(form.Value, "uri", "URI"); + o.anonymous = true; + o.addremove = true; + + o = m.section(form.TableSection, "interface_peer", _("Interface peers"), + _("List of connection strings for outbound peer connections in URI format, " + + "arranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. " + + "Note that SOCKS peerings will NOT be affected by this option and should " + + "go in the \"Peers\" section instead.")); + o.option(form.Value, "interface", _("Interface")); + o.option(form.Value, "uri", "URI"); + o.anonymous = true; + o.addremove = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/session_firewall.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/session_firewall.js new file mode 100644 index 000000000..4a91d5e02 --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/session_firewall.js @@ -0,0 +1,38 @@ +'use strict'; +'require form'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('yggdrasil', 'Yggdrasil'); + + s = m.section(form.TypedSection, "yggdrasil", _("Session firewall settings")); + s.anonymous = true; + + s.option(form.Flag, "SessionFirewall_Enable", _("Enable session firewall"), + _("If disabled, network traffic from any node will be allowed. If enabled, the below rules apply")); + s.option(form.Flag, "SessionFirewall_AllowFromDirect", _("Allow from direct"), + _("Allow network traffic from directly connected peers")); + s.option(form.Flag, "SessionFirewall_AllowFromRemote", _("Allow from remote"), + _("Allow network traffic from remote nodes on the network that you are not directly peered with")); + s.option(form.Flag, "SessionFirewall_AlwaysAllowOutbound", + _("Always allow outbound"), _("Allow outbound network traffic regardless of AllowFromDirect or AllowFromRemote")); + + s = m.section(form.TableSection, "whitelisted_encryption_public_key", + _("Whitelisted public keys"), + _("Network traffic is always accepted from those peers, regardless of AllowFromDirect or AllowFromRemote")); + s.option(form.Value, "key", _("Public key")); + s.anonymous = true; + s.addremove = true; + + s = m.section(form.TableSection, "blacklisted_encryption_public_key", + _("Blacklisted public keys"), + _("Network traffic is always rejected from those peers, regardless of AllowFromDirect or AllowFromRemote")); + s.option(form.Value, "key", _("Public key")); + s.anonymous = true; + s.addremove = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/settings.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/settings.js new file mode 100644 index 000000000..b72c2fac2 --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/settings.js @@ -0,0 +1,64 @@ +'use strict'; +'require form'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('yggdrasil', 'Yggdrasil'); + + s = m.section(form.TypedSection, 'yggdrasil', _('General settings')); + s.anonymous = true; + + s.option(form.Value, "IfName", _("Yggdrasil's network interface name")); + s.option(form.Value, "LinkLocalTCPPort", _("Link-local TCP port"), + _("The port number to be used for the link-local TCP listeners for the "+ + "configured MulticastInterfaces. This option does not affect listeners" + + "specified in the Listen option. Unless you plan to firewall link-local" + + "traffic, it is best to leave this as the default value of 0. This " + + "option cannot currently be changed by reloading config during runtime.")); + + s.option(form.Flag, "NodeInfoPrivacy", _("Enable NodeInfo privacy"), + _("By default, nodeinfo contains some defaults including the platform," + + " architecture and Yggdrasil version. These can help when surveying" + + " the network and diagnosing network routing problems. Enabling" + + " nodeinfo privacy prevents this, so that only items specified in" + + " \"NodeInfo\" are sent back if specified.")); + + o = s.option(form.Value, "NodeInfo", _("NodeInfo"), + _("Optional node info. This must be a { \"key\": \"value\", ... } map " + + "or set as null. This is entirely optional but, if set, is visible " + + "to the whole network on request.")); + o.validate = function(k, v) { + try { JSON.parse(v); return true; } catch (e) { return e.message; } + } + + s.option(form.Flag, "IfTAPMode", _("Enable tap mode")); + s.option(form.Value, "IfMTU", _("MTU size for the interface")); + s.option(form.Value, "SwitchOptions_MaxTotalQueueSize", + _("Maximum size of all switch queues combined")); + + o = m.section(form.TableSection, "multicast_interface", _("Multicast interfaces"), + _("Regular expressions for which interfaces multicast peer discovery " + + "should be enabled on. If none specified, multicast peer discovery is " + + "disabled. The default value is .* which uses all interfaces.")); + o.option(form.Value, "name", _("Interface name"), + _("Set .* to multicast on all interfaces")); + o.anonymous = true; + o.addremove = true; + + o = m.section(form.TableSection, "listen_address", _("Listen addresses"), + _("Listen addresses for incoming connections. You will need to add " + + "listeners in order to accept incoming peerings from non-local nodes. " + + "Multicast peer discovery will work regardless of any listeners set " + + "here. Each listener should be specified in URI format as above, e.g. " + + "tcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces.")); + o.option(form.Value, "address", + _("Address to listen for incoming connections"), + _("e.g. tcp://0.0.0.0:0 or tcp://[::]:0")); + o.anonymous = true; + o.addremove = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/status.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/status.js new file mode 100644 index 000000000..3fb6c1f7f --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/status.js @@ -0,0 +1,113 @@ +'use strict'; +'require fs'; +'require form'; + +function init_view() { + var view = document.createElement("div"); + var self_info = document.createElement("div"); self_info.setAttribute("class", "table"); + + var table_data = { + "IPv6 address": "self-address", + "IPv6 subnet": "self-subnet", + "Coords": "self-coords", + "Public key": "self-boxpubkey", + "Build name": "self-buildname", + "Build version": "self-version" + }; + + Object.keys(table_data).forEach(function(k) { + var tr = document.createElement("div"); + tr.setAttribute("class", "tr"); + var td1 = document.createElement("div"); td1.setAttribute("class", "td left"); + td1.textContent = k; + var td2 = document.createElement("div"); td2.setAttribute("class", "td left"); + td2.id = table_data[k]; + + tr.appendChild(td1); tr.appendChild(td2); self_info.appendChild(tr); + }); + + var info_title = document.createElement("h2"); info_title.innerText = _("Yggdrasil node status"); + view.appendChild(info_title); + view.appendChild(self_info); + var peering_title = document.createElement("h3"); peering_title.innerText = _("Active peers"); + view.appendChild(peering_title); + + var peerings = document.createElement("table"); + peerings.setAttribute("class", "table"); peerings.id = "yggdrasil-peerings"; + var tr = document.createElement("tr"); + tr.setAttribute("class", "tr table-titles"); + ["Endpoint", "Address", "Proto", "Uptime", "Received", "Transmitted"].forEach(function(t) { + var th = document.createElement("th"); th.setAttribute("class", "th nowrap left"); + th.innerText = t; + tr.appendChild(th); + }); + peerings.appendChild(tr); + view.appendChild(peerings); + return view; +} + +function update_active_peers() { + fs.exec("/usr/sbin/yggdrasilctl", ["-json", "getPeers"]).then(function(res){ + if (res && res.code === 0) { + var peers = JSON.parse(res.stdout.trim())["peers"]; + var table = document.querySelector('#yggdrasil-peerings'); + while (table.rows.length > 1) { table.deleteRow(1); } + Object.keys(peers).forEach(function(address) { + var row = table.insertRow(-1); + row.insertCell(-1).textContent = peers[address].endpoint; + row.insertCell(-1).textContent = address; + row.insertCell(-1).textContent = peers[address].proto; + row.insertCell(-1).textContent = '%t'.format(peers[address].uptime); + row.insertCell(-1).textContent = '%1024.2mB'.format(peers[address].bytes_recvd); + row.insertCell(-1).textContent = '%1024.2mB'.format(peers[address].bytes_sent); + }); + } + }); +} + +return L.view.extend({ + load: function() { + return Promise.all([ + L.resolveDefault(fs.stat("/usr/sbin/yggdrasilctl"), null), + L.resolveDefault(fs.exec("/usr/sbin/yggdrasilctl", ["-json", "getSelf"]), null), + L.resolveDefault(fs.exec("/usr/sbin/yggdrasilctl", ["-json", "getPeers"]), null) + ]); + }, + render: function(info) { + var view = init_view(); + + if (info[0] && info[1] && info[1].code === 0) { + var obj = JSON.parse(info[1].stdout.trim())["self"]; + var peers = JSON.parse(info[2].stdout.trim())["peers"]; + + var address = Object.keys(obj)[0]; + var r = obj[address]; + view.querySelector('#self-address').innerText = address; + view.querySelector('#self-subnet').innerText = r.subnet; + view.querySelector('#self-coords').innerText = r.coords; + view.querySelector('#self-boxpubkey').innerText = r.box_pub_key; + view.querySelector('#self-buildname').innerText = r.build_name; + view.querySelector('#self-version').innerText = r.build_version; + + var table = view.querySelector('#yggdrasil-peerings'); + Object.keys(peers).forEach(function(address) { + var row = table.insertRow(-1); + row.insertCell(-1).textContent = peers[address].endpoint; + row.insertCell(-1).textContent = address; + row.insertCell(-1).textContent = peers[address].proto; + row.insertCell(-1).textContent = '%t'.format(peers[address].uptime); + row.insertCell(-1).textContent = '%1024.2mB'.format(peers[address].bytes_recvd); + row.insertCell(-1).textContent = '%1024.2mB'.format(peers[address].bytes_sent); + + }); + setInterval(update_active_peers, 5000); + } else { + view.innerHTML = "

Yggdrasil is not running

"; + } + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/tunnel_routing.js b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/tunnel_routing.js new file mode 100644 index 000000000..77cfbc2dc --- /dev/null +++ b/applications/luci-app-yggdrasil/htdocs/luci-static/resources/view/yggdrasil/tunnel_routing.js @@ -0,0 +1,49 @@ +'use strict'; +'require form'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('yggdrasil', 'Yggdrasil'); + + s = m.section(form.TypedSection, "yggdrasil", _("Tunnel Routing")); + s.anonymous = true; + s.option(form.Flag, "TunnelRouting_Enable", "Enable tunnel routing", + _("Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively " + + "allows you to use Yggdrasil to route to, or to bridge other networks, " + + "similar to a VPN tunnel. Tunnelling works between any two nodes and " + + "does not require them to be directly peered.")); + + o = m.section(form.TableSection, "ipv4_remote_subnet", _("IPv4 remote subnet"), + _("IPv4 subnets belonging to remote nodes, mapped to the node's public")); + o.option(form.Value, "key", _("Key"), _("Public encryption key")); + o.option(form.Value, "subnet", _("Subnet"), _("IPv4 subnet")); + o.anonymous = true; + o.addremove = true; + + o = m.section(form.TableSection, "ipv4_local_subnet", _("IPv4 local subnet"), + _("IPv4 subnets belonging to this node's end of the tunnels. Only traffic " + + "from these ranges will be tunnelled.")); + o.option(form.Value, "subnet", _("Subnet"), _("IPv4 subnet")); + o.anonymous = true; + o.addremove = true; + + o = m.section(form.TableSection, "ipv6_remote_subnet", _("IPv6 remote subnet"), + _("IPv6 subnets belonging to remote nodes, mapped to the node's public")); + o.option(form.Value, "key", _("Key"), _("Public encryption key")); + o.option(form.Value, "subnet", _("Subnet"), _("IPv6 subnet")); + o.anonymous = true; + o.addremove = true; + + o = m.section(form.TableSection, "ipv6_local_subnet", _("IPv6 local subnet"), + _("IPv6 subnets belonging to this node's end of the tunnels. Only traffic " + + "from these ranges (or the Yggdrasil node's IPv6 address/subnet) " + + "will be tunnelled.")); + o.option(form.Value, "subnet", _("Subnet"), _("IPv6 subnet")); + o.anonymous = true; + o.addremove = true; + + return m.render(); + } +}); diff --git a/applications/luci-app-yggdrasil/luasrc/controller/yggdrasil.lua b/applications/luci-app-yggdrasil/luasrc/controller/yggdrasil.lua new file mode 100644 index 000000000..8a955520f --- /dev/null +++ b/applications/luci-app-yggdrasil/luasrc/controller/yggdrasil.lua @@ -0,0 +1,16 @@ +module("luci.controller.yggdrasil", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/yggdrasil") then + return + end + + entry({"admin", "network", "yggdrasil"}, firstchild(), "Yggdrasil").dependent = true + entry({"admin", "network", "yggdrasil", "status"}, view("yggdrasil/status"), _("Status"), 1).leaf = false + + entry({"admin", "network", "yggdrasil", "peers"}, view("yggdrasil/peers"), _("Peers"), 2).leaf = false + entry({"admin", "network", "yggdrasil", "settings"}, view("yggdrasil/settings"), _("Settings"), 3).leaf = false + entry({"admin", "network", "yggdrasil", "keys"}, view("yggdrasil/keys"), _("Encryption keys"), 4).leaf = false + entry({"admin", "network", "yggdrasil", "session_firewall"}, view("yggdrasil/session_firewall"), _("Session firewall"), 5).leaf = false + entry({"admin", "network", "yggdrasil", "tunnel_routing"}, view("yggdrasil/tunnel_routing"), _("Tunnel routing"), 6).leaf = false +end diff --git a/applications/luci-app-yggdrasil/root/usr/share/rpcd/acl.d/luci-app-yggdrasil.json b/applications/luci-app-yggdrasil/root/usr/share/rpcd/acl.d/luci-app-yggdrasil.json new file mode 100644 index 000000000..22e00ef8f --- /dev/null +++ b/applications/luci-app-yggdrasil/root/usr/share/rpcd/acl.d/luci-app-yggdrasil.json @@ -0,0 +1,10 @@ +{ + "luci-app-yggdrasil": { + "description": "Grant access to LuCI app yggdrasil", + "write": { + "file": { + "/usr/sbin/yggdrasilctl": [ "exec" ] + } + } + } +}