1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | 'use strict';
|
18 |
|
19 | const cryptoUtil = require('../lib/cryptoUtil');
|
20 | const assert = require('assert');
|
21 | const q = require('q');
|
22 | const options = require('commander');
|
23 | const Logger = require('../lib/logger');
|
24 | const ipc = require('../lib/ipc');
|
25 | const signals = require('../lib/signals');
|
26 | const util = require('../lib/util');
|
27 | const localKeyUtil = require('../lib/localKeyUtil');
|
28 | const KEYS = require('../lib/sharedConstants').KEYS;
|
29 | const REG_EXPS = require('../lib/sharedConstants').REG_EXPS;
|
30 |
|
31 | (function run() {
|
32 | const runner = {
|
33 | |
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | run(argv, cb) {
|
45 | const loggerOptions = {};
|
46 | let logger;
|
47 | let loggableArgs;
|
48 | let logFileName;
|
49 | let waitPromise;
|
50 |
|
51 | let exiting;
|
52 |
|
53 | const DEFAULT_LOG_FILE = '/tmp/encryptDataToFile.log';
|
54 | const KEYS_TO_MASK = ['--data'];
|
55 |
|
56 | try {
|
57 |
|
58 |
|
59 |
|
60 | options
|
61 | .version('4.23.0-beta.3')
|
62 | .option(
|
63 | '--background',
|
64 | 'Spawn a background process to do the work. If you are running in cloud init, you probably want this option.'
|
65 | )
|
66 | .option(
|
67 | '--signal <signal>',
|
68 | 'Signal to send when done. Default ENCRYPTION_DONE.'
|
69 | )
|
70 | .option(
|
71 | '--wait-for <signal>',
|
72 | 'Wait for the named signal before running.'
|
73 | )
|
74 | .option(
|
75 | '--log-level <level>',
|
76 | 'Log level (none, error, warn, info, verbose, debug, silly). Default is info.', 'info'
|
77 | )
|
78 | .option(
|
79 | '-o, --output <file>',
|
80 | `Log to file as well as console. This is the default if background process is spawned. Default is ${DEFAULT_LOG_FILE}`
|
81 | )
|
82 | .option(
|
83 | '-e, --error-file <file>',
|
84 | 'Log exceptions to a specific file. Default is /tmp/cloudLibsError.log, or cloudLibsError.log in --output file directory'
|
85 | )
|
86 | .option(
|
87 | '--data <data>',
|
88 | 'Data to encrypt (use this or --data-file)'
|
89 | )
|
90 | .option(
|
91 | '--data-file <data_file>',
|
92 | 'Full path to file with data (use this or --data)'
|
93 | )
|
94 | .option(
|
95 | '--out-file <file_name>',
|
96 | `Full path to file in which to write encrypted data. If symmetric option is used, file format will be:
|
97 | {
|
98 | encryptedKey: <encryptedKey>,
|
99 | iv: <initializationVector>,
|
100 | privateKey: {
|
101 | name: <private_key_name>,
|
102 | folder: <private_key_folder>
|
103 | },
|
104 | encryptedData: <base64_encoded_encryptedData>
|
105 | }`
|
106 | )
|
107 | .option(
|
108 | '--private-key-name <name_for_private_key>',
|
109 | 'Name of the private key. Will be created if missing. Default is sharedConstants.KEYS.LOCAL_PRIVATE_KEY. Matching public key is written to sharedConstants.KEYS.LOCAL_PUBLIC_KEY_DIR. If this option is specified, public key is also installed as an ifile.'
|
110 | )
|
111 | .option(
|
112 | '--private-key-folder <name_for_private_key_folder>',
|
113 | 'Name of the BIG-IP folder in which to find/create the private key. If private-key-name is specified, default is Common. Otherwise, this is ignored.'
|
114 | )
|
115 | .option(
|
116 | '--symmetric',
|
117 | 'Use symmetric encryption and place the encrypted symmetric key in <out-file>'
|
118 | )
|
119 | .option(
|
120 | '--no-console',
|
121 | 'Do not log to console. Default false (log to console).'
|
122 | )
|
123 | .parse(argv);
|
124 |
|
125 |
|
126 | assert.ok(options.data || options.dataFile, 'One of --data or --data-file must be specified');
|
127 | if (options.data && options.dataFile) {
|
128 | assert.fail('Only one of --data or --data-file may be specified.');
|
129 | }
|
130 | assert.ok(options.outFile, '--out-file parameter is required');
|
131 |
|
132 | loggerOptions.console = options.console;
|
133 | loggerOptions.logLevel = options.logLevel;
|
134 | loggerOptions.module = module;
|
135 |
|
136 | if (options.output) {
|
137 | loggerOptions.fileName = options.output;
|
138 | }
|
139 |
|
140 | if (options.errorFile) {
|
141 | loggerOptions.errorFile = options.errorFile;
|
142 | }
|
143 |
|
144 | logger = Logger.getLogger(loggerOptions);
|
145 | ipc.setLoggerOptions(loggerOptions);
|
146 | util.setLoggerOptions(loggerOptions);
|
147 | localKeyUtil.setLoggerOptions(loggerOptions);
|
148 |
|
149 |
|
150 |
|
151 | if (options.background) {
|
152 | logFileName = options.output || DEFAULT_LOG_FILE;
|
153 | logger.info('Spawning child process to do the work. Output will be in', logFileName);
|
154 | util.runInBackgroundAndExit(process, logFileName);
|
155 | }
|
156 |
|
157 |
|
158 | loggableArgs = argv.slice();
|
159 | for (let i = 0; i < loggableArgs.length; i++) {
|
160 | if (KEYS_TO_MASK.indexOf(loggableArgs[i]) !== -1) {
|
161 | loggableArgs[i + 1] = '*******';
|
162 | }
|
163 | }
|
164 | logger.info(`${loggableArgs[1]} called with`, loggableArgs.join(' '));
|
165 |
|
166 | if (options.waitFor) {
|
167 | logger.info('Waiting for', options.waitFor);
|
168 | waitPromise = ipc.once(options.waitFor);
|
169 | } else {
|
170 | waitPromise = q();
|
171 | }
|
172 |
|
173 | const generateOptions = {};
|
174 | let publicKeyPath;
|
175 | let privateKeyFolder;
|
176 | let privateKeyName;
|
177 |
|
178 | if (options.privateKeyName) {
|
179 |
|
180 | privateKeyName = (options.privateKeyName.match(REG_EXPS.KEY_SUFFIX))
|
181 | ? options.privateKeyName
|
182 | : `${options.privateKeyName}.key`;
|
183 | privateKeyFolder = options.privateKeyFolder || 'Common';
|
184 |
|
185 | const publicKeyName = `${privateKeyName.replace(REG_EXPS.KEY_SUFFIX, '')}`;
|
186 | publicKeyPath = `${KEYS.LOCAL_PUBLIC_KEY_DIR}${publicKeyName}.pub`;
|
187 | generateOptions.installPublic = true;
|
188 | } else {
|
189 | privateKeyName = KEYS.LOCAL_PRIVATE_KEY;
|
190 | privateKeyFolder = KEYS.LOCAL_PRIVATE_KEY_FOLDER;
|
191 | publicKeyPath = KEYS.LOCAL_PUBLIC_KEY_PATH;
|
192 | }
|
193 |
|
194 | waitPromise
|
195 | .then(() => {
|
196 | logger.info('Encrypt data to file starting.');
|
197 | ipc.send(signals.ENCRYPTION_RUNNING);
|
198 | })
|
199 | .then(() => {
|
200 | return localKeyUtil.generateAndInstallKeyPair(
|
201 | KEYS.LOCAL_PUBLIC_KEY_DIR,
|
202 | publicKeyPath,
|
203 | privateKeyFolder,
|
204 | privateKeyName,
|
205 | generateOptions
|
206 | );
|
207 | })
|
208 | .then((updatedPublicKeyPath) => {
|
209 |
|
210 |
|
211 | if (updatedPublicKeyPath) {
|
212 | publicKeyPath = updatedPublicKeyPath;
|
213 | }
|
214 |
|
215 | if (options.data) {
|
216 | return q(options.data);
|
217 | }
|
218 | return util.readDataFromFile(options.dataFile);
|
219 | })
|
220 | .then((data) => {
|
221 | logger.info('Encrypting data.');
|
222 | if (options.symmetric) {
|
223 | logger.info('Symmetric encryption');
|
224 | return cryptoUtil.symmetricEncrypt(publicKeyPath, data.toString());
|
225 | }
|
226 | return cryptoUtil.encrypt(publicKeyPath, data.toString());
|
227 | })
|
228 | .then((encryptedData) => {
|
229 | logger.info('Writing encrypted data to', options.outFile);
|
230 | const updatedData = encryptedData;
|
231 | let dataToWrite;
|
232 | if (options.symmetric) {
|
233 | updatedData.privateKey = {
|
234 | name: privateKeyName,
|
235 | folder: privateKeyFolder
|
236 | };
|
237 | dataToWrite = JSON.stringify(updatedData);
|
238 | } else {
|
239 | dataToWrite = updatedData;
|
240 | }
|
241 |
|
242 | return util.writeDataToFile(dataToWrite, options.outFile);
|
243 | })
|
244 | .catch((err) => {
|
245 | ipc.send(signals.CLOUD_LIBS_ERROR);
|
246 |
|
247 | const error = `Encryption failed: ${err.message}`;
|
248 | util.logError(error, loggerOptions);
|
249 | util.logAndExit(error, 'error', 1);
|
250 |
|
251 | exiting = true;
|
252 | })
|
253 | .done(() => {
|
254 | if (!exiting) {
|
255 | ipc.send(options.signal || signals.ENCRYPTION_DONE);
|
256 | }
|
257 |
|
258 | if (cb) {
|
259 | cb();
|
260 | }
|
261 | if (!exiting) {
|
262 | util.logAndExit('Encryption done.');
|
263 | }
|
264 | });
|
265 |
|
266 |
|
267 | ipc.once(signals.CLOUD_LIBS_ERROR)
|
268 | .then(() => {
|
269 | ipc.send(options.signal || signals.ENCRYPTION_DONE);
|
270 | util.logAndExit('ERROR signaled from other script. Exiting');
|
271 | });
|
272 | } catch (err) {
|
273 | if (logger) {
|
274 | logger.error('Encryption error:', err);
|
275 | if (cb) {
|
276 | cb(err);
|
277 | }
|
278 | } else if (cb) {
|
279 | cb(err);
|
280 | }
|
281 | }
|
282 | }
|
283 | };
|
284 |
|
285 | module.exports = runner;
|
286 |
|
287 |
|
288 |
|
289 | if (!module.parent) {
|
290 | runner.run(process.argv);
|
291 | }
|
292 | }());
|