diff options
-rw-r--r-- | assets/js/main.js | 466 | ||||
-rw-r--r-- | index.html | 36 |
2 files changed, 487 insertions, 15 deletions
diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..b042c2a --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,466 @@ +var iface = 'wg0'; +var persistent_keepalive = 25; + +function parse_placeholder(val) { + return true; +} + +function parse_endpoint(val) { + val = val.trim(); + if (val.length == 0) { + throw new Error('Server endpoint cannot be an empty string.'); + } + return val; +} + +function parse_key(val) { + val = val.trim(); + if (val.length == 0) { + throw new Error('Key as used by WireGuard cannot be an empty string.'); + } + return val; +} + +var IPAddr = function(src, parser) { + this.src = src; + this.parser = parser; + + try { + var val = parser.parseCIDR(src); + } catch (err) { + throw new Error('Sorry, IP address validation failed with the following error:\n' + err.message); + } + + this.addr = val[0]; + this.netmask = val[1]; + + this.str = this.addr.toString(); + + if (this.addr.kind() == 'ipv4') { + var networkID = parser.networkAddressFromCIDR(this.src); + if (this.str == networkID) { + throw new Error('Please use a real host IP address, not a network identifier.'); + } + var broadcast = parser.broadcastAddressFromCIDR(this.src); + if (this.str == broadcast) { + throw new Error('Please use a real host IP address, not a broadcast address.'); + } + } +} + +IPAddr.prototype.for_client = function() { + return this.str + '/' + this.netmask; +} + +IPAddr.prototype.for_server = function() { + if (this.addr.kind() == 'ipv4') { + var netmask = 32; + } else if (this.addr.kind() == 'ipv6') { + var netmask = 128; + } else { + throw new Error('What? Not IPv4, and _not_ IPv6?!'); + } + return this.str + '/' + netmask; +} + +function parse_ip(src, parser) { + src = src.trim(); + if (src.length == 0) { + throw new Error('IP address cannot be an empty string.'); + } + return new IPAddr(src, parser); +} + +function parse_ipv4(val) { + return parse_ip(val, ipaddr.IPv4); +} + +function parse_ipv6(val) { + return parse_ip(val, ipaddr.IPv6); +} + +var Input = function(name) { + this.name = name; +} + +Input.prototype.input_id = function() { + return '#param_' + this.name; +} + +Input.prototype.error_id = function() { + return this.input_id() + '_error'; +} + +Input.prototype.value = function() { + return $(this.input_id()).val(); +} + +Input.prototype.show_error = function(msg) { + $(this.error_id()).text(msg); + $(this.error_id()).show(); +} + +Input.prototype.hide_error = function() { + $(this.error_id()).hide(); +} + +var Value = function(name, parser) { + this.input = new Input(name); + this.parser = parser; + + this.value = null; + this.error = null; + try { + this.value = parser(this.input.value()); + this.input.hide_error(); + } catch (err) { + this.error = err.message; + this.input.show_error(this.error); + } +} + +var Endpoint = function(name) { + Value.call(this, name, parse_endpoint); +} + +Endpoint.prototype = Object.create(Value.prototype); +Endpoint.prototype.constructor = Endpoint; + +var Key = function(name) { + Value.call(this, name, parse_key); +} + +Key.prototype = Object.create(Value.prototype); +Key.prototype.constructor = Key; + +var IPv4 = function(name) { + Value.call(this, name, parse_ipv4); +} + +IPv4.prototype = Object.create(Value.prototype); +IPv4.prototype.constructor = IPv4; + +var IPv6 = function(name) { + Value.call(this, name, parse_ipv6); +} + +IPv6.prototype = Object.create(Value.prototype); +IPv6.prototype.constructor = IPv6; + +var Data = function() { + this.server_endpoint = new Endpoint('server_endpoint'); + this.server_public = new Key('server_public_key'); + this.server_preshared = new Key('server_preshared_key'); + this.client_public = new Key('client_public_key'); + this.client_private = new Key('client_private_key'); + this.client_ipv4 = new IPv4('client_ipv4'); + this.client_ipv6 = new IPv6('client_ipv6'); + + this.values = [ + this.server_endpoint, + this.server_public, + this.server_preshared, + this.client_public, + this.client_private, + this.client_ipv4, + this.client_ipv6 + ]; + + if (this.has_errors()) { + this.show_error('Please correct the input errors above first.'); + } else { + this.hide_error(); + } +} + +Data.prototype.has_errors = function() { + var has = false; + this.values.forEach(function(value) { + if (value.error) { + has = true; + } + }); + return has; +} + +Data.prototype.show_error = function(msg) { + $('#params_error').text(msg); + $('#params_error').show(); +} + +Data.prototype.hide_error = function() { + $('#params_error').hide(); +} + +var ConfigFile = function(name, contents) { + this.name = name; + this.contents = contents; +} + +ConfigFile.prototype.toString = function() { + return this.contents; +} + +var Script = function(text) { + this.text = text; +} + +Script.prototype.toString = function() { + return this.text; +} + +function wg_quick_client_file(data) { + var path = `/etc/wireguard/${iface}.conf`; + return new ConfigFile(path, +`# On the client, put this to ${path} and either start the +# the wg-quick@${iface} systemd service, or run \`wg-quick up ${iface}\`. + +[Interface] +PrivateKey = ${data.client_private.value} + +[Peer] +Endpoint = ${data.server_endpoint.value} +PublicKey = ${data.server_public.value} +PresharedKey = ${data.server_preshared.value} +AllowedIPs = ${data.client_ipv4.value.for_client()}, ${data.client_ipv6.value.for_client()} +PersistentKeepalive = ${persistent_keepalive} +`); +} + +function wg_quick_server_file(data) { + var path = `/etc/wireguard/${iface}.conf`; + return new ConfigFile(path, +`# On the server, add this to ${path} and either restart +# the wg-quick@${iface} systemd service, or run \`wg syncconf ${iface} +# ${path}\`. + +# Previous contents goes here... + +[Peer] +PublicKey = ${data.client_public.value} +PresharedKey = ${data.server_preshared.value} +AllowedIPs = ${data.client_ipv4.value.for_server()}, ${data.client_ipv6.value.for_server()} +`); +} + +function systemd_client_netdev_file(data) { + var path = `/etc/systemd/network/${iface}.netdev`; + return new ConfigFile(path, +`# On the client, you need two files. Put this into ${path} +# and after you're done with both files, restart the systemd-networkd service. + +[NetDev] +Name = ${iface} +Kind = wireguard + +[WireGuard] +PrivateKey = ${data.client_private.value} + +[WireGuardPeer] +Endpoint = ${data.server_endpoint.value} +PublicKey = ${data.server_public.value} +PresharedKey = ${data.server_preshared.value} +AllowedIPs = ${data.client_ipv4.value.for_client()}, ${data.client_ipv6.value.for_client()} +PersistentKeepalive = ${persistent_keepalive} +`); +} + +function systemd_client_network_file(data) { + var path = `/etc/systemd/network/${iface}.network`; + return new ConfigFile(path, +`# This is the second file. Put this into ${path} +# and if you're done the first file already, restart the systemd-networkd +# service. + +[Match] +Name = ${iface} + +[Network] +Address = ${data.client_ipv4.value.for_client()} +Address = ${data.client_ipv6.value.for_client()} +`); +} + +function systemd_server_netdev_file(data) { + var path = `/etc/systemd/network/${iface}.netdev`; + return new ConfigFile(path, +`# On the server, add this to ${path} and restart +# the systemd-networkd service. + +# Previous contents goes here... + +[WireGuardPeer] +PublicKey = ${data.client_public.value} +PresharedKey = ${data.server_preshared.value} +AllowedIPs = ${data.client_ipv4.value.for_server()}, ${data.client_ipv6.value.for_server()} +`); +} + +function nmcli_client_file(data) { + var path = `/etc/NetworkManager/system-connections/${iface}.nmconnection`; + return new ConfigFile(path, +`# On the client, put this to ${path} +# and run \`nmcli c reload && nmcli c up ${iface}\`. + +[connection] +id=${iface} +type=wireguard +interface-name=${iface} + +[wireguard] +private-key=${data.client_private.value} +private-key-flags=0 + +[wireguard-peer.${data.server_public.value}] +endpoint=${data.server_endpoint.value} +preshared-key=${data.server_preshared.value} +preshared-key-flags=0 +allowed-ips=${data.client_ipv4.value.for_client()};${data.client_ipv6.value.for_client()}; +persistent-keepalive=${persistent_keepalive} + +[ipv4] +address1=${data.client_ipv4.value.for_client()} +method=manual + +[ipv6] +address1=${data.client_ipv6.value.for_client()} +method=manual +`); +} + +function nmcli_server_file(data) { + var path = `/etc/NetworkManager/system-connections/${iface}.nmconnection`; + return new ConfigFile(path, +`# On the server, add this to ${path} +# and run \`nmcli c reload && nmcli c up ${iface}\'; + +# Previous contents goes here... + +[wireguard-peer.${data.client_public.value}] +preshared-key=${data.server_preshared.value} +preshared-key-flags=0 +allowed-ips=${data.client_ipv4.value.for_server()};${data.client_ipv6.value.for_server()}; +`); +} + +function manual_client_script(data) { + return new Script( +`# On the client, run this to set up a connection. +# Make sure your .bash_history or whatever is secure before running this. + +ip link add dev ${iface} type wireguard +ip addr add ${data.client_ipv4.value.for_client()} dev ${iface} +ip addr add ${data.client_ipv6.value.for_client()} dev ${iface} +wg set ${iface} private-key <( echo ${data.client_private.value} ) +wg set ${iface} peer ${data.server_public.value} preshared-key <( echo ${data.server_preshared.value} ) endpoint ${data.server_endpoint.value} allowed-ips ${data.client_ipv4.value.for_client()},${data.client_ipv6.value.for_client()} +ip link set ${iface} up +`); +} + +function manual_server_script(data) { + return new Script( +`# On the server, run this to tweak an existing connection. +# Make sure your .bash_history or whatever is secure before running this. + +wg set ${iface} peer ${data.client_public.value} preshared-key <( echo ${data.server_preshared.value} ) allowed-ips ${data.client_ipv4.value.for_server()},${data.client_ipv6.value.for_server()} +`); +} + +var InstructWgQuick = function() {} + +InstructWgQuick.prototype.name = function() { return 'wg-quick'; } + +InstructWgQuick.prototype.for_client = function(data) { + return [wg_quick_client_file(data)]; +} + +InstructWgQuick.prototype.for_server = function(data) { + return [wg_quick_server_file(data)]; +} + +var InstructSystemd = function() {} + +InstructSystemd.prototype.name = function() { return 'systemd-networkd'; } + +InstructSystemd.prototype.for_client = function(data) { + return [systemd_client_netdev_file(data), systemd_client_network_file(data)]; +} +InstructSystemd.prototype.for_server = function(data) { + return [systemd_server_netdev_file(data)]; +} + +var InstructNetworkManager = function() {} + +InstructNetworkManager.prototype.name = function() { return 'NetworkManager'; } + +InstructNetworkManager.prototype.for_client = function(data) { + return [nmcli_client_file(data)]; +} + +InstructNetworkManager.prototype.for_server = function(data) { + return [nmcli_server_file(data)]; +} + +var InstructManual = function() {} + +InstructManual.prototype.name = function() { return 'Manual'; } + +InstructManual.prototype.for_client = function(data) { + return [manual_client_script(data)]; +} + +InstructManual.prototype.for_server = function(data) { + return [manual_server_script(data)]; +} + +function clear_instructors() { + $('#instructors').empty(); +} + +function format_instructions(instructions) { + var container = $('<div/>'); + instructions.forEach(function(instruction) { + container.append($('<pre/>').text(instruction.toString())); + }); + return container; +} + +function format_server_instructions(instructor, data) { + return format_instructions(instructor.for_server(data)); +} + +function format_client_instructions(instructor, data) { + return format_instructions(instructor.for_client(data)); +} + +function add_instructor(instructor, data) { + $('#instructors').append($('<div/>') + .append($('<h2/>').text(instructor.name())) + .append($('<div class="row"/>') + .append($('<div class="col-md-6"/>') + .append(format_server_instructions(instructor, data))) + .append($('<div class="col-md-6"/>') + .append(format_client_instructions(instructor, data))))); +} + +function form_on_submit() { + clear_instructors(); + + var data = new Data(); + if (data.has_errors()) { + return false; + } + + var instructors = [ + new InstructWgQuick(), + new InstructSystemd(), + new InstructNetworkManager(), + new InstructManual() + ]; + + instructors.forEach(function(instructor) { + add_instructor(instructor, data); + }); + + return false; +} @@ -4,6 +4,9 @@ layout: plain navbar: link: Main icon: home +custom_js: + - https://cdn.jsdelivr.net/npm/ipaddr.js@2.0.0/ipaddr.min.js + - main.js params: - id: server @@ -68,38 +71,41 @@ show the configuration that can be easily consumed by the new client.</p> </div> </div> -<form class="form-horizontal"> +<form action="javascript:void(0);" onsubmit="form_on_submit(); return false;"> <div class="row"> {% for grp in page.params %} <div class="col-md-6"> - <div class="form-group"> - <div class="row"> + <div class="form-horizontal"> + <div class="form-group"> <div class="col-sm-offset-3 col-sm-9"> <h2 class="text-center h3" style="margin-bottom: 0;">{{ grp.desc }}</h2> </div> </div> - </div> {% for param in grp.items %} - <div class="form-group"> - <div class="row"> + <div class="form-group"> <label for="param_{{ grp.id }}_{{ param.id }}" class="col-sm-3 control-label">{{ param.name }}</label> <div class="col-sm-9"> <input type="text" class="form-control" id="param_{{ grp.id }}_{{ param.id }}" placeholder="{{ param.placeholder }}" aria-describedby="param_{{ grp.id }}_{{ param.id }}_help"> - <span id="param_{{ grp.id }}_{{ param.id }}_help" class="help-block" style="margin-bottom: 0;">{{ param.help }}</span> + <span id="param_{{ grp.id }}_{{ param.id }}_help" class="help-block" style="margin-bottom: 0;"> + <span id="param_{{ grp.id }}_{{ param.id }}_error" class="text-danger small" style="display: block; white-space: pre-line;"></span> + <span>{{ param.help }}</span> + </span> </div> </div> - </div> {% endfor %} + </div> </div> {% endfor %} - <div class="col-md-offset-6 col-md-6"> - <div class="form-group"> - <div class="row"> - <div class="col-sm-offset-3 col-sm-9 text-center"> - <button type="submit" class="btn btn-primary">Generate</button> - </div> - </div> + <div class="row"> + <div class="col-md-offset-6 col-md-6 text-center"> + <button type="submit" class="btn btn-primary">Generate</button> + <span class="help-block" style="margin-bottom: 0;"> + <span id="params_error" class="text-danger small" style="white-space: pre-line;"></span> + </span> </div> </div> </div> </form> + +<div id="instructors"> +</div> |