networkController.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /*
  2. ztncui - ZeroTier network controller UI
  3. Copyright (C) 2017 Key Networks (https://key-networks.com)
  4. Licensed under GPLv3 - see LICENSE for details.
  5. */
  6. const fs = require('fs');
  7. const ipaddr = require('ip-address');
  8. const storage = require('node-persist');
  9. const zt = require('./zt');
  10. storage.initSync({dir: 'etc/storage'});
  11. // ZT network controller home page
  12. exports.index = async function(req, res) {
  13. const page = 'controller_home';
  14. try {
  15. zt_address = await zt.get_zt_address();
  16. res.render('index', {title: 'ztncui', page: page, zt_address: zt_address});
  17. } catch (err) {
  18. res.render('index', {title: 'ztncui',
  19. page: page, error: 'ERROR resolving ZT address: ' + err});
  20. }
  21. };
  22. // Display list of all networks on this ZT network controller
  23. exports.network_list = async function(req, res) {
  24. const page = 'networks';
  25. try {
  26. networks = await zt.network_list();
  27. res.render('networks', {title: 'Networks on this controller', page: page, networks: networks});
  28. } catch (err) {
  29. res.render('networks', {title: 'Networks on this controller', page: page, error: 'Error retrieving list of networks on this controller: ' + err});
  30. }
  31. };
  32. // Display detail page for specific network
  33. exports.network_detail = async function(req, res) {
  34. const page = 'networks';
  35. try {
  36. const network = await zt.network_detail(req.params.nwid);
  37. const members = await zt.members(req.params.nwid);
  38. res.render('network_detail', {title: 'Detail for network', page: page, network: network, members: members});
  39. } catch (err) {
  40. res.render('network_detail', {title: 'Detail for network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err});
  41. }
  42. };
  43. // Display Network create form on GET
  44. exports.network_create_get = function(req, res) {
  45. const page = 'add_network';
  46. res.render('network_create', {title: 'Create network', page: page});
  47. };
  48. // Handle Network create on POST
  49. exports.network_create_post = async function(req, res) {
  50. const page = 'add_network';
  51. req.checkBody('name', 'Network name required').notEmpty();
  52. req.sanitize('name').escape();
  53. req.sanitize('name').trim();
  54. const errors = req.validationErrors();
  55. const name = { name: req.body.name };
  56. if (errors) {
  57. res.render('network_create', {title: 'Create Network', page: page, name: name, errors: errors});
  58. return;
  59. } else {
  60. try {
  61. const network = await zt.network_create(name);
  62. res.redirect('/controller/networks');
  63. } catch (err) {
  64. res.render('network_detail', {title: 'Create Network - error', page: page, error: 'Error creating network ' + name.name});
  65. }
  66. }
  67. };
  68. // Display Network delete form on GET
  69. exports.network_delete_get = async function(req, res) {
  70. const page = 'networks';
  71. try {
  72. const network = await zt.network_detail(req.params.nwid);
  73. res.render('network_delete', {title: 'Delete network', page: page,
  74. nwid: req.params.nwid, network: network});
  75. } catch (err) {
  76. res.render('network_delete', {title: 'Delete network', page: page, error: 'Error resolving network ' + req.params.nwid + ': ' + err});
  77. }
  78. };
  79. // Handle Network delete on POST
  80. exports.network_delete_post = async function(req, res) {
  81. const page = 'networks';
  82. try {
  83. const network = await zt.network_delete(req.params.nwid);
  84. res.render('network_delete', {title: 'Delete network', page: page, network: network});
  85. } catch (err) {
  86. res.render('network_delete', {title: 'Delete network', page: page, error: 'Error deleting network ' + req.params.nwid + ': ' + err});
  87. }
  88. };
  89. // Network object GET
  90. exports.network_object = async function(req, res) {
  91. const page = 'networks';
  92. try {
  93. const network = await zt.network_detail(req.params.nwid);
  94. res.render(req.params.object, {title: req.params.object, page: page, network: network}, function(err, html) {
  95. if (err) {
  96. if (err.message.indexOf('Failed to lookup view') !== -1 ) {
  97. return res.render('not_implemented', {title: req.params.object, page: page, network: network});
  98. }
  99. throw err;
  100. }
  101. res.send(html);
  102. });
  103. } catch (err) {
  104. res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err});
  105. }
  106. }
  107. // Handle Network rename form on POST
  108. exports.name = async function(req, res) {
  109. const page = 'networks';
  110. req.checkBody('name', 'Network name required').notEmpty();
  111. req.sanitize('name').escape();
  112. req.sanitize('name').trim();
  113. const errors = req.validationErrors();
  114. const name = { name: req.body.name };
  115. if (errors) {
  116. try {
  117. const network = await zt.network_detail(req.params.nwid);
  118. res.render('name', {title: 'Rename network', page: page, network: network, name: name, errors: errors});
  119. } catch (err) {
  120. res.render('name', {title: 'Rename network', page: page, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err});
  121. }
  122. } else {
  123. try {
  124. const network = await zt.network_object(req.params.nwid, name);
  125. res.redirect('/controller/networks');
  126. } catch ( err) {
  127. res.render('name', {title: 'Rename network', page: page, error: 'Error renaming network ' + req.params.nwid + ': ' + err});
  128. }
  129. }
  130. };
  131. // ipAssignmentPools POST
  132. exports.ipAssignmentPools = async function(req, res) {
  133. const page = 'networks';
  134. req.checkBody('ipRangeStart', 'IP range start required').notEmpty();
  135. req.checkBody('ipRangeStart', 'IP range start needs a valid IPv4 or IPv6 address').isIP();
  136. req.sanitize('ipRangeStart').escape();
  137. req.sanitize('ipRangeStart').trim();
  138. req.checkBody('ipRangeEnd', 'IP range end required').notEmpty();
  139. req.checkBody('ipRangeEnd', 'IP range end needs a valid IPv4 or IPv6 address').isIP();
  140. req.sanitize('ipRangEnd').escape();
  141. req.sanitize('ipRangEnd').trim();
  142. const errors = req.validationErrors();
  143. const ipAssignmentPool =
  144. {
  145. ipRangeStart: req.body.ipRangeStart,
  146. ipRangeEnd: req.body.ipRangeEnd
  147. };
  148. if (errors) {
  149. try {
  150. const network = await zt.network_detail(req.params.nwid);
  151. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network, errors: errors});
  152. } catch (err) {
  153. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err});
  154. }
  155. } else {
  156. try {
  157. const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'add');
  158. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network});
  159. } catch (err) {
  160. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error applying IP Assignment Pools for network ' + req.params.nwid + ': ' + err});
  161. }
  162. }
  163. }
  164. isValidPrefix = function(str, max) {
  165. const num = Math.floor(Number(str));
  166. return String(num) == str && num >= 0 && num <= max;
  167. }
  168. // routes POST
  169. exports.routes = async function (req, res) {
  170. const page = 'networks';
  171. req.checkBody('target', 'Target network is required').notEmpty();
  172. req.sanitize('target').trim();
  173. req.checkBody('target', 'Target network must be valid CIDR format')
  174. .custom(value => {
  175. const parts = value.split('/');
  176. const ipv4 = new ipaddr.Address4(parts[0]);
  177. const ipv6 = new ipaddr.Address6(parts[0]);
  178. let isValidIPv4orIPv6 = false;
  179. let prefixMax = 32;
  180. if (ipv4.isValid()) {
  181. isValidIPv4orIPv6 = true;
  182. } else {
  183. }
  184. if (ipv6.isValid()) {
  185. isValidIPv4orIPv6 = true;
  186. prefixMax = 128;
  187. } else {
  188. }
  189. return isValidIPv4orIPv6 && isValidPrefix(parts[1], prefixMax);
  190. });
  191. req.checkBody('via', 'Gateway must be a valid IPv4 or IPv6 address').optional({ checkFalsy: true }).isIP();
  192. req.sanitize('via').escape();
  193. req.sanitize('via').trim();
  194. if (! req.body.via) {
  195. req.body.via = null;
  196. }
  197. const errors = req.validationErrors();
  198. const route =
  199. {
  200. target: req.body.target,
  201. via: req.body.via
  202. };
  203. if (errors) {
  204. try {
  205. const network = await zt.network_detail(req.params.nwid);
  206. res.render('routes', {title: 'routes', page: page, route: route, network: network, errors: errors});
  207. } catch (err) {
  208. res.render('routes', {title: 'routes', page: page, error: 'Error resolving network detail'});
  209. }
  210. } else {
  211. try {
  212. const network = await zt.routes(req.params.nwid, route, 'add');
  213. res.render('routes', {title: 'routes', page: page, route: route, network: network});
  214. } catch (err) {
  215. res.render('routes', {title: 'routes', page: page, error: 'Error adding route for network ' + req.params.nwid + ': ' + err});
  216. }
  217. }
  218. }
  219. // route_delete GET
  220. exports.route_delete = async function (req, res) {
  221. const page = 'networks';
  222. const route =
  223. {
  224. target: req.params.target_ip + '/' + req.params.target_prefix,
  225. via: null
  226. };
  227. try {
  228. const network = await zt.routes(req.params.nwid, route, 'delete');
  229. res.render('routes', {title: 'routes', page: page, route: route, network: network});
  230. } catch (err) {
  231. res.render('routes', {title: 'routes', page: page, error: 'Error deleting route for network ' + req.params.nwid + ': ' + err});
  232. }
  233. }
  234. // ipAssignmentPool_delete GET
  235. exports.ipAssignmentPool_delete = async function (req, res) {
  236. const page = 'networks';
  237. const ipAssignmentPool =
  238. {
  239. ipRangeStart: req.params.ipRangeStart,
  240. ipRangeEnd: req.params.ipRangeEnd
  241. };
  242. try {
  243. const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'delete');
  244. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network});
  245. } catch (err) {
  246. res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error deleting IP Assignment Pool for network ' + req.params.nwid + ': ' + err});
  247. }
  248. }
  249. // v4AssignMode POST
  250. exports.v4AssignMode = async function (req, res) {
  251. const page = 'networks';
  252. const v4AssignMode =
  253. {
  254. v4AssignMode: { zt: req.body.zt }
  255. };
  256. try {
  257. const network = await zt.network_object(req.params.nwid, v4AssignMode);
  258. res.render('v4AssignMode', {title: 'v4AssignMode', page: page, network: network});
  259. } catch (err) {
  260. res.render('v4AssignMode', {title: 'v4AssignMode', page: page, error: 'Error applying v4AssignMode for network ' + req.params.nwid + ': ' + err});
  261. }
  262. }
  263. // v6AssignMode POST
  264. exports.v6AssignMode = async function (req, res) {
  265. const page = 'networks';
  266. const v6AssignMode =
  267. {
  268. v6AssignMode:
  269. {
  270. '6plane': req.body['6plane'],
  271. rfc4193: req.body.rfc4193,
  272. zt: req.body.zt
  273. }
  274. };
  275. try {
  276. const network = await zt.network_object(req.params.nwid, v6AssignMode);
  277. res.render('v6AssignMode', {title: 'v6AssignMode', page: page, network: network});
  278. } catch (err) {
  279. res.render('v6AssignMode', {title: 'v6AssignMode', page: page, error: 'Error applying v6AssignMode for network ' + req.params.nwid + ': ' + err});
  280. }
  281. }
  282. // Display detail page for specific member
  283. exports.member_detail = async function(req, res) {
  284. const page = 'networks';
  285. try {
  286. const network = await zt.network_detail(req.params.nwid);
  287. const member = await zt.member_detail(req.params.nwid, req.params.id);
  288. res.render('member_detail', {title: 'Network member detail', page: page, network: network, member: member});
  289. } catch (err) {
  290. res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err});
  291. }
  292. };
  293. // Member object GET
  294. exports.member_object = async function(req, res) {
  295. const page = 'networks';
  296. try {
  297. const network = await zt.network_detail(req.params.nwid);
  298. const member = await zt.member_detail(req.params.nwid, req.params.id);
  299. res.render(req.params.object, {title: req.params.object, page: page, network: network, member: member}, function(err, html) {
  300. if (err) {
  301. if (err.message.indexOf('Failed to lookup view') !== -1 ) {
  302. return res.render('not_implemented', {title: req.params.object, page: page, network: network, member: member});
  303. }
  304. throw err;
  305. }
  306. res.send(html);
  307. });
  308. } catch (err) {
  309. res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err});
  310. }
  311. }
  312. // Member authorized POST
  313. exports.member_authorized = async function(req, res) {
  314. const page = 'networks';
  315. const authorized = { authorized: req.body.authorized };
  316. try {
  317. const network = await zt.network_detail(req.params.nwid);
  318. const member = await zt.member_object(req.params.nwid, req.params.id, authorized);
  319. res.render('authorized', {title: 'authorized', page: page, network: network, member: member});
  320. } catch (err) {
  321. res.render('authorized', {title: 'authorized', page: page, error: 'Error authorizing member ' + req.params.id + ' on network ' + req.params.nwid + ': ' + err});
  322. }
  323. }
  324. // Easy network setup GET
  325. exports.easy_get = async function(req, res) {
  326. const page = 'networks';
  327. try {
  328. const network = await zt.network_detail(req.params.nwid);
  329. res.render('network_easy', {title: 'Easy setup of network', page: page, network: network});
  330. } catch (err) {
  331. res.render('network_easy', {title: 'Easy setup of network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err});
  332. }
  333. }
  334. // Easy network setup POST
  335. exports.easy_post = async function(req, res) {
  336. const page = 'networks';
  337. req.checkBody('networkCIDR', 'Network address is required').notEmpty();
  338. req.sanitize('networkCIDR').trim();
  339. req.checkBody('networkCIDR', 'Network address must be in CIDR notation')
  340. .custom(value => {
  341. const parts = value.split('/');
  342. const ipv4 = new ipaddr.Address4(parts[0]);
  343. return ipv4.isValid() && isValidPrefix(parts[1], 32);
  344. });
  345. req.checkBody('poolStart', 'Start of IP assignment pool is required')
  346. .notEmpty();
  347. req.checkBody('poolStart', 'Start of IP assignment pool must be valid IPv4 address')
  348. .isIP(4);
  349. req.sanitize('poolStart').escape();
  350. req.sanitize('poolStart').trim();
  351. req.checkBody('poolEnd', 'End of IP assignment pool is required')
  352. .notEmpty();
  353. req.checkBody('poolEnd', 'End of IP assignment pool must be valid IPv4 address')
  354. .isIP(4);
  355. req.sanitize('poolEnd').escape();
  356. req.sanitize('poolEnd').trim();
  357. const errors = req.validationErrors();
  358. const ipAssignmentPools =
  359. [{
  360. ipRangeStart: req.body.poolStart,
  361. ipRangeEnd: req.body.poolEnd
  362. }];
  363. const routes =
  364. [{
  365. target: req.body.networkCIDR,
  366. via: null
  367. }];
  368. const v4AssignMode =
  369. {
  370. zt: true
  371. };
  372. if (errors) {
  373. network =
  374. {
  375. ipAssignmentPools: ipAssignmentPools,
  376. routes: routes,
  377. v4AssignMode: v4AssignMode
  378. };
  379. res.render('network_easy', {title: 'Easy setup of network', page: page, network: network, errors: errors});
  380. } else {
  381. try {
  382. const network = await zt.network_easy_setup(req.params.nwid,
  383. routes,
  384. ipAssignmentPools,
  385. v4AssignMode);
  386. res.render('network_easy', {title: 'Easy setup of network', page: page, network: network, message: 'Network setup succeeded'});
  387. } catch (err) {
  388. res.render('network_easy', {title: 'Easy setup of network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err});
  389. }
  390. }
  391. }
  392. // Easy members auth GET or POST
  393. exports.members = async function(req, res) {
  394. const page = 'networks';
  395. let errors = null;
  396. if (req.method === 'POST') {
  397. req.checkBody('id', 'Member ID is required').notEmpty();
  398. req.sanitize('id').trim();
  399. req.sanitize('id').escape();
  400. if (req.body.auth) {
  401. req.checkBody('auth', 'Authorization state must be boolean').isBoolean();
  402. req.sanitize('auth').trim();
  403. req.sanitize('auth').escape();
  404. errors = req.validationErrors();
  405. if (!errors) {
  406. const auth =
  407. {
  408. authorized: req.body.auth
  409. };
  410. try {
  411. const mem = await zt.member_object(req.params.nwid, req.body.id, auth);
  412. } catch (err) {
  413. throw err;
  414. }
  415. }
  416. } else if (req.body.name) {
  417. req.sanitize('name').trim();
  418. req.sanitize('name').escape();
  419. errors = req.validationErrors();
  420. if (!errors) {
  421. try {
  422. const ret = await storage.setItem(req.body.id, req.body.name);
  423. } catch (err) {
  424. throw err;
  425. }
  426. }
  427. }
  428. }
  429. try {
  430. const network = await zt.network_detail(req.params.nwid);
  431. const member_ids = await zt.members(req.params.nwid);
  432. const members = [];
  433. for (id in member_ids) {
  434. let member = await zt.member_detail(req.params.nwid, id);
  435. let name = await storage.getItem(member.id);
  436. if (!name) name = '';
  437. member.name = name;
  438. members.push(member);
  439. }
  440. res.render('members', {title: 'Members of this network', page: page,
  441. network: network, members: members, errors: errors});
  442. } catch (err) {
  443. res.render('members', {title: 'Members of this network', page: page,
  444. error: 'Error resolving detail for network ' + req.params.nwid
  445. + ': ' + err});
  446. }
  447. }
  448. // Member delete GET and POST
  449. exports.member_delete = async function(req, res) {
  450. const page = 'networks';
  451. try {
  452. const network = await zt.network_detail(req.params.nwid);
  453. let member = null;
  454. let name = null;
  455. if (req.method === 'POST') {
  456. member = await zt.member_delete(req.params.nwid, req.params.id);
  457. if (member.deleted) {
  458. name = await storage.removeItem(member.id);
  459. }
  460. } else {
  461. member = await zt.member_detail(req.params.nwid, req.params.id);
  462. name = await storage.getItem(member.id);
  463. }
  464. if (!name) name = '';
  465. member.name = name;
  466. res.render('member_delete', {title: 'Delete member from ' + network.name,
  467. network: network, member: member});
  468. } catch (err) {
  469. res.render('member_delete', {title: 'Delete member from network', page: page,
  470. error: 'Error resolving detail for member ' + req.params.id
  471. + ' of network ' + req.params.nwid + ': ' + err});
  472. }
  473. }