UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * Copyright 2017-2018 F5 Networks, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17'use strict';
18
19const cryptoUtil = require('../lib/cryptoUtil');
20const assert = require('assert');
21const q = require('q');
22const options = require('commander');
23const Logger = require('../lib/logger');
24const ipc = require('../lib/ipc');
25const signals = require('../lib/signals');
26const util = require('../lib/util');
27const localKeyUtil = require('../lib/localKeyUtil');
28const KEYS = require('../lib/sharedConstants').KEYS;
29const REG_EXPS = require('../lib/sharedConstants').REG_EXPS;
30
31(function run() {
32 const runner = {
33 /**
34 * Runs the encryptDataToFile script
35 *
36 * Notes:
37 *
38 * + Only runs locally on a BIG-IP. Cannot run on a remote BIG-IP.
39 * + Uses tmsh rather than iControl REST so that we do not need to take in a password
40 *
41 * @param {String[]} argv - The process arguments
42 * @param {Function} cb - Optional cb to call when done
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 /* eslint-disable max-len */
58
59 // Can't use getCommonOptions here because we don't take host, user, password options
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 /* eslint-enable max-len */
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 // When running in cloud init, we need to exit so that cloud init can complete and
150 // allow the BIG-IP services to start
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 // Log the input, but don't log passwords
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 // Force Private Key Name to use .key suffix
180 privateKeyName = (options.privateKeyName.match(REG_EXPS.KEY_SUFFIX))
181 ? options.privateKeyName
182 : `${options.privateKeyName}.key`;
183 privateKeyFolder = options.privateKeyFolder || 'Common';
184 // If a Private Key is specified with .key suffix, replace it with '.pub'.
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 // If we installed our own public key (ie, we're not using the default
210 // locations, update our public key path)
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 // If another script has signaled an error, exit, marking ourselves as DONE
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 // If we're called from the command line, run
288 // This allows for test code to call us as a module
289 if (!module.parent) {
290 runner.run(process.argv);
291 }
292}());