luci-proto-wireguard: rewrite rpcd handler in ucode
authorJo-Philipp Wich <jo@mein.io>
Fri, 23 Sep 2022 19:04:44 +0000 (21:04 +0200)
committerJo-Philipp Wich <jo@mein.io>
Mon, 24 Oct 2022 23:03:37 +0000 (01:03 +0200)
Rewrite the wireguard rpcd plugin in ucode to prevent an implicit dependency
on the LuCI Lua runtime.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
protocols/luci-proto-wireguard/Makefile
protocols/luci-proto-wireguard/root/usr/libexec/rpcd/luci.wireguard [deleted file]
protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard [new file with mode: 0644]

index 75c5e1868fae93ddd9956edb1291c3267a2447d6..7d2614a0a8221de8b8fc63c1358c10f6f9d84228 100644 (file)
@@ -7,7 +7,7 @@
 include $(TOPDIR)/rules.mk
 
 LUCI_TITLE:=Support for WireGuard VPN
-LUCI_DEPENDS:=+wireguard-tools +libuci-lua
+LUCI_DEPENDS:=+wireguard-tools +ucode
 LUCI_PKGARCH:=all
 
 include ../../luci.mk
diff --git a/protocols/luci-proto-wireguard/root/usr/libexec/rpcd/luci.wireguard b/protocols/luci-proto-wireguard/root/usr/libexec/rpcd/luci.wireguard
deleted file mode 100755 (executable)
index a42b6fa..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env lua
-
-local json = require "luci.jsonc"
-local util = require "luci.util"
-local sys = require "luci.sys"
-local io = require "io"
-local uci = require "uci"
-local fs = require "nixio.fs"
-
-local methods = {
-       generatePsk = {
-               call = function()
-                       local psk = sys.exec("wg genpsk"):sub(1, -2)
-
-                       return {psk = psk}
-               end
-       },
-       generateKeyPair = {
-               call = function()
-                       local prv = sys.exec("wg genkey 2>/dev/null"):sub(1, -2)
-                       local pub = sys.exec("echo %s | wg pubkey 2>/dev/null" % util.shellquote(prv)):sub(1, -2)
-
-                       return {keys = {priv = prv, pub = pub}}
-               end
-       },
-       getPublicAndPrivateKeyFromPrivate = {
-               args = {privkey = "privkey"},
-               call = function(args)
-                       local pubkey = sys.exec("echo %s | wg pubkey 2>/dev/null" % util.shellquote(args.privkey)):sub(1, -2)
-
-                       return {keys = {priv = args.privkey, pub = pubkey}}
-               end
-       },
-       getWgInstances = {
-               call = function()
-                       local data = {}
-                       local last_device = ""
-                       local qr_pubkey = {}
-
-                       local wg_dump = io.popen("wg show all dump 2>/dev/null")
-                       if wg_dump then
-                               local line
-                               for line in wg_dump:lines() do
-                                       local line = string.split(line, "\t")
-                                       if not (last_device == line[1]) then
-                                               last_device = line[1]
-                                               data[line[1]] = {
-                                                       name = line[1],
-                                                       public_key = line[3],
-                                                       listen_port = line[4],
-                                                       fwmark = line[5],
-                                                       peers = {}
-                                               }
-                                               if not line[3] or line[3] == "" or line[3] == "(none)" then
-                                                       qr_pubkey[line[1]] = ""
-                                               else
-                                                       qr_pubkey[line[1]] = "PublicKey = " .. line[3]
-                                               end
-                                       else
-                                               local peer_name
-                                               local cur = uci.cursor()
-
-                                               cur:foreach(
-                                                       "network",
-                                                       "wireguard_" .. line[1],
-                                                       function(s)
-                                                               if s.public_key == line[2] then
-                                                                       peer_name = s.description
-                                                               end
-                                                       end
-                                               )
-
-                                               local peer = {
-                                                       name = peer_name,
-                                                       public_key = line[2],
-                                                       endpoint = line[4],
-                                                       allowed_ips = {},
-                                                       latest_handshake = line[6],
-                                                       transfer_rx = line[7],
-                                                       transfer_tx = line[8],
-                                                       persistent_keepalive = line[9]
-                                               }
-
-                                               if not (line[4] == "(none)") then
-                                                       local ipkey, ipvalue
-                                                       for ipkey, ipvalue in pairs(string.split(line[5], ",")) do
-                                                               if #ipvalue > 0 then
-                                                                       table.insert(peer["allowed_ips"], ipvalue)
-                                                               end
-                                                       end
-                                               end
-
-                                               table.insert(data[line[1]].peers, peer)
-                                       end
-                               end
-                       end
-
-                       return data
-               end
-       }
-}
-
-local function parseInput()
-       local parse = json.new()
-       local done, err
-
-       while true do
-               local chunk = io.read(4096)
-               if not chunk then
-                       break
-               elseif not done and not err then
-                       done, err = parse:parse(chunk)
-               end
-       end
-
-       if not done then
-               print(json.stringify({error = err or "Incomplete input"}))
-               os.exit(1)
-       end
-
-       return parse:get()
-end
-
-local function validateArgs(func, uargs)
-       local method = methods[func]
-       if not method then
-               print(json.stringify({error = "Method not found"}))
-               os.exit(1)
-       end
-
-       if type(uargs) ~= "table" then
-               print(json.stringify({error = "Invalid arguments"}))
-               os.exit(1)
-       end
-
-       uargs.ubus_rpc_session = nil
-
-       local k, v
-       local margs = method.args or {}
-       for k, v in pairs(uargs) do
-               if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then
-                       print(json.stringify({error = "Invalid arguments"}))
-                       os.exit(1)
-               end
-       end
-
-       return method
-end
-
-if arg[1] == "list" then
-       local _, method, rv = nil, nil, {}
-       for _, method in pairs(methods) do
-               rv[_] = method.args or {}
-       end
-       print((json.stringify(rv):gsub(":%[%]", ":{}")))
-elseif arg[1] == "call" then
-       local args = parseInput()
-       local method = validateArgs(arg[2], args)
-       local result, code = method.call(args)
-       print((json.stringify(result):gsub("^%[%]$", "{}")))
-       os.exit(code or 0)
-end
diff --git a/protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard b/protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard
new file mode 100644 (file)
index 0000000..fc99919
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
+// Licensed to the public under the Apache License 2.0.
+
+'use strict';
+
+import { cursor } from 'uci';
+import { popen } from 'fs';
+
+
+function shellquote(s) {
+       return `'${replace(s ?? '', "'", "'\\''")}'`;
+}
+
+function command(cmd) {
+       return trim(popen(cmd)?.read?.('all'));
+}
+
+
+const methods = {
+       generatePsk: {
+               call: function() {
+                       return { psk: command('wg genpsk 2>/dev/null') };
+               }
+       },
+
+       generateKeyPair: {
+               call: function() {
+                       const priv = command('wg genkey 2>/dev/null');
+                       const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
+
+                       return { keys: { priv, pub } };
+               }
+       },
+
+       getPublicAndPrivateKeyFromPrivate: {
+               args: { privkey: "privkey" },
+               call: function(req) {
+                       const priv = req.args?.privkey;
+                       const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
+
+                       return { keys: { priv, pub } };
+               }
+       },
+
+       getWgInstances: {
+               call: function() {
+                       const data = {};
+                       let last_device;
+                       let qr_pubkey = {};
+
+                       const uci = cursor();
+                       const wg_dump = popen("wg show all dump 2>/dev/null");
+
+                       if (wg_dump) {
+                               for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) {
+                                       const record = split(rtrim(line, '\n'), '\t');
+
+                                       if (last_device != record[0]) {
+                                               last_device = record[0];
+                                               data[last_device] = {
+                                                       name: last_device,
+                                                       public_key: record[2],
+                                                       listen_port: record[3],
+                                                       fwmark: record[4],
+                                                       peers: []
+                                               };
+
+                                               if (!length(record[2]) || record[2] == '(none)')
+                                                       qr_pubkey[last_device] = '';
+                                               else
+                                                       qr_pubkey[last_device] = `PublicKey = ${record[2]}`;
+                                       }
+                                       else {
+                                               let peer_name;
+
+                                               uci.foreach('network', `wireguard_${last_device}`, (s) => {
+                                                       if (s.public_key == record[1])
+                                                               peer_name = s.description;
+                                               });
+
+                                               const peer = {
+                                                       name: peer_name,
+                                                       public_key: record[1],
+                                                       endpoint: record[3],
+                                                       allowed_ips: [],
+                                                       latest_handshake: record[5],
+                                                       transfer_rx: record[6],
+                                                       transfer_tx: record[7],
+                                                       persistent_keepalive: record[8]
+                                               };
+
+                                               if (record[3] != '(none)' && length(record[4]))
+                                                       push(peer.allowed_ips, ...split(record[4], ','));
+
+                                               push(data[last_device].peers, peer);
+                                       }
+                               }
+                       }
+
+                       return data;
+               }
+       }
+};
+
+return { 'luci.wireguard': methods };