migrate.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. 'use strict';
  2. var sqlite3 = require('sqlite3').verbose();
  3. var fs = require('fs');
  4. var async = require('async');
  5. function blobToIPv4(b)
  6. {
  7. if (!b)
  8. return null;
  9. if (b.length !== 16)
  10. return null;
  11. return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString();
  12. }
  13. function blobToIPv6(b)
  14. {
  15. if (!b)
  16. return null;
  17. if (b.length !== 16)
  18. return null;
  19. var s = '';
  20. for(var i=0;i<16;++i) {
  21. var x = b.readUInt8(i).toString(16);
  22. if (x.length === 1)
  23. s += '0';
  24. s += x;
  25. if ((((i+1) & 1) === 0)&&(i !== 15))
  26. s += ':';
  27. }
  28. return s;
  29. }
  30. if (process.argv.length !== 4) {
  31. console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility');
  32. console.log('(c)2017 ZeroTier, Inc. [GPL3]');
  33. console.log('');
  34. console.log('Usage: node migrate.js </path/to/controller.db> </path/to/controller.d>');
  35. console.log('');
  36. console.log('The first argument must be the path to the old Sqlite3 controller.db');
  37. console.log('file. The second must be the path to the EMPTY controller.d database');
  38. console.log('directory for a new (1.1.17 or newer) controller. If this path does');
  39. console.log('not exist it will be created.');
  40. console.log('');
  41. console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.');
  42. console.log('If your controller is old you should first upgrade to 1.1.14 and run the');
  43. console.log('controller so that it will brings its Sqlite3 database up to the latest');
  44. console.log('version before running this migration.');
  45. console.log('');
  46. process.exit(1);
  47. }
  48. var oldDbPath = process.argv[2];
  49. var newDbPath = process.argv[3];
  50. console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...');
  51. console.log('');
  52. var old = new sqlite3.Database(oldDbPath);
  53. var networks = {};
  54. var nodeIdentities = {};
  55. var networkCount = 0;
  56. var memberCount = 0;
  57. var routeCount = 0;
  58. var ipAssignmentPoolCount = 0;
  59. var ipAssignmentCount = 0;
  60. var ruleCount = 0;
  61. var oldSchemaVersion = -1;
  62. async.series([function(nextStep) {
  63. old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) {
  64. oldSchemaVersion = parseInt(row.v)||-1;
  65. },nextStep);
  66. },function(nextStep) {
  67. if (oldSchemaVersion !== 4) {
  68. console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old');
  69. console.log('controller to 1.1.14 first and run it once to bring its DB up to date.');
  70. return process.exit(1);
  71. }
  72. console.log('Reading networks...');
  73. old.each('SELECT * FROM Network',function(err,row) {
  74. if ((typeof row.id === 'string')&&(row.id.length === 16)) {
  75. var flags = parseInt(row.flags)||0;
  76. networks[row.id] = {
  77. id: row.id,
  78. nwid: row.id,
  79. objtype: 'network',
  80. authTokens: [],
  81. capabilities: [],
  82. creationTime: parseInt(row.creationTime)||0,
  83. enableBroadcast: !!row.enableBroadcast,
  84. ipAssignmentPools: [],
  85. lastModified: Date.now(),
  86. multicastLimit: row.multicastLimit||32,
  87. name: row.name||'',
  88. private: !!row.private,
  89. revision: parseInt(row.revision)||1,
  90. rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all
  91. routes: [],
  92. v4AssignMode: {
  93. 'zt': ((flags & 1) !== 0)
  94. },
  95. v6AssignMode: {
  96. '6plane': ((flags & 4) !== 0),
  97. 'rfc4193': ((flags & 2) !== 0),
  98. 'zt': ((flags & 8) !== 0)
  99. },
  100. _members: {} // temporary
  101. };
  102. ++networkCount;
  103. //console.log(networks[row.id]);
  104. }
  105. },nextStep);
  106. },function(nextStep) {
  107. console.log(' '+networkCount+' networks.');
  108. console.log('Reading network route definitions...');
  109. old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
  110. var network = networks[row.networkId];
  111. if (network) {
  112. var rt = {
  113. target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits),
  114. via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null)
  115. };
  116. network.routes.push(rt);
  117. ++routeCount;
  118. }
  119. },nextStep);
  120. },function(nextStep) {
  121. console.log(' '+routeCount+' routes in '+networkCount+' networks.');
  122. console.log('Reading IP assignment pools...');
  123. old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
  124. var network = networks[row.networkId];
  125. if (network) {
  126. var p = {
  127. ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)),
  128. ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd))
  129. };
  130. network.ipAssignmentPools.push(p);
  131. ++ipAssignmentPoolCount;
  132. }
  133. },nextStep);
  134. },function(nextStep) {
  135. console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.');
  136. console.log('Reading known node identities...');
  137. old.each('SELECT * FROM Node',function(err,row) {
  138. nodeIdentities[row.id] = row.identity;
  139. },nextStep);
  140. },function(nextStep) {
  141. console.log(' '+Object.keys(nodeIdentities).length+' known identities.');
  142. console.log('Reading network members...');
  143. old.each('SELECT * FROM Member',function(err,row) {
  144. var network = networks[row.networkId];
  145. if (network) {
  146. network._members[row.nodeId] = {
  147. id: row.nodeId,
  148. address: row.nodeId,
  149. objtype: 'member',
  150. authorized: !!row.authorized,
  151. activeBridge: !!row.activeBridge,
  152. authHistory: [],
  153. capabilities: [],
  154. creationTime: 0,
  155. identity: nodeIdentities[row.nodeId]||null,
  156. ipAssignments: [],
  157. lastAuthorizedTime: (row.authorized) ? Date.now() : 0,
  158. lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(),
  159. lastModified: Date.now(),
  160. lastRequestMetaData: '',
  161. noAutoAssignIps: false,
  162. nwid: row.networkId,
  163. revision: parseInt(row.memberRevision)||1,
  164. tags: [],
  165. recentLog: []
  166. };
  167. ++memberCount;
  168. //console.log(network._members[row.nodeId]);
  169. }
  170. },nextStep);
  171. },function(nextStep) {
  172. console.log(' '+memberCount+' members of '+networkCount+' networks.');
  173. console.log('Reading static IP assignments...');
  174. old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
  175. var network = networks[row.networkId];
  176. if (network) {
  177. var member = network._members[row.nodeId];
  178. if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts
  179. if (row.ipVersion == 4) {
  180. member.ipAssignments.push(blobToIPv4(row.ip));
  181. ++ipAssignmentCount;
  182. } else if (row.ipVersion == 6) {
  183. member.ipAssignments.push(blobToIPv6(row.ip));
  184. ++ipAssignmentCount;
  185. }
  186. }
  187. }
  188. },nextStep);
  189. },function(nextStep) {
  190. // Old versions only supported Ethertype whitelisting, so that's
  191. // all we mirror forward. The other fields were always unused.
  192. console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.');
  193. console.log('Reading allowed Ethernet types (old basic rules)...');
  194. var etherTypesByNetwork = {};
  195. old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) {
  196. if (row.networkId in networks) {
  197. var et = parseInt(row.etherType)||0;
  198. var ets = etherTypesByNetwork[row.networkId];
  199. if (!ets)
  200. etherTypesByNetwork[row.networkId] = [ et ];
  201. else ets.push(et);
  202. }
  203. },function(err) {
  204. if (err) return nextStep(err);
  205. for(var nwid in etherTypesByNetwork) {
  206. var ets = etherTypesByNetwork[nwid].sort();
  207. var network = networks[nwid];
  208. if (network) {
  209. var rules = [];
  210. if (ets.indexOf(0) >= 0) {
  211. // If 0 is in the list, all Ethernet types are allowed so we accept all.
  212. rules.push({ 'type': 'ACTION_ACCEPT' });
  213. } else {
  214. // Otherwise we whitelist.
  215. for(var i=0;i<ets.length;++i) {
  216. rules.push({
  217. 'etherType': ets[i],
  218. 'not': true,
  219. 'or': false,
  220. 'type': 'MATCH_ETHERTYPE'
  221. });
  222. }
  223. rules.push({ 'type': 'ACTION_DROP' });
  224. rules.push({ 'type': 'ACTION_ACCEPT' });
  225. }
  226. network.rules = rules;
  227. ++ruleCount;
  228. }
  229. }
  230. return nextStep(null);
  231. });
  232. }],function(err) {
  233. if (err) {
  234. console.log('FATAL: '+err.toString());
  235. return process.exit(1);
  236. }
  237. console.log(' '+ruleCount+' ethernet type whitelists converted to new format rules.');
  238. old.close();
  239. console.log('Done reading and converting Sqlite3 database! Writing JSONDB files...');
  240. try {
  241. fs.mkdirSync(newDbPath,0o700);
  242. } catch (e) {}
  243. var nwBase = newDbPath+'/network';
  244. try {
  245. fs.mkdirSync(nwBase,0o700);
  246. } catch (e) {}
  247. nwBase = nwBase + '/';
  248. var nwids = Object.keys(networks).sort();
  249. var fileCount = 0;
  250. for(var ni=0;ni<nwids.length;++ni) {
  251. var network = networks[nwids[ni]];
  252. var mids = Object.keys(network._members).sort();
  253. if (mids.length > 0) {
  254. try {
  255. fs.mkdirSync(nwBase+network.id);
  256. } catch (e) {}
  257. var mbase = nwBase+network.id+'/member';
  258. try {
  259. fs.mkdirSync(mbase,0o700);
  260. } catch (e) {}
  261. mbase = mbase + '/';
  262. for(var mi=0;mi<mids.length;++mi) {
  263. var member = network._members[mids[mi]];
  264. fs.writeFileSync(mbase+member.id+'.json',JSON.stringify(member,null,1),{ mode: 0o600 });
  265. ++fileCount;
  266. //console.log(mbase+member.id+'.json');
  267. }
  268. }
  269. delete network._members; // temporary field, not part of actual JSONDB, so don't write
  270. fs.writeFileSync(nwBase+network.id+'.json',JSON.stringify(network,null,1),{ mode: 0o600 });
  271. ++fileCount;
  272. //console.log(nwBase+network.id+'.json');
  273. }
  274. console.log('');
  275. console.log('SUCCESS! Wrote '+fileCount+' JSONDB files.');
  276. console.log('');
  277. console.log('You should still inspect the new DB before going live. Also be sure');
  278. console.log('to "chown -R" and "chgrp -R" the new DB to the user and group under');
  279. console.log('which the ZeroTier One instance acting as controller will be running.');
  280. console.log('The controller must be able to read and write the DB, of course.');
  281. console.log('');
  282. console.log('Have fun!');
  283. return process.exit(0);
  284. });