diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-08-20 15:31:35 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-09-10 15:28:16 +0200 |
commit | 6a2a53a82918ea2ccbbbe23510aa0279827b2783 (patch) | |
tree | 7f065f96cc0f255ced7256c1d9092f0e1433fda0 /protocols/luci-proto-openconnect | |
parent | 0674fc20414e575c346ceb2066ff3af7e8601a48 (diff) |
protocols: add client side protocol handler implementations
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'protocols/luci-proto-openconnect')
3 files changed, 254 insertions, 0 deletions
diff --git a/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js new file mode 100644 index 0000000000..14fc8f6d35 --- /dev/null +++ b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js @@ -0,0 +1,159 @@ +'use strict'; +'require rpc'; +'require form'; +'require network'; + +var callGetCertificateFiles = rpc.declare({ + object: 'luci.openconnect', + method: 'getCertificates', + params: [ 'interface' ], + expect: { '': {} } +}); + +var callSetCertificateFiles = rpc.declare({ + object: 'luci.openconnect', + method: 'setCertificates', + params: [ 'interface', 'user_certificate', 'user_privatekey', 'ca_certificate' ], + expect: { '': {} } +}); + +network.registerPatternVirtual(/^vpn-.+$/); + +function sanitizeCert(s) { + if (typeof(s) != 'string') + return null; + + s = s.trim(); + + if (s == '') + return null; + + s = s.replace(/\r\n?/g, '\n'); + + if (!s.match(/\n$/)) + s += '\n'; + + return s; +} + +function validateCert(priv, section_id, value) { + var beg = priv ? /^-----BEGIN RSA PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/, + end = priv ? /^-----END RSA PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/, + lines = value.trim().split(/[\r\n]/), + start = false, + i; + + for (i = 0; i < lines.length; i++) { + if (lines[i].match(beg)) + start = true; + else if (start && !lines[i].match(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)) + break; + } + + if (!start || i < lines.length - 1 || !lines[i].match(end)) + return _('This does not look like a valid PEM file'); + + return true; +} + +return network.registerProtocol('openconnect', { + getI18n: function() { + return _('OpenConnect (CISCO AnyConnect)'); + }, + + getIfname: function() { + return this._ubus('l3_device') || 'vpn-%s'.format(this.sid); + }, + + getOpkgPackage: function() { + return 'openconnect'; + }, + + isFloating: function() { + return true; + }, + + isVirtual: function() { + return true; + }, + + getDevices: function() { + return null; + }, + + containsDevice: function(ifname) { + return (network.getIfnameOf(ifname) == this.getIfname()); + }, + + renderFormOptions: function(s) { + var dev = this.getDevice().getName(), + certLoadPromise = null, + o; + + o = s.taboption('general', form.Value, 'server', _('VPN Server')); + o.datatype = 'host(0)'; + + o = s.taboption('general', form.Value, 'port', _('VPN Server port')); + o.placeholder = '443'; + o.datatype = 'port'; + + s.taboption('general', form.Value, 'serverhash', _("VPN Server's certificate SHA1 hash")); + s.taboption('general', form.Value, 'authgroup', _('Auth Group')); + s.taboption("general", form.Value, "username", _("Username")); + + o = s.taboption('general', form.Value, 'password', _('Password')); + o.password = true; + + o = s.taboption('general', form.Value, 'password2', _('Password2')); + o.password = true; + + o = s.taboption('general', form.TextValue, 'usercert', _('User certificate (PEM encoded)')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, false); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.user_certificate }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, sanitizeCert(value), null, null); + }; + + o = s.taboption('general', form.TextValue, 'userkey', _('User key (PEM encoded)')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, true); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.user_privatekey }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, null, sanitizeCert(value), null); + }; + + o = s.taboption('general', form.TextValue, 'ca', _('CA certificate; if empty it will be saved after the first connection.')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, false); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.ca_certificate }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, null, null, sanitizeCert(value)); + }; + + o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured')); + o.default = o.enabled; + + o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric')); + o.placeholder = '0'; + o.datatype = 'uinteger'; + o.depends('defaultroute', '1'); + + o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU')); + o.optional = true; + o.placeholder = 1406; + o.datatype = 'range(68, 9200)'; + } +}); diff --git a/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect b/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect new file mode 100755 index 0000000000..9378cc518b --- /dev/null +++ b/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect @@ -0,0 +1,78 @@ +#!/usr/bin/env lua + +local json = require "luci.jsonc" +local fs = require "nixio.fs" + +local function readfile(path) + local s = fs.readfile(path) + return s and (s:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function writefile(path, data) + local n = fs.writefile(path, data) + return (n == #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 + +if arg[1] == "list" then + print(json.stringify({ + getCertificates = { + interface = "interface" + }, + setCertificates = { + interface = "interface", + user_certificate = "PEM file data", + user_privatekey = "PEM file data", + ca_certificate = "PEM file data" + } + })) +elseif arg[1] == "call" then + local args = parseInput() + + if not args.interface or + type(args.interface) ~= "string" or + not args.interface:match("^[a-zA-Z0-9_]+$") + then + print(json.stringify({ error = "Invalid interface name" })) + os.exit(1) + end + + if arg[2] == "getCertificates" then + print(json.stringify({ + user_certificate = readfile(string.format("/etc/openconnect/user-cert-%s.pem", args.interface)), + user_privatekey = readfile(string.format("/etc/openconnect/user-key-%s.pem", args.interface)), + ca_certificate = readfile(string.format("/etc/openconnect/ca-%s.pem", args.interface)) + })) + elseif arg[2] == "setCertificates" then + if args.user_certificate then + writefile(string.format("/etc/openconnect/user-cert-%s.pem", args.interface), args.user_certificate) + end + if args.user_privatekey then + writefile(string.format("/etc/openconnect/user-key-%s.pem", args.interface), args.user_privatekey) + end + if args.ca_certificate then + writefile(string.format("/etc/openconnect/ca-%s.pem", args.interface), args.ca_certificate) + end + print(json.stringify({ result = true })) + end +end diff --git a/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json b/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json new file mode 100644 index 0000000000..66bc5ac24f --- /dev/null +++ b/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json @@ -0,0 +1,17 @@ +{ + "luci-proto-openconnect": { + "description": "Grant access to LuCI OpenConnect procedures", + "read": { + "ubus": { + "luci.openconnect": [ "getCertificates" ] + }, + "uci": [ "network" ] + }, + "write": { + "ubus": { + "luci.openconnect": [ "setCertificates" ] + }, + "uci": [ "network" ] + } + } +} |