Skip to content

Commit 3d9c5eb

Browse files
committed
Modernise and update the migrator to work with new storage backend
1 parent c0e18f3 commit 3d9c5eb

12 files changed

Lines changed: 302 additions & 332 deletions

File tree

bin/vault

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ const key = process.env.VAULT_KEY ||
1818
const vaultPath = process.env.VAULT_PATH || '.vault'
1919
const pathname = path.resolve(home, vaultPath)
2020

21+
let logger = {
22+
info (...args) {
23+
console.error('[info]', ...args)
24+
},
25+
error (error) {
26+
console.error('[error]', process.env.DEBUG ? error.stack : error.message)
27+
}
28+
}
29+
2130
let cli = new CLI({
2231
config: { path: pathname, key },
2332
stdout: process.stdout,
@@ -32,11 +41,11 @@ let cli = new CLI({
3241

3342
async function main () {
3443
try {
35-
await legacy.migrateConfig(pathname, key)
44+
await legacy.migrate(logger, pathname, key)
3645
await cli.run(process.argv)
3746
process.exit(0)
3847
} catch (error) {
39-
console.error('[ERROR] ' + (process.env.DEBUG ? error.stack : error.message))
48+
logger.error(error)
4049
process.exit(1)
4150
}
4251
}

lib/crypto/node_crypto.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,33 @@ module.exports = {
1313
let bytes = Math.ceil(size / 8)
1414
return fn(pw, salt, iterations, bytes, 'sha1')
1515
}
16+
},
17+
18+
legacy: {
19+
hmacSha256: {
20+
async sign (key, data) {
21+
let hmac = crypto.createHmac('sha256', key)
22+
hmac.update(data)
23+
return hmac.digest()
24+
},
25+
26+
async verify (key, data, signature) {
27+
let expected = await this.sign(key, data)
28+
return crypto.timingSafeEqual(expected, signature)
29+
}
30+
},
31+
32+
aes256cbc: {
33+
async decrypt (key, iv, data) {
34+
let cipher = (iv === null)
35+
? crypto.createDecipher('aes256', key)
36+
: crypto.createDecipheriv('aes-256-cbc', key, iv)
37+
38+
return Buffer.concat([
39+
cipher.update(data),
40+
cipher.final()
41+
])
42+
}
43+
}
1644
}
1745
}

lib/legacy/index.js

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,66 @@
1-
'use strict';
1+
'use strict'
22

3-
var fs = require('fs'),
4-
Migrator = require('./migrator'),
5-
confirm = require('../cli/confirm');
3+
const fs = require('fs')
4+
const Migrator = require('./migrator')
5+
const confirm = require('../cli/confirm')
66

7-
var SEP = '========================================================================';
7+
const SEP = '========================================================================'
88

9-
var formatMessage = function(text) {
10-
if (!process.stderr.getWindowSize) return message;
9+
async function migrate (logger, pathname, password) {
10+
let stat
1111

12-
var width = process.stderr.getWindowSize()[0] - 4,
13-
words = text.split(/\s+/),
14-
lines = [''],
15-
last, word;
12+
try {
13+
stat = fs.statSync(pathname)
14+
} catch (error) {
15+
return false
16+
}
1617

17-
while (words.length > 0) {
18-
last = lines[lines.length - 1];
19-
word = words.shift();
18+
if (!stat.isFile()) return false
2019

21-
if (last.length + word.length + 1 > width)
22-
lines.push('');
20+
message(
21+
'It looks as though your config file (' + pathname + ') was created ' +
22+
'with an old version of Vault. In order to continue, it needs to be ' +
23+
'converted to a new format.')
2324

24-
last = lines[lines.length - 1];
25-
lines[lines.length - 1] = (last === '') ? word : last + ' ' + word;
26-
}
27-
return lines.join('\n');
28-
};
29-
30-
var message = function(text) {
31-
console.error('\n' + formatMessage(text) + '\n');
32-
};
33-
34-
var migrate = function(pathname, password) {
35-
var migrator = new Migrator(pathname, password);
36-
37-
migrator.on('message', function(message) {
38-
console.error('[INFO] ' + message);
39-
});
40-
41-
return migrator.run();
42-
};
43-
44-
module.exports = {
45-
migrateConfig: function(pathname, password) {
46-
var stat;
47-
try {
48-
stat = fs.statSync(pathname);
49-
} catch (error) {
50-
return Promise.resolve();
51-
}
25+
await confirm('Would you like Vault to perform this conversion now?')
26+
27+
console.error('\n' + SEP)
28+
29+
let migrator = new Migrator({ logger, pathname, password })
30+
let backupPath = await migrator.run()
31+
32+
console.error(SEP)
5233

53-
if (!stat.isFile()) return Promise.resolve();
34+
message(
35+
'Your original config file has been backed up at ' + backupPath + '. ' +
36+
'If you are happy with how Vault is working, you should delete that file ' +
37+
'as soon as possible.')
38+
}
5439

55-
message(
56-
'It looks as though your config file (' + pathname + ') was created' +
57-
' with an old version of Vault. In order to continue, it needs to be' +
58-
' converted to a new format.');
40+
function message (text) {
41+
console.error('\n' + formatMessage(text) + '\n')
42+
}
5943

60-
return confirm('Would you like Vault to perform this conversion now?').then(function() {
61-
console.error('\n' + SEP);
62-
return migrate(pathname, password);
44+
function formatMessage (text) {
45+
if (!process.stderr.getWindowSize) return message
6346

64-
}).then(function(backupPath) {
65-
console.error(SEP);
47+
let width = process.stderr.getWindowSize()[0] - 4
48+
let words = text.split(/\s+/)
49+
let lines = ['']
6650

67-
message(
68-
'Your original config file has been backed up at ' + backupPath +
69-
'. If you are happy with how Vault is working, you should delete' +
70-
' that file as soon as possible.');
71-
});
51+
while (words.length > 0) {
52+
let last = lines[lines.length - 1]
53+
let word = words.shift()
54+
55+
if (last.length + word.length + 1 > width) {
56+
lines.push('')
57+
}
58+
59+
last = lines[lines.length - 1]
60+
lines[lines.length - 1] = (last === '') ? word : last + ' ' + word
7261
}
73-
};
62+
63+
return lines.join('\n')
64+
}
65+
66+
module.exports = { migrate }

lib/legacy/migrator.js

Lines changed: 71 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,94 @@
11
'use strict';
22

3-
var assert = require('assert'),
4-
crypto = require('crypto'),
5-
EventEmitter = require('events').EventEmitter,
6-
fs = require('fs'),
7-
util = require('util'),
8-
escodb = require('../escodb');
9-
10-
var READERS = [
11-
require('./v03_reader'),
12-
require('./v02_reader')
13-
];
14-
15-
var random = function() {
16-
return crypto.randomBytes(6).toString('hex');
17-
};
18-
19-
var Migrator = function(filename, password) {
20-
this._filename = filename;
21-
this._password = password;
22-
};
23-
util.inherits(Migrator, EventEmitter);
24-
25-
Migrator.prototype.run = function() {
26-
var self = this;
27-
28-
return new Promise(function(resolve, reject) {
29-
try {
30-
resolve(self._migrate());
31-
} catch (error) {
32-
reject(error);
33-
}
34-
});
35-
};
36-
37-
Migrator.prototype.log = function(message) {
38-
this.emit('message', message);
39-
};
40-
41-
Migrator.prototype._migrate = function() {
42-
var self = this;
43-
44-
this._readInputFile();
45-
this._decryptFile();
46-
47-
this._createStore().then(function() {
48-
return this._copySettings();
49-
}).then(function() {
50-
return self._swapFiles();
51-
}).then(function() {
52-
self.log('done');
53-
return self._backuppath;
54-
});
55-
};
56-
57-
Migrator.prototype._readInputFile = function() {
58-
this.log('reading input file: ' + this._filename);
59-
60-
try {
61-
this._content = fs.readFileSync(this._filename, 'utf8');
62-
} catch (error) {
63-
throw new Error('File is unreadable: ' + this._filename);
3+
const assert = require('assert')
4+
const fs = require('fs')
5+
6+
const crypto = require('../crypto')
7+
const escodb = require('../escodb')
8+
const Reader = require('./reader')
9+
10+
class Migrator {
11+
constructor ({ logger, pathname, password }) {
12+
this._logger = logger
13+
this._filename = pathname
14+
this._password = password
15+
};
16+
17+
log (message) {
18+
this._logger.info(message)
6419
}
6520

66-
this._buffer = Buffer.from(this._content, 'base64');
67-
};
21+
async run () {
22+
await this._readInputFile()
23+
await this._decryptFile()
24+
await this._createStore()
25+
await this._copySettings()
26+
await this._swapFiles()
27+
28+
this.log('done')
29+
return this._backuppath
30+
}
6831

69-
Migrator.prototype._decryptFile = function() {
70-
READERS.forEach(function(Reader) {
71-
if (this._data) return;
32+
_readInputFile () {
33+
this.log('reading input file: ' + this._filename)
7234

73-
var reader = new Reader(this, this._buffer, this._password);
7435
try {
75-
this._data = reader.run();
36+
this._content = fs.readFileSync(this._filename, 'utf8')
7637
} catch (error) {
77-
this.log(error.message);
38+
throw new Error('file is unreadable: ' + this._filename)
7839
}
79-
}, this);
80-
81-
if (!this._data)
82-
throw new Error('Failed to decrypt the file');
83-
};
84-
85-
Migrator.prototype._createStore = function() {
86-
this._storepath = '/tmp/vault-convert-' + random();
87-
this.log('creating new storage target: ' + this._storepath);
88-
89-
var adapter = escodb.createFileAdapter(this._storepath);
40+
}
9041

91-
return escodb.createStore({adapter: adapter, password: this._password}).then(function(store) {
92-
this._store = store;
93-
});
94-
};
42+
async _decryptFile () {
43+
let config = { logger: this._logger, password: this._password }
44+
this._data = await Reader.read(config, this._content)
9545

96-
Migrator.prototype._copySettings = function() {
97-
var copies = [['/global', this._data.global]];
46+
if (!this._data) {
47+
throw new Error('failed to decrypt the file')
48+
}
49+
}
9850

99-
for (var service in this._data.services)
100-
copies.push(['/services/' + service, this._data.services[service]]);
51+
async _createStore () {
52+
this._storepath = '/tmp/vault-convert-' + random()
53+
this.log('creating new storage target: ' + this._storepath)
10154

102-
var self = this;
55+
let adapter = escodb.createFileAdapter(this._storepath)
56+
this._store = await escodb.createStore({ adapter, password: this._password })
57+
}
10358

104-
return copies.reduce(function(state, copy) {
105-
var key = copy[0],
106-
value = copy[1];
59+
async _copySettings () {
60+
let copies = [['/global', this._data.global]];
10761

108-
if (value === undefined) return state;
62+
for (let service in this._data.services) {
63+
copies.push(['/services/' + service, this._data.services[service]])
64+
}
10965

110-
return state.then(function() {
111-
return self._store.update(key, function() { return value });
66+
for (let [key, value] of copies) {
67+
await this._store.update(key, () => value)
68+
let stored = await this._store.get(key)
11269

113-
}).then(function() {
114-
return self._store.get(key);
70+
assert.deepEqual(value, stored,
71+
'failed to write: [' + key + '] ' + JSON.stringify(value))
11572

116-
}).then(function(result) {
117-
var message = 'Failed to write: [' + key + '] ' + JSON.stringify(value);
118-
assert.deepEqual(result, value, message);
73+
this.log('wrote setting: ' + key)
74+
}
75+
}
11976

120-
self.log('wrote setting: ' + key);
121-
});
122-
}, Promise.resolve());
123-
};
77+
_swapFiles () {
78+
this._backuppath = '/tmp/vault-backup-' + random()
12479

125-
Migrator.prototype._swapFiles = function() {
126-
this._backuppath = '/tmp/vault-backup-' + random();
80+
this._rename(this._filename, this._backuppath)
81+
this._rename(this._storepath, this._filename)
82+
}
12783

128-
this.log('moving old file: ' + this._filename + ' -> ' + this._backuppath);
129-
fs.renameSync(this._filename, this._backuppath);
84+
_rename (a, b) {
85+
this.log('moving file: ' + a + ' -> ' + b)
86+
fs.renameSync(a, b)
87+
}
88+
}
13089

131-
this.log('moving new file: ' + this._storepath + ' -> ' + this._filename);
132-
fs.renameSync(this._storepath, this._filename);
133-
};
90+
function random () {
91+
return crypto.randomBytes(6).toString('hex')
92+
}
13493

135-
module.exports = Migrator;
94+
module.exports = Migrator

0 commit comments

Comments
 (0)