@naturalcycles/nodejs-lib 12.65.3 → 12.67.1
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/dist/bin/secrets-decrypt.js +17 -8
- package/dist/bin/secrets-encrypt.js +18 -8
- package/dist/index.js +36 -36
- package/dist/secret/secrets-decrypt.util.d.ts +3 -2
- package/dist/secret/secrets-decrypt.util.js +24 -6
- package/dist/secret/secrets-encrypt.util.d.ts +3 -2
- package/dist/secret/secrets-encrypt.util.js +24 -9
- package/dist/security/crypto.util.d.ts +16 -2
- package/dist/security/crypto.util.js +39 -8
- package/dist/security/secret.util.d.ts +9 -1
- package/dist/security/secret.util.js +29 -8
- package/dist/util/lruMemoCache.d.ts +1 -2
- package/package.json +1 -1
- package/src/bin/secrets-decrypt.ts +17 -8
- package/src/bin/secrets-encrypt.ts +18 -8
- package/src/secret/secrets-decrypt.util.ts +34 -9
- package/src/secret/secrets-encrypt.util.ts +33 -12
- package/src/security/crypto.util.ts +40 -16
- package/src/security/secret.util.ts +46 -8
- package/src/util/lruMemoCache.ts +1 -1
|
@@ -6,18 +6,22 @@ const colors_1 = require("../colors");
|
|
|
6
6
|
const script_1 = require("../script");
|
|
7
7
|
const secrets_decrypt_util_1 = require("../secret/secrets-decrypt.util");
|
|
8
8
|
(0, script_1.runScript)(() => {
|
|
9
|
-
const { dir,
|
|
10
|
-
(0, secrets_decrypt_util_1.secretsDecrypt)(dir, encKey,
|
|
9
|
+
const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions();
|
|
10
|
+
(0, secrets_decrypt_util_1.secretsDecrypt)(dir, file, encKey, del, jsonMode);
|
|
11
11
|
});
|
|
12
12
|
function getDecryptCLIOptions() {
|
|
13
13
|
require('dotenv').config();
|
|
14
|
-
let { dir, encKey, encKeyVar,
|
|
14
|
+
let { dir, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
15
15
|
dir: {
|
|
16
16
|
type: 'array',
|
|
17
17
|
desc: 'Directory with secrets. Can be many',
|
|
18
18
|
// demandOption: true,
|
|
19
19
|
default: './secret',
|
|
20
20
|
},
|
|
21
|
+
file: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
24
|
+
},
|
|
21
25
|
encKey: {
|
|
22
26
|
type: 'string',
|
|
23
27
|
desc: 'Encryption key',
|
|
@@ -29,14 +33,19 @@ function getDecryptCLIOptions() {
|
|
|
29
33
|
desc: 'Env variable name to get `encKey` from.',
|
|
30
34
|
default: 'SECRET_ENCRYPTION_KEY',
|
|
31
35
|
},
|
|
32
|
-
algorithm: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
+
// algorithm: {
|
|
37
|
+
// type: 'string',
|
|
38
|
+
// default: 'aes-256-cbc',
|
|
39
|
+
// },
|
|
36
40
|
del: {
|
|
37
41
|
type: 'boolean',
|
|
38
42
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
39
43
|
},
|
|
44
|
+
jsonMode: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
40
49
|
}).argv;
|
|
41
50
|
if (!encKey) {
|
|
42
51
|
encKey = process.env[encKeyVar];
|
|
@@ -48,5 +57,5 @@ function getDecryptCLIOptions() {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
51
|
-
return { dir: dir,
|
|
60
|
+
return { dir: dir, file, encKey, del, jsonMode };
|
|
52
61
|
}
|
|
@@ -6,12 +6,12 @@ const colors_1 = require("../colors");
|
|
|
6
6
|
const script_1 = require("../script");
|
|
7
7
|
const secrets_encrypt_util_1 = require("../secret/secrets-encrypt.util");
|
|
8
8
|
(0, script_1.runScript)(() => {
|
|
9
|
-
const { pattern,
|
|
10
|
-
(0, secrets_encrypt_util_1.secretsEncrypt)(pattern, encKey,
|
|
9
|
+
const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions();
|
|
10
|
+
(0, secrets_encrypt_util_1.secretsEncrypt)(pattern, file, encKey, del, jsonMode);
|
|
11
11
|
});
|
|
12
12
|
function getEncryptCLIOptions() {
|
|
13
13
|
require('dotenv').config();
|
|
14
|
-
let { pattern, encKey, encKeyVar,
|
|
14
|
+
let { pattern, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
15
15
|
pattern: {
|
|
16
16
|
type: 'string',
|
|
17
17
|
array: true,
|
|
@@ -19,6 +19,10 @@ function getEncryptCLIOptions() {
|
|
|
19
19
|
// demandOption: true,
|
|
20
20
|
default: './secret/**',
|
|
21
21
|
},
|
|
22
|
+
file: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
25
|
+
},
|
|
22
26
|
encKey: {
|
|
23
27
|
type: 'string',
|
|
24
28
|
desc: 'Encryption key',
|
|
@@ -30,13 +34,19 @@ function getEncryptCLIOptions() {
|
|
|
30
34
|
desc: 'Env variable name to get `encKey` from.',
|
|
31
35
|
default: 'SECRET_ENCRYPTION_KEY',
|
|
32
36
|
},
|
|
33
|
-
algorithm: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
+
// algorithm: {
|
|
38
|
+
// type: 'string',
|
|
39
|
+
// default: 'aes-256-cbc',
|
|
40
|
+
// },
|
|
37
41
|
del: {
|
|
38
42
|
type: 'boolean',
|
|
39
43
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
44
|
+
default: false,
|
|
45
|
+
},
|
|
46
|
+
jsonMode: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
49
|
+
default: false,
|
|
40
50
|
},
|
|
41
51
|
}).argv;
|
|
42
52
|
if (!encKey) {
|
|
@@ -49,5 +59,5 @@ function getEncryptCLIOptions() {
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
52
|
-
return { pattern: pattern,
|
|
62
|
+
return { pattern: pattern, file, encKey, del, jsonMode };
|
|
53
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -11,23 +11,23 @@ const buffer_util_1 = require("./buffer/buffer.util");
|
|
|
11
11
|
Object.defineProperty(exports, "_chunkBuffer", { enumerable: true, get: function () { return buffer_util_1._chunkBuffer; } });
|
|
12
12
|
const tableDiff_1 = require("./diff/tableDiff");
|
|
13
13
|
Object.defineProperty(exports, "tableDiff", { enumerable: true, get: function () { return tableDiff_1.tableDiff; } });
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
tslib_1.__exportStar(require("./got/getGot"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./infra/process.util"), exports);
|
|
16
16
|
const debug_1 = require("./log/debug");
|
|
17
17
|
Object.defineProperty(exports, "Debug", { enumerable: true, get: function () { return debug_1.Debug; } });
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
tslib_1.__exportStar(require("./security/hash.util"), exports);
|
|
19
|
+
tslib_1.__exportStar(require("./security/id.util"), exports);
|
|
20
|
+
tslib_1.__exportStar(require("./security/secret.util"), exports);
|
|
21
|
+
tslib_1.__exportStar(require("./colors/colors"), exports);
|
|
22
|
+
tslib_1.__exportStar(require("./log/log.util"), exports);
|
|
23
23
|
const slack_service_1 = require("./slack/slack.service");
|
|
24
24
|
Object.defineProperty(exports, "slackDefaultMessagePrefixHook", { enumerable: true, get: function () { return slack_service_1.slackDefaultMessagePrefixHook; } });
|
|
25
25
|
Object.defineProperty(exports, "SlackService", { enumerable: true, get: function () { return slack_service_1.SlackService; } });
|
|
26
26
|
const ndjson_model_1 = require("./stream/ndjson/ndjson.model");
|
|
27
27
|
Object.defineProperty(exports, "NDJsonStats", { enumerable: true, get: function () { return ndjson_model_1.NDJsonStats; } });
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
tslib_1.__exportStar(require("./stream/ndjson/ndJsonFileRead"), exports);
|
|
29
|
+
tslib_1.__exportStar(require("./stream/ndjson/ndJsonFileWrite"), exports);
|
|
30
|
+
tslib_1.__exportStar(require("./stream/ndjson/ndjsonMap"), exports);
|
|
31
31
|
const ndjsonStreamForEach_1 = require("./stream/ndjson/ndjsonStreamForEach");
|
|
32
32
|
Object.defineProperty(exports, "ndjsonStreamForEach", { enumerable: true, get: function () { return ndjsonStreamForEach_1.ndjsonStreamForEach; } });
|
|
33
33
|
const pipelineFromNDJsonFile_1 = require("./stream/ndjson/pipelineFromNDJsonFile");
|
|
@@ -41,41 +41,41 @@ Object.defineProperty(exports, "bufferReviver", { enumerable: true, get: functio
|
|
|
41
41
|
Object.defineProperty(exports, "transformJsonParse", { enumerable: true, get: function () { return transformJsonParse_1.transformJsonParse; } });
|
|
42
42
|
const transformToNDJson_1 = require("./stream/ndjson/transformToNDJson");
|
|
43
43
|
Object.defineProperty(exports, "transformToNDJson", { enumerable: true, get: function () { return transformToNDJson_1.transformToNDJson; } });
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
tslib_1.__exportStar(require("./stream/pipeline/pipeline"), exports);
|
|
45
|
+
tslib_1.__exportStar(require("./stream/readable/readableCreate"), exports);
|
|
46
|
+
tslib_1.__exportStar(require("./stream/readable/readableForEach"), exports);
|
|
47
|
+
tslib_1.__exportStar(require("./stream/readable/readableFromArray"), exports);
|
|
48
|
+
tslib_1.__exportStar(require("./stream/readable/readableMap"), exports);
|
|
49
|
+
tslib_1.__exportStar(require("./stream/readable/readableMapToArray"), exports);
|
|
50
|
+
tslib_1.__exportStar(require("./stream/readable/readableToArray"), exports);
|
|
51
|
+
tslib_1.__exportStar(require("./stream/transform/transformBuffer"), exports);
|
|
52
|
+
tslib_1.__exportStar(require("./stream/transform/transformFilter"), exports);
|
|
53
|
+
tslib_1.__exportStar(require("./stream/transform/transformLimit"), exports);
|
|
54
|
+
tslib_1.__exportStar(require("./stream/transform/transformLogProgress"), exports);
|
|
55
55
|
const transformMap_1 = require("./stream/transform/transformMap");
|
|
56
56
|
Object.defineProperty(exports, "transformMap", { enumerable: true, get: function () { return transformMap_1.transformMap; } });
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
tslib_1.__exportStar(require("./stream/transform/transformMapSimple"), exports);
|
|
58
|
+
tslib_1.__exportStar(require("./stream/transform/transformNoOp"), exports);
|
|
59
59
|
const transformMapSync_1 = require("./stream/transform/transformMapSync");
|
|
60
60
|
Object.defineProperty(exports, "transformMapSync", { enumerable: true, get: function () { return transformMapSync_1.transformMapSync; } });
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
tslib_1.__exportStar(require("./stream/transform/transformSplit"), exports);
|
|
62
|
+
tslib_1.__exportStar(require("./stream/transform/transformTap"), exports);
|
|
63
|
+
tslib_1.__exportStar(require("./stream/transform/transformToArray"), exports);
|
|
64
|
+
tslib_1.__exportStar(require("./stream/transform/transformToString"), exports);
|
|
65
65
|
const baseWorkerClass_1 = require("./stream/transform/worker/baseWorkerClass");
|
|
66
66
|
Object.defineProperty(exports, "BaseWorkerClass", { enumerable: true, get: function () { return baseWorkerClass_1.BaseWorkerClass; } });
|
|
67
67
|
const transformMultiThreaded_1 = require("./stream/transform/worker/transformMultiThreaded");
|
|
68
68
|
Object.defineProperty(exports, "transformMultiThreaded", { enumerable: true, get: function () { return transformMultiThreaded_1.transformMultiThreaded; } });
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
tslib_1.__exportStar(require("./stream/writable/writableForEach"), exports);
|
|
70
|
+
tslib_1.__exportStar(require("./stream/writable/writableFork"), exports);
|
|
71
|
+
tslib_1.__exportStar(require("./stream/writable/writablePushToArray"), exports);
|
|
72
|
+
tslib_1.__exportStar(require("./stream/writable/writableVoid"), exports);
|
|
73
73
|
const inspectAny_1 = require("./string/inspectAny");
|
|
74
74
|
Object.defineProperty(exports, "inspectAny", { enumerable: true, get: function () { return inspectAny_1.inspectAny; } });
|
|
75
75
|
Object.defineProperty(exports, "inspectAnyStringifyFn", { enumerable: true, get: function () { return inspectAny_1.inspectAnyStringifyFn; } });
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
tslib_1.__exportStar(require("./util/env.util"), exports);
|
|
77
|
+
tslib_1.__exportStar(require("./util/lruMemoCache"), exports);
|
|
78
|
+
tslib_1.__exportStar(require("./util/zip.util"), exports);
|
|
79
79
|
const ajv_util_1 = require("./validation/ajv/ajv.util");
|
|
80
80
|
Object.defineProperty(exports, "readAjvSchemas", { enumerable: true, get: function () { return ajv_util_1.readAjvSchemas; } });
|
|
81
81
|
Object.defineProperty(exports, "readJsonSchemas", { enumerable: true, get: function () { return ajv_util_1.readJsonSchemas; } });
|
|
@@ -83,10 +83,10 @@ const ajvSchema_1 = require("./validation/ajv/ajvSchema");
|
|
|
83
83
|
Object.defineProperty(exports, "AjvSchema", { enumerable: true, get: function () { return ajvSchema_1.AjvSchema; } });
|
|
84
84
|
const ajvValidationError_1 = require("./validation/ajv/ajvValidationError");
|
|
85
85
|
Object.defineProperty(exports, "AjvValidationError", { enumerable: true, get: function () { return ajvValidationError_1.AjvValidationError; } });
|
|
86
|
-
|
|
86
|
+
tslib_1.__exportStar(require("./validation/ajv/getAjv"), exports);
|
|
87
87
|
const joi_extensions_1 = require("./validation/joi/joi.extensions");
|
|
88
88
|
Object.defineProperty(exports, "Joi", { enumerable: true, get: function () { return joi_extensions_1.Joi; } });
|
|
89
|
-
|
|
89
|
+
tslib_1.__exportStar(require("./validation/joi/joi.shared.schemas"), exports);
|
|
90
90
|
const joi_validation_error_1 = require("./validation/joi/joi.validation.error");
|
|
91
91
|
Object.defineProperty(exports, "JoiValidationError", { enumerable: true, get: function () { return joi_validation_error_1.JoiValidationError; } });
|
|
92
92
|
const joi_validation_util_1 = require("./validation/joi/joi.validation.util");
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export interface DecryptCLIOptions {
|
|
2
2
|
dir: string[];
|
|
3
|
+
file?: string;
|
|
3
4
|
encKey: string;
|
|
4
|
-
algorithm?: string;
|
|
5
5
|
del?: boolean;
|
|
6
|
+
jsonMode?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
9
10
|
* Using provided encKey.
|
|
10
11
|
*/
|
|
11
|
-
export declare function secretsDecrypt(dir: string[],
|
|
12
|
+
export declare function secretsDecrypt(dir: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
|
|
@@ -2,22 +2,40 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.secretsDecrypt = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const fs = require("fs-extra");
|
|
6
7
|
const globby = require("globby");
|
|
7
8
|
const colors_1 = require("../colors");
|
|
8
9
|
const crypto_util_1 = require("../security/crypto.util");
|
|
10
|
+
// Debug it like this:
|
|
11
|
+
// yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
12
|
+
// yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
9
13
|
/**
|
|
10
14
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
11
15
|
* Using provided encKey.
|
|
12
16
|
*/
|
|
13
|
-
function secretsDecrypt(dir, encKey,
|
|
14
|
-
|
|
17
|
+
function secretsDecrypt(dir, file, encKey, del = false, jsonMode = false) {
|
|
18
|
+
// If `file` is provided - only this one file is used
|
|
19
|
+
const patterns = file ? [file] : dir.map(d => `${d}/**/*.enc`);
|
|
15
20
|
const filenames = globby.sync(patterns);
|
|
16
21
|
filenames.forEach(filename => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
let plainFilename;
|
|
23
|
+
if (jsonMode) {
|
|
24
|
+
(0, js_lib_1._assert)(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`);
|
|
25
|
+
(0, js_lib_1._assert)(!filename.endsWith('.plain.json'), `${path.basename(filename)} MUST NOT end with '.plain.json'`);
|
|
26
|
+
plainFilename = filename.replace('.json', '.plain.json');
|
|
27
|
+
const json = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
28
|
+
(0, js_lib_1._stringMapEntries)(json).forEach(([k, v]) => {
|
|
29
|
+
json[k] = (0, crypto_util_1.decryptString)(v, encKey);
|
|
30
|
+
});
|
|
31
|
+
fs.writeFileSync(plainFilename, JSON.stringify(json, null, 2));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const enc = fs.readFileSync(filename);
|
|
35
|
+
const plain = (0, crypto_util_1.decryptRandomIVBuffer)(enc, encKey);
|
|
36
|
+
plainFilename = filename.slice(0, filename.length - '.enc'.length);
|
|
37
|
+
fs.writeFileSync(plainFilename, plain);
|
|
38
|
+
}
|
|
21
39
|
if (del) {
|
|
22
40
|
fs.unlinkSync(filename);
|
|
23
41
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export interface EncryptCLIOptions {
|
|
2
2
|
pattern: string[];
|
|
3
|
+
file?: string;
|
|
3
4
|
encKey: string;
|
|
4
|
-
algorithm?: string;
|
|
5
5
|
del?: boolean;
|
|
6
|
+
jsonMode?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
|
|
9
10
|
* Using provided encKey.
|
|
10
11
|
*/
|
|
11
|
-
export declare function secretsEncrypt(pattern: string[],
|
|
12
|
+
export declare function secretsEncrypt(pattern: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.secretsEncrypt = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const fs = require("fs-extra");
|
|
6
7
|
const globby = require("globby");
|
|
7
8
|
const colors_1 = require("../colors");
|
|
@@ -10,17 +11,31 @@ const crypto_util_1 = require("../security/crypto.util");
|
|
|
10
11
|
* Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
|
|
11
12
|
* Using provided encKey.
|
|
12
13
|
*/
|
|
13
|
-
function secretsEncrypt(pattern, encKey,
|
|
14
|
-
const patterns =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
function secretsEncrypt(pattern, file, encKey, del = false, jsonMode = false) {
|
|
15
|
+
const patterns = file
|
|
16
|
+
? [file]
|
|
17
|
+
: [
|
|
18
|
+
...pattern,
|
|
19
|
+
`!**/*.enc`, // excluding already encoded
|
|
20
|
+
];
|
|
18
21
|
const filenames = globby.sync(patterns);
|
|
22
|
+
let encFilename;
|
|
19
23
|
filenames.forEach(filename => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (jsonMode) {
|
|
25
|
+
(0, js_lib_1._assert)(filename.endsWith('.plain.json'), `${path.basename(filename)} MUST end with '.plain.json'`);
|
|
26
|
+
encFilename = filename.replace('.plain', '');
|
|
27
|
+
const json = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
28
|
+
(0, js_lib_1._stringMapEntries)(json).forEach(([k, plain]) => {
|
|
29
|
+
json[k] = (0, crypto_util_1.encryptString)(plain, encKey);
|
|
30
|
+
});
|
|
31
|
+
fs.writeFileSync(encFilename, JSON.stringify(json, null, 2));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const plain = fs.readFileSync(filename);
|
|
35
|
+
const enc = (0, crypto_util_1.encryptRandomIVBuffer)(plain, encKey);
|
|
36
|
+
encFilename = `${filename}.enc`;
|
|
37
|
+
fs.writeFileSync(encFilename, enc);
|
|
38
|
+
}
|
|
24
39
|
if (del) {
|
|
25
40
|
fs.unlinkSync(filename);
|
|
26
41
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Using aes-256-cbc
|
|
4
|
+
*/
|
|
5
|
+
export declare function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer;
|
|
6
|
+
/**
|
|
7
|
+
* Using aes-256-cbc
|
|
8
|
+
*/
|
|
9
|
+
export declare function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer;
|
|
10
|
+
/**
|
|
11
|
+
* Using aes-256-cbc
|
|
12
|
+
*/
|
|
13
|
+
export declare function decryptString(str: string, secretKey: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Using aes-256-cbc
|
|
16
|
+
*/
|
|
17
|
+
export declare function encryptString(str: string, secretKey: string): string;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decryptRandomIVBuffer = exports.encryptRandomIVBuffer = void 0;
|
|
3
|
+
exports.encryptString = exports.decryptString = exports.decryptRandomIVBuffer = exports.encryptRandomIVBuffer = void 0;
|
|
4
4
|
const crypto = require("crypto");
|
|
5
5
|
const hash_util_1 = require("./hash.util");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function encryptRandomIVBuffer(input, secretKeyBase64, algorithm = 'aes-256-cbc') {
|
|
6
|
+
const algorithm = 'aes-256-cbc';
|
|
7
|
+
/**
|
|
8
|
+
* Using aes-256-cbc
|
|
9
|
+
*/
|
|
10
|
+
function encryptRandomIVBuffer(input, secretKeyBase64) {
|
|
12
11
|
const key = aes256Key(secretKeyBase64);
|
|
13
12
|
// Random iv to achieve non-deterministic encryption (but deterministic decryption)
|
|
14
13
|
// const iv = await randomBytes(16)
|
|
@@ -17,7 +16,10 @@ function encryptRandomIVBuffer(input, secretKeyBase64, algorithm = 'aes-256-cbc'
|
|
|
17
16
|
return Buffer.concat([iv, cipher.update(input), cipher.final()]);
|
|
18
17
|
}
|
|
19
18
|
exports.encryptRandomIVBuffer = encryptRandomIVBuffer;
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Using aes-256-cbc
|
|
21
|
+
*/
|
|
22
|
+
function decryptRandomIVBuffer(input, secretKeyBase64) {
|
|
21
23
|
const key = aes256Key(secretKeyBase64);
|
|
22
24
|
// iv is first 16 bytes of encrypted buffer, the rest is payload
|
|
23
25
|
const iv = input.slice(0, 16);
|
|
@@ -26,3 +28,32 @@ function decryptRandomIVBuffer(input, secretKeyBase64, algorithm = 'aes-256-cbc'
|
|
|
26
28
|
return Buffer.concat([decipher.update(payload), decipher.final()]);
|
|
27
29
|
}
|
|
28
30
|
exports.decryptRandomIVBuffer = decryptRandomIVBuffer;
|
|
31
|
+
/**
|
|
32
|
+
* Using aes-256-cbc
|
|
33
|
+
*/
|
|
34
|
+
function decryptString(str, secretKey) {
|
|
35
|
+
const { algorithm, key, iv } = getCryptoParams(secretKey);
|
|
36
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
37
|
+
let decrypted = decipher.update(str, 'base64', 'utf8');
|
|
38
|
+
return (decrypted += decipher.final('utf8'));
|
|
39
|
+
}
|
|
40
|
+
exports.decryptString = decryptString;
|
|
41
|
+
/**
|
|
42
|
+
* Using aes-256-cbc
|
|
43
|
+
*/
|
|
44
|
+
function encryptString(str, secretKey) {
|
|
45
|
+
const { algorithm, key, iv } = getCryptoParams(secretKey);
|
|
46
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
47
|
+
let encrypted = cipher.update(str, 'utf8', 'base64');
|
|
48
|
+
return (encrypted += cipher.final('base64'));
|
|
49
|
+
}
|
|
50
|
+
exports.encryptString = encryptString;
|
|
51
|
+
function getCryptoParams(secretKey) {
|
|
52
|
+
const key = (0, hash_util_1.md5)(secretKey);
|
|
53
|
+
const iv = (0, hash_util_1.md5)(secretKey + key).slice(0, 16);
|
|
54
|
+
return { algorithm, key, iv };
|
|
55
|
+
}
|
|
56
|
+
function aes256Key(secretKeyBase64) {
|
|
57
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
58
|
+
return (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
|
|
59
|
+
}
|
|
@@ -14,8 +14,16 @@ export declare function removeSecretsFromEnv(): void;
|
|
|
14
14
|
* Does NOT delete previous secrets from secretMap.
|
|
15
15
|
*
|
|
16
16
|
* If SECRET_ENCRYPTION_KEY argument is passed - will decrypt the contents of the file first, before parsing it as JSON.
|
|
17
|
+
*
|
|
18
|
+
* Whole file is encrypted.
|
|
19
|
+
* For "json-values encrypted" style - use `loadSecretsFromEncryptedJsonFileValues`
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadSecretsFromEncryptedJsonFile(filePath: string, secretEncryptionKey?: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Whole file is NOT encrypted, but instead individual json values ARE encrypted..
|
|
24
|
+
* For whole-file encryption - use `loadSecretsFromEncryptedJsonFile`
|
|
17
25
|
*/
|
|
18
|
-
export declare function
|
|
26
|
+
export declare function loadSecretsFromEncryptedJsonFileValues(filePath: string, secretEncryptionKey?: string): void;
|
|
19
27
|
/**
|
|
20
28
|
* json secrets are always base64'd
|
|
21
29
|
*/
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setSecretMap = exports.getSecretMap = exports.secretOptional = exports.secret = exports.
|
|
3
|
+
exports.setSecretMap = exports.getSecretMap = exports.secretOptional = exports.secret = exports.loadSecretsFromEncryptedJsonFileValues = exports.loadSecretsFromEncryptedJsonFile = exports.removeSecretsFromEnv = exports.loadSecretsFromEnv = void 0;
|
|
4
4
|
const fs = require("fs");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const __1 = require("..");
|
|
6
7
|
const crypto_util_1 = require("./crypto.util");
|
|
7
8
|
let loaded = false;
|
|
@@ -39,16 +40,17 @@ exports.removeSecretsFromEnv = removeSecretsFromEnv;
|
|
|
39
40
|
* Does NOT delete previous secrets from secretMap.
|
|
40
41
|
*
|
|
41
42
|
* If SECRET_ENCRYPTION_KEY argument is passed - will decrypt the contents of the file first, before parsing it as JSON.
|
|
43
|
+
*
|
|
44
|
+
* Whole file is encrypted.
|
|
45
|
+
* For "json-values encrypted" style - use `loadSecretsFromEncryptedJsonFileValues`
|
|
42
46
|
*/
|
|
43
47
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
throw new Error(`loadSecretsFromPlainJsonFile() cannot load from path: ${filePath}`);
|
|
47
|
-
}
|
|
48
|
+
function loadSecretsFromEncryptedJsonFile(filePath, secretEncryptionKey) {
|
|
49
|
+
(0, js_lib_1._assert)(fs.existsSync(filePath), `loadSecretsFromEncryptedJsonFile() cannot load from path: ${filePath}`);
|
|
48
50
|
let secrets;
|
|
49
|
-
if (
|
|
51
|
+
if (secretEncryptionKey) {
|
|
50
52
|
const buf = fs.readFileSync(filePath);
|
|
51
|
-
const plain = (0, crypto_util_1.decryptRandomIVBuffer)(buf,
|
|
53
|
+
const plain = (0, crypto_util_1.decryptRandomIVBuffer)(buf, secretEncryptionKey).toString('utf8');
|
|
52
54
|
secrets = JSON.parse(plain);
|
|
53
55
|
}
|
|
54
56
|
else {
|
|
@@ -60,7 +62,26 @@ function loadSecretsFromJsonFile(filePath, SECRET_ENCRYPTION_KEY) {
|
|
|
60
62
|
.map(s => s.toUpperCase())
|
|
61
63
|
.join(', ')}`);
|
|
62
64
|
}
|
|
63
|
-
exports.
|
|
65
|
+
exports.loadSecretsFromEncryptedJsonFile = loadSecretsFromEncryptedJsonFile;
|
|
66
|
+
/**
|
|
67
|
+
* Whole file is NOT encrypted, but instead individual json values ARE encrypted..
|
|
68
|
+
* For whole-file encryption - use `loadSecretsFromEncryptedJsonFile`
|
|
69
|
+
*/
|
|
70
|
+
function loadSecretsFromEncryptedJsonFileValues(filePath, secretEncryptionKey) {
|
|
71
|
+
(0, js_lib_1._assert)(fs.existsSync(filePath), `loadSecretsFromEncryptedJsonFileValues() cannot load from path: ${filePath}`);
|
|
72
|
+
const secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
73
|
+
if (secretEncryptionKey) {
|
|
74
|
+
(0, js_lib_1._stringMapEntries)(secrets).forEach(([k, enc]) => {
|
|
75
|
+
secrets[k] = (0, crypto_util_1.decryptString)(enc, secretEncryptionKey);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
|
|
79
|
+
loaded = true;
|
|
80
|
+
console.log(`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
|
|
81
|
+
.map(s => s.toUpperCase())
|
|
82
|
+
.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
exports.loadSecretsFromEncryptedJsonFileValues = loadSecretsFromEncryptedJsonFileValues;
|
|
64
85
|
/**
|
|
65
86
|
* json secrets are always base64'd
|
|
66
87
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MemoCache } from '@naturalcycles/js-lib';
|
|
2
2
|
import LRUCache = require('lru-cache');
|
|
3
|
-
export
|
|
4
|
-
}
|
|
3
|
+
export declare type LRUMemoCacheOptions<KEY, VALUE> = Partial<LRUCache.Options<KEY, VALUE>>;
|
|
5
4
|
/**
|
|
6
5
|
* @example
|
|
7
6
|
* Use it like this:
|
package/package.json
CHANGED
|
@@ -6,21 +6,25 @@ import { runScript } from '../script'
|
|
|
6
6
|
import { DecryptCLIOptions, secretsDecrypt } from '../secret/secrets-decrypt.util'
|
|
7
7
|
|
|
8
8
|
runScript(() => {
|
|
9
|
-
const { dir,
|
|
9
|
+
const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions()
|
|
10
10
|
|
|
11
|
-
secretsDecrypt(dir, encKey,
|
|
11
|
+
secretsDecrypt(dir, file, encKey, del, jsonMode)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
15
15
|
require('dotenv').config()
|
|
16
16
|
|
|
17
|
-
let { dir, encKey, encKeyVar,
|
|
17
|
+
let { dir, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
18
18
|
dir: {
|
|
19
19
|
type: 'array',
|
|
20
20
|
desc: 'Directory with secrets. Can be many',
|
|
21
21
|
// demandOption: true,
|
|
22
22
|
default: './secret',
|
|
23
23
|
},
|
|
24
|
+
file: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
27
|
+
},
|
|
24
28
|
encKey: {
|
|
25
29
|
type: 'string',
|
|
26
30
|
desc: 'Encryption key',
|
|
@@ -32,14 +36,19 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
|
32
36
|
desc: 'Env variable name to get `encKey` from.',
|
|
33
37
|
default: 'SECRET_ENCRYPTION_KEY',
|
|
34
38
|
},
|
|
35
|
-
algorithm: {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
+
// algorithm: {
|
|
40
|
+
// type: 'string',
|
|
41
|
+
// default: 'aes-256-cbc',
|
|
42
|
+
// },
|
|
39
43
|
del: {
|
|
40
44
|
type: 'boolean',
|
|
41
45
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
42
46
|
},
|
|
47
|
+
jsonMode: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
43
52
|
}).argv
|
|
44
53
|
|
|
45
54
|
if (!encKey) {
|
|
@@ -55,5 +64,5 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
58
|
-
return { dir: dir as any,
|
|
67
|
+
return { dir: dir as any, file, encKey, del, jsonMode }
|
|
59
68
|
}
|
|
@@ -6,15 +6,15 @@ import { runScript } from '../script'
|
|
|
6
6
|
import { EncryptCLIOptions, secretsEncrypt } from '../secret/secrets-encrypt.util'
|
|
7
7
|
|
|
8
8
|
runScript(() => {
|
|
9
|
-
const { pattern,
|
|
9
|
+
const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions()
|
|
10
10
|
|
|
11
|
-
secretsEncrypt(pattern, encKey,
|
|
11
|
+
secretsEncrypt(pattern, file, encKey, del, jsonMode)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
15
15
|
require('dotenv').config()
|
|
16
16
|
|
|
17
|
-
let { pattern, encKey, encKeyVar,
|
|
17
|
+
let { pattern, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
18
18
|
pattern: {
|
|
19
19
|
type: 'string',
|
|
20
20
|
array: true,
|
|
@@ -22,6 +22,10 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
22
22
|
// demandOption: true,
|
|
23
23
|
default: './secret/**',
|
|
24
24
|
},
|
|
25
|
+
file: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
28
|
+
},
|
|
25
29
|
encKey: {
|
|
26
30
|
type: 'string',
|
|
27
31
|
desc: 'Encryption key',
|
|
@@ -33,13 +37,19 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
33
37
|
desc: 'Env variable name to get `encKey` from.',
|
|
34
38
|
default: 'SECRET_ENCRYPTION_KEY',
|
|
35
39
|
},
|
|
36
|
-
algorithm: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
+
// algorithm: {
|
|
41
|
+
// type: 'string',
|
|
42
|
+
// default: 'aes-256-cbc',
|
|
43
|
+
// },
|
|
40
44
|
del: {
|
|
41
45
|
type: 'boolean',
|
|
42
46
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
jsonMode: {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
52
|
+
default: false,
|
|
43
53
|
},
|
|
44
54
|
}).argv
|
|
45
55
|
|
|
@@ -56,5 +66,5 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
59
|
-
return { pattern: pattern as any,
|
|
69
|
+
return { pattern: pattern as any, file, encKey, del, jsonMode }
|
|
60
70
|
}
|
|
@@ -1,36 +1,61 @@
|
|
|
1
1
|
import * as path from 'path'
|
|
2
|
+
import { _assert, _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
|
|
2
3
|
import * as fs from 'fs-extra'
|
|
3
4
|
import globby = require('globby')
|
|
4
5
|
import { dimGrey, yellow } from '../colors'
|
|
5
|
-
import { decryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
|
+
import { decryptRandomIVBuffer, decryptString } from '../security/crypto.util'
|
|
6
7
|
|
|
7
8
|
export interface DecryptCLIOptions {
|
|
8
9
|
dir: string[]
|
|
10
|
+
file?: string
|
|
9
11
|
encKey: string
|
|
10
|
-
algorithm?: string
|
|
11
12
|
del?: boolean
|
|
13
|
+
jsonMode?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
// Debug it like this:
|
|
17
|
+
// yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
18
|
+
// yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
16
22
|
* Using provided encKey.
|
|
17
23
|
*/
|
|
18
24
|
export function secretsDecrypt(
|
|
19
25
|
dir: string[],
|
|
26
|
+
file: string | undefined,
|
|
20
27
|
encKey: string,
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
del = false,
|
|
29
|
+
jsonMode = false,
|
|
23
30
|
): void {
|
|
24
|
-
|
|
31
|
+
// If `file` is provided - only this one file is used
|
|
32
|
+
const patterns = file ? [file] : dir.map(d => `${d}/**/*.enc`)
|
|
25
33
|
|
|
26
34
|
const filenames = globby.sync(patterns)
|
|
27
35
|
|
|
28
36
|
filenames.forEach(filename => {
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
let plainFilename
|
|
38
|
+
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
_assert(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`)
|
|
41
|
+
_assert(
|
|
42
|
+
!filename.endsWith('.plain.json'),
|
|
43
|
+
`${path.basename(filename)} MUST NOT end with '.plain.json'`,
|
|
44
|
+
)
|
|
45
|
+
plainFilename = filename.replace('.json', '.plain.json')
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
const json: StringMap = JSON.parse(fs.readFileSync(filename, 'utf8'))
|
|
48
|
+
_stringMapEntries(json).forEach(([k, v]) => {
|
|
49
|
+
json[k] = decryptString(v, encKey)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(plainFilename, JSON.stringify(json, null, 2))
|
|
53
|
+
} else {
|
|
54
|
+
const enc = fs.readFileSync(filename)
|
|
55
|
+
const plain = decryptRandomIVBuffer(enc, encKey)
|
|
56
|
+
plainFilename = filename.slice(0, filename.length - '.enc'.length)
|
|
57
|
+
fs.writeFileSync(plainFilename, plain)
|
|
58
|
+
}
|
|
34
59
|
|
|
35
60
|
if (del) {
|
|
36
61
|
fs.unlinkSync(filename)
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import * as path from 'path'
|
|
2
|
+
import { _assert, _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
|
|
2
3
|
import * as fs from 'fs-extra'
|
|
3
4
|
import globby = require('globby')
|
|
4
5
|
import { dimGrey, yellow } from '../colors'
|
|
5
|
-
import { encryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
|
+
import { encryptRandomIVBuffer, encryptString } from '../security/crypto.util'
|
|
6
7
|
|
|
7
8
|
export interface EncryptCLIOptions {
|
|
8
9
|
pattern: string[]
|
|
10
|
+
file?: string
|
|
9
11
|
encKey: string
|
|
10
|
-
algorithm?: string
|
|
11
12
|
del?: boolean
|
|
13
|
+
jsonMode?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -17,22 +19,41 @@ export interface EncryptCLIOptions {
|
|
|
17
19
|
*/
|
|
18
20
|
export function secretsEncrypt(
|
|
19
21
|
pattern: string[],
|
|
22
|
+
file: string | undefined,
|
|
20
23
|
encKey: string,
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
del = false,
|
|
25
|
+
jsonMode = false,
|
|
23
26
|
): void {
|
|
24
|
-
const patterns =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const patterns = file
|
|
28
|
+
? [file]
|
|
29
|
+
: [
|
|
30
|
+
...pattern,
|
|
31
|
+
`!**/*.enc`, // excluding already encoded
|
|
32
|
+
]
|
|
28
33
|
const filenames = globby.sync(patterns)
|
|
34
|
+
let encFilename
|
|
29
35
|
|
|
30
36
|
filenames.forEach(filename => {
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
_assert(
|
|
39
|
+
filename.endsWith('.plain.json'),
|
|
40
|
+
`${path.basename(filename)} MUST end with '.plain.json'`,
|
|
41
|
+
)
|
|
42
|
+
encFilename = filename.replace('.plain', '')
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
const json: StringMap = JSON.parse(fs.readFileSync(filename, 'utf8'))
|
|
45
|
+
|
|
46
|
+
_stringMapEntries(json).forEach(([k, plain]) => {
|
|
47
|
+
json[k] = encryptString(plain, encKey)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(encFilename, JSON.stringify(json, null, 2))
|
|
51
|
+
} else {
|
|
52
|
+
const plain = fs.readFileSync(filename)
|
|
53
|
+
const enc = encryptRandomIVBuffer(plain, encKey)
|
|
54
|
+
encFilename = `${filename}.enc`
|
|
55
|
+
fs.writeFileSync(encFilename, enc)
|
|
56
|
+
}
|
|
36
57
|
|
|
37
58
|
if (del) {
|
|
38
59
|
fs.unlinkSync(filename)
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import * as crypto from 'crypto'
|
|
2
2
|
import { md5 } from './hash.util'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const algorithm = 'aes-256-cbc'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function encryptRandomIVBuffer(
|
|
12
|
-
input: Buffer,
|
|
13
|
-
secretKeyBase64: string,
|
|
14
|
-
algorithm = 'aes-256-cbc',
|
|
15
|
-
): Buffer {
|
|
6
|
+
/**
|
|
7
|
+
* Using aes-256-cbc
|
|
8
|
+
*/
|
|
9
|
+
export function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer {
|
|
16
10
|
const key = aes256Key(secretKeyBase64)
|
|
17
11
|
|
|
18
12
|
// Random iv to achieve non-deterministic encryption (but deterministic decryption)
|
|
@@ -24,11 +18,10 @@ export function encryptRandomIVBuffer(
|
|
|
24
18
|
return Buffer.concat([iv, cipher.update(input), cipher.final()])
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
): Buffer {
|
|
21
|
+
/**
|
|
22
|
+
* Using aes-256-cbc
|
|
23
|
+
*/
|
|
24
|
+
export function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer {
|
|
32
25
|
const key = aes256Key(secretKeyBase64)
|
|
33
26
|
|
|
34
27
|
// iv is first 16 bytes of encrypted buffer, the rest is payload
|
|
@@ -39,3 +32,34 @@ export function decryptRandomIVBuffer(
|
|
|
39
32
|
|
|
40
33
|
return Buffer.concat([decipher.update(payload), decipher.final()])
|
|
41
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Using aes-256-cbc
|
|
38
|
+
*/
|
|
39
|
+
export function decryptString(str: string, secretKey: string): string {
|
|
40
|
+
const { algorithm, key, iv } = getCryptoParams(secretKey)
|
|
41
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv)
|
|
42
|
+
let decrypted = decipher.update(str, 'base64', 'utf8')
|
|
43
|
+
return (decrypted += decipher.final('utf8'))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Using aes-256-cbc
|
|
48
|
+
*/
|
|
49
|
+
export function encryptString(str: string, secretKey: string): string {
|
|
50
|
+
const { algorithm, key, iv } = getCryptoParams(secretKey)
|
|
51
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv)
|
|
52
|
+
let encrypted = cipher.update(str, 'utf8', 'base64')
|
|
53
|
+
return (encrypted += cipher.final('base64'))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getCryptoParams(secretKey: string): { algorithm: string; key: string; iv: string } {
|
|
57
|
+
const key = md5(secretKey)
|
|
58
|
+
const iv = md5(secretKey + key).slice(0, 16)
|
|
59
|
+
return { algorithm, key, iv }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function aes256Key(secretKeyBase64: string): string {
|
|
63
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
64
|
+
return md5(Buffer.from(secretKeyBase64, 'base64'))
|
|
65
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs'
|
|
2
|
-
import { StringMap } from '@naturalcycles/js-lib'
|
|
2
|
+
import { _assert, _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { base64ToString } from '..'
|
|
4
|
-
import { decryptRandomIVBuffer } from './crypto.util'
|
|
4
|
+
import { decryptRandomIVBuffer, decryptString } from './crypto.util'
|
|
5
5
|
|
|
6
6
|
let loaded = false
|
|
7
7
|
|
|
@@ -46,18 +46,25 @@ export function removeSecretsFromEnv(): void {
|
|
|
46
46
|
* Does NOT delete previous secrets from secretMap.
|
|
47
47
|
*
|
|
48
48
|
* If SECRET_ENCRYPTION_KEY argument is passed - will decrypt the contents of the file first, before parsing it as JSON.
|
|
49
|
+
*
|
|
50
|
+
* Whole file is encrypted.
|
|
51
|
+
* For "json-values encrypted" style - use `loadSecretsFromEncryptedJsonFileValues`
|
|
49
52
|
*/
|
|
50
53
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
51
|
-
export function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
export function loadSecretsFromEncryptedJsonFile(
|
|
55
|
+
filePath: string,
|
|
56
|
+
secretEncryptionKey?: string,
|
|
57
|
+
): void {
|
|
58
|
+
_assert(
|
|
59
|
+
fs.existsSync(filePath),
|
|
60
|
+
`loadSecretsFromEncryptedJsonFile() cannot load from path: ${filePath}`,
|
|
61
|
+
)
|
|
55
62
|
|
|
56
63
|
let secrets: StringMap
|
|
57
64
|
|
|
58
|
-
if (
|
|
65
|
+
if (secretEncryptionKey) {
|
|
59
66
|
const buf = fs.readFileSync(filePath)
|
|
60
|
-
const plain = decryptRandomIVBuffer(buf,
|
|
67
|
+
const plain = decryptRandomIVBuffer(buf, secretEncryptionKey).toString('utf8')
|
|
61
68
|
secrets = JSON.parse(plain)
|
|
62
69
|
} else {
|
|
63
70
|
secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
@@ -73,6 +80,37 @@ export function loadSecretsFromJsonFile(filePath: string, SECRET_ENCRYPTION_KEY?
|
|
|
73
80
|
)
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Whole file is NOT encrypted, but instead individual json values ARE encrypted..
|
|
85
|
+
* For whole-file encryption - use `loadSecretsFromEncryptedJsonFile`
|
|
86
|
+
*/
|
|
87
|
+
export function loadSecretsFromEncryptedJsonFileValues(
|
|
88
|
+
filePath: string,
|
|
89
|
+
secretEncryptionKey?: string,
|
|
90
|
+
): void {
|
|
91
|
+
_assert(
|
|
92
|
+
fs.existsSync(filePath),
|
|
93
|
+
`loadSecretsFromEncryptedJsonFileValues() cannot load from path: ${filePath}`,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const secrets: StringMap = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
97
|
+
|
|
98
|
+
if (secretEncryptionKey) {
|
|
99
|
+
_stringMapEntries(secrets).forEach(([k, enc]) => {
|
|
100
|
+
secrets[k] = decryptString(enc, secretEncryptionKey)
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
|
|
105
|
+
|
|
106
|
+
loaded = true
|
|
107
|
+
console.log(
|
|
108
|
+
`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
|
|
109
|
+
.map(s => s.toUpperCase())
|
|
110
|
+
.join(', ')}`,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
76
114
|
/**
|
|
77
115
|
* json secrets are always base64'd
|
|
78
116
|
*/
|
package/src/util/lruMemoCache.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { MemoCache } from '@naturalcycles/js-lib'
|
|
|
2
2
|
import LRUCache = require('lru-cache')
|
|
3
3
|
|
|
4
4
|
// Partial, to be able to provide default `max`
|
|
5
|
-
export
|
|
5
|
+
export type LRUMemoCacheOptions<KEY, VALUE> = Partial<LRUCache.Options<KEY, VALUE>>
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @example
|