summaryrefslogtreecommitdiff
path: root/controller/migrate-sqlite/migrate.js
diff options
context:
space:
mode:
Diffstat (limited to 'controller/migrate-sqlite/migrate.js')
-rw-r--r--controller/migrate-sqlite/migrate.js320
1 files changed, 320 insertions, 0 deletions
diff --git a/controller/migrate-sqlite/migrate.js b/controller/migrate-sqlite/migrate.js
new file mode 100644
index 00000000..ac9678a7
--- /dev/null
+++ b/controller/migrate-sqlite/migrate.js
@@ -0,0 +1,320 @@
+'use strict';
+
+var sqlite3 = require('sqlite3').verbose();
+var fs = require('fs');
+var async = require('async');
+
+function blobToIPv4(b)
+{
+ if (!b)
+ return null;
+ if (b.length !== 16)
+ return null;
+ return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString();
+}
+function blobToIPv6(b)
+{
+ if (!b)
+ return null;
+ if (b.length !== 16)
+ return null;
+ var s = '';
+ for(var i=0;i<16;++i) {
+ var x = b.readUInt8(i).toString(16);
+ if (x.length === 1)
+ s += '0';
+ s += x;
+ if ((((i+1) & 1) === 0)&&(i !== 15))
+ s += ':';
+ }
+ return s;
+}
+
+if (process.argv.length !== 4) {
+ console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility');
+ console.log('(c)2017 ZeroTier, Inc. [GPL3]');
+ console.log('');
+ console.log('Usage: node migrate.js </path/to/controller.db> </path/to/controller.d>');
+ console.log('');
+ console.log('The first argument must be the path to the old Sqlite3 controller.db');
+ console.log('file. The second must be the path to the EMPTY controller.d database');
+ console.log('directory for a new (1.1.17 or newer) controller. If this path does');
+ console.log('not exist it will be created.');
+ console.log('');
+ console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.');
+ console.log('If your controller is old you should first upgrade to 1.1.14 and run the');
+ console.log('controller so that it will brings its Sqlite3 database up to the latest');
+ console.log('version before running this migration.');
+ console.log('');
+ process.exit(1);
+}
+
+var oldDbPath = process.argv[2];
+var newDbPath = process.argv[3];
+
+console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...');
+console.log('');
+
+var old = new sqlite3.Database(oldDbPath);
+
+var networks = {};
+
+var nodeIdentities = {};
+var networkCount = 0;
+var memberCount = 0;
+var routeCount = 0;
+var ipAssignmentPoolCount = 0;
+var ipAssignmentCount = 0;
+var ruleCount = 0;
+var oldSchemaVersion = -1;
+
+async.series([function(nextStep) {
+
+ old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) {
+ oldSchemaVersion = parseInt(row.v)||-1;
+ },nextStep);
+
+},function(nextStep) {
+
+ if (oldSchemaVersion !== 4) {
+ console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old');
+ console.log('controller to 1.1.14 first and run it once to bring its DB up to date.');
+ return process.exit(1);
+ }
+
+ console.log('Reading networks...');
+ old.each('SELECT * FROM Network',function(err,row) {
+ if ((typeof row.id === 'string')&&(row.id.length === 16)) {
+ var flags = parseInt(row.flags)||0;
+ networks[row.id] = {
+ id: row.id,
+ nwid: row.id,
+ objtype: 'network',
+ authTokens: [],
+ capabilities: [],
+ creationTime: parseInt(row.creationTime)||0,
+ enableBroadcast: !!row.enableBroadcast,
+ ipAssignmentPools: [],
+ lastModified: Date.now(),
+ multicastLimit: row.multicastLimit||32,
+ name: row.name||'',
+ private: !!row.private,
+ revision: parseInt(row.revision)||1,
+ rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all
+ routes: [],
+ v4AssignMode: {
+ 'zt': ((flags & 1) !== 0)
+ },
+ v6AssignMode: {
+ '6plane': ((flags & 4) !== 0),
+ 'rfc4193': ((flags & 2) !== 0),
+ 'zt': ((flags & 8) !== 0)
+ },
+ _members: {} // temporary
+ };
+ ++networkCount;
+ //console.log(networks[row.id]);
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+networkCount+' networks.');
+ console.log('Reading network route definitions...');
+ old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var rt = {
+ target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits),
+ via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null)
+ };
+ network.routes.push(rt);
+ ++routeCount;
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+routeCount+' routes in '+networkCount+' networks.');
+ console.log('Reading IP assignment pools...');
+ old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var p = {
+ ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)),
+ ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd))
+ };
+ network.ipAssignmentPools.push(p);
+ ++ipAssignmentPoolCount;
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.');
+ console.log('Reading known node identities...');
+ old.each('SELECT * FROM Node',function(err,row) {
+ nodeIdentities[row.id] = row.identity;
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+Object.keys(nodeIdentities).length+' known identities.');
+ console.log('Reading network members...');
+ old.each('SELECT * FROM Member',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ network._members[row.nodeId] = {
+ id: row.nodeId,
+ address: row.nodeId,
+ objtype: 'member',
+ authorized: !!row.authorized,
+ activeBridge: !!row.activeBridge,
+ authHistory: [],
+ capabilities: [],
+ creationTime: 0,
+ identity: nodeIdentities[row.nodeId]||null,
+ ipAssignments: [],
+ lastAuthorizedTime: (row.authorized) ? Date.now() : 0,
+ lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(),
+ lastModified: Date.now(),
+ lastRequestMetaData: '',
+ noAutoAssignIps: false,
+ nwid: row.networkId,
+ revision: parseInt(row.memberRevision)||1,
+ tags: [],
+ recentLog: []
+ };
+ ++memberCount;
+ //console.log(network._members[row.nodeId]);
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+memberCount+' members of '+networkCount+' networks.');
+ console.log('Reading static IP assignments...');
+ old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var member = network._members[row.nodeId];
+ if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts
+ if (row.ipVersion == 4) {
+ member.ipAssignments.push(blobToIPv4(row.ip));
+ ++ipAssignmentCount;
+ } else if (row.ipVersion == 6) {
+ member.ipAssignments.push(blobToIPv6(row.ip));
+ ++ipAssignmentCount;
+ }
+ }
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ // Old versions only supported Ethertype whitelisting, so that's
+ // all we mirror forward. The other fields were always unused.
+
+ console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.');
+ console.log('Reading allowed Ethernet types (old basic rules)...');
+ var etherTypesByNetwork = {};
+ old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) {
+ if (row.networkId in networks) {
+ var et = parseInt(row.etherType)||0;
+ var ets = etherTypesByNetwork[row.networkId];
+ if (!ets)
+ etherTypesByNetwork[row.networkId] = [ et ];
+ else ets.push(et);
+ }
+ },function(err) {
+ if (err) return nextStep(err);
+ for(var nwid in etherTypesByNetwork) {
+ var ets = etherTypesByNetwork[nwid].sort();
+ var network = networks[nwid];
+ if (network) {
+ var rules = [];
+ if (ets.indexOf(0) >= 0) {
+ // If 0 is in the list, all Ethernet types are allowed so we accept all.
+ rules.push({ 'type': 'ACTION_ACCEPT' });
+ } else {
+ // Otherwise we whitelist.
+ for(var i=0;i<ets.length;++i) {
+ rules.push({
+ 'etherType': ets[i],
+ 'not': true,
+ 'or': false,
+ 'type': 'MATCH_ETHERTYPE'
+ });
+ }
+ rules.push({ 'type': 'ACTION_DROP' });
+ rules.push({ 'type': 'ACTION_ACCEPT' });
+ }
+ network.rules = rules;
+ ++ruleCount;
+ }
+ }
+ return nextStep(null);
+ });
+
+}],function(err) {
+
+ if (err) {
+ console.log('FATAL: '+err.toString());
+ return process.exit(1);
+ }
+
+ console.log(' '+ruleCount+' ethernet type whitelists converted to new format rules.');
+ old.close();
+ console.log('Done reading and converting Sqlite3 database! Writing JSONDB files...');
+
+ try {
+ fs.mkdirSync(newDbPath,0o700);
+ } catch (e) {}
+ var nwBase = newDbPath+'/network';
+ try {
+ fs.mkdirSync(nwBase,0o700);
+ } catch (e) {}
+ nwBase = nwBase + '/';
+ var nwids = Object.keys(networks).sort();
+ var fileCount = 0;
+ for(var ni=0;ni<nwids.length;++ni) {
+ var network = networks[nwids[ni]];
+
+ var mids = Object.keys(network._members).sort();
+ if (mids.length > 0) {
+ try {
+ fs.mkdirSync(nwBase+network.id);
+ } catch (e) {}
+ var mbase = nwBase+network.id+'/member';
+ try {
+ fs.mkdirSync(mbase,0o700);
+ } catch (e) {}
+ mbase = mbase + '/';
+
+ for(var mi=0;mi<mids.length;++mi) {
+ var member = network._members[mids[mi]];
+ fs.writeFileSync(mbase+member.id+'.json',JSON.stringify(member,null,1),{ mode: 0o600 });
+ ++fileCount;
+ //console.log(mbase+member.id+'.json');
+ }
+ }
+
+ delete network._members; // temporary field, not part of actual JSONDB, so don't write
+ fs.writeFileSync(nwBase+network.id+'.json',JSON.stringify(network,null,1),{ mode: 0o600 });
+ ++fileCount;
+ //console.log(nwBase+network.id+'.json');
+ }
+
+ console.log('');
+ console.log('SUCCESS! Wrote '+fileCount+' JSONDB files.');
+
+ console.log('');
+ console.log('You should still inspect the new DB before going live. Also be sure');
+ console.log('to "chown -R" and "chgrp -R" the new DB to the user and group under');
+ console.log('which the ZeroTier One instance acting as controller will be running.');
+ console.log('The controller must be able to read and write the DB, of course.');
+ console.log('');
+ console.log('Have fun!');
+
+ return process.exit(0);
+});