├── .gitignore ├── ztncui_code@0.8.6 ├── build │ ├── before-upgrade.sh │ ├── after-remove.sh │ ├── .gitignore │ ├── before-remove.sh │ ├── ztncui.service │ ├── before-install.sh │ ├── binding.gyp.patch │ ├── after-upgrade.sh │ ├── after-install.sh │ ├── build.sh │ └── openssl.cnf └── src │ ├── etc │ ├── .gitignore │ ├── tls │ │ └── .gitignore │ └── default.passwd │ ├── public │ ├── .well-known │ │ └── acme-challenge │ │ │ └── .gitignore │ ├── favicon.ico │ ├── images │ │ └── key-logo.svg │ └── stylesheets │ │ └── style.css │ ├── .gitignore │ ├── views │ ├── error.pug │ ├── front_door.pug │ ├── login_layout.pug │ ├── controller_layout.pug │ ├── not_implemented.pug │ ├── users_layout.pug │ ├── member_detail.pug │ ├── index.pug │ ├── network_layout.pug │ ├── users.pug │ ├── network_delete.pug │ ├── v4AssignMode.pug │ ├── network_create.pug │ ├── private.pug │ ├── user_delete.pug │ ├── member_delete.pug │ ├── v6AssignMode.pug │ ├── networks.pug │ ├── login.pug │ ├── dns.pug │ ├── routes.pug │ ├── head_layout.pug │ ├── ipAssignmentPools.pug │ ├── password.pug │ ├── ipAssignments.pug │ ├── network_easy.pug │ └── network_detail.pug │ ├── controllers │ ├── token.js │ ├── auth.js │ ├── usersController.js │ ├── zt.js │ └── networkController.js │ ├── package.json │ ├── routes │ ├── users.js │ ├── index.js │ └── zt_controller.js │ ├── app.js │ └── bin │ └── www └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .PrivateProjects -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/before-upgrade.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/etc/.gitignore: -------------------------------------------------------------------------------- 1 | passwd 2 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/after-remove.sh: -------------------------------------------------------------------------------- 1 | systemctl daemon-reload 2 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/.gitignore: -------------------------------------------------------------------------------- 1 | Release/ 2 | Staging/ 3 | ztncui 4 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/etc/tls/.gitignore: -------------------------------------------------------------------------------- 1 | fullchain.pem 2 | privkey.pem 3 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/public/.well-known/acme-challenge/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/before-remove.sh: -------------------------------------------------------------------------------- 1 | systemctl stop ztncui 2 | systemctl disable ztncui 3 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | etc/passwd 3 | etc/storage/ 4 | node_modules/ 5 | *.swp 6 | .env 7 | ztncui 8 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/error.pug: -------------------------------------------------------------------------------- 1 | extends controller_layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcDhlPro/ZeroTierOne-SelfHostingNetworkControllers-ZtnCui/HEAD/ztncui_code@0.8.6/src/public/favicon.ico -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/etc/default.passwd: -------------------------------------------------------------------------------- 1 | {"admin":{"name":"admin","pass_set":false,"hash":"$argon2i$v=19$m=4096,t=3,p=1$/VYxjWHBzbkuCEO6Hh0AUw$nJaTJtth57vCAyYvg+UbtnscilR0UcE02AfLOhERe3A"}} -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/ztncui.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ztncui - ZeroTier network controller user interface 3 | Documentation=https://key-networks.com 4 | After=network.target 5 | 6 | [Service] 7 | Type=simple 8 | User=ztncui 9 | WorkingDirectory=/opt/key-networks/ztncui 10 | ExecStart=/opt/key-networks/ztncui/ztncui 11 | Restart=on-failure 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/front_door.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends login_layout 7 | 8 | block login_content 9 | h1!= title 10 | 11 | h2 12 | a(href='https://zerotier.com' target='_blank') ZeroTier 13 | | network controller UI 14 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/before-install.sh: -------------------------------------------------------------------------------- 1 | getent passwd ztncui || useradd --system --home-dir /opt/key-networks/ztncui --shell /bin/false ztncui 2 | if [ $(getent group zerotier-one) ]; then 3 | echo "Adding user ztncui to group zerotier-one..." 4 | usermod -a -G zerotier-one ztncui 5 | chmod g+r /var/lib/zerotier-one/authtoken.secret 6 | else 7 | echo "Could not add user ztncui to group zerotier-one... is zerotier-one installed?" 8 | fi 9 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/binding.gyp.patch: -------------------------------------------------------------------------------- 1 | --- ../src/node_modules/argon2/binding.gyp 2 | +++ ../src/node_modules/argon2/binding.gyp 3 | @@ -47,6 +47,7 @@ 4 | ], 5 | "cflags+": ["-Wno-cast-function-type"], 6 | "include_dirs+": ["#{zt_status.address} 20 | h4 ZeroTier version #{zt_status.version} 21 | h4 22 | a(href='/controller/networks') List all networks on this network controller 23 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/network_layout.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends controller_layout 7 | 8 | block content 9 | if error 10 | b #{error} 11 | else 12 | .row 13 | .col-sm-10 14 | block network_title 15 | h2 16 | | Network 17 | a(href='/controller/network/' + network.nwid) #{network.name} 18 | | (#{network.nwid}): 19 | block title 20 | if title 21 | h3= title 22 | 23 | .col-sm-2 24 | h2.right 25 | a.btn.btn-default(href=navigate.whence role='button') Back 26 | block net_content 27 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/users.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends users_layout 7 | 8 | block users_content 9 | table.table.table-responsive.table-striped.table-hover 10 | each user in users 11 | tr 12 | td(width='3%') 13 | a(href='/users/' + user.name + '/delete') 14 | i.glyphicon.glyphicon-trash 15 | td(width='15%') 16 | a(href='/users/' + user.name + '/password') #{user.name} 17 | td(width='82%') 18 | a(href='/users/' + user.name + '/password') set password 19 | 20 | else 21 | .alert.alert-info 22 | strong There are no users on this system 23 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/network_delete.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | if network.deleted 10 | .alert.alert-success 11 | strong #{network.name} (#{network.nwid}) was deleted 12 | 13 | else 14 | .alert.alert-danger 15 | strong Warning! Deleting a network cannot be undone 16 | form(method='POST' action='') 17 | button.btn.btn-danger(type='submit', name='delete') Delete #{network.name} (#{network.nwid}) 18 | = ' ' 19 | a.btn.btn-default(href='/controller/networks', name='cancel', role='button') Cancel 20 | 21 | if errors 22 | ul 23 | for err in errors 24 | li!= err.msg 25 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/v4AssignMode.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | script. 10 | $(function() { 11 | $('.checkbox').on('click', function() { 12 | $.post('', {'zt': $('#v4AssignModeCheckBox').is(':checked')}); 13 | }); 14 | }); 15 | 16 | form(method='POST' action='') 17 | table.table.table-responsive.table-striped.table-hover 18 | tr 19 | td(width='3%') 20 | input#v4AssignModeCheckBox.checkbox(type='checkbox' checked=(network.v4AssignMode.zt? true : false)) 21 | td Auto-assign from IP Assignment Pool 22 | 23 | if errors 24 | ul 25 | for err in errors 26 | li!= err.msg 27 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/after-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ETC='/opt/key-networks/ztncui/etc' 4 | if [ -f ${ETC}/passwd ]; then 5 | echo "Password file aready exists" 6 | else 7 | echo "Copying default password file..." 8 | cp -pv ${ETC}/default.passwd ${ETC}/passwd 9 | fi 10 | if [ -f /opt/key-networks/ztncui/etc/tls/privkey.pem ] && [ -f /opt/key-networks/ztncui/etc/tls/fullchain.pem ]; then 11 | echo "TLS key and certificate already exist" 12 | else 13 | echo "Generating new TLS key and self-signed certificate..." 14 | openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout /opt/key-networks/ztncui/etc/tls/privkey.pem -out /opt/key-networks/ztncui/etc/tls/fullchain.pem -subj "/C=XX/ST=YY/L=ZZ/O=Security/OU=SelfSigned/CN=example.com" 15 | fi 16 | chown ztncui.ztncui /opt/key-networks/ztncui/etc/tls/* 17 | echo "Enabling and starting ztncui service..." 18 | systemctl enable ztncui 19 | systemctl start ztncui 20 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/network_create.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends controller_layout 7 | 8 | block content 9 | .row 10 | .col-sm-12 11 | h1= title 12 | 13 | if error 14 | b #{error} 15 | else 16 | 17 | form(method='POST' action='') 18 | .form-group.row 19 | .col-sm-2 20 | label(for='name') Network name: 21 | .col-sm-10 22 | input#name.form-control(type='text' name='name' placeholder='Enter new network name' value=(undefined===name ? '' : name.name)) 23 | 24 | .form-group.row 25 | .col-sm-12 26 | button.btn.btn-primary(type='submit') Create Network 27 | 28 | if errors 29 | .row 30 | .col-sm-12 31 | ul 32 | for err in errors 33 | li!= err.msg 34 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/private.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | script. 10 | $(function() { 11 | $('.checkbox').on('click', function() { 12 | $.post('', {'private': $('#privateCheckBox').is(':checked')}); 13 | }); 14 | }); 15 | 16 | form(method='POST' action='') 17 | table.table.table-responsive.table-striped.table-hover 18 | tr 19 | td(width='3%') 20 | input#privateCheckBox.checkbox(type='checkbox' checked=(network.private? true : false)) 21 | td Enable access control. Warning: if you disable this, you will not be able to de-authorize members of the network. Disable this only if you know what you are doing. 22 | 23 | if errors 24 | ul 25 | for err in errors 26 | li!= err.msg 27 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/user_delete.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends users_layout 7 | 8 | block users_content 9 | if user === null 10 | .alert.alert-warning 11 | strong No such user 12 | 13 | else if self_delete === true 14 | .alert.alert-danger 15 | strong You may not delete yourself 16 | 17 | else if deleted 18 | .alert.alert-success 19 | strong #{user.name} was deleted 20 | 21 | else 22 | .alert.alert-danger 23 | strong Warning! Deleting a user cannot be undone 24 | form(method='POST' action='') 25 | button.btn.btn-danger(type='submit', name='delete' value='delete') Delete #{user.name} 26 | = ' ' 27 | a.btn.btn-default(href='/users', name='cancel', role='button') Cancel 28 | 29 | if errors 30 | ul 31 | for err in errors 32 | li!= err.msg 33 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ztncui", 3 | "version": "0.8.6", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "devstart": "nodemon ./bin/www" 8 | }, 9 | "dependencies": { 10 | "argon2": "^0.19.3", 11 | "body-parser": "^1.18.3", 12 | "bootstrap": "^3.4.1", 13 | "cookie-parser": "~1.4.3", 14 | "debug": "~3.1.0", 15 | "dotenv": "^4.0.0", 16 | "express": "^4.16.3", 17 | "express-session": "^1.15.6", 18 | "express-validator": "^4.3.0", 19 | "got": "^7.1.0", 20 | "helmet": "^3.23.0", 21 | "ip-address": "^5.8.9", 22 | "jquery": "~3.4.1", 23 | "morgan": "~1.9.1", 24 | "node-persist": "^2.1.0", 25 | "pug": "^3.0.2", 26 | "serve-favicon": "~2.5.0" 27 | }, 28 | "devDependencies": { 29 | "nodemon": "^2.0.7" 30 | }, 31 | "pkg": { 32 | "assets": [ 33 | "views/*", 34 | "public/**/*", 35 | "etc/**/*", 36 | "node_modules/jquery/dist/jquery.min.js" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/member_delete.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | if member.deleted 10 | .alert.alert-success 11 | strong #{member.name} (#{member.id}) was deleted 12 | a.btn.btn-default(href=('/controller/network/' + network.nwid + '#members') name='networks' role='button') Members 13 | 14 | else 15 | .alert.alert-info 16 | strong 17 | ul 18 | li To undo a member deletion, just get the member to join the network again. 19 | li After deleting a member, you may see them appear in the list of members again. This is a ZeroTier issue. Just get the user to leave the network. 20 | 21 | form(method='POST' action='') 22 | button.btn.btn-primary(type='submit', name='delete') Delete #{member.name} (#{member.id}) 23 | = ' ' 24 | a.btn.btn-default(href='/controller/network/' + network.nwid + '#members', 25 | name='cancel', role='button') Cancel 26 | 27 | if errors 28 | ul 29 | for err in errors 30 | li!= err.msg 31 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/routes/users.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const express = require('express'); 8 | const router = express.Router(); 9 | const auth = require('../controllers/auth'); 10 | const restrict = auth.restrict; 11 | const usersController = require('../controllers/usersController'); 12 | 13 | // GET request for users 14 | router.get('/', restrict, usersController.users_list); 15 | 16 | // GET request for password 17 | router.get('/:name/password', restrict, usersController.password_get); 18 | 19 | // POST request for password 20 | router.post('/:name/password', restrict, usersController.password_post); 21 | 22 | // GET request for user create 23 | router.get('/create', restrict, usersController.user_create_get); 24 | 25 | // POST request for user create 26 | router.post('/create', restrict, usersController.user_create_post); 27 | 28 | // GET request for user delete 29 | router.get('/:name/delete', restrict, usersController.user_delete); 30 | 31 | // POST request for user delete 32 | router.post('/:name/delete', restrict, usersController.user_delete); 33 | 34 | module.exports = router; 35 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/v6AssignMode.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | script. 10 | $(function() { 11 | $('.checkbox').on('click', function() { 12 | $.post('', {'6plane': $('#6planeCheckBox').is(':checked'), 'rfc4193': $('#rfc4193CheckBox').is(':checked'), 'zt': $('#ztCheckBox').is(':checked')}); 13 | }); 14 | }); 15 | 16 | form(method='POST' action='') 17 | table.table.table-responsive.table-striped.table-hover 18 | tr 19 | td(width='3%') 20 | input#6planeCheckBox.checkbox(type='checkbox' checked=(network.v6AssignMode['6plane']? true : false)) 21 | td ZT 6plane (/80 routable for each device) 22 | tr 23 | td 24 | input#rfc4193CheckBox.checkbox(type='checkbox' checked=(network.v6AssignMode['rfc4193']? true : false)) 25 | td ZT rfc4193 (/128 for each device) 26 | tr 27 | td 28 | input#ztCheckBox.checkbox(type='checkbox' checked=(network.v6AssignMode['zt']? true : false)) 29 | td Auto-assign from IP Assignment Pool 30 | 31 | if errors 32 | ul 33 | for err in errors 34 | li!= err.msg 35 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/networks.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends controller_layout 7 | 8 | block content 9 | h1= title 10 | 11 | if error 12 | b #{error} 13 | else 14 | table.table.table-responsive.table-striped.table-hover 15 | tr 16 | th(width='3%') 17 | = '' 18 | th(width='20%') 19 | | Network name 20 | th(width='20%') 21 | | Network ID 22 | th(width='8%') 23 | = '' 24 | th(width='12%') 25 | = '' 26 | th(width='37%') 27 | = '' 28 | each network in networks 29 | - const nwurl = '/controller/network/' + network.nwid; 30 | tr 31 | td 32 | a(href=nwurl + '/delete') 33 | i.glyphicon.glyphicon-trash 34 | td 35 | a(href=nwurl) #{network.name} 36 | td 37 | = network.nwid 38 | td 39 | a(href=nwurl) detail 40 | td 41 | a(href=nwurl + '/easy') easy setup 42 | td 43 | a(href=nwurl + "#members") members 44 | 45 | else 46 | .alert.alert-info 47 | strong There are no networks on this network controller - click "Add network" above to create a new network. 48 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/controllers/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const argon2 = require('argon2'); 8 | const usersController = require('../controllers/usersController'); 9 | 10 | const hash_check = async function(user, password) { 11 | let verified = false; 12 | try { 13 | var users = await usersController.get_users(); 14 | } catch (err) { 15 | throw err; 16 | } 17 | try { 18 | verified = await argon2.verify(users[user].hash, password); 19 | } catch (err) { 20 | throw err; 21 | } 22 | return verified; 23 | } 24 | 25 | exports.authenticate = async function(name, pass, callback) { 26 | try { 27 | var users = await usersController.get_users(); 28 | } catch (err) { 29 | throw err; 30 | } 31 | let user = users[name]; 32 | if (!user) return callback(new Error('cannot find user')); 33 | let verified = await hash_check(name, pass); 34 | if (verified) { 35 | return callback(null, user); 36 | } else { 37 | return callback(new Error('invalid password')); 38 | } 39 | } 40 | 41 | exports.restrict = function(req, res, next) { 42 | if (req.session.user) { 43 | next(); 44 | } else { 45 | req.session.error = 'Access denied!'; 46 | res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/login.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends login_layout 7 | 8 | block login_content 9 | 10 | if error 11 | b #{error} 12 | else 13 | .row 14 | .col-sm-12 15 | h1= title 16 | 17 | if message 18 | .alert.alert-info 19 | strong= message 20 | 21 | form.form-horizontal(method='POST' action='') 22 | .form-group.row 23 | .col-sm-2 24 | label.control-label(for='username') Username: 25 | .col-sm-10 26 | .input-group 27 | span.input-group-addon 28 | i.glyphicon.glyphicon-user 29 | input#username.form-control(type='text' name='username' placeholder='Enter your username') 30 | 31 | .form-group.row 32 | .col-sm-2 33 | label.control-label(for='password') Password: 34 | .col-sm-10 35 | .input-group 36 | span.input-group-addon 37 | i.glyphicon.glyphicon-lock 38 | input#password.form-control(type='password' name='password' placeholder='Enter your password') 39 | 40 | .form-group.row 41 | .col-sm-2 42 | .col-sm-10 43 | button.btn.btn-primary(type='submit') Login 44 | = ' ' 45 | a.btn.btn-default(href='/' name='cancel' role='button') Cancel 46 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/dns.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | - const dns = network.dns || {}; 10 | if (!dns.domain && !(dns.servers && dns.servers.length > 0)) 11 | .row 12 | .col-sm-12 13 | b No DNS configuration on this network. 14 | else 15 | .row 16 | .col-sm-2 17 | b Domain: 18 | .col-sm-10 19 | p= dns.domain 20 | .row 21 | .col-sm-2 22 | b Servers: 23 | .col-sm-10 24 | .row 25 | each server in dns.servers 26 | .col-sm-12= server 27 | 28 | .row 29 | .col-sm-12 30 | h3 Change DNS configuration: 31 | 32 | form(method='POST' action='') 33 | .form-group.row 34 | .col-sm-12 35 | label(for='domain') Domain: 36 | .col-sm-12 37 | input#domain.form-control(type='text' name='domain' value=dns.domain) 38 | 39 | .form-group.row 40 | .col-sm-12 41 | label(for='servers') Servers: 42 | .col-sm-12 43 | textarea#servers.form-control(type='text' name='servers' placeholder='(one IP address per line)') 44 | = !dns.servers ? '' : dns.servers.join('\n') 45 | 46 | .form-group.row 47 | .col-sm-12 48 | button.btn.btn-primary(type='submit') Submit 49 | = ' ' 50 | a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel 51 | 52 | if errors 53 | .row 54 | .col-sm-12 55 | ul 56 | for err in errors 57 | li!= err.msg 58 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/routes.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | .row 10 | .col-sm-12 11 | table.table.table-responsive.table-striped.table-hover 12 | tr 13 | th 14 | th Target 15 | th Gateway 16 | each route in network.routes 17 | tr 18 | td(width='3%') 19 | a(href='/controller/network/' + network.nwid + '/routes/' + route.target + '/delete') 20 | i.glyphicon.glyphicon-trash 21 | td= route.target 22 | td= route.via 23 | 24 | .row 25 | .col-sm-12 26 | h3 Add new route: 27 | 28 | form(method='POST' action='/controller/network/' + network.nwid + '/routes') 29 | .form-group.row 30 | .col-sm-12 31 | label(for='target') Target: 32 | .col-sm-12 33 | input#target.form-control(type='text' name='target' placeholder='e.g. 10.11.12.0/24' value=(undefined===route? '' : route.target)) 34 | 35 | .form-group.row 36 | .col-sm-12 37 | label(for='via') Gateway: 38 | .col-sm-12 39 | input#via.form-control(type='text' name='via' placeholder='e.g. 172.16.2.1 or leave blank if the target is the ZT network' value=(undefined===route? '' : route.via)) 40 | 41 | .form-group.row 42 | .col-sm-12 43 | button.btn.btn-primary(type='submit') Submit 44 | = ' ' 45 | a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel 46 | 47 | if errors 48 | .row 49 | .col-sm-12 50 | ul 51 | for err in errors 52 | li!= err.msg 53 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/head_layout.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | mixin nav_item(name, displayName, href) 7 | li(class=((navigate && navigate.active === name) ? 'active' : '')) 8 | a(href=href) #{displayName} 9 | 10 | mixin json_value(value) 11 | - if ((!!value ) && (value.constructor == Object || value.constructor == Array)) 12 | code(style='white-space: pre;')= JSON.stringify(value, null, 2) 13 | - else 14 | code= value 15 | 16 | doctype html 17 | html(lang='en') 18 | head 19 | title= title 20 | meta(charset='utf-8') 21 | meta(name='viewport', content='width=device-width, initial-scale=1') 22 | link(rel='stylesheet', href='/bscss/bootstrap.min.css') 23 | link(rel='stylesheet', href='/stylesheets/style.css') 24 | script(src='/jqjs/jquery.min.js') 25 | script(src='/bsjs/bootstrap.min.js') 26 | body 27 | nav.navbar.navbar-inverse.navbar-fixed-top 28 | .container-fluid 29 | .navbar-header 30 | button.navbar-toggle(type='button' data-toggle='collapse' data-target='#BarNav') 31 | span.icon-bar 32 | span.icon-bar 33 | span.icon-bar 34 | a.navbar-brand(href='https://key-networks.com' target='_blank') 35 | img(src='/images/key-logo.svg' alt='Key Networks logo' height='25px' width='25px' style='display: inline') 36 | | Key Networks 37 | .collapse.navbar-collapse(id='BarNav') 38 | ul.nav.navbar-nav 39 | block nav_items 40 | ul.nav.navbar-nav.navbar-right 41 | li 42 | block nav_login 43 | a(href='/logout') 44 | span.glyphicon.glyphicon-log-out 45 | | Logout 46 | block body_content 47 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const express = require('express'); 8 | const auth = require('../controllers/auth'); 9 | const authenticate = auth.authenticate; 10 | const restrict = auth.restrict; 11 | const router = express.Router(); 12 | 13 | /** Redirect logged user to controler page */ 14 | function guest_only(req, res, next) { 15 | if (req.session.user) { 16 | res.redirect('/controller'); 17 | } else { 18 | next(); 19 | } 20 | } 21 | 22 | /* GET home page. */ 23 | router.get('/', guest_only, function(req, res, next) { 24 | res.render('front_door', {title: 'ztncui'}); 25 | }); 26 | 27 | router.get('/logout', function(req, res) { 28 | req.session.destroy(function() { 29 | res.redirect('/'); 30 | }); 31 | }); 32 | 33 | router.get('/login', guest_only, function(req, res) { 34 | let message = null; 35 | if (req.session.error) { 36 | if (req.session.error !== 'Access denied!') { 37 | message = req.session.error; 38 | } 39 | } else { 40 | message = req.session.success; 41 | } 42 | res.render('login', { title: 'Login', message: message }); 43 | }); 44 | 45 | router.post('/login', async function(req, res) { 46 | await authenticate(req.body.username, req.body.password, function(err, user) { 47 | if (user) { 48 | req.session.regenerate(function() { 49 | req.session.user = user; 50 | req.session.success = 'Authenticated as ' + user.name; 51 | if (user.pass_set) { 52 | res.redirect(req.query.redirect || '/controller'); 53 | } else { 54 | res.redirect('/users/' + user.name + '/password'); 55 | } 56 | }); 57 | } else { 58 | req.session.error = 'Authentication failed, please check your username and password.' 59 | res.redirect('/login'); 60 | } 61 | }); 62 | }); 63 | module.exports = router; 64 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/ipAssignmentPools.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | .row 10 | .col-sm-12 11 | table.table.table-responsive.table-striped.table-hover 12 | tr 13 | th 14 | th IP range start 15 | th IP range end 16 | each ipAssignmentPool in network.ipAssignmentPools 17 | tr 18 | td(width='3%') 19 | a(href='/controller/network/' + network.nwid + '/ipAssignmentPools/' + ipAssignmentPool.ipRangeStart + '/' + ipAssignmentPool.ipRangeEnd + '/delete') 20 | i.glyphicon.glyphicon-trash 21 | td= ipAssignmentPool.ipRangeStart 22 | td= ipAssignmentPool.ipRangeEnd 23 | 24 | .row 25 | .col-sm-12 26 | h3 Add new IP Assignment Pool: 27 | 28 | form(method='POST' action='/controller/network/' + network.nwid + '/ipAssignmentPools') 29 | .form-group.row 30 | .col-sm-2 31 | label(for='ipRangeStart') IP range start: 32 | .col-sm-12 33 | input#ipRangeStart.form-control(type='text' name='ipRangeStart' placeholder='IP range start' value=(undefined===ipAssignmentPool? '' : ipAssignmentPool.ipRangeStart)) 34 | 35 | .form-group.row 36 | .col-sm-2 37 | label(for='ipRangeEnd') IP range end: 38 | .col-sm-12 39 | input#ipRangeEnd.form-control(type='text' name='ipRangeEnd' placeholder='IP range end' value=(undefined===ipAssignmentPool? '' : ipAssignmentPool.ipRangeEnd)) 40 | 41 | .form-group.row 42 | .col-sm-12 43 | button.btn.btn-primary(type='submit') Submit 44 | = ' ' 45 | a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel 46 | 47 | if errors 48 | .row 49 | .col-sm-12 50 | ul 51 | for err in errors 52 | li!= err.msg 53 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/password.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends users_layout 7 | 8 | block users_content 9 | if message 10 | .row 11 | .col-sm-12 12 | .alert.alert-info 13 | strong= message 14 | 15 | form.form-horizontal(method='POST' action='') 16 | .form-group.row 17 | .col-sm-2 18 | label(for='username') Username: 19 | .col-sm-10 20 | .input-group 21 | span.input-group-addon 22 | i.glyphicon.glyphicon-user 23 | input#username.form-control(type='text' name='username' placeholder='Enter username' value=user.name readonly=readonly) 24 | 25 | .form-group.row 26 | .col-sm-2 27 | label(for='password1') Enter new password: 28 | .col-sm-10 29 | .input-group 30 | span.input-group-addon 31 | i.glyphicon.glyphicon-lock 32 | input#password1.form-control(type='password' name='password1' placeholder='Enter new password' value=(undefined===user.password1? '' : user.password1)) 33 | 34 | .form-group.row 35 | .col-sm-2 36 | label(for='password2') Re-enter password: 37 | .col-sm-10 38 | .input-group 39 | span.input-group-addon 40 | i.glyphicon.glyphicon-lock 41 | input#password2.form-control(type='password' name='password2' placeholder='Re-enter password' value=(undefined===user.password2? '' : user.password2)) 42 | 43 | .form-group.row 44 | .col-sm-2 45 | label(for='pass_set') Change password on next login: 46 | .col-sm-10 47 | input#pass_set(type='checkbox' name='pass_set' value='check') 48 | 49 | .form-group.row 50 | .col-sm-2 51 | .col-sm-10 52 | button.btn.btn-primary(type='submit') Set password 53 | = ' ' 54 | a.btn.btn-default(href='/users' name='cancel' role='button') Cancel 55 | 56 | if errors 57 | .form-group.row 58 | .col-sm-12 59 | ul 60 | for err in errors 61 | li!= err.msg 62 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/ipAssignments.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | if errors 10 | .row 11 | .col-sm-12 12 | .alert.alert-warning 13 | b Note errors listed below 14 | 15 | form(method='POST' action='') 16 | 17 | .row 18 | .col-sm-6 19 | h4 Member name: 20 | b= member.name 21 | .col-sm-6.right 22 | h4 ZeroTier address: 23 | b= member.id 24 | 25 | .row 26 | .col-sm-12 27 | table.table.table-responsive.table-striped.table-hover 28 | tr 29 | th(width='3%') 30 | th IP address 31 | 32 | each ipAssignment, index in member.ipAssignments 33 | tr 34 | td(width='3%') 35 | a.btn.btn-link(role='button' href='/controller/network/' + network.nwid + '/member/' + member.id + '/ipAssignments/' + index + '/delete') 36 | i.glyphicon.glyphicon-trash 37 | td 38 | each digit in ipAssignment 39 | = digit 40 | 41 | tr 42 | td 43 | button.btn.btn-link(type='submit') 44 | i.glyphicon.glyphicon-plus 45 | td 46 | input#ipAddress.form-control(type='text' name='ipAddress' placeholder='IP address' value=(undefined===ipAssignment? '' : ipAssignment.ipAddress)) 47 | 48 | .row 49 | .col-sm-12 50 | a(href='/controller/network/' + network.nwid + '/routes') 51 | h3 Managed routes 52 | table.table.table-responsive.table-striped.table-hover 53 | tr 54 | th 55 | th Target 56 | th Gateway 57 | each route in network.routes 58 | tr 59 | td(width='3%') 60 | a.btn.btn-link(role='button' href='/controller/network/' + network.nwid + '/routes/' + route.target + '/delete') 61 | i.glyphicon.glyphicon-trash 62 | td= route.target 63 | td= route.via 64 | 65 | if errors 66 | .row 67 | .col-sm-12 68 | ul 69 | for err in errors 70 | li!= err.msg 71 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | require('dotenv').config(); 8 | 9 | const express = require('express'); 10 | const path = require('path'); 11 | const favicon = require('serve-favicon'); 12 | const logger = require('morgan'); 13 | const cookieParser = require('cookie-parser'); 14 | const bodyParser = require('body-parser'); 15 | const expressValidator = require('express-validator'); 16 | const session = require('express-session'); 17 | const helmet = require('helmet'); 18 | 19 | const index = require('./routes/index'); 20 | const users = require('./routes/users'); 21 | const zt_controller = require('./routes/zt_controller'); 22 | 23 | const app = express(); 24 | 25 | const session_secret = Math.random().toString(36).substring(2,12); 26 | 27 | // view engine setup 28 | app.set('views', path.join(__dirname, 'views')); 29 | app.set('view engine', 'pug'); 30 | 31 | app.use(helmet()); 32 | app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 33 | app.use(logger('dev')); 34 | app.use(bodyParser.json()); 35 | app.use(bodyParser.urlencoded({ extended: false })); 36 | app.use(session({ 37 | resave: false, 38 | saveUninitialized: false, 39 | secret: session_secret 40 | })); 41 | app.use(expressValidator()); 42 | app.use(cookieParser()); 43 | app.use(express.static(path.join(__dirname, 'public'))); 44 | app.use('/fonts', express.static(path.join(__dirname, 'node_modules/bootstrap/fonts'))); 45 | app.use('/bscss', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))); 46 | app.use('/jqjs', express.static(path.join(__dirname, 'node_modules/jquery/dist'))); 47 | app.use('/bsjs', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js'))); 48 | 49 | app.use('/', index); 50 | app.use('/users', users); 51 | app.use('/controller', zt_controller); 52 | 53 | // catch 404 and forward to error handler 54 | app.use(function(req, res, next) { 55 | var err = req.session.error; 56 | var msg = req.session.success; 57 | delete req.session.error; 58 | delete req.session.success; 59 | res.locals.message = ''; 60 | if (err) res.locals.message = '

' + err + '

'; 61 | if (msg) res.locals.message = '

' + msg + '

'; 62 | next(); 63 | }); 64 | 65 | // error handler 66 | app.use(function(err, req, res, next) { 67 | // set locals, only providing error in development 68 | res.locals.message = err.message; 69 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 70 | 71 | // render the error page 72 | res.status(err.status || 500); 73 | res.render('error'); 74 | }); 75 | 76 | module.exports = app; 77 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/public/images/key-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font: 16px "Lucida Grande", Helvetica, Arial, sans-serif; 3 | } 4 | 5 | input[type=checkbox] { 6 | transform: scale(1.5); 7 | } 8 | 9 | input[type=radio] { 10 | transform: scale(1.5); 11 | } 12 | 13 | .table > tbody > tr > td { 14 | vertical-align: middle; 15 | } 16 | 17 | .left { 18 | float: left; 19 | text-align: left; 20 | } 21 | 22 | .right { 23 | float: right; 24 | text-align: right; 25 | } 26 | 27 | .navbar-inverse { 28 | background-color: #315b80; 29 | border-color: #23415c; 30 | } 31 | .navbar-inverse .navbar-brand { 32 | color: #ecf0f1; 33 | } 34 | .navbar-inverse .navbar-brand:hover, 35 | .navbar-inverse .navbar-brand:focus { 36 | color: #ffffff; 37 | } 38 | .navbar-inverse .navbar-text { 39 | color: #ecf0f1; 40 | } 41 | .navbar-inverse .navbar-nav > li > a { 42 | color: #ecf0f1; 43 | } 44 | .navbar-inverse .navbar-nav > li > a:hover, 45 | .navbar-inverse .navbar-nav > li > a:focus { 46 | color: #ffffff; 47 | } 48 | .navbar-inverse .navbar-nav > li > .dropdown-menu { 49 | background-color: #315b80; 50 | } 51 | .navbar-inverse .navbar-nav > li > .dropdown-menu > li > a { 52 | color: #ecf0f1; 53 | } 54 | .navbar-inverse .navbar-nav > li > .dropdown-menu > li > a:hover, 55 | .navbar-inverse .navbar-nav > li > .dropdown-menu > li > a:focus { 56 | color: #ffffff; 57 | background-color: #23415c; 58 | } 59 | .navbar-inverse .navbar-nav > li > .dropdown-menu > li.divider { 60 | background-color: #23415c; 61 | } 62 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, 63 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, 64 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { 65 | color: #ffffff; 66 | background-color: #23415c; 67 | } 68 | .navbar-inverse .navbar-nav > .active > a, 69 | .navbar-inverse .navbar-nav > .active > a:hover, 70 | .navbar-inverse .navbar-nav > .active > a:focus { 71 | color: #ffffff; 72 | background-color: #23415c; 73 | } 74 | .navbar-inverse .navbar-nav > .open > a, 75 | .navbar-inverse .navbar-nav > .open > a:hover, 76 | .navbar-inverse .navbar-nav > .open > a:focus { 77 | color: #ffffff; 78 | background-color: #23415c; 79 | } 80 | .navbar-inverse .navbar-toggle { 81 | border-color: #23415c; 82 | } 83 | .navbar-inverse .navbar-toggle:hover, 84 | .navbar-inverse .navbar-toggle:focus { 85 | background-color: #23415c; 86 | } 87 | .navbar-inverse .navbar-toggle .icon-bar { 88 | background-color: #ecf0f1; 89 | } 90 | .navbar-inverse .navbar-collapse, 91 | .navbar-inverse .navbar-form { 92 | border-color: #ecf0f1; 93 | } 94 | .navbar-inverse .navbar-link { 95 | color: #ecf0f1; 96 | } 97 | .navbar-inverse .navbar-link:hover { 98 | color: #ffffff; 99 | } 100 | 101 | @media (max-width: 767px) { 102 | .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { 103 | color: #ecf0f1; 104 | } 105 | .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, 106 | .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { 107 | color: #ffffff; 108 | } 109 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, 110 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, 111 | .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { 112 | color: #ffffff; 113 | background-color: #23415c; 114 | } 115 | } 116 | 117 | .navbar { 118 | background-image: none; 119 | } 120 | 121 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const app = require('../app'); 8 | const debug = require('debug')('ztncui:server'); 9 | const http = require('http'); 10 | const https = require('https'); 11 | const fs = require('fs'); 12 | 13 | /** 14 | * Get ports from environment and store in Express. 15 | */ 16 | 17 | const http_port = normalizePort(process.env.HTTP_PORT || '3000'); 18 | app.set('http_port', http_port); 19 | const https_port = normalizePort(process.env.HTTPS_PORT || null); 20 | app.set('https_port', https_port); 21 | 22 | /** 23 | * Get interface address on which to listen for HTTPS requests from env. 24 | */ 25 | const https_host = process.env.HTTPS_HOST || null; 26 | app.set('https_host', https_host); 27 | 28 | /** Create HTTPS server and listen for protocols on interfaces and ports 29 | * according to environment variables as follows: 30 | * Environment variable Protocol Listen On Port 31 | * -------------------- -------- --------- ---- 32 | * HTTP localhost 3000 33 | * HTTP_PORT HTTP localhost HTTP_PORT 34 | * HTTP_ALL_INTERFACES HTTP all interfaces HTTP_PORT || 3000 35 | * HTTPS_PORT HTTPS all interfaces HTTPS_PORT 36 | * HTTPS_HOST HTTPS HTTPS_HOST HTTPS_PORT 37 | */ 38 | 39 | const http_all_interfaces = process.env.HTTP_ALL_INTERFACES || null; 40 | if (http_all_interfaces) { 41 | console.log('Listening for HTTP requests on port ' + http_port + ' on all interfaces'); 42 | app.listen(http_port); 43 | } else { 44 | console.log('Listening for HTTP requests on port ' + http_port + ' on localhost'); 45 | app.listen(http_port, 'localhost'); 46 | } 47 | 48 | const options = !https_port ? {} : { 49 | cert: fs.readFileSync('etc/tls/fullchain.pem'), 50 | key: fs.readFileSync('etc/tls/privkey.pem') 51 | }; 52 | 53 | const server = https.createServer(options, app); 54 | 55 | if (https_port) { 56 | if (https_host) { 57 | console.log('Listening for HTTPS requests on port ' + https_port + ' on address ' + https_host); 58 | } else { 59 | console.log('Listening for HTTPS requests on port ' + https_port + ' on all interfaces'); 60 | } 61 | server.listen(https_port, https_host); 62 | } 63 | 64 | server.on('error', onError); 65 | server.on('listening', onListening); 66 | 67 | /** 68 | * Normalize a port into a number, string, or false. 69 | */ 70 | 71 | function normalizePort(val) { 72 | const port = parseInt(val, 10); 73 | 74 | if (isNaN(port)) { 75 | // named pipe 76 | return val; 77 | } 78 | 79 | if (port >= 0) { 80 | // port number 81 | return port; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | /** 88 | * Event listener for HTTP/S server "error" event. 89 | */ 90 | 91 | function onError(error) { 92 | if (error.syscall !== 'listen') { 93 | throw error; 94 | } 95 | 96 | const bind = typeof http_port === 'string' 97 | ? 'Pipe ' + http_port 98 | : 'Port ' + http_port; 99 | 100 | const sbind = typeof https_port === 'string' 101 | ? 'Pipe ' + https_port 102 | : 'Port ' + https_port; 103 | 104 | // handle specific listen errors with friendly messages 105 | switch (error.code) { 106 | case 'EACCES': 107 | console.error(bind + ' and ' + sbind + ' require elevated privileges'); 108 | process.exit(1); 109 | break; 110 | case 'EADDRINUSE': 111 | console.error(bind + ' and/or ' + sbind + ' already in use'); 112 | process.exit(1); 113 | break; 114 | default: 115 | throw error; 116 | } 117 | } 118 | 119 | /** 120 | * Event listener for HTTPS server "listening" event. 121 | */ 122 | 123 | function onListening() { 124 | const addr = server.address(); 125 | const bind = typeof addr === 'string' 126 | ? 'pipe ' + addr 127 | : 'port ' + addr.port; 128 | debug('Listening on ' + bind); 129 | } 130 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | THISDIR=`pwd` 6 | if [ `basename $THISDIR` != 'build' ]; then 7 | echo "Execute `basename $0` from the build directory" 8 | exit 1 9 | fi 10 | 11 | BASE_DIR=`dirname $THISDIR` 12 | SRC_DIR=$BASE_DIR/src 13 | BUILD_DIR=$BASE_DIR/build 14 | PKG_DIR=$BASE_DIR/Release 15 | STAGING_DIR=$BASE_DIR/Staging 16 | 17 | NAME='ztncui' 18 | DESCRIPTION='ZeroTier network controller user interface' 19 | VERSION=`grep version ../src/package.json | cut -f4 -d'"'` 20 | VENDOR='Key Networks' 21 | MAINTAINER='https://key-networks.com/contact' 22 | URL='https://key-networks.com' 23 | LICENSE='GPLv3' 24 | 25 | BINDINGGYP='node_modules/argon2/binding.gyp' 26 | 27 | NODE_VER='v16' 28 | 29 | if [ ! -f /usr/lib/gcc/x86_64-redhat-linux/8/libstdc++.a ]; then 30 | echo "You must install libstdc++-static" 31 | exit 1 32 | fi 33 | 34 | DEPS="rpmbuild rpmsign npm node" 35 | 36 | for DEP in ${DEPS}; do 37 | if ! which ${DEP}; then 38 | echo "Missing dependency ${DEP}" 39 | exit 1 40 | fi 41 | done 42 | 43 | rm -fr $STAGING_DIR && mkdir $STAGING_DIR 44 | rm -fr $PKG_DIR && mkdir $PKG_DIR 45 | 46 | pushd . 47 | cd ../src 48 | pushd . 49 | 50 | NVER=`node --version` 51 | if [[ ${NVER%%.*} != ${NODE_VER} ]]; then 52 | echo "Missing dependency node ${NODE_VER}" 53 | exit 1 54 | fi 55 | 56 | [[ -d ../src/node_modules ]] && rm -fr ../src/node_modules 57 | 58 | npm install 59 | 60 | patch --forward --dry-run --silent $BINDINGGYP $BUILD_DIR/binding.gyp.patch 61 | if [ $? -eq 0 ]; then 62 | echo "Applying patch to $BINDINGGYP..." 63 | patch --forward $BINDINGGYP $BUILD_DIR/binding.gyp.patch 64 | fi 65 | if [ $? -ne 0 ]; then 66 | echo "Failed to patch $BINDINGGYP" 67 | exit 1 68 | fi 69 | 70 | cd node_modules/argon2/ 71 | node-gyp rebuild 72 | if [ $? -ne 0 ]; then 73 | echo "Failed to rebuild argon2" 74 | exit 1 75 | fi 76 | 77 | popd 78 | pkg -c ./package.json -t node16-linux-x64 bin/www -o $BUILD_DIR/ztncui 79 | 80 | popd 81 | 82 | install -m 755 -d $STAGING_DIR/opt 83 | install -m 750 -d $STAGING_DIR/opt/key-networks 84 | install -m 750 -d $STAGING_DIR/opt/key-networks/ztncui 85 | install -m 750 -d $STAGING_DIR/opt/key-networks/ztncui/etc 86 | install -m 750 -d $STAGING_DIR/opt/key-networks/ztncui/etc/tls 87 | install -m 750 -d $STAGING_DIR/opt/key-networks/ztncui/node_modules/argon2/build/Release 88 | install -m 755 -d $STAGING_DIR/lib/systemd/system 89 | install -m 600 $SRC_DIR/etc/default.passwd $STAGING_DIR/opt/key-networks/ztncui/etc/default.passwd 90 | install -m 755 $SRC_DIR/node_modules/argon2/build/Release/argon2.node $STAGING_DIR/opt/key-networks/ztncui/node_modules/argon2/build/Release/ 91 | install -m 755 $BUILD_DIR/ztncui $STAGING_DIR/opt/key-networks/ztncui/ 92 | install -m 644 $BUILD_DIR/ztncui.service $STAGING_DIR/lib/systemd/system 93 | 94 | rm -f $BUILD_DIR/ztncui 95 | 96 | GENERAL_FPM_FLAGS=" 97 | --name $NAME 98 | --version $VERSION 99 | --url $URL 100 | --license $LICENSE 101 | --chdir $STAGING_DIR 102 | --package $PKG_DIR 103 | --directories /opt/key-networks 104 | --depends zerotier-one 105 | --depends openssl 106 | --before-install before-install.sh 107 | --after-install after-install.sh 108 | --before-remove before-remove.sh 109 | --after-remove after-remove.sh 110 | --before-upgrade before-upgrade.sh 111 | --after-upgrade after-upgrade.sh 112 | " 113 | 114 | fpm -s dir -t rpm \ 115 | $GENERAL_FPM_FLAGS \ 116 | --vendor "$VENDOR" \ 117 | --maintainer "$MAINTAINER" \ 118 | --description "$DESCRIPTION" \ 119 | --rpm-user ztncui \ 120 | --rpm-group ztncui \ 121 | . 122 | 123 | fpm -s dir -t deb \ 124 | $GENERAL_FPM_FLAGS \ 125 | --vendor "$VENDOR" \ 126 | --maintainer "$MAINTAINER" \ 127 | --description "$DESCRIPTION" \ 128 | --deb-user ztncui \ 129 | --deb-group ztncui \ 130 | . 131 | 132 | rpm --addsign ../Release/ztncui*rpm 133 | rpm --checksig ../Release/ztncui*rpm 134 | 135 | createrepo $PKG_DIR 136 | gpg -u 'Key Networks' --detach-sign --armor $PKG_DIR/repodata/repomd.xml 137 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/routes/zt_controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const express = require('express'); 8 | const auth = require('../controllers/auth'); 9 | const restrict = auth.restrict; 10 | const router = express.Router(); 11 | 12 | var networkController = require('../controllers/networkController'); 13 | 14 | // network routes // 15 | 16 | // GET ZT network controller home page 17 | router.get('/', restrict, networkController.index); 18 | 19 | // Get request for creating a network 20 | router.get('/network/create', restrict, networkController.network_create_get); 21 | 22 | // POST request for creating a network 23 | router.post('/network/create', restrict, networkController.network_create_post); 24 | 25 | // GET request to delete network 26 | router.get('/network/:nwid/delete', restrict, networkController.network_delete_get); 27 | 28 | // POST request to delete network 29 | router.post('/network/:nwid/delete', restrict, networkController.network_delete_post); 30 | 31 | // POST request for Network name 32 | router.post('/network/:nwid/name', restrict, networkController.name); 33 | 34 | // GET request for ipAssignmentPool delete 35 | router.get('/network/:nwid/ipAssignmentPools/:ipRangeStart/:ipRangeEnd/delete', restrict, networkController.ipAssignmentPool_delete); 36 | 37 | // POST request for ipAssignmentPools 38 | router.post('/network/:nwid/ipAssignmentPools', restrict, networkController.ipAssignmentPools); 39 | 40 | // GET request for route delete 41 | router.get('/network/:nwid/routes/:target_ip/:target_prefix/delete', restrict, networkController.route_delete); 42 | 43 | // POST request for routes 44 | router.post('/network/:nwid/routes', restrict, networkController.routes); 45 | 46 | // POST request for dns 47 | router.post('/network/:nwid/dns', restrict, networkController.dns); 48 | 49 | // POST request for private 50 | router.post('/network/:nwid/private', restrict, networkController.private); 51 | 52 | // POST request for v4AssignMode 53 | router.post('/network/:nwid/v4AssignMode', restrict, networkController.v4AssignMode); 54 | 55 | // POST request for v6AssignMode 56 | router.post('/network/:nwid/v6AssignMode', restrict, networkController.v6AssignMode); 57 | 58 | // GET request for member delete 59 | router.get('/network/:nwid/member/:id/delete', restrict, networkController.member_delete); 60 | 61 | // POST request for member delete 62 | router.post('/network/:nwid/member/:id/delete', restrict, networkController.member_delete); 63 | 64 | // GET request for any member object 65 | router.get('/network/:nwid/member/:id/:object', restrict, networkController.member_object); 66 | 67 | // GET request for member detail 68 | router.get('/network/:nwid/member/:id', restrict, networkController.member_detail); 69 | 70 | // GET request for easy network setup 71 | router.get('/network/:nwid/easy', restrict, networkController.easy_get); 72 | 73 | // POST request for easy network setup 74 | router.post('/network/:nwid/easy', restrict, networkController.easy_post); 75 | 76 | // GET request for easy member (de)authorization 77 | router.get('/network/:nwid/members', restrict, networkController.members); 78 | 79 | // POST request for easy member (de)authorization 80 | router.post('/network/:nwid/members', restrict, networkController.members); 81 | 82 | // GET request for member ipAssignment delete 83 | router.get('/network/:nwid/member/:id/ipAssignments/:index/delete', restrict, networkController.delete_ip); 84 | 85 | // POST request for member ipAssignment add 86 | router.post('/network/:nwid/member/:id/ipAssignments', restrict, networkController.assign_ip); 87 | 88 | 89 | // GET request for any network object 90 | router.get('/network/:nwid/:object', restrict, networkController.network_object); 91 | 92 | // GET request for one network 93 | router.get('/network/:nwid', restrict, networkController.network_detail); 94 | 95 | // GET request for list of all networks 96 | router.get('/networks', restrict, networkController.network_list); 97 | 98 | module.exports = router; 99 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/network_easy.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | block net_content 9 | script. 10 | function randomOctet() { 11 | return Math.floor(Math.random() * 255); 12 | } 13 | 14 | function randomIPv4() { 15 | const networkCIDR = document.getElementById('networkCIDR'); 16 | const CIDR = '10.' + randomOctet() + '.' + randomOctet() + '.0/24'; 17 | networkCIDR.value = CIDR; 18 | CIDRtoPool(CIDR); 19 | } 20 | 21 | function int32toIPv4String(int32) { 22 | let ipv4 = ''; 23 | ipv4 = ((int32 & 0xff000000)>>>24).toString(); 24 | ipv4 += '.' + ((int32 & 0x00ff0000)>>>16).toString(); 25 | ipv4 += '.' + ((int32 & 0x0000ff00)>>>8).toString(); 26 | ipv4 += '.' + (int32 & 0x000000ff).toString(); 27 | return ipv4; 28 | } 29 | 30 | function CIDRtoPool(CIDR) { 31 | const [start, prefix] = CIDR.split('/'); 32 | if (undefined !== start && undefined !== prefix && 33 | prefix > 0 && prefix < 33 && 34 | /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(start) 35 | ){ 36 | const host32 = ((1 << (32 - parseInt(prefix))) - 1) >>> 0; 37 | const net = start.split('.').map(oct => {return parseInt(oct)}); 38 | let net32 = 0 >>> 0; 39 | net32 = (net[0]<<24) + (net[1]<<16) + (net[2]<<8) + (net[3]); 40 | net32 &= ~host32; 41 | bcast32 = net32 + host32; 42 | const networkCIDR = document.getElementById('networkCIDR'); 43 | const poolStart = document.getElementById('poolStart'); 44 | const poolEnd = document.getElementById('poolEnd'); 45 | networkCIDR.value = int32toIPv4String(net32) + '/' + prefix; 46 | poolStart.value = int32toIPv4String(net32 + 1); 47 | poolEnd.value = int32toIPv4String(bcast32 - 1); 48 | } else { 49 | poolStart.value = 'Invalid network CIDR'; 50 | poolEnd.value = 'Invalid network CIDR'; 51 | } 52 | } 53 | 54 | if message 55 | .alert.alert-info 56 | strong= message 57 | 58 | form(method='POST' action='') 59 | .form-group 60 | button.btn.btn-link.float-right(type='button' data-toggle='collapse' data-target='#help') Help 61 | .collapse(id='help') 62 | p Please note that this utility only supports IPv4 at this stage. 63 | p Use the following button to automatically generate a random network address, otherwise fill in the network address CIDR manually and the IP assignment pool will be automatically calculated for you. You can manually alter these calculated values. 64 | 65 | .form-group 66 | button.btn.btn-primary(id='genIPv4' type='button' onclick='randomIPv4()') Generate network address 67 | 68 | .form-group 69 | label(for='networkCIDR') Network address in CIDR notation 70 | input#networkCIDR.form-control(type='text' name='networkCIDR' onchange='CIDRtoPool(this.value)' placeholder='e.g. 10.11.12.0/24' value=(undefined===network.routes[0]? '' : network.routes[0].target)) 71 | 72 | .form-group 73 | label(for='poolStart') Start of IP assignment pool 74 | input#poolStart.form-control(type='text' name='poolStart' placeholder='e.g. 10.11.12.1' value=(undefined===network.ipAssignmentPools[0]? '' : network.ipAssignmentPools[0].ipRangeStart)) 75 | 76 | .form-group 77 | label(for='poolEnd') End of IP assignment pool 78 | input#poolEnd.form-control(type='text' name='poolEnd' placeholder='e.g. 10.11.12.254' value=(undefined===network.ipAssignmentPools[0]? '' : network.ipAssignmentPools[0].ipRangeEnd)) 79 | 80 | .form-group(style='padding-top: 10px') 81 | button.btn.btn-primary(type='submit') Submit 82 | = ' ' 83 | a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel 84 | 85 | if errors 86 | ul 87 | for err in errors 88 | li!= err.msg 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### [key-networks/ztncui]汉化及一键安装ZtnCui控制器 - ZeroTier根节点服务端: 2 | --- 3 | 4 | # 适配Debian10.10 / 10.13: 5 | - 全自动安装 6 | - 自动识别主机系统 7 | - 自动识别主机IP,切换对应的下载节点 8 | 9 | # 注意事项及参考: 10 | - 建议用个干干净净的Debian10系统进行安装 ZtnCui控制器 - 根节点服务端 11 | - 测试用的服务器系统为: Debian10.10 64位 12 | - 提供的一键安装脚本只做了适配Debian10系统 13 | - 如果想用Centos, 请自行解决环境搭建相关的问题 14 | - 程序名: [ztncui-0.8.6-1.x86_64.rpm](https://agent-github.08w80.com/https://github.com/TcDhlPro/ZeroTierOne-SelfHostingNetworkControllers-ZtnCui/releases/download/v1.0.0/ztncui-0.8.6-1.x86_64.rpm) 15 | - 证书名: [RPM-KEY-TcDhlProForZtnCui@20230402](https://agent-github.08w80.com/https://github.com/TcDhlPro/ZeroTierOne-SelfHostingNetworkControllers-ZtnCui/releases/download/v1.0.0/RPM-KEY-TcDhlProForZtnCui@20230402) 16 | - 新版本的ZtnCui控制器完全汉化, 会在空闲时间慢慢完成 17 | - 汉化已完成 ! 18 | 19 | # 安装步骤 20 | - 进入目录: 21 | ```shell 22 | cd ~ 23 | ``` 24 | - 下载脚本: 25 | ```shell 26 | wget https://agent-github.08w80.com/https://github.com/TcDhlPro/ZeroTierOne-SelfHostingNetworkControllers-ZtnCui/releases/download/v1.0.0/Auto_Install_ZtncuiForZerotier.tar.gz 27 | ``` 28 | - 解包: 29 | ```shell 30 | tar -zxvf Auto_Install_ZtncuiForZerotier.tar.gz 31 | ``` 32 | - 给脚本文件夹权限: 33 | ```shell 34 | chmod -R 775 Auto_Install_ZtncuiForZerotier 35 | ``` 36 | - 进入目录: 37 | ```shell 38 | cd /root/Auto_Install_ZtncuiForZerotier 39 | ``` 40 | - 启动脚本进行自动化安装: 41 | ```shell 42 | ./Auto_Install_ZtncuiForZerotier 43 | ``` 44 | - 初次安装时, 脚本一般都会在倒计时后, 主动断开ssh连接, 会有提示, 是为了重新配置环境 45 | - 断开ssh连接后, 重新连接服务器 46 | - 进入目录```cd /root/Auto_Install_ZtncuiForZerotier``` 47 | - 运行脚本```./Auto_Install_ZtncuiForZerotier``` 48 | - 会有一小段时间配置环境...... 49 | - 终端会提示你输入两个自定义端口```[ZtnCui的Https端口] [ZeroTier的TCP/UDP端口]``` 50 | - 输入```3000```以外未占用的端口, 因为3000端口是被默认用作ZtnCui-Http 51 | - 要记得在防火墙放开```[ZtnCui的Http端口]```或```[ZtnCui的Https端口]```和```[ZeroTier的TCP/UDP端口]``` 52 | - 如果```[ZeroTier的```TCPUDP```端口]```, 没有在你们服务器上的防火墙中放行, 或没有在安全组中放行, 或者只放行了其中一个, 客户端在替换Planet文件后,大概率是通讯失败的, 常见的情况是:加入自建的网络显示ok, 但服务端那边看不到任何客户端的连接 53 | - 等安装完成, 访问ZtnCui后台地址进行查看 54 | 55 | 在官方仓库 [key-networks/ztncui](https://github.com/key-networks/ztncui.git) 基础上进行汉化并打包成可直接安装的linux包,包含deb和rpm格式(附带了证书RPM-KEY-TcDhlProForZtnCui)的软件包 56 | 57 | --- 58 | ## 修复两项内容 59 | - 编辑日期: 2023年04月02日 60 | - 修复网络界面中, `节点地址`显示多行的Bug 61 | - 修复上一版本在网络界面中, 出现`Cannot read properties of undefined (reading "address")`这个错误 62 | - 修复执行安装程序时, 出现类似`UnicodeEncodeError: 'latin-1' codec can't encode characters in position 8-13: ordinal not in range(256)`的错误 63 | 64 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_11.png) 65 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_12.png) 66 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_13.png) 67 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_14.png) 68 | ## 新版ZtnCui(0.8.6)控制器相关截图(汉化完成度98%): 69 | - 编辑日期: 2023年01月10日 70 | - 98%是因为有些没必要汉化 71 | 72 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_5.png) 73 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_6.png) 74 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_7.png) 75 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_8.png) 76 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_9.png) 77 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_10.png) 78 | ## 新版ZtnCui(0.8.6)控制器相关截图(未完全汉化阶段): 79 | - 编辑日期: 2022年 80 | 81 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_1.png) 82 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_2.png) 83 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_3.png) 84 | ![输入图片说明](https://cdn-jsdelivr-agent-github.08w80.com/gh/TcDhlPro/blog_res/ZtnCui-images/ztncui086zh_ch_4.png) 85 | 86 | --- 87 | 88 | ### 客户端替换Planet文件 89 | - 服务器安装完成后会在脚本执行的目录找到planet文件,例如/root下生成的planet文件 90 | - 或者使用```/var/lib/zerotier-one```目录中的planet文件 91 | 92 | ### 重启服务 93 | - linux重启Zerotier: ```service zerotier-one restart``` 94 | - win系统重启Zerotier: ```需要在服务中重启ZeroTier One这个服务``` 95 | 96 | ### 加入自建的根节点网络 97 | - 客户端执行```zerotier-cli join 网络ID```, 之后就可以在web控制中心找到设备。 98 | 99 | ### 特别注意:安装成功后会变更ZeroTier默认的9993端口为你自己设定的端口 100 | - 初始安装成功后,``` zerotier-cli listpeers```服务端执行命令查看节点列表,如果打印出的节点列表是空的,则安装正确 101 | - 无论是什么样的安装方法,在初始安装完成后打印出来如果有其他节点信息,都是错误的 102 | - 自行部署的Zerotier根服务器,服务端在打印出的节点信息中,应该只显示有效连接的客户端 103 | - 客户端替换planet文件后,在不加入自己根服务器网络的情况下, 打印出的节点列表也是空的 104 | - 加入自己根服务器网络后```zerotier-cli listpeers```客户端执行命令查看节点信息, 如果Planet只有一个,且IP为自己服务器,则安装正确. -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/controllers/usersController.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const fs = require('fs'); 8 | const argon2 = require('argon2'); 9 | const util = require('util'); 10 | 11 | const passwd_file = 'etc/passwd'; 12 | const min_pass_len = 10; 13 | 14 | const readFile = util.promisify(fs.readFile); 15 | const writeFile = util.promisify(fs.writeFile); 16 | const chmod = util.promisify(fs.chmod); 17 | 18 | let _users = null; 19 | 20 | const get_users = async function() { 21 | if (_users) { 22 | return _users; 23 | } else { 24 | try { 25 | _users = JSON.parse(await readFile(passwd_file, 'utf8')); 26 | return _users; 27 | } catch(err) { 28 | throw(err); 29 | } 30 | } 31 | } 32 | exports.get_users = get_users; 33 | 34 | const update_users = async function(users) { 35 | try { 36 | await writeFile(passwd_file, JSON.stringify(users), 'utf8'); 37 | await chmod(passwd_file, 0600); 38 | 39 | } catch (err) { 40 | throw err; 41 | } 42 | _users = null; 43 | return await get_users(); 44 | } 45 | 46 | exports.users_list = async function(req, res) { 47 | const navigate = 48 | { 49 | active: 'users', 50 | } 51 | 52 | try { 53 | const users = await get_users(); 54 | res.render('users', { title: 'Admin users', navigate: navigate, message: 'List of users with admin priviledges', users: users }); 55 | } catch (err) { 56 | res.render('users', { title: 'Admin users', navigate: navigate, message: 'Error', users: null, error: 'Error returning list of users: ' + err }); 57 | } 58 | } 59 | 60 | exports.password_get = async function(req, res) { 61 | const navigate = 62 | { 63 | active: 'users', 64 | } 65 | 66 | const user = 67 | { 68 | name: req.params.name, 69 | password1: null, 70 | password2: null 71 | }; 72 | res.render('password', { title: 'Set password', navigate: navigate, user: user, readonly: true, message: '' }); 73 | } 74 | 75 | exports.password_post = async function(req, res) { 76 | const navigate = 77 | { 78 | active: 'users', 79 | } 80 | 81 | req.checkBody('username', 'Username required').notEmpty(); 82 | req.sanitize('username').escape(); 83 | req.sanitize('username').trim(); 84 | 85 | req.checkBody('password1', 'Password required').notEmpty(); 86 | req.checkBody('password1', 'Minimum password length is ' + min_pass_len + ' characters').isLength({ min: min_pass_len, max: 160 }); 87 | 88 | req.checkBody('password2', 'Please re-enter password').notEmpty(); 89 | req.checkBody('password2', 'Minimum password length is ' + min_pass_len + ' characters').isLength({ min: min_pass_len, max: 160 }); 90 | req.checkBody('password2', 'Passwords are not the same').equals(req.body.password1); 91 | 92 | const errors = req.validationErrors(); 93 | 94 | if (errors) { 95 | const user = 96 | { 97 | name: req.body.username, 98 | password1: req.body.password1, 99 | password2: req.body.password2 100 | }; 101 | const message = 'Please check errors below'; 102 | res.render('password', { title: 'Set password', navigate: navigate, user: user, readonly: true, message: message, errors: errors }); 103 | } else { 104 | let pass_set = true; 105 | if (req.body.pass_set === 'check') pass_set = false; 106 | 107 | const hash = await argon2.hash(req.body.password1); 108 | 109 | const user = 110 | { 111 | name: req.body.username, 112 | pass_set: pass_set, 113 | hash: hash 114 | }; 115 | 116 | const passwd_user = 117 | { 118 | [req.body.username]: user 119 | }; 120 | 121 | let users = await get_users(); 122 | users[req.body.username] = user; 123 | 124 | users = await update_users(users); 125 | 126 | const message = 'Successfully set password for ' + req.body.username; 127 | res.render('password', { title: 'Set password', navigate: navigate, user: user, readonly: true, message: message }); 128 | } 129 | } 130 | 131 | exports.user_create_get = async function(req, res) { 132 | const navigate = 133 | { 134 | active: 'create_user', 135 | } 136 | 137 | const user = 138 | { 139 | name: null, 140 | password1: null, 141 | password2: null 142 | }; 143 | 144 | res.render('password', { title: 'Create new admin user', navigate: navigate, user: user, readonly: false}); 145 | } 146 | 147 | exports.user_create_post = async function(req, res) { 148 | const navigate = 149 | { 150 | active: 'create_user', 151 | } 152 | 153 | res.redirect(307, '/users/' + req.body.username + '/password'); 154 | } 155 | 156 | exports.user_delete = async function(req, res) { 157 | const navigate = 158 | { 159 | active: 'users', 160 | } 161 | 162 | try { 163 | var users = await get_users(); 164 | } catch (err) { 165 | throw err; 166 | } 167 | 168 | const user = users[req.params.name]; 169 | 170 | if (user && (req.session.user.name === user.name)) { 171 | res.render('user_delete', { title: 'Delete user', navigate: navigate, user: user, self_delete: true }); 172 | } 173 | 174 | if (req.body.delete === 'delete') { 175 | if (user) { 176 | const deleted_user = { name: user.name }; 177 | delete users[user.name]; 178 | users = await update_users(users); 179 | res.render('user_delete', { title: 'Deleted user', navigate: navigate, user: deleted_user, deleted: true }); 180 | } else { 181 | res.render('user_delete', { title: 'Delete user', navigate: navigate, user: null }); 182 | } 183 | } else { 184 | if (user) { 185 | res.render('user_delete', { title: 'Delete user', navigate: navigate, user: user }); 186 | } else { 187 | res.render('user_delete', { title: 'Delete user', navigate: navigate, user: null }); 188 | } 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/views/network_detail.pug: -------------------------------------------------------------------------------- 1 | //- 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | 6 | extends network_layout 7 | 8 | //- Don't display that title 9 | block title 10 | 11 | //- Replace the network title with the editable one 12 | block network_title 13 | h2 14 | | Network 15 | a#change-name(href='#') 16 | span#name= network.name 17 | i.glyphicon.glyphicon-pencil(style='font-size: 20px;') 18 | input#name-input.form-control(type='text' style='width: 200px; display: none;') 19 | | (#{network.nwid}): 20 | script. 21 | $(function() { 22 | var nwurl = '/controller/network/#{network.nwid}'; 23 | var name = !{JSON.stringify(network.name)}; 24 | 25 | function toggleNameEditor(show) { 26 | $('#change-name').css('display', !show ? '' : 'none'); 27 | $('#name-input').css('display', show ? 'inline-block' : 'none'); 28 | } 29 | 30 | function submit() { 31 | var newName = $('#name-input').val(); 32 | if (newName != name) { 33 | name = newName; 34 | $.post(nwurl + '/name', {'name': name}) 35 | .done(function () { 36 | $('#name').text(newName); 37 | }); 38 | } 39 | toggleNameEditor(false); 40 | } 41 | 42 | $('#change-name').on('click', function() { 43 | toggleNameEditor(true); 44 | $('#name-input').val(name); 45 | $('#name-input').focus(); 46 | }); 47 | $('#name-input').on('focusout', submit); 48 | $('#name-input').keypress(function (e) { 49 | if (e.which == 13) submit(); 50 | }); 51 | }); 52 | 53 | block net_content 54 | - const nwurl = '/controller/network/' + network.nwid; 55 | 56 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/private') role='button') 57 | = network.private ? "Private" : "Public" 58 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/easy') role='button') Easy setup 59 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/routes') role='button') Routes 60 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/ipAssignmentPools') role='button') Assignment Pools 61 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/v4AssignMode') role='button') IPv4 Assign Mode 62 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/v6AssignMode') role='button') IPv6 Assign Mode 63 | a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/dns') role='button') DNS 64 | 65 | if (members !== undefined) 66 | script. 67 | $(function() { 68 | const url = "#{nwurl}/members"; 69 | $('.authCheck').on('click', function() { 70 | $.post(url, {'id': this.value, 'auth': this.checked}); 71 | }); 72 | $('.bridgeCheck').on('click', function() { 73 | $.post(url, {'id': this.value, 'activeBridge': this.checked}); 74 | }); 75 | $('.text').on('change', function() { 76 | $.post(url, {'id': this.name, 'name': this.value}); 77 | }); 78 | }); 79 | h3#members Members (#{members.length}) 80 | form(method='POST' action='') 81 | table.table.table-responsive.table-striped.table-hover 82 | tr 83 | td(width='3%') 84 | = '' 85 | td(width='20%') 86 | | Member name 87 | td(width='10%') 88 | | Member ID 89 | td(width='10%') 90 | | Authorized 91 | td(width='10%') 92 | | Active bridge 93 | td(width='17%') 94 | | IP assignment 95 | td(width='17%') 96 | | Peer status 97 | td(width='13%') 98 | | Peer address / latency 99 | each member in members 100 | - const peer = member.peer; 101 | tr 102 | - const url = nwurl + '/member/' + member.id 103 | td 104 | a(href=url + '/delete') 105 | i.glyphicon.glyphicon-trash 106 | td 107 | input.form-control.text(type='text' name=member.id value=member.name) 108 | td 109 | a(href=url) #{member.id} 110 | td 111 | input.authCheck(type='checkbox' value=member.id checked=(member.authorized? true : false)) 112 | td 113 | input.bridgeCheck(type='checkbox' value=member.id checked=(member.activeBridge? true : false)) 114 | td 115 | each ipAssignment in member.ipAssignments 116 | a(href=nwurl + '/member/' + member.id + '/ipAssignments') 117 | each digit in ipAssignment 118 | = digit 119 | = ' ' 120 | else 121 | a(href=nwurl + '/member/' + member.id + '/ipAssignments') 122 | | IP assignment 123 | td 124 | if (peer && peer.latency != -1 && peer.versionMajor != -1) 125 | if (peer.latency != -1) 126 | span(style='color: green;') 127 | | ONLINE (v#{peer.version}) 128 | else 129 | span(style='color: orange;') 130 | | RELAY (v#{peer.version}) 131 | else if (member.id == zt_address) 132 | span(style='color: green;') CONTROLLER 133 | else 134 | span(style='color: red;') OFFLINE 135 | td 136 | if (peer) 137 | each path in peer.paths 138 | - const [ip, port] = path.address.split('/'); 139 | = ip 140 | span(style='color: gray;') /#{port} 141 | = ' ' 142 | if (peer.latency != -1) 143 | br 144 | | (#{peer.latency} ms) 145 | else 146 | .alert.alert-info 147 | strong There are no members on this network - invite users to join #{network.nwid} 148 | 149 | a.btn.btn-default(href='' name='refresh' role='button') Refresh 150 | 151 | h3#detail Detail for network 152 | each value, key in network 153 | .row(style='margin: 5px 0;') 154 | .col-sm-2 155 | a(href= network.nwid + '/' + key) #{key}: 156 | .col-sm-10 157 | +json_value(value) 158 | 159 | a.btn.btn-default(href='/controller/networks' name='networks' role='button' style='margin-top: 10px;') Networks 160 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/controllers/zt.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const got = require('got'); 8 | const ipaddr = require('ip-address'); 9 | const token = require('./token'); 10 | 11 | const ZT_ADDR = process.env.ZT_ADDR || 'localhost:9993'; 12 | 13 | const init_options = async function() { 14 | let tok = null; 15 | 16 | try { 17 | tok = await token.get(); 18 | } catch (err) { 19 | throw(err); 20 | } 21 | 22 | options = { 23 | json: true, 24 | headers: { 25 | 'X-ZT1-Auth': tok 26 | } 27 | } 28 | 29 | return options; 30 | } 31 | 32 | const get_zt_status = async function() { 33 | const options = await init_options(); 34 | 35 | try { 36 | const response = await got(ZT_ADDR + '/status', options); 37 | return response.body; 38 | } catch(err) { 39 | throw(err); 40 | } 41 | } 42 | exports.get_zt_status = get_zt_status; 43 | 44 | const get_zt_address = async function() { 45 | return (await get_zt_status()).address; 46 | } 47 | exports.get_zt_address = get_zt_address; 48 | 49 | exports.network_list = async function() { 50 | const options = await init_options(); 51 | 52 | let network = {}; 53 | let networks = []; 54 | let nwids = []; 55 | 56 | try { 57 | const response = await got(ZT_ADDR + '/controller/network', options); 58 | nwids = response.body; 59 | } catch(err) { 60 | throw(err); 61 | } 62 | 63 | for (let nwid of nwids) { 64 | try { 65 | const response = await got(ZT_ADDR + '/controller/network/' 66 | + nwid, options); 67 | network = (({name, nwid}) => ({name, nwid}))(response.body); 68 | networks.push(network); 69 | } catch(err) { 70 | throw(err); 71 | } 72 | } 73 | return networks; 74 | } 75 | 76 | const network_detail = async function(nwid) { 77 | const options = await init_options(); 78 | 79 | try { 80 | const response = await got(ZT_ADDR + '/controller/network/' 81 | + nwid, options); 82 | return response.body; 83 | } catch(err) { 84 | throw(err); 85 | } 86 | } 87 | exports.network_detail = network_detail; 88 | 89 | exports.network_create = async function(name) { 90 | const options = await init_options(); 91 | options.method = 'POST'; 92 | options.body = name; 93 | 94 | const zt_address = await get_zt_address(); 95 | 96 | try { 97 | const response = await got(ZT_ADDR + '/controller/network/' 98 | + zt_address + '______', options); 99 | return response.body; 100 | } catch(err) { 101 | throw(err); 102 | } 103 | } 104 | 105 | exports.network_delete = async function(nwid) { 106 | const options = await init_options(); 107 | options.method = 'DELETE'; 108 | 109 | try { 110 | const response = await got(ZT_ADDR + '/controller/network/' 111 | + nwid, options); 112 | response.body.deleted = true; 113 | return response.body; 114 | } catch(err) { 115 | throw(err); 116 | } 117 | } 118 | 119 | exports.ipAssignmentPools = async function(nwid, ipAssignmentPool, action) { 120 | const options = await init_options(); 121 | options.method = 'POST'; 122 | 123 | const network = await network_detail(nwid); 124 | let ipAssignmentPools = network.ipAssignmentPools; 125 | 126 | if (action === 'add') { 127 | ipAssignmentPools.push(ipAssignmentPool); 128 | } else if (action === 'delete') { 129 | const pool = ipAssignmentPools.find(pool => 130 | pool.ipRangeStart === ipAssignmentPool.ipRangeStart && 131 | pool.ipRangeEnd === ipAssignmentPool.ipRangeEnd); 132 | ipAssignmentPools = ipAssignmentPools.filter(p => p != pool); 133 | } 134 | 135 | options.body = { ipAssignmentPools: ipAssignmentPools }; 136 | 137 | 138 | try { 139 | const response = await got(ZT_ADDR + '/controller/network/' 140 | + nwid, options); 141 | return response.body; 142 | } catch(err) { 143 | throw(err); 144 | } 145 | } 146 | 147 | exports.ipAssignmentDelete = async function(nwid, id, ipAssignmentIndex) { 148 | const options = await init_options(); 149 | options.method = 'POST'; 150 | 151 | try { 152 | const member = await member_detail(nwid, id); 153 | const ipAssignments = member.ipAssignments; 154 | ipAssignments.splice(ipAssignmentIndex, 1); 155 | options.body = { ipAssignments: ipAssignments }; 156 | const response = await got(ZT_ADDR + '/controller/network/' 157 | + nwid + '/member/' + id, options); 158 | response.body.deleted = true; 159 | return response.body; 160 | } catch(err) { 161 | throw(err); 162 | } 163 | } 164 | 165 | exports.ipAssignmentAdd = async function(nwid, id, ipAssignment) { 166 | const options = await init_options(); 167 | options.method = 'POST'; 168 | 169 | try { 170 | const member = await member_detail(nwid, id); 171 | const ipAssignments = member.ipAssignments; 172 | ipAssignments.push(ipAssignment.ipAddress); 173 | options.body = { ipAssignments: ipAssignments }; 174 | const response = await got(ZT_ADDR + '/controller/network/' 175 | + nwid + '/member/' + id, options); 176 | response.body.added = true; 177 | return response.body; 178 | } catch(err) { 179 | throw(err); 180 | } 181 | } 182 | 183 | exports.routes = async function(nwid, route, action) { 184 | const options = await init_options(); 185 | options.method = 'POST'; 186 | 187 | const network = await network_detail(nwid); 188 | let routes = network.routes; 189 | route.target = canonicalTarget(route.target); 190 | 191 | const route_to_del = routes.find(rt => canonicalTarget(rt.target) === route.target); 192 | 193 | if (!route_to_del) { 194 | if (action === 'add') { 195 | routes.push(route); 196 | } else if (action === 'delete') { 197 | throw new Error('Cannot delete non-existent route target'); 198 | } 199 | } else { 200 | if (action === 'add') { 201 | throw new Error('Route target is not unique'); 202 | } else if (action === 'delete') { 203 | routes = routes.filter(rt => rt != route_to_del); 204 | } 205 | } 206 | 207 | options.body = { routes: routes }; 208 | 209 | 210 | try { 211 | const response = await got(ZT_ADDR + '/controller/network/' 212 | + nwid, options); 213 | return response.body; 214 | } catch(err) { 215 | throw(err); 216 | } 217 | } 218 | 219 | function canonicalTarget(target) { 220 | const target6 = new ipaddr.Address6(target); 221 | if (target6.isValid()) { 222 | const parts = target.split('/'); 223 | return target6.canonicalForm() + '/' + parts[1]; 224 | } 225 | return target; 226 | } 227 | 228 | exports.network_object = async function(nwid, object) { 229 | const options = await init_options(); 230 | options.method = 'POST'; 231 | options.body = object; 232 | 233 | try { 234 | const response = await got(ZT_ADDR + '/controller/network/' 235 | + nwid, options); 236 | return response.body; 237 | } catch(err) { 238 | throw(err); 239 | } 240 | } 241 | 242 | exports.members = async function(nwid) { 243 | const options = await init_options(); 244 | 245 | try { 246 | const response = await got(ZT_ADDR + '/controller/network/' 247 | + nwid + '/member', options); 248 | return response.body; 249 | } catch(err) { 250 | throw(err); 251 | } 252 | } 253 | 254 | const member_detail = async function(nwid, id) { 255 | const options = await init_options(); 256 | 257 | try { 258 | const response = await got(ZT_ADDR + '/controller/network/' 259 | + nwid + '/member/' + id, options); 260 | return response.body; 261 | } catch(err) { 262 | throw(err); 263 | } 264 | } 265 | exports.member_detail = member_detail; 266 | 267 | exports.member_object = async function(nwid, id, object) { 268 | const options = await init_options(); 269 | options.method = 'POST'; 270 | options.body = object; 271 | 272 | try { 273 | const response = await got(ZT_ADDR + '/controller/network/' 274 | + nwid + '/member/' + id, options); 275 | return response.body; 276 | } catch(err) { 277 | throw(err); 278 | } 279 | } 280 | 281 | exports.member_delete = async function(nwid, id) { 282 | const options = await init_options(); 283 | options.method = 'DELETE'; 284 | 285 | try { 286 | const response = await got(ZT_ADDR + '/controller/network/' 287 | + nwid + '/member/' + id, options); 288 | response.body.deleted = true; 289 | return response.body; 290 | } catch(err) { 291 | throw(err); 292 | } 293 | } 294 | 295 | exports.network_easy_setup = async function(nwid, 296 | routes, 297 | ipAssignmentPools, 298 | v4AssignMode) { 299 | const options = await init_options(); 300 | options.method = 'POST'; 301 | options.body = 302 | { 303 | ipAssignmentPools: ipAssignmentPools, 304 | routes: routes, 305 | v4AssignMode: v4AssignMode 306 | }; 307 | 308 | try { 309 | const response = await got(ZT_ADDR + '/controller/network/' 310 | + nwid, options); 311 | return response.body; 312 | } catch(err) { 313 | throw(err); 314 | } 315 | } 316 | 317 | exports.peers = async function() { 318 | const options = await init_options(); 319 | const response = await got(ZT_ADDR + '/peer', options); 320 | return response.body; 321 | } 322 | 323 | exports.peer = async function(id) { 324 | const options = await init_options(); 325 | try { 326 | const response = await got(ZT_ADDR + '/peer/' + id, options); 327 | return response.body; 328 | } catch (error) { 329 | if (error instanceof got.HTTPError && error.statusCode == 404) { 330 | return null; 331 | } 332 | throw error; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/build/openssl.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | # Policies used by the TSA examples. 31 | tsa_policy1 = 1.2.3.4.1 32 | tsa_policy2 = 1.2.3.4.5.6 33 | tsa_policy3 = 1.2.3.4.5.7 34 | 35 | #################################################################### 36 | [ ca ] 37 | default_ca = CA_default # The default ca section 38 | 39 | #################################################################### 40 | [ CA_default ] 41 | 42 | dir = /etc/pki/CA # Where everything is kept 43 | certs = $dir/certs # Where the issued certs are kept 44 | crl_dir = $dir/crl # Where the issued crl are kept 45 | database = $dir/index.txt # database index file. 46 | #unique_subject = no # Set to 'no' to allow creation of 47 | # several certs with same subject. 48 | new_certs_dir = $dir/newcerts # default place for new certs. 49 | 50 | certificate = $dir/cacert.pem # The CA certificate 51 | serial = $dir/serial # The current serial number 52 | crlnumber = $dir/crlnumber # the current crl number 53 | # must be commented out to leave a V1 CRL 54 | crl = $dir/crl.pem # The current CRL 55 | private_key = $dir/private/cakey.pem# The private key 56 | RANDFILE = $dir/private/.rand # private random number file 57 | 58 | x509_extensions = usr_cert # The extensions to add to the cert 59 | 60 | # Comment out the following two lines for the "traditional" 61 | # (and highly broken) format. 62 | name_opt = ca_default # Subject Name options 63 | cert_opt = ca_default # Certificate field options 64 | 65 | # Extension copying option: use with caution. 66 | # copy_extensions = copy 67 | 68 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 69 | # so this is commented out by default to leave a V1 CRL. 70 | # crlnumber must also be commented out to leave a V1 CRL. 71 | # crl_extensions = crl_ext 72 | 73 | default_days = 365 # how long to certify for 74 | default_crl_days= 30 # how long before next CRL 75 | default_md = sha256 # use SHA-256 by default 76 | preserve = no # keep passed DN ordering 77 | 78 | # A few difference way of specifying how similar the request should look 79 | # For type CA, the listed attributes must be the same, and the optional 80 | # and supplied fields are just that :-) 81 | policy = policy_match 82 | 83 | # For the CA policy 84 | [ policy_match ] 85 | countryName = match 86 | stateOrProvinceName = match 87 | organizationName = match 88 | organizationalUnitName = optional 89 | commonName = supplied 90 | emailAddress = optional 91 | 92 | # For the 'anything' policy 93 | # At this point in time, you must list all acceptable 'object' 94 | # types. 95 | [ policy_anything ] 96 | countryName = optional 97 | stateOrProvinceName = optional 98 | localityName = optional 99 | organizationName = optional 100 | organizationalUnitName = optional 101 | commonName = supplied 102 | emailAddress = optional 103 | 104 | #################################################################### 105 | [ req ] 106 | prompt = no 107 | default_bits = 2048 108 | default_md = sha256 109 | default_keyfile = privkey.pem 110 | distinguished_name = req_distinguished_name 111 | attributes = req_attributes 112 | x509_extensions = v3_ca # The extensions to add to the self signed cert 113 | 114 | # Passwords for private keys if not present they will be prompted for 115 | # input_password = secret 116 | # output_password = secret 117 | 118 | # This sets a mask for permitted string types. There are several options. 119 | # default: PrintableString, T61String, BMPString. 120 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004) 121 | # utf8only: only UTF8Strings (PKIX recommendation after 2004). 122 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 123 | # MASK:XXXX a literal mask value. 124 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. 125 | string_mask = utf8only 126 | 127 | # req_extensions = v3_req # The extensions to add to a certificate request 128 | 129 | [ req_distinguished_name ] 130 | countryName = AU 131 | 132 | stateOrProvinceName = Western Australia 133 | #stateOrProvinceName_default = Default Province 134 | 135 | localityName = Perth 136 | 137 | 0.organizationName = Key Networks (https://key-networks.com) 138 | 139 | # we can do this but it is not needed normally :-) 140 | #1.organizationName = Second Organization Name (eg, company) 141 | #1.organizationName_default = World Wide Web Pty Ltd 142 | 143 | organizationalUnitName = Engineering 144 | #organizationalUnitName_default = 145 | 146 | commonName = localhost 147 | 148 | emailAddress = noreply@key-networks.com 149 | 150 | # SET-ex3 = SET extension number 3 151 | 152 | [ req_attributes ] 153 | challengePassword = A challenge password 154 | challengePassword_min = 4 155 | challengePassword_max = 20 156 | 157 | unstructuredName = An optional company name 158 | 159 | [ usr_cert ] 160 | 161 | # These extensions are added when 'ca' signs a request. 162 | 163 | # This goes against PKIX guidelines but some CAs do it and some software 164 | # requires this to avoid interpreting an end user certificate as a CA. 165 | 166 | basicConstraints=CA:FALSE 167 | 168 | # Here are some examples of the usage of nsCertType. If it is omitted 169 | # the certificate can be used for anything *except* object signing. 170 | 171 | # This is OK for an SSL server. 172 | # nsCertType = server 173 | 174 | # For an object signing certificate this would be used. 175 | # nsCertType = objsign 176 | 177 | # For normal client use this is typical 178 | # nsCertType = client, email 179 | 180 | # and for everything including object signing: 181 | # nsCertType = client, email, objsign 182 | 183 | # This is typical in keyUsage for a client certificate. 184 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 185 | 186 | # This will be displayed in Netscape's comment listbox. 187 | nsComment = "OpenSSL Generated Certificate" 188 | 189 | # PKIX recommendations harmless if included in all certificates. 190 | subjectKeyIdentifier=hash 191 | authorityKeyIdentifier=keyid,issuer 192 | 193 | # This stuff is for subjectAltName and issuerAltname. 194 | # Import the email address. 195 | # subjectAltName=email:copy 196 | # An alternative to produce certificates that aren't 197 | # deprecated according to PKIX. 198 | # subjectAltName=email:move 199 | 200 | # Copy subject details 201 | # issuerAltName=issuer:copy 202 | 203 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 204 | #nsBaseUrl 205 | #nsRevocationUrl 206 | #nsRenewalUrl 207 | #nsCaPolicyUrl 208 | #nsSslServerName 209 | 210 | # This is required for TSA certificates. 211 | # extendedKeyUsage = critical,timeStamping 212 | 213 | [ v3_req ] 214 | 215 | # Extensions to add to a certificate request 216 | 217 | basicConstraints = CA:FALSE 218 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 219 | 220 | [ v3_ca ] 221 | 222 | 223 | # Extensions for a typical CA 224 | 225 | 226 | # PKIX recommendation. 227 | 228 | subjectKeyIdentifier=hash 229 | 230 | authorityKeyIdentifier=keyid:always,issuer 231 | 232 | basicConstraints = critical,CA:true 233 | 234 | # Key usage: this is typical for a CA certificate. However since it will 235 | # prevent it being used as an test self-signed certificate it is best 236 | # left out by default. 237 | # keyUsage = cRLSign, keyCertSign 238 | 239 | # Some might want this also 240 | # nsCertType = sslCA, emailCA 241 | 242 | # Include email address in subject alt name: another PKIX recommendation 243 | # subjectAltName=email:copy 244 | # Copy issuer details 245 | # issuerAltName=issuer:copy 246 | 247 | # DER hex encoding of an extension: beware experts only! 248 | # obj=DER:02:03 249 | # Where 'obj' is a standard or added object 250 | # You can even override a supported extension: 251 | # basicConstraints= critical, DER:30:03:01:01:FF 252 | 253 | [ crl_ext ] 254 | 255 | # CRL extensions. 256 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 257 | 258 | # issuerAltName=issuer:copy 259 | authorityKeyIdentifier=keyid:always 260 | 261 | [ proxy_cert_ext ] 262 | # These extensions should be added when creating a proxy certificate 263 | 264 | # This goes against PKIX guidelines but some CAs do it and some software 265 | # requires this to avoid interpreting an end user certificate as a CA. 266 | 267 | basicConstraints=CA:FALSE 268 | 269 | # Here are some examples of the usage of nsCertType. If it is omitted 270 | # the certificate can be used for anything *except* object signing. 271 | 272 | # This is OK for an SSL server. 273 | # nsCertType = server 274 | 275 | # For an object signing certificate this would be used. 276 | # nsCertType = objsign 277 | 278 | # For normal client use this is typical 279 | # nsCertType = client, email 280 | 281 | # and for everything including object signing: 282 | # nsCertType = client, email, objsign 283 | 284 | # This is typical in keyUsage for a client certificate. 285 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 286 | 287 | # This will be displayed in Netscape's comment listbox. 288 | nsComment = "OpenSSL Generated Certificate" 289 | 290 | # PKIX recommendations harmless if included in all certificates. 291 | subjectKeyIdentifier=hash 292 | authorityKeyIdentifier=keyid,issuer 293 | 294 | # This stuff is for subjectAltName and issuerAltname. 295 | # Import the email address. 296 | # subjectAltName=email:copy 297 | # An alternative to produce certificates that aren't 298 | # deprecated according to PKIX. 299 | # subjectAltName=email:move 300 | 301 | # Copy subject details 302 | # issuerAltName=issuer:copy 303 | 304 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 305 | #nsBaseUrl 306 | #nsRevocationUrl 307 | #nsRenewalUrl 308 | #nsCaPolicyUrl 309 | #nsSslServerName 310 | 311 | # This really needs to be in place for it to be a proxy certificate. 312 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 313 | 314 | #################################################################### 315 | [ tsa ] 316 | 317 | default_tsa = tsa_config1 # the default TSA section 318 | 319 | [ tsa_config1 ] 320 | 321 | # These are used by the TSA reply generation only. 322 | dir = ./demoCA # TSA root directory 323 | serial = $dir/tsaserial # The current serial number (mandatory) 324 | crypto_device = builtin # OpenSSL engine to use for signing 325 | signer_cert = $dir/tsacert.pem # The TSA signing certificate 326 | # (optional) 327 | certs = $dir/cacert.pem # Certificate chain to include in reply 328 | # (optional) 329 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional) 330 | signer_digest = sha256 # Signing digest to use. (Optional) 331 | default_policy = tsa_policy1 # Policy if request did not specify it 332 | # (optional) 333 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) 334 | digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory) 335 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional) 336 | clock_precision_digits = 0 # number of digits after dot. (optional) 337 | ordering = yes # Is ordering defined for timestamps? 338 | # (optional, default: no) 339 | tsa_name = yes # Must the TSA name be included in the reply? 340 | # (optional, default: no) 341 | ess_cert_id_chain = no # Must the ESS cert id chain be included? 342 | # (optional, default: no) 343 | -------------------------------------------------------------------------------- /ztncui_code@0.8.6/src/controllers/networkController.js: -------------------------------------------------------------------------------- 1 | /* 2 | ztncui - ZeroTier network controller UI 3 | Copyright (C) 2017-2021 Key Networks (https://key-networks.com) 4 | Licensed under GPLv3 - see LICENSE for details. 5 | */ 6 | 7 | const fs = require('fs'); 8 | const ipaddr = require('ip-address'); 9 | const storage = require('node-persist'); 10 | const zt = require('./zt'); 11 | const util = require('util'); 12 | 13 | storage.initSync({dir: 'etc/storage'}); 14 | 15 | async function get_network_with_members(nwid) { 16 | const [network, peers, members] = await Promise.all([ 17 | zt.network_detail(nwid), 18 | zt.peers(), 19 | zt.members(nwid) 20 | .then(member_ids => 21 | Promise.all( 22 | Object.keys(member_ids) 23 | .map(id => Promise.all([ 24 | zt.member_detail(nwid, id), 25 | storage.getItem(id) 26 | ])) 27 | ) 28 | ).then(results => results.map(([member, name]) => { 29 | member.name = name || ''; 30 | return member; 31 | })) 32 | ]); 33 | for (const member of members) { 34 | member.peer = peers.find(x => x.address === member.address); 35 | } 36 | return {network, members}; 37 | } 38 | 39 | async function get_network_member(nwid, memberid) { 40 | const [network, member, peer, name] = await Promise.all([ 41 | zt.network_detail(nwid), 42 | zt.member_detail(nwid, memberid), 43 | zt.peer(memberid), 44 | storage.getItem(memberid) 45 | ]); 46 | member.name = name || ''; 47 | member.peer = peer; 48 | return {network, member}; 49 | } 50 | 51 | // ZT network controller home page 52 | exports.index = async function(req, res) { 53 | const navigate = 54 | { 55 | active: 'controller_home', 56 | } 57 | 58 | try { 59 | const zt_status = await zt.get_zt_status(); 60 | res.render('index', {title: 'ztncui', navigate: navigate, zt_status}); 61 | } catch (err) { 62 | res.render('index', {title: 'ztncui', 63 | navigate: navigate, error: 'ERROR getting ZT status: ' + err}); 64 | } 65 | }; 66 | 67 | // Display list of all networks on this ZT network controller 68 | exports.network_list = async function(req, res) { 69 | const navigate = 70 | { 71 | active: 'networks', 72 | } 73 | 74 | try { 75 | networks = await zt.network_list(); 76 | res.render('networks', {title: 'Networks on this controller', navigate: navigate, networks: networks}); 77 | } catch (err) { 78 | res.render('networks', {title: 'Networks on this controller', navigate: navigate, error: 'Error retrieving list of networks on this controller: ' + err}); 79 | } 80 | }; 81 | 82 | // Display detail page for specific network 83 | exports.network_detail = async function(req, res) { 84 | const navigate = 85 | { 86 | active: 'networks', 87 | whence: '/controller/networks' 88 | } 89 | 90 | try { 91 | const [ 92 | {network, members}, 93 | zt_address 94 | ] = await Promise.all([ 95 | get_network_with_members(req.params.nwid), 96 | zt.get_zt_address() 97 | ]); 98 | res.render('network_detail', {title: 'Network ' + network.name, navigate: navigate, network: network, members: members, zt_address: zt_address}); 99 | } catch (err) { 100 | res.render('network_detail', {title: 'Detail for network', navigate: navigate, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); 101 | } 102 | }; 103 | 104 | // Display Network create form on GET 105 | exports.network_create_get = function(req, res) { 106 | const navigate = 107 | { 108 | active: 'add_network', 109 | } 110 | 111 | res.render('network_create', {title: 'Create network', navigate: navigate}); 112 | }; 113 | 114 | // Handle Network create on POST 115 | exports.network_create_post = async function(req, res) { 116 | const navigate = 117 | { 118 | active: 'add_network', 119 | } 120 | 121 | req.checkBody('name', 'Network name required').notEmpty(); 122 | 123 | req.sanitize('name').escape(); 124 | req.sanitize('name').trim(); 125 | 126 | const errors = req.validationErrors(); 127 | 128 | let name = { name: req.body.name }; 129 | 130 | if (errors) { 131 | res.render('network_create', {title: 'Create Network', navigate: navigate, name: name, errors: errors}); 132 | return; 133 | } else { 134 | try { 135 | const network = await zt.network_create(name); 136 | res.redirect('/controller/network/' + network.nwid); 137 | } catch (err) { 138 | res.render('network_detail', {title: 'Create Network - error', navigate: navigate, error: 'Error creating network ' + name.name}); 139 | } 140 | } 141 | }; 142 | 143 | // Display Network delete form on GET 144 | exports.network_delete_get = async function(req, res) { 145 | const navigate = 146 | { 147 | active: 'networks', 148 | whence: '/controller/networks' 149 | } 150 | 151 | try { 152 | const network = await zt.network_detail(req.params.nwid); 153 | res.render('network_delete', {title: 'Delete network', navigate: navigate, 154 | nwid: req.params.nwid, network: network}); 155 | } catch (err) { 156 | res.render('network_delete', {title: 'Delete network', navigate: navigate, error: 'Error resolving network ' + req.params.nwid + ': ' + err}); 157 | } 158 | }; 159 | 160 | // Handle Network delete on POST 161 | exports.network_delete_post = async function(req, res) { 162 | const navigate = 163 | { 164 | active: 'networks', 165 | whence: '/controller/networks' 166 | } 167 | 168 | try { 169 | const network = await zt.network_delete(req.params.nwid); 170 | res.render('network_delete', {title: 'Delete network', navigate: navigate, network: network}); 171 | } catch (err) { 172 | res.render('network_delete', {title: 'Delete network', navigate: navigate, error: 'Error deleting network ' + req.params.nwid + ': ' + err}); 173 | } 174 | }; 175 | 176 | // Network object GET 177 | exports.network_object = async function(req, res) { 178 | const navigate = 179 | { 180 | active: 'networks', 181 | whence: '' 182 | } 183 | 184 | try { 185 | const network = await zt.network_detail(req.params.nwid); 186 | navigate.whence = '/controller/network/' + network.nwid; 187 | res.render(req.params.object, {title: req.params.object, navigate: navigate, network: network}, function(err, html) { 188 | if (err) { 189 | if (err.message.indexOf('Failed to lookup view') !== -1 ) { 190 | return res.render('not_implemented', {title: req.params.object, navigate: navigate, network: network}); 191 | } 192 | throw err; 193 | } 194 | res.send(html); 195 | }); 196 | } catch (err) { 197 | res.render(req.params.object, {title: req.params.object, navigate: navigate, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); 198 | } 199 | } 200 | 201 | // Handle Network rename form on POST 202 | exports.name = async function(req, res) { 203 | const navigate = 204 | { 205 | active: 'networks', 206 | whence: '/controller/networks' 207 | } 208 | 209 | req.checkBody('name', 'Network name required').notEmpty(); 210 | req.sanitize('name').escape(); 211 | req.sanitize('name').trim(); 212 | 213 | const errors = req.validationErrors(); 214 | 215 | let name = { name: req.body.name }; 216 | 217 | if (errors) { 218 | console.error("network name validation errors", errors); 219 | } else { 220 | try { 221 | const network = await zt.network_object(req.params.nwid, name); 222 | } catch ( err) { 223 | console.error("Error renaming network " + req.params.nwid, err); 224 | } 225 | } 226 | res.redirect('/controller/network/' + req.params.nwid); 227 | }; 228 | 229 | // ipAssignmentPools POST 230 | exports.ipAssignmentPools = async function(req, res) { 231 | const navigate = 232 | { 233 | active: 'networks', 234 | whence: '' 235 | } 236 | 237 | req.checkBody('ipRangeStart', 'IP range start required').notEmpty(); 238 | req.checkBody('ipRangeStart', 'IP range start needs a valid IPv4 or IPv6 address').isIP(); 239 | req.sanitize('ipRangeStart').escape(); 240 | req.sanitize('ipRangeStart').trim(); 241 | req.checkBody('ipRangeEnd', 'IP range end required').notEmpty(); 242 | req.checkBody('ipRangeEnd', 'IP range end needs a valid IPv4 or IPv6 address').isIP(); 243 | req.sanitize('ipRangEnd').escape(); 244 | req.sanitize('ipRangEnd').trim(); 245 | 246 | const errors = req.validationErrors(); 247 | 248 | const ipAssignmentPool = 249 | { 250 | ipRangeStart: req.body.ipRangeStart, 251 | ipRangeEnd: req.body.ipRangeEnd 252 | }; 253 | 254 | if (errors) { 255 | try { 256 | const network = await zt.network_detail(req.params.nwid); 257 | navigate.whence = '/controller/network/' + network.nwid; 258 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network, errors: errors}); 259 | } catch (err) { 260 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err}); 261 | } 262 | } else { 263 | try { 264 | const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'add'); 265 | navigate.whence = '/controller/network/' + network.nwid; 266 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network}); 267 | } catch (err) { 268 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error applying IP Assignment Pools for network ' + req.params.nwid + ': ' + err}); 269 | } 270 | } 271 | } 272 | 273 | isValidPrefix = function(str, max) { 274 | const num = Math.floor(Number(str)); 275 | return String(num) == str && num >= 0 && num <= max; 276 | } 277 | 278 | // routes POST 279 | exports.routes = async function (req, res) { 280 | const navigate = 281 | { 282 | active: 'networks', 283 | whence: '' 284 | } 285 | 286 | req.checkBody('target', 'Target network is required').notEmpty(); 287 | req.sanitize('target').trim(); 288 | req.checkBody('target', 'Target network must be valid CIDR format') 289 | .custom(value => { 290 | const parts = value.split('/'); 291 | const ipv4 = new ipaddr.Address4(parts[0]); 292 | const ipv6 = new ipaddr.Address6(parts[0]); 293 | let isValidIPv4orIPv6 = false; 294 | let prefixMax = 32; 295 | if (ipv4.isValid()) { 296 | isValidIPv4orIPv6 = true; 297 | } else { 298 | } 299 | if (ipv6.isValid()) { 300 | isValidIPv4orIPv6 = true; 301 | prefixMax = 128; 302 | } else { 303 | } 304 | return isValidIPv4orIPv6 && isValidPrefix(parts[1], prefixMax); 305 | }); 306 | req.checkBody('via', 'Gateway must be a valid IPv4 or IPv6 address').optional({ checkFalsy: true }).isIP(); 307 | req.sanitize('via').escape(); 308 | req.sanitize('via').trim(); 309 | if (! req.body.via) { 310 | req.body.via = null; 311 | } 312 | 313 | const errors = req.validationErrors(); 314 | 315 | const route = 316 | { 317 | target: req.body.target, 318 | via: req.body.via 319 | }; 320 | 321 | if (errors) { 322 | try { 323 | const network = await zt.network_detail(req.params.nwid); 324 | navigate.whence = '/controller/network/' + network.nwid; 325 | res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network, errors: errors}); 326 | } catch (err) { 327 | res.render('routes', {title: 'routes', navigate: navigate, error: 'Error resolving network detail'}); 328 | } 329 | } else { 330 | try { 331 | const network = await zt.routes(req.params.nwid, route, 'add'); 332 | navigate.whence = '/controller/network/' + network.nwid; 333 | res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network}); 334 | } catch (err) { 335 | res.render('routes', {title: 'routes', navigate: navigate, error: 'Error adding route for network ' + req.params.nwid + ': ' + err}); 336 | } 337 | } 338 | 339 | } 340 | 341 | // route_delete GET 342 | exports.route_delete = async function (req, res) { 343 | const navigate = 344 | { 345 | active: 'networks', 346 | whence: '' 347 | } 348 | 349 | const route = 350 | { 351 | target: req.params.target_ip + '/' + req.params.target_prefix, 352 | via: null 353 | }; 354 | 355 | 356 | try { 357 | const network = await zt.routes(req.params.nwid, route, 'delete'); 358 | navigate.whence = '/controller/network/' + network.nwid; 359 | res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network}); 360 | } catch (err) { 361 | res.render('routes', {title: 'routes', navigate: navigate, error: 'Error deleting route for network ' + req.params.nwid + ': ' + err}); 362 | } 363 | } 364 | 365 | // ipAssignmentPool_delete GET 366 | exports.ipAssignmentPool_delete = async function (req, res) { 367 | const navigate = 368 | { 369 | active: 'networks', 370 | whence: '' 371 | } 372 | 373 | const ipAssignmentPool = 374 | { 375 | ipRangeStart: req.params.ipRangeStart, 376 | ipRangeEnd: req.params.ipRangeEnd 377 | }; 378 | 379 | 380 | try { 381 | const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'delete'); 382 | navigate.whence = '/controller/network/' + network.nwid; 383 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network}); 384 | } catch (err) { 385 | res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error deleting IP Assignment Pool for network ' + req.params.nwid + ': ' + err}); 386 | } 387 | } 388 | 389 | // private POST 390 | exports.private = async function (req, res) { 391 | const navigate = 392 | { 393 | active: 'networks', 394 | whence: '' 395 | } 396 | 397 | const private = 398 | { 399 | private: req.body.private 400 | }; 401 | 402 | try { 403 | const network = await zt.network_object(req.params.nwid, private); 404 | navigate.whence = '/controller/network/' + network.nwid; 405 | res.render('private', {title: 'private', navigate: navigate, network: network}); 406 | } catch (err) { 407 | res.render('private', {title: 'private', navigate: navigate, error: 'Error applying private for network ' + req.params.nwid + ': ' + err}); 408 | } 409 | } 410 | 411 | // v4AssignMode POST 412 | exports.v4AssignMode = async function (req, res) { 413 | const navigate = 414 | { 415 | active: 'networks', 416 | whence: '' 417 | } 418 | 419 | const v4AssignMode = 420 | { 421 | v4AssignMode: { zt: req.body.zt } 422 | }; 423 | 424 | try { 425 | const network = await zt.network_object(req.params.nwid, v4AssignMode); 426 | navigate.whence = '/controller/network/' + network.nwid; 427 | res.render('v4AssignMode', {title: 'v4AssignMode', navigate: navigate, network: network}); 428 | } catch (err) { 429 | res.render('v4AssignMode', {title: 'v4AssignMode', navigate: navigate, error: 'Error applying v4AssignMode for network ' + req.params.nwid + ': ' + err}); 430 | } 431 | } 432 | 433 | // v6AssignMode POST 434 | exports.v6AssignMode = async function (req, res) { 435 | const navigate = 436 | { 437 | active: 'networks', 438 | whence: '' 439 | } 440 | 441 | const v6AssignMode = 442 | { 443 | v6AssignMode: 444 | { 445 | '6plane': req.body['6plane'], 446 | 'rfc4193': req.body.rfc4193, 447 | 'zt': req.body.zt 448 | } 449 | }; 450 | 451 | try { 452 | const network = await zt.network_object(req.params.nwid, v6AssignMode); 453 | navigate.whence = '/controller/network/' + network.nwid; 454 | res.render('v6AssignMode', {title: 'v6AssignMode', navigate: navigate, network: network}); 455 | } catch (err) { 456 | res.render('v6AssignMode', {title: 'v6AssignMode', navigate: navigate, error: 'Error applying v6AssignMode for network ' + req.params.nwid + ': ' + err}); 457 | } 458 | } 459 | 460 | // dns POST 461 | exports.dns = async function (req, res) { 462 | const navigate = { 463 | active: 'networks', 464 | whence: '' 465 | }; 466 | 467 | const dns = { 468 | dns: { 469 | domain: req.body.domain, 470 | servers: req.body.servers 471 | .split('\n') 472 | .map(x => x.trim()) 473 | .filter(ip => 474 | new ipaddr.Address4(ip).isValid() || 475 | new ipaddr.Address6(ip).isValid() 476 | ) 477 | } 478 | }; 479 | 480 | try { 481 | const network = await zt.network_object(req.params.nwid, dns); 482 | navigate.whence = '/controller/network/' + network.nwid; 483 | res.render('dns', {title: 'dns', navigate: navigate, network: network}); 484 | } catch (err) { 485 | res.render('dns', {title: 'dns', navigate: navigate, error: 'Error updating dns for network ' + req.params.nwid + ': ' + err}); 486 | } 487 | } 488 | 489 | // Display detail page for specific member 490 | exports.member_detail = async function(req, res) { 491 | const navigate = 492 | { 493 | active: 'networks', 494 | whence: '' 495 | } 496 | 497 | try { 498 | const {network, member} = await get_network_member(req.params.nwid, req.params.id); 499 | navigate.whence = '/controller/network/' + network.nwid + '#members'; 500 | res.render('member_detail', {title: 'Network member detail', navigate: navigate, network: network, member: member}); 501 | } catch (err) { 502 | console.error(err); 503 | res.render('error', {title: req.params.object, navigate: navigate, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); 504 | } 505 | }; 506 | 507 | // Member object GET 508 | exports.member_object = async function(req, res) { 509 | const navigate = 510 | { 511 | active: 'networks', 512 | whence: '' 513 | } 514 | 515 | try { 516 | const {network, member} = await get_network_member(req.params.nwid, req.params.id); 517 | navigate.whence = '/controller/network/' + network.nwid + '#members'; 518 | res.render(req.params.object, {title: req.params.object, navigate: navigate, network: network, member: member}, function(err, html) { 519 | if (err) { 520 | if (err.message.indexOf('Failed to lookup view') !== -1 ) { 521 | return res.render('not_implemented', {title: req.params.object, navigate: navigate, network: network, member: member}); 522 | } 523 | throw err; 524 | } 525 | res.send(html); 526 | }); 527 | } catch (err) { 528 | res.render(req.params.object, {title: req.params.object, navigate: navigate, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); 529 | } 530 | } 531 | 532 | // Easy network setup GET 533 | exports.easy_get = async function(req, res) { 534 | const navigate = 535 | { 536 | active: 'networks', 537 | whence: '/controller/network/' + req.params.nwid 538 | } 539 | 540 | try { 541 | const network = await zt.network_detail(req.params.nwid); 542 | res.render('network_easy', {title: 'Easy setup of network', navigate: navigate, network: network}); 543 | } catch (err) { 544 | res.render('network_easy', {title: 'Easy setup of network', navigate: navigate, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); 545 | } 546 | } 547 | 548 | // Easy network setup POST 549 | exports.easy_post = async function(req, res) { 550 | const navigate = 551 | { 552 | active: 'networks', 553 | whence: '/controller/networks' 554 | } 555 | 556 | req.checkBody('networkCIDR', 'Network address is required').notEmpty(); 557 | req.sanitize('networkCIDR').trim(); 558 | req.checkBody('networkCIDR', 'Network address must be in CIDR notation') 559 | .custom(value => { 560 | const parts = value.split('/'); 561 | const ipv4 = new ipaddr.Address4(parts[0]); 562 | return ipv4.isValid() && isValidPrefix(parts[1], 32); 563 | }); 564 | req.checkBody('poolStart', 'Start of IP assignment pool is required') 565 | .notEmpty(); 566 | req.checkBody('poolStart', 'Start of IP assignment pool must be valid IPv4 address') 567 | .isIP(4); 568 | req.sanitize('poolStart').escape(); 569 | req.sanitize('poolStart').trim(); 570 | req.checkBody('poolEnd', 'End of IP assignment pool is required') 571 | .notEmpty(); 572 | req.checkBody('poolEnd', 'End of IP assignment pool must be valid IPv4 address') 573 | .isIP(4); 574 | req.sanitize('poolEnd').escape(); 575 | req.sanitize('poolEnd').trim(); 576 | 577 | const errors = req.validationErrors(); 578 | 579 | const ipAssignmentPools = 580 | [{ 581 | ipRangeStart: req.body.poolStart, 582 | ipRangeEnd: req.body.poolEnd 583 | }]; 584 | 585 | const routes = 586 | [{ 587 | target: req.body.networkCIDR, 588 | via: null 589 | }]; 590 | 591 | const v4AssignMode = 592 | { 593 | zt: true 594 | }; 595 | 596 | if (errors) { 597 | network = 598 | { 599 | ipAssignmentPools: ipAssignmentPools, 600 | routes: routes, 601 | v4AssignMode: v4AssignMode 602 | }; 603 | 604 | res.render('network_easy', {title: 'Easy setup of network', navigate: navigate, network: network, errors: errors}); 605 | } else { 606 | try { 607 | const network = await zt.network_easy_setup(req.params.nwid, 608 | routes, 609 | ipAssignmentPools, 610 | v4AssignMode); 611 | res.render('network_easy', {title: 'Easy setup of network', navigate: navigate, network: network, message: 'Network setup succeeded'}); 612 | } catch (err) { 613 | res.render('network_easy', {title: 'Easy setup of network', navigate: navigate, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); 614 | } 615 | } 616 | } 617 | 618 | // Easy members auth POST 619 | exports.members = async function(req, res) { 620 | const navigate = 621 | { 622 | active: 'networks', 623 | whence: '/controller/networks' 624 | } 625 | 626 | let errors = null; 627 | 628 | if (req.method === 'POST') { 629 | 630 | req.checkBody('id', 'Member ID is required').notEmpty(); 631 | req.sanitize('id').trim(); 632 | req.sanitize('id').escape(); 633 | 634 | if (req.body.auth) { 635 | req.checkBody('auth', 'Authorization state must be boolean').isBoolean(); 636 | req.sanitize('auth').trim(); 637 | req.sanitize('auth').escape(); 638 | 639 | errors = req.validationErrors(); 640 | 641 | if (!errors) { 642 | const auth = 643 | { 644 | authorized: req.body.auth 645 | }; 646 | 647 | try { 648 | const mem = await zt.member_object(req.params.nwid, req.body.id, auth); 649 | } catch (err) { 650 | throw err; 651 | } 652 | } 653 | } else if (req.body.activeBridge) { 654 | req.checkBody('activeBridge', 'activeBridge state must be boolean').isBoolean(); 655 | req.sanitize('activeBridge').trim(); 656 | req.sanitize('activeBridge').escape(); 657 | 658 | errors = req.validationErrors(); 659 | 660 | if (!errors) { 661 | const activeBridge = 662 | { 663 | activeBridge: req.body.activeBridge 664 | }; 665 | 666 | try { 667 | const mem = await zt.member_object(req.params.nwid, req.body.id, activeBridge); 668 | } catch (err) { 669 | throw err; 670 | } 671 | } 672 | } else if (req.body.name) { 673 | req.sanitize('name').trim(); 674 | req.sanitize('name').escape(); 675 | 676 | errors = req.validationErrors(); 677 | 678 | if (!errors) { 679 | try { 680 | const ret = await storage.setItem(req.body.id, req.body.name); 681 | } catch (err) { 682 | throw err; 683 | } 684 | } 685 | } 686 | } else { // GET 687 | res.redirect("/controller/network/" + req.params.nwid + "#members"); 688 | } 689 | } 690 | 691 | // Member delete GET or POST 692 | exports.member_delete = async function(req, res) { 693 | const navigate = 694 | { 695 | active: 'networks', 696 | whence: '' 697 | } 698 | 699 | try { 700 | const network = await zt.network_detail(req.params.nwid); 701 | let member = null; 702 | let name = null; 703 | if (req.method === 'POST') { 704 | member = await zt.member_delete(req.params.nwid, req.params.id); 705 | if (member.deleted) { 706 | name = await storage.removeItem(member.id); 707 | } 708 | } else { 709 | member = await zt.member_detail(req.params.nwid, req.params.id); 710 | name = await storage.getItem(member.id); 711 | } 712 | member.name = name || ''; 713 | 714 | navigate.whence = '/controller/network/' + network.nwid; 715 | res.render('member_delete', {title: 'Delete member from ' + network.name, 716 | navigate: navigate, network: network, member: member}); 717 | } catch (err) { 718 | res.render('member_delete', {title: 'Delete member from network', navigate: navigate, 719 | error: 'Error resolving detail for member ' + req.params.id 720 | + ' of network ' + req.params.nwid + ': ' + err}); 721 | } 722 | } 723 | 724 | // ipAssignment delete GET 725 | exports.delete_ip = async function(req, res) { 726 | const navigate = 727 | { 728 | active: 'networks', 729 | whence: '' 730 | } 731 | 732 | try { 733 | const network = await zt.network_detail(req.params.nwid); 734 | let member = await zt.member_detail(req.params.nwid, req.params.id); 735 | navigate.whence = '/controller/network/' + network.nwid; 736 | member.name = await storage.getItem(member.id) | ''; 737 | if (req.params.index) { 738 | member = await zt.ipAssignmentDelete(network.nwid, member.id, 739 | req.params.index); 740 | res.redirect('/controller/network/' + network.nwid + '/member/' + 741 | member.id + '/ipAssignments'); 742 | } 743 | res.render('ipAssignments', {title: 'ipAssignments ' + network.name, 744 | navigate: navigate, index: req.params.index, network: network, member: member}); 745 | } catch (err) { 746 | res.render('ipAssignments', {title: 'ipAssignments', navigate: navigate, 747 | error: 'Error resolving detail for member ' + req.params.id 748 | + ' of network ' + req.params.nwid + ': ' + err}); 749 | } 750 | } 751 | 752 | // ipAssignments POST 753 | exports.assign_ip = async function(req, res) { 754 | const navigate = 755 | { 756 | active: 'networks', 757 | whence: '' 758 | } 759 | 760 | try { 761 | var network = await zt.network_detail(req.params.nwid); 762 | } catch (err) { 763 | throw err; 764 | } 765 | 766 | req.checkBody('ipAddress', 'IP address required').notEmpty(); 767 | req.checkBody('ipAddress', 'IP address must be a valid IPv4 or IPv6 address').isIP(); 768 | req.checkBody('ipAddress', 'IP address must fall within a managed route') 769 | .custom(value => { 770 | let ipAddressInManagedRoute = false; 771 | network.routes.forEach(function(item) { 772 | let ipv4 = new ipaddr.Address4(value); 773 | let target4 = new ipaddr.Address4(item.target); 774 | if (ipv4.isValid() && target4.isValid()) { 775 | if (ipv4.isInSubnet(target4)) ipAddressInManagedRoute = true; 776 | } 777 | let ipv6 = new ipaddr.Address6(value); 778 | let target6 = new ipaddr.Address6(item.target); 779 | if (ipv6.isValid() && target6.isValid()) { 780 | if (ipv6.isInSubnet(target6)) ipAddressInManagedRoute = true; 781 | } 782 | }); 783 | return ipAddressInManagedRoute; 784 | }); 785 | req.sanitize('ipAddress').escape(); 786 | req.sanitize('ipAddress').trim(); 787 | 788 | const errors = req.validationErrors(); 789 | 790 | const ipAssignment = { ipAddress: req.body.ipAddress }; 791 | 792 | try { 793 | let member = await zt.member_detail(req.params.nwid, req.params.id); 794 | navigate.whence = '/controller/network/' + network.nwid; 795 | 796 | if (!errors) { 797 | member = await zt.ipAssignmentAdd(network.nwid, member.id, ipAssignment); 798 | } 799 | 800 | member.name = await storage.getItem(member.id) | ''; 801 | 802 | res.render('ipAssignments', {title: 'ipAssignments', navigate: navigate, 803 | ipAssignment: ipAssignment, network: network, member: member, 804 | errors: errors}); 805 | } catch (err) { 806 | res.render('ipAssignments', {title: 'ipAssignments', navigate: navigate, 807 | error: 'Error resolving detail for member ' + req.params.id 808 | + ' of network ' + req.params.nwid + ': ' + err}); 809 | } 810 | } 811 | --------------------------------------------------------------------------------