cloudron 5.4.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/actions.js +5 -4
- package/src/backup-tools.js +52 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"author": "Cloudron Developers <support@cloudron.io>",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"async": "^3.2.4",
|
|
21
|
-
"cloudron-manifestformat": "^5.
|
|
21
|
+
"cloudron-manifestformat": "^5.22.1",
|
|
22
22
|
"commander": "^9.5.0",
|
|
23
23
|
"debug": "^4.3.4",
|
|
24
24
|
"delay": "^5.0.0",
|
package/src/actions.js
CHANGED
|
@@ -109,9 +109,10 @@ async function selectDomain(location, options) {
|
|
|
109
109
|
|
|
110
110
|
let domain;
|
|
111
111
|
let subdomain = location;
|
|
112
|
-
|
|
112
|
+
// find longest matching domain name. this maps to DNS where subdomain is a zone of it's own
|
|
113
|
+
const matchingDomain = domains
|
|
113
114
|
.map(function (d) { return d.domain; } )
|
|
114
|
-
.sort(function(a, b) { return
|
|
115
|
+
.sort(function(a, b) { return b.length - a.length; })
|
|
115
116
|
.find(function (d) { return location.endsWith(d); });
|
|
116
117
|
|
|
117
118
|
if (matchingDomain) {
|
|
@@ -120,7 +121,7 @@ async function selectDomain(location, options) {
|
|
|
120
121
|
} else { // use the admin domain
|
|
121
122
|
domain = domains
|
|
122
123
|
.map(function (d) { return d.domain; } )
|
|
123
|
-
.sort(function(a, b) { return
|
|
124
|
+
.sort(function(a, b) { return b.length - a.length; })
|
|
124
125
|
.find(function (d) { return adminFqdn.endsWith(d); });
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -346,7 +347,7 @@ async function authenticate(adminFqdn, username, password, options) {
|
|
|
346
347
|
|
|
347
348
|
const request = superagent.post(`https://${adminFqdn}/api/v1/auth/login`)
|
|
348
349
|
.timeout(60000)
|
|
349
|
-
.send({ username, password, totpToken })
|
|
350
|
+
.send({ username, password, totpToken, type: 'cid-cli' })
|
|
350
351
|
.ok(() => true)
|
|
351
352
|
.set('User-Agent', 'cloudron-cli');
|
|
352
353
|
if (!rejectUnauthorized) request.disableTLSCerts();
|
package/src/backup-tools.js
CHANGED
|
@@ -16,6 +16,7 @@ const assert = require('assert'),
|
|
|
16
16
|
debug = require('debug')('cloudron-backup'),
|
|
17
17
|
fs = require('fs'),
|
|
18
18
|
path = require('path'),
|
|
19
|
+
readlinePromises = require('readline/promises'),
|
|
19
20
|
safe = require('safetydance'),
|
|
20
21
|
TransformStream = require('stream').Transform;
|
|
21
22
|
|
|
@@ -25,7 +26,7 @@ function encryptFilePath(filePath, encryption) {
|
|
|
25
26
|
|
|
26
27
|
const encryptedParts = filePath.split('/').map(function (part) {
|
|
27
28
|
let hmac = crypto.createHmac('sha256', Buffer.from(encryption.filenameHmacKey, 'hex'));
|
|
28
|
-
const iv = hmac.update(part).digest().
|
|
29
|
+
const iv = hmac.update(part).digest().subarray(0, 16); // iv has to be deterministic, for our sync (copy) logic to work
|
|
29
30
|
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryption.filenameKey, 'hex'), iv);
|
|
30
31
|
let crypt = cipher.update(part);
|
|
31
32
|
crypt = Buffer.concat([ iv, crypt, cipher.final() ]);
|
|
@@ -49,22 +50,22 @@ function decryptFilePath(filePath, encryption) {
|
|
|
49
50
|
|
|
50
51
|
try {
|
|
51
52
|
const buffer = Buffer.from(part, 'base64');
|
|
52
|
-
const iv = buffer.
|
|
53
|
+
const iv = buffer.subarray(0, 16);
|
|
53
54
|
|
|
54
55
|
const decrypt = crypto.createDecipheriv('aes-256-cbc', Buffer.from(encryption.filenameKey, 'hex'), iv);
|
|
55
|
-
const plainText = decrypt.update(buffer.
|
|
56
|
+
const plainText = decrypt.update(buffer.subarray(16));
|
|
56
57
|
const plainTextString = Buffer.concat([ plainText, decrypt.final() ]).toString('utf8');
|
|
57
58
|
const hmac = crypto.createHmac('sha256', Buffer.from(encryption.filenameHmacKey, 'hex'));
|
|
58
|
-
if (!hmac.update(plainTextString).digest().
|
|
59
|
+
if (!hmac.update(plainTextString).digest().subarray(0, 16).equals(iv)) return { error: new Error(`mac error decrypting part ${part} of path ${filePath}`) };
|
|
59
60
|
|
|
60
61
|
decryptedParts.push(plainTextString);
|
|
61
62
|
} catch (error) {
|
|
62
63
|
debug(`Error decrypting file ${filePath} part ${part}:`, error);
|
|
63
|
-
return
|
|
64
|
+
return { error };
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
return { decryptedFilePath: decryptedParts.join('/') };
|
|
68
|
+
return { error: null, decryptedFilePath: decryptedParts.join('/') };
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
class EncryptStream extends TransformStream {
|
|
@@ -131,27 +132,27 @@ class DecryptStream extends TransformStream {
|
|
|
131
132
|
|
|
132
133
|
debug('got chunk', chunk.length);
|
|
133
134
|
if (this._header.length !== 20) { // not gotten IV yet
|
|
134
|
-
this._header = Buffer.concat([this._header, chunk.
|
|
135
|
+
this._header = Buffer.concat([this._header, chunk.subarray(0, needed)]);
|
|
135
136
|
if (this._header.length !== 20) return callback();
|
|
136
137
|
|
|
137
|
-
if (!this._header.
|
|
138
|
+
if (!this._header.subarray(0, 4).equals(new Buffer.from('CBV2'))) return callback(new Error('Invalid magic in header'));
|
|
138
139
|
|
|
139
|
-
const iv = this._header.
|
|
140
|
+
const iv = this._header.subarray(4);
|
|
140
141
|
this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, iv);
|
|
141
142
|
this._hmac.update(this._header);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
debug('needed is', needed);
|
|
145
|
-
this._buffer = Buffer.concat([ this._buffer, chunk.
|
|
146
|
+
this._buffer = Buffer.concat([ this._buffer, chunk.subarray(needed) ]);
|
|
146
147
|
debug('buffer is ', this._buffer.length);
|
|
147
148
|
if (this._buffer.length < 32) return callback();
|
|
148
149
|
|
|
149
150
|
try {
|
|
150
|
-
const cipherText = this._buffer.
|
|
151
|
+
const cipherText = this._buffer.subarray(0, -32);
|
|
151
152
|
debug('Got:', cipherText.toString('hex'));
|
|
152
153
|
this._hmac.update(cipherText);
|
|
153
154
|
const plainText = this._decipher.update(cipherText);
|
|
154
|
-
this._buffer = this._buffer.
|
|
155
|
+
this._buffer = this._buffer.subarray(-32);
|
|
155
156
|
callback(null, plainText);
|
|
156
157
|
} catch (error) {
|
|
157
158
|
callback(error);
|
|
@@ -185,15 +186,20 @@ function exit(msgOrError) {
|
|
|
185
186
|
function aesKeysFromPassword(password) {
|
|
186
187
|
const derived = crypto.scryptSync(password, Buffer.from('CLOUDRONSCRYPTSALT', 'utf8'), 128);
|
|
187
188
|
return {
|
|
188
|
-
dataKey: derived.
|
|
189
|
-
dataHmacKey: derived.
|
|
190
|
-
filenameKey: derived.
|
|
191
|
-
filenameHmacKey: derived.
|
|
189
|
+
dataKey: derived.subarray(0, 32).toString('hex'),
|
|
190
|
+
dataHmacKey: derived.subarray(32, 64).toString('hex'),
|
|
191
|
+
filenameKey: derived.subarray(64, 96).toString('hex'),
|
|
192
|
+
filenameHmacKey: derived.subarray(96).toString('hex')
|
|
192
193
|
};
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
function encrypt(input, options) {
|
|
196
|
-
if (!options.password)
|
|
196
|
+
async function encrypt(input, options) {
|
|
197
|
+
if (!options.password) {
|
|
198
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
199
|
+
options.password = rl.question('Enter encryption password: ');
|
|
200
|
+
rl.close();
|
|
201
|
+
if (!options.password) return exit('--password is needed');
|
|
202
|
+
}
|
|
197
203
|
|
|
198
204
|
const encryption = aesKeysFromPassword(options.password);
|
|
199
205
|
|
|
@@ -207,16 +213,26 @@ function encrypt(input, options) {
|
|
|
207
213
|
inStream.pipe(encryptStream).pipe(outStream);
|
|
208
214
|
}
|
|
209
215
|
|
|
210
|
-
function encryptFilename(filePath, options) {
|
|
211
|
-
if (!options.password)
|
|
216
|
+
async function encryptFilename(filePath, options) {
|
|
217
|
+
if (!options.password) {
|
|
218
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
220
|
+
rl.close();
|
|
221
|
+
if (!options.password) return exit('--password is needed');
|
|
222
|
+
}
|
|
212
223
|
|
|
213
224
|
const encryption = aesKeysFromPassword(options.password);
|
|
214
225
|
|
|
215
226
|
console.log(encryptFilePath(filePath, encryption));
|
|
216
227
|
}
|
|
217
228
|
|
|
218
|
-
function decrypt(input, options) {
|
|
219
|
-
if (!options.password)
|
|
229
|
+
async function decrypt(input, options) {
|
|
230
|
+
if (!options.password) {
|
|
231
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
232
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
233
|
+
rl.close();
|
|
234
|
+
if (!options.password) return exit('--password is needed');
|
|
235
|
+
}
|
|
220
236
|
|
|
221
237
|
const fd = safe.fs.openSync(input, 'r');
|
|
222
238
|
if (!fd) return exit(safe.error);
|
|
@@ -237,8 +253,13 @@ function decrypt(input, options) {
|
|
|
237
253
|
inStream.pipe(decryptStream).pipe(outStream);
|
|
238
254
|
}
|
|
239
255
|
|
|
240
|
-
function decryptDir(inDir, outDir, options) {
|
|
241
|
-
if (!options.password)
|
|
256
|
+
async function decryptDir(inDir, outDir, options) {
|
|
257
|
+
if (!options.password) {
|
|
258
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
259
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
260
|
+
rl.close();
|
|
261
|
+
if (!options.password) return exit('--password is needed');
|
|
262
|
+
}
|
|
242
263
|
|
|
243
264
|
const encryption = aesKeysFromPassword(options.password);
|
|
244
265
|
|
|
@@ -274,8 +295,13 @@ function decryptDir(inDir, outDir, options) {
|
|
|
274
295
|
}, exit);
|
|
275
296
|
}
|
|
276
297
|
|
|
277
|
-
function decryptFilename(filePath, options) {
|
|
278
|
-
if (!options.password)
|
|
298
|
+
async function decryptFilename(filePath, options) {
|
|
299
|
+
if (!options.password) {
|
|
300
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
301
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
302
|
+
rl.close();
|
|
303
|
+
if (!options.password) return exit('--password is needed');
|
|
304
|
+
}
|
|
279
305
|
|
|
280
306
|
const encryption = aesKeysFromPassword(options.password);
|
|
281
307
|
|