1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | 'use strict';
|
18 |
|
19 | const assert = require('assert');
|
20 | const fs = require('fs');
|
21 | const childProcess = require('child_process');
|
22 | const q = require('q');
|
23 | const cryptoUtil = require('../lib/cryptoUtil');
|
24 | const util = require('../lib/util');
|
25 | const Logger = require('./logger');
|
26 | const REG_EXPS = require('../lib/sharedConstants').REG_EXPS;
|
27 |
|
28 | let logger = Logger.getLogger({
|
29 | logLevel: 'none',
|
30 | module
|
31 | });
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | module.exports = {
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | generateAndInstallKeyPair(
|
63 | publicKeyDirectory,
|
64 | publicKeyOutFile,
|
65 | privateKeyFolder,
|
66 | privateKeyName,
|
67 | options
|
68 | ) {
|
69 | const PASSPHRASE_LENGTH = 18;
|
70 | const PRIVATE_KEY_TEMP_FILE = `/tmp/cloudLibsPrivate${Date.now()}.pem`;
|
71 |
|
72 | const force = options ? options.force : false;
|
73 | const installPublic = options ? options.installPublic : false;
|
74 |
|
75 | let passphrase;
|
76 | let installedPublicKeyPath;
|
77 |
|
78 | assert.equal(typeof publicKeyDirectory, 'string', 'publicKeyDirectory must be a string');
|
79 | assert.equal(typeof publicKeyOutFile, 'string', 'publicKeyOutFile must be a string');
|
80 | assert.equal(typeof privateKeyFolder, 'string', 'privateKeyFolder must be a string');
|
81 | assert.equal(typeof privateKeyName, 'string', 'privateKeyName must be a string');
|
82 |
|
83 |
|
84 | return this.getExistingPrivateKeyName(privateKeyFolder, privateKeyName)
|
85 | .then((existingPrivateKey) => {
|
86 | if (existingPrivateKey && !force) {
|
87 | logger.debug('Private key already exists');
|
88 | if (installPublic) {
|
89 | return q(this.getKeyFilePath('Common', 'ifile', existingPrivateKey));
|
90 | }
|
91 | return q();
|
92 | }
|
93 |
|
94 | if (existingPrivateKey) {
|
95 | logger.debug('Private key exists. Regenerating.');
|
96 | } else {
|
97 | logger.debug('No private key found - generating key pair');
|
98 | }
|
99 |
|
100 | return createDirectory(publicKeyDirectory)
|
101 | .then(() => {
|
102 | return cryptoUtil.generateRandomBytes(PASSPHRASE_LENGTH, 'base64');
|
103 | })
|
104 | .then((response) => {
|
105 | passphrase = response;
|
106 | return cryptoUtil.generateKeyPair(
|
107 | PRIVATE_KEY_TEMP_FILE,
|
108 | {
|
109 | publicKeyOutFile,
|
110 | passphrase,
|
111 | keyLength: '3072'
|
112 | }
|
113 | );
|
114 | })
|
115 | .then(() => {
|
116 | return installPrivateKey(
|
117 | PRIVATE_KEY_TEMP_FILE,
|
118 | privateKeyFolder,
|
119 | privateKeyName,
|
120 | { passphrase }
|
121 | );
|
122 | })
|
123 | .then(() => {
|
124 | if (installPublic) {
|
125 | return installPublicKey.call(
|
126 | this,
|
127 | publicKeyOutFile,
|
128 | privateKeyName
|
129 | );
|
130 | }
|
131 | return q();
|
132 | })
|
133 | .then((publicKeyPath) => {
|
134 | installedPublicKeyPath = publicKeyPath;
|
135 | const func = function () {
|
136 | return util.runTmshCommand('save sys config');
|
137 | };
|
138 |
|
139 | return util.tryUntil(this, util.MEDIUM_RETRY, func);
|
140 | })
|
141 | .then(() => {
|
142 | return q(installedPublicKeyPath);
|
143 | });
|
144 | });
|
145 | },
|
146 |
|
147 | |
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | getKeyFilePath(folder, keyType, name) {
|
154 | const KEY_DIR = `/config/filestore/files_d/${folder}_d/${keyType}_d/`;
|
155 |
|
156 | assert.equal(typeof folder, 'string', 'folder must be a string');
|
157 | assert.equal(typeof name, 'string', 'name must be a string');
|
158 |
|
159 | return util.runShellCommand(`ls -1t ${KEY_DIR}`)
|
160 | .then((response) => {
|
161 | const KEY_FILE_PREFIX = `:${folder}:${name}`;
|
162 | const files = response.split('\n');
|
163 | const ourKey = files.find((element) => {
|
164 | return element.startsWith(KEY_FILE_PREFIX);
|
165 | });
|
166 | if (ourKey) {
|
167 | return KEY_DIR + ourKey;
|
168 | }
|
169 | return q();
|
170 | });
|
171 | },
|
172 |
|
173 | getPrivateKeyFilePath(folder, name) {
|
174 | return this.getKeyFilePath(folder, 'certificate_key', name);
|
175 | },
|
176 |
|
177 | |
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 | getPrivateKeyMetadata(folder, name) {
|
184 | assert.equal(typeof folder, 'string', 'folder must be a string');
|
185 | assert.equal(typeof name, 'string', 'name must be a string');
|
186 |
|
187 | return listPrivateKey('file ssl-key', folder, name)
|
188 | .then((response) => {
|
189 | return util.parseTmshResponse(response.keyData);
|
190 | });
|
191 | },
|
192 |
|
193 | getExistingPrivateKeyName(folder, name) {
|
194 | let privateKeyName;
|
195 | return listPrivateKey('crypto key', folder, name)
|
196 | .then((privateKeyResponse) => {
|
197 | if (privateKeyResponse) {
|
198 | privateKeyName = privateKeyResponse.privateKeyName;
|
199 | return this.getPrivateKeyFilePath(folder, privateKeyName);
|
200 | }
|
201 | return false;
|
202 | })
|
203 | .then((response) => {
|
204 | if (response) {
|
205 | return q(privateKeyName);
|
206 | }
|
207 | return q();
|
208 | });
|
209 | },
|
210 |
|
211 |
|
212 | setLogger(aLogger) {
|
213 | logger = aLogger;
|
214 | },
|
215 |
|
216 | setLoggerOptions(loggerOptions) {
|
217 | const loggerOpts = Object.assign({}, loggerOptions);
|
218 | loggerOpts.module = module;
|
219 | logger = Logger.getLogger(loggerOpts);
|
220 | }
|
221 | };
|
222 |
|
223 | function installPrivateKey(privateKeyFile, folder, name, options) {
|
224 | const deferred = q.defer();
|
225 | let installCmd;
|
226 |
|
227 | const passphrase = options ? options.passphrase : undefined;
|
228 |
|
229 | installCmd = `install sys crypto key /${folder}/${name} from-local-file ${privateKeyFile}`;
|
230 | if (passphrase) {
|
231 | installCmd += ` passphrase ${passphrase}`;
|
232 | }
|
233 |
|
234 | ready()
|
235 | .then(() => {
|
236 | return createBigIpFolder(`/${folder}`);
|
237 | })
|
238 | .then(() => {
|
239 | return util.runTmshCommand(installCmd);
|
240 | })
|
241 | .then(() => {
|
242 | fs.unlink(privateKeyFile, (err) => {
|
243 | if (err) {
|
244 | logger.debug('Failed to delete private key:', err);
|
245 | }
|
246 |
|
247 | deferred.resolve();
|
248 | });
|
249 | })
|
250 | .catch((err) => {
|
251 | deferred.reject(err);
|
252 | });
|
253 |
|
254 | return deferred.promise;
|
255 | }
|
256 |
|
257 | function installPublicKey(publicKeyPath, name) {
|
258 | return ready()
|
259 | .then(() => {
|
260 | return util.runTmshCommand(`create sys file ifile ${name} source-path file://${publicKeyPath}`);
|
261 | })
|
262 | .then(() => {
|
263 | return this.getKeyFilePath('Common', 'ifile', name);
|
264 | })
|
265 | .catch((err) => {
|
266 | return q.reject(err);
|
267 | });
|
268 | }
|
269 |
|
270 | function ready() {
|
271 | const deferred = q.defer();
|
272 |
|
273 | childProcess.execFile(`${__dirname}/../scripts/waitForMcp.sh`, (error) => {
|
274 | if (error) {
|
275 | deferred.reject(error);
|
276 | } else {
|
277 | deferred.resolve();
|
278 | }
|
279 | });
|
280 |
|
281 | return deferred.promise;
|
282 | }
|
283 |
|
284 | function createDirectory(directory) {
|
285 | const deferred = q.defer();
|
286 |
|
287 | fs.access(directory, (fsAccessErr) => {
|
288 | if (fsAccessErr) {
|
289 | fs.mkdir(directory, (mkdirErr) => {
|
290 | if (mkdirErr) {
|
291 | deferred.reject(mkdirErr);
|
292 | } else {
|
293 | deferred.resolve();
|
294 | }
|
295 | });
|
296 | } else {
|
297 | deferred.resolve();
|
298 | }
|
299 | });
|
300 |
|
301 | return deferred.promise;
|
302 | }
|
303 |
|
304 | function createBigIpFolder(folder) {
|
305 | return folderExists(folder)
|
306 | .then((exists) => {
|
307 | if (exists) {
|
308 | return q();
|
309 | }
|
310 | return util.runTmshCommand(`create sys folder ${folder} device-group none traffic-group none`);
|
311 | });
|
312 | }
|
313 |
|
314 | function folderExists(folder) {
|
315 |
|
316 | return util.runTmshCommand(`list sys folder ${folder}`)
|
317 | .then(() => {
|
318 | return q(true);
|
319 | })
|
320 | .catch(() => {
|
321 | return q(false);
|
322 | });
|
323 | }
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 | function listPrivateKey(keyType, folder, name, noRetry) {
|
344 | let privateKeyName;
|
345 |
|
346 |
|
347 | if (noRetry) {
|
348 |
|
349 | privateKeyName = name.replace(REG_EXPS.KEY_SUFFIX, '');
|
350 | } else {
|
351 |
|
352 | privateKeyName = (name.match(REG_EXPS.KEY_SUFFIX)) ? name : `${name}.key`;
|
353 | }
|
354 |
|
355 | return util.runTmshCommand(`list sys ${keyType} /${folder}/${privateKeyName}`)
|
356 | .then((keyData) => {
|
357 |
|
358 | if (!keyData) {
|
359 | if (!noRetry) {
|
360 | return listPrivateKey(keyType, folder, name, true);
|
361 | }
|
362 | return q();
|
363 | }
|
364 | return q({ privateKeyName, keyData });
|
365 | })
|
366 | .catch((err) => {
|
367 |
|
368 | const notFoundRegex = /01020036:3/;
|
369 | if (err.message.match(notFoundRegex) && !noRetry) {
|
370 | return listPrivateKey(keyType, folder, name, true);
|
371 | }
|
372 | return q.reject(err);
|
373 | });
|
374 | }
|