1 | /**
|
2 | * Copyright 2016-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 | ;
|
18 |
|
19 | const q = require('q');
|
20 | const BigIp = require('../lib/bigIp');
|
21 | const Logger = require('../lib/logger');
|
22 | const ActiveError = require('../lib/activeError');
|
23 | const ipc = require('../lib/ipc');
|
24 | const signals = require('../lib/signals');
|
25 | const util = require('../lib/util');
|
26 | const metricsCollector = require('../lib/metricsCollector');
|
27 | const commonOptions = require('./commonOptions');
|
28 | const cloudProviderFactory = require('../lib/cloudProviderFactory');
|
29 | const localCryptoUtil = require('../lib/localCryptoUtil');
|
30 | const cryptoUtil = require('../lib/cryptoUtil');
|
31 |
|
32 | (function run() {
|
33 | const runner = {
|
34 | /**
|
35 | * Runs the onboarding script
|
36 | *
|
37 | * @param {String[]} argv - The process arguments
|
38 | * @param {Object} testOpts - Options used during testing
|
39 | * @param {Object} testOpts.bigIp - BigIp object to use for testing
|
40 | * @param {Function} cb - Optional cb to call when done
|
41 | */
|
42 | run(argv, testOpts, cb) {
|
43 | const DEFAULT_LOG_FILE = '/tmp/onboard.log';
|
44 | const ARGS_FILE_ID = `onboard_${Date.now()}`;
|
45 | const ARGS_TO_STRIP = ['--wait-for'];
|
46 |
|
47 | const KEYS_TO_MASK = [
|
48 | '-p',
|
49 | '--password',
|
50 | '--set-password',
|
51 | '--set-root-password',
|
52 | '--big-iq-password'
|
53 | ];
|
54 | const OPTIONS_TO_UNDEFINE = [
|
55 | 'bigIqPasswordUri',
|
56 | 'bigIqPassword',
|
57 | 'metrics',
|
58 | 'password',
|
59 | 'passwordUrl',
|
60 | 'skuKeyword1',
|
61 | 'skuKeyword2',
|
62 | 'unitOfMeasure',
|
63 | 'tenant'
|
64 | ];
|
65 | const REQUIRED_OPTIONS = ['host'];
|
66 | const globalSettings = {};
|
67 | const dbVars = {};
|
68 | const provisionModules = {};
|
69 | const provisionModule = {};
|
70 | const rootPasswords = {};
|
71 | const updateUsers = [];
|
72 | const loggerOptions = {};
|
73 | const metrics = {};
|
74 | const createRegKeyPool = {};
|
75 | const createLicensePool = {};
|
76 | const optionsForTest = {};
|
77 |
|
78 | const providerOptions = {};
|
79 |
|
80 | let loggableArgs;
|
81 | let logger;
|
82 | let logFileName;
|
83 | let bigIp;
|
84 | let rebooting;
|
85 | let exiting;
|
86 | let index;
|
87 | let randomUser;
|
88 |
|
89 | let provider;
|
90 | let bigIqPasswordData;
|
91 |
|
92 | Object.assign(optionsForTest, testOpts);
|
93 |
|
94 | /**
|
95 | * Special case of util.pair. Used to parse root password options in the form of
|
96 | * old:oldRootPassword,new:newRootPassword
|
97 | * Since passwords can contain any character, a delimiter is difficult to find.
|
98 | * Compromise by looking for ',new:' as a delimeter
|
99 | */
|
100 | const parseRootPasswords = function (passwordsValue) {
|
101 | const set = passwordsValue.split(',new:');
|
102 |
|
103 | if (set.length === 2) {
|
104 | rootPasswords.old = set[0].split('old:')[1];
|
105 | rootPasswords.new = set[1];
|
106 | }
|
107 | };
|
108 |
|
109 | /**
|
110 | * Control whether to load f5-cloud-libs-{provider} library.
|
111 | * There are cases where the cloud name is needed, but a cloud provider is not.
|
112 | */
|
113 | const shouldLoadProviderLibrary = function (options) {
|
114 | return (!!options.signalResource);
|
115 | };
|
116 |
|
117 | try {
|
118 | /* eslint-disable max-len */
|
119 | const options = commonOptions.getCommonOptions(DEFAULT_LOG_FILE)
|
120 | .option(
|
121 | '--ntp <ntp_server>',
|
122 | 'Set NTP server. For multiple NTP servers, use multiple --ntp entries.',
|
123 | util.collect,
|
124 | []
|
125 | )
|
126 | .option(
|
127 | '--tz <timezone>',
|
128 | 'Set timezone for NTP setting.'
|
129 | )
|
130 | .option(
|
131 | '--dns <DNS server>',
|
132 | 'Set DNS server. For multiple DNS severs, use multiple --dns entries.',
|
133 | util.collect,
|
134 | []
|
135 | )
|
136 | .option(
|
137 | '--ssl-port <ssl_port>', 'Set the SSL port for the management IP',
|
138 | parseInt
|
139 | )
|
140 | .option(
|
141 | '-l, --license <license_key>',
|
142 | 'License device with <license_key>.'
|
143 | )
|
144 | .option(
|
145 | '-a, --add-on <add_on_key>',
|
146 | 'License device with <add_on_key>. For multiple keys, use multiple -a entries.',
|
147 | util.collect,
|
148 | []
|
149 | )
|
150 | .option(
|
151 | '--cloud <provider>',
|
152 | 'Cloud provider (aws | azure | etc.). This is required if licensing via BIG-IQ 5.4+ is being used, signalling resource provisioned, or providing a primary passphrase'
|
153 | )
|
154 | .option(
|
155 | '--provider-options <cloud_options>',
|
156 | 'Options specific to cloud_provider. Ex: param1:value1,param2:value2',
|
157 | util.map,
|
158 | providerOptions
|
159 | )
|
160 | .option(
|
161 | '--license-pool',
|
162 | 'License BIG-IP from a BIG-IQ license pool. Supply the following:'
|
163 | )
|
164 | .option(
|
165 | ' --big-iq-host <ip_address or FQDN>',
|
166 | ' IP address or FQDN of BIG-IQ'
|
167 | )
|
168 | .option(
|
169 | ' --big-iq-user <user>',
|
170 | ' BIG-IQ admin user name'
|
171 | )
|
172 | .option(
|
173 | ' --big-iq-password [password]',
|
174 | ' BIG-IQ admin user password.'
|
175 | )
|
176 | .option(
|
177 | ' --big-iq-password-uri [password_uri]',
|
178 | ' URI (file, http(s), arn) to location that contains BIG-IQ admin user password. Use this or --big-iq-password.'
|
179 | )
|
180 | .option(
|
181 | ' --big-iq-password-encrypted',
|
182 | ' Indicates that the BIG-IQ password is encrypted.'
|
183 | )
|
184 | .option(
|
185 | ' --license-pool-name <pool_name>',
|
186 | ' Name of BIG-IQ license pool.'
|
187 | )
|
188 | .option(
|
189 | ' --sku-keyword-1 [sku_keyword_1]',
|
190 | ' skuKeyword1 parameter for CLPv2 licensing. Default none.'
|
191 | )
|
192 | .option(
|
193 | ' --sku-keyword-2 [sku_keyword_2]',
|
194 | ' skuKeyword2 parameter for CLPv2 licensing. Default none.'
|
195 | )
|
196 | .option(
|
197 | ' --unit-of-measure [unit_of_measure]',
|
198 | ' unitOfMeasure parameter for CLPv2 licensing. Default none.'
|
199 | )
|
200 | .option(
|
201 | ' --tenant [tenant]',
|
202 | ' tenant parameter for CLPv2 licensing. Default none.'
|
203 | )
|
204 | .option(
|
205 | ' --big-ip-mgmt-address <big_ip_address>',
|
206 | ' IP address or FQDN of BIG-IP management port. Use this if BIG-IP reports an address not reachable from BIG-IQ.'
|
207 | )
|
208 | .option(
|
209 | ' --big-ip-mgmt-port <big_ip_port>',
|
210 | ' Port for the management address. Use this if the BIG-IP is not reachable from BIG-IQ via the port used in --port'
|
211 | )
|
212 | .option(
|
213 | ' --no-unreachable',
|
214 | ' Do not use the unreachable API even if it is supported by BIG-IQ.'
|
215 | )
|
216 | .option(
|
217 | ' --revoke',
|
218 | ' Request BIG-IQ to revoke this units license rather than granting one.'
|
219 | )
|
220 | .option(
|
221 | '--signal-resource',
|
222 | 'Signal cloud provider when BIG-IP has been provisioned.'
|
223 | )
|
224 | .option(
|
225 | '--big-iq-password-data-uri <key_uri>',
|
226 | 'URI (arn, url, etc.) to a JSON file containing the BIG-IQ passwords (required keys: admin, root, primarypassphrase)'
|
227 | )
|
228 | .option(
|
229 | ' --big-iq-password-data-encrypted',
|
230 | ' Indicates that the BIG-IQ password data is encrypted (either with encryptDataToFile or generatePassword)'
|
231 | )
|
232 | .option(
|
233 | '-n, --hostname <hostname>',
|
234 | 'Set device hostname.'
|
235 | )
|
236 | .option(
|
237 | '-g, --global-setting <name:value>',
|
238 | 'Set global setting <name> to <value>. For multiple settings, use multiple -g entries.',
|
239 | util.pair,
|
240 | globalSettings
|
241 | )
|
242 | .option(
|
243 | '-d, --db <name:value>',
|
244 | 'Set db variable <name> to <value>. For multiple settings, use multiple -d entries.',
|
245 | util.pair,
|
246 | dbVars
|
247 | )
|
248 | .option(
|
249 | '--set-root-password <old:old_password,new:new_password>',
|
250 | 'Set the password for the root user from <old_password> to <new_password>.',
|
251 | parseRootPasswords
|
252 | )
|
253 | .option(
|
254 | '--set-primary-key',
|
255 | 'If running on a BIG-IQ, set the primary key with a random passphrase'
|
256 | )
|
257 | .option(
|
258 | '--create-license-pool <name:reg_key>',
|
259 | 'If running on a BIG-IQ, create a pool-style license (purchased pool, utility, volume, or FPS) with the name and reg key.',
|
260 | util.pair,
|
261 | createLicensePool
|
262 | )
|
263 | .option(
|
264 | '--create-reg-key-pool <name:reg_key_list>',
|
265 | 'If running on a BIG-IQ, create a reg key pool with the given name and reg keys. Reg keys should be comma separated.',
|
266 | util.pair,
|
267 | createRegKeyPool
|
268 | )
|
269 | .option(
|
270 | '--update-user <user:user,password:password,passwordUrl:passwordUrl,role:role,shell:shell>',
|
271 | 'Update user password (or password from passwordUrl), or create user with password, role, and shell. Role and shell are only valid on create.',
|
272 | util.mapArray,
|
273 | updateUsers
|
274 | )
|
275 | .option(
|
276 | '-m, --module <name:level>',
|
277 | 'Provision module <name> to <level>. For multiple entries, use --modules',
|
278 | util.pair,
|
279 | provisionModule
|
280 | )
|
281 | .option(
|
282 | '--modules <name:level>',
|
283 | 'Provision module(s) <name> to <level> (comma-separated list of module:level pairs).',
|
284 | util.map,
|
285 | provisionModules
|
286 | )
|
287 | .option(
|
288 | '--install-ilx-package <package_uri>',
|
289 | 'URI (file) of an iControl LX/iApps LX package to install. The package must already exist at this location.',
|
290 | util.collect,
|
291 | []
|
292 | )
|
293 | .option(
|
294 | '--ping [address]',
|
295 | 'Do a ping at the end of onboarding to verify that the network is up. Default address is f5.com'
|
296 | )
|
297 | .option(
|
298 | '--update-sigs',
|
299 | 'Update ASM signatures'
|
300 | )
|
301 | .option(
|
302 | '--metrics [customerId:unique_id, deploymentId:deployment_id, templateName:template_name, templateVersion:template_version, cloudName:[aws | azure | gce | etc.], region:region, bigIpVersion:big_ip_version, licenseType:[byol | payg]]',
|
303 | 'Optional usage metrics to collect. Customer ID should not identify a specific customer.',
|
304 | util.map,
|
305 | metrics
|
306 | )
|
307 | .option(
|
308 | '--force-reboot',
|
309 | 'Force a reboot at the end. This may be necessary for certain configurations. Option --force-reboot and --no-reboot cannot be specified simultaneously.'
|
310 | )
|
311 | .parse(argv);
|
312 | /* eslint-enable max-len */
|
313 |
|
314 | loggerOptions.console = options.console;
|
315 | loggerOptions.logLevel = options.logLevel;
|
316 | loggerOptions.module = module;
|
317 |
|
318 | if (options.output) {
|
319 | loggerOptions.fileName = options.output;
|
320 | }
|
321 |
|
322 | if (options.errorFile) {
|
323 | loggerOptions.errorFile = options.errorFile;
|
324 | }
|
325 |
|
326 | logger = Logger.getLogger(loggerOptions);
|
327 | ipc.setLoggerOptions(loggerOptions);
|
328 | util.setLoggerOptions(loggerOptions);
|
329 | metricsCollector.setLoggerOptions(loggerOptions);
|
330 |
|
331 | // Remove specific options with no provided value
|
332 | OPTIONS_TO_UNDEFINE.forEach((opt) => {
|
333 | if (typeof options[opt] === 'boolean') {
|
334 | logger.debug(`No value set for option ${opt}. Removing option.`);
|
335 | options[opt] = undefined;
|
336 | }
|
337 | });
|
338 |
|
339 | // Log the input, but don't log passwords
|
340 | loggableArgs = argv.slice();
|
341 | for (let i = 0; i < loggableArgs.length; i++) {
|
342 | if (KEYS_TO_MASK.indexOf(loggableArgs[i]) !== -1) {
|
343 | loggableArgs[i + 1] = '*******';
|
344 | }
|
345 | }
|
346 | index = loggableArgs.indexOf('--update-user');
|
347 | if (index !== -1) {
|
348 | loggableArgs[index + 1] = loggableArgs[index + 1].replace(/password:([^,])+/, '*******');
|
349 | }
|
350 | logger.info(`${loggableArgs[1]} called with`, loggableArgs.join(' '));
|
351 |
|
352 | for (let i = 0; i < REQUIRED_OPTIONS.length; i++) {
|
353 | if (!options[REQUIRED_OPTIONS[i]]) {
|
354 | const error = `${REQUIRED_OPTIONS[i]} is a required command line option.`;
|
355 |
|
356 | ipc.send(signals.CLOUD_LIBS_ERROR);
|
357 |
|
358 | util.logError(error, loggerOptions);
|
359 | util.logAndExit(error, 'error', 1);
|
360 | }
|
361 | }
|
362 |
|
363 | if (options.forceReboot && !options.reboot) {
|
364 | const error = 'Option --force-reboot and --no-reboot cannot be specified simultaneously.';
|
365 |
|
366 | ipc.send(signals.CLOUD_LIBS_ERROR);
|
367 |
|
368 | util.logError(error, loggerOptions);
|
369 | util.logAndExit(error, 'error', 1);
|
370 | }
|
371 |
|
372 | if (options.user && !(options.password || options.passwordUrl)) {
|
373 | const error = 'If specifying --user, --password or --password-url is required.';
|
374 |
|
375 | ipc.send(signals.CLOUD_LIBS_ERROR);
|
376 |
|
377 | util.logError(error, loggerOptions);
|
378 | util.logAndExit(error, 'error', 1);
|
379 | }
|
380 |
|
381 | // When running in cloud init, we need to exit so that cloud init can complete and
|
382 | // allow the device services to start
|
383 | if (options.background) {
|
384 | logFileName = options.output || DEFAULT_LOG_FILE;
|
385 | logger.info('Spawning child process to do the work. Output will be in', logFileName);
|
386 | util.runInBackgroundAndExit(process, logFileName);
|
387 | }
|
388 |
|
389 | // Use hostname if both hostname and global-settings hostname are set
|
390 | if (globalSettings && options.hostname) {
|
391 | if (globalSettings.hostname || globalSettings.hostName) {
|
392 | logger.info('Using host-name option to override global-settings hostname');
|
393 | delete globalSettings.hostName;
|
394 | delete globalSettings.hostname;
|
395 | }
|
396 | }
|
397 |
|
398 | // Check whether a cloud provider is required
|
399 | if (options.cloud && shouldLoadProviderLibrary(options)) {
|
400 | provider = optionsForTest.cloudProvider;
|
401 | if (!provider) {
|
402 | provider = cloudProviderFactory.getCloudProvider(
|
403 | options.cloud,
|
404 | {
|
405 | loggerOptions
|
406 | }
|
407 | );
|
408 | }
|
409 | }
|
410 | // Start processing...
|
411 |
|
412 | // Save args in restart script in case we need to reboot to recover from an error
|
413 | util.saveArgs(argv, ARGS_FILE_ID)
|
414 | .then(() => {
|
415 | if (options.waitFor) {
|
416 | logger.info('Waiting for', options.waitFor);
|
417 | return ipc.once(options.waitFor);
|
418 | }
|
419 |
|
420 | return q();
|
421 | })
|
422 | .then(() => {
|
423 | // Whatever we're waiting for is done, so don't wait for
|
424 | // that again in case of a reboot
|
425 | return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
|
426 | })
|
427 | .then(() => {
|
428 | if (provider) {
|
429 | logger.info('Initializing cloud provider');
|
430 | return provider.init(providerOptions);
|
431 | }
|
432 | return q();
|
433 | })
|
434 | .then(() => {
|
435 | logger.info('Onboard starting.');
|
436 | ipc.send(signals.ONBOARD_RUNNING);
|
437 |
|
438 | // Retrieve, and save, stored password data
|
439 | if (options.bigIqPasswordDataUri) {
|
440 | return util.readData(options.bigIqPasswordDataUri,
|
441 | true,
|
442 | {
|
443 | clOptions: providerOptions,
|
444 | logger,
|
445 | loggerOptions
|
446 | })
|
447 | .then((uriData) => {
|
448 | if (options.bigIqPasswordDataEncrypted) {
|
449 | return localCryptoUtil.decryptPassword(uriData);
|
450 | }
|
451 | return q(uriData);
|
452 | })
|
453 | .then((uriData) => {
|
454 | const parsedData = (typeof uriData === 'string')
|
455 | ? JSON.parse(uriData.trim())
|
456 | : uriData;
|
457 | bigIqPasswordData = util.lowerCaseKeys(
|
458 | parsedData
|
459 | );
|
460 | })
|
461 | .then(() => {
|
462 | if (!bigIqPasswordData.admin
|
463 | || !bigIqPasswordData.root
|
464 | || !bigIqPasswordData.primarypassphrase
|
465 | ) {
|
466 | const msg =
|
467 | 'Required passwords missing from --biq-iq-password-data-uri';
|
468 | logger.info(msg);
|
469 | return q.reject(msg);
|
470 | }
|
471 | return q();
|
472 | })
|
473 | .catch((err) => {
|
474 | logger.info('Unable to retrieve JSON from --big-iq-password-data-uri');
|
475 | return q.reject(err);
|
476 | });
|
477 | }
|
478 | return q();
|
479 | })
|
480 | .then(() => {
|
481 | if (!options.user) {
|
482 | logger.info('Generating temporary user.');
|
483 | return cryptoUtil.nextRandomUser();
|
484 | }
|
485 |
|
486 | return q(
|
487 | {
|
488 | user: options.user,
|
489 | password: options.password || options.passwordUrl
|
490 | }
|
491 | );
|
492 | })
|
493 | .then((credentials) => {
|
494 | randomUser = credentials.user; // we need this info later to delete it
|
495 |
|
496 | // Create the bigIp client object
|
497 | bigIp = optionsForTest.bigIp || new BigIp({ loggerOptions });
|
498 |
|
499 | logger.info('Initializing device.');
|
500 | return bigIp.init(
|
501 | options.host,
|
502 | credentials.user,
|
503 | credentials.password,
|
504 | {
|
505 | port: options.port,
|
506 | passwordIsUrl: typeof options.passwordUrl !== 'undefined',
|
507 | passwordEncrypted: options.passwordEncrypted,
|
508 | clOptions: providerOptions
|
509 | }
|
510 | );
|
511 | })
|
512 | .then(() => {
|
513 | logger.info('Waiting for device to be ready.');
|
514 | return bigIp.ready();
|
515 | })
|
516 | .then(() => {
|
517 | logger.info('Device is ready.');
|
518 | // Set admin password
|
519 | if (options.bigIqPasswordDataUri) {
|
520 | return bigIp.onboard.updateUser('admin', bigIqPasswordData.admin);
|
521 | }
|
522 | return q();
|
523 | })
|
524 | .then(() => {
|
525 | const deferred = q.defer();
|
526 | // Set the PrimaryKey if it's not set, using either a random passphrase or
|
527 | // a passphrase provided via --password-data-uri
|
528 | if (bigIp.isBigIq()) {
|
529 | bigIp.onboard.isPrimaryKeySet()
|
530 | .then((isSet) => {
|
531 | if (isSet) {
|
532 | logger.info('Primary key is already set.');
|
533 | deferred.resolve();
|
534 | } else if (options.setPrimaryKey) {
|
535 | logger.info('Setting primary key.');
|
536 |
|
537 | bigIp.onboard.setRandomPrimaryPassphrase()
|
538 | .then(() => {
|
539 | deferred.resolve();
|
540 | })
|
541 | .catch((err) => {
|
542 | logger.info(
|
543 | 'Unable to set primary key',
|
544 | err && err.message ? err.message : err
|
545 | );
|
546 | deferred.reject(err);
|
547 | });
|
548 | } else if (bigIqPasswordData) {
|
549 | logger.info('Setting primary passphrase from password data uri');
|
550 | bigIp.onboard.setPrimaryPassphrase(
|
551 | bigIqPasswordData.primarypassphrase
|
552 | )
|
553 | .then(() => {
|
554 | deferred.resolve();
|
555 | })
|
556 | .catch((err) => {
|
557 | logger.info(
|
558 | 'Unable to set primary passphrase',
|
559 | err && err.message ? err.message : err
|
560 | );
|
561 | deferred.reject(err);
|
562 | });
|
563 | }
|
564 | })
|
565 | .catch((err) => {
|
566 | logger.info(
|
567 | 'Unable to check primary key', err && err.message ? err.message : err
|
568 | );
|
569 | deferred.reject(err);
|
570 | });
|
571 | } else {
|
572 | deferred.resolve();
|
573 | }
|
574 |
|
575 | return deferred.promise;
|
576 | })
|
577 | .then(() => {
|
578 | if (options.sslPort) {
|
579 | logger.info('Setting SSL port.');
|
580 | return bigIp.onboard.sslPort(options.sslPort);
|
581 | }
|
582 |
|
583 | return q();
|
584 | })
|
585 | .then((response) => {
|
586 | let portIndex;
|
587 |
|
588 | logger.debug(response);
|
589 |
|
590 | // If we just successfully changed the SSL port, save --port
|
591 | // as an argument in case we reboot
|
592 | if (options.sslPort) {
|
593 | // If there is already a port argument, remove it
|
594 | if (options.port) {
|
595 | portIndex = argv.indexOf('--port');
|
596 | if (portIndex !== -1) {
|
597 | argv.splice(portIndex, 2);
|
598 | }
|
599 | }
|
600 | argv.push('--port', options.sslPort);
|
601 | return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
|
602 | }
|
603 |
|
604 | return q();
|
605 | })
|
606 | .then((response) => {
|
607 | logger.debug(response);
|
608 |
|
609 | if (Object.keys(rootPasswords).length > 0) {
|
610 | if (!rootPasswords.old || !rootPasswords.new) {
|
611 | return q.reject('Old or new password missing for root user.');
|
612 | }
|
613 |
|
614 | logger.info('Setting rootPassword.');
|
615 | return bigIp.onboard.password('root', rootPasswords.new, rootPasswords.old);
|
616 | }
|
617 |
|
618 | return q();
|
619 | })
|
620 | .then((response) => {
|
621 | logger.debug(response);
|
622 |
|
623 | if (options.cloud && options.bigIqPasswordDataUri) {
|
624 | logger.info('Setting root password');
|
625 | return bigIp.onboard.setRootPassword(
|
626 | bigIqPasswordData.root,
|
627 | undefined,
|
628 | { enableRoot: true }
|
629 | );
|
630 | }
|
631 | return q();
|
632 | })
|
633 | .then((response) => {
|
634 | const promises = [];
|
635 |
|
636 | logger.debug(response);
|
637 |
|
638 | if (updateUsers.length > 0) {
|
639 | for (let i = 0; i < updateUsers.length; i++) {
|
640 | logger.info('Updating user', updateUsers[i].user);
|
641 | promises.push(bigIp.onboard.updateUser(
|
642 | updateUsers[i].user,
|
643 | updateUsers[i].password || updateUsers[i].passwordUrl,
|
644 | updateUsers[i].role,
|
645 | updateUsers[i].shell,
|
646 | {
|
647 | passwordIsUrl: typeof updateUsers[i].passwordUrl !== 'undefined'
|
648 | }
|
649 | ));
|
650 | }
|
651 | return q.all(promises);
|
652 | }
|
653 |
|
654 | return q();
|
655 | })
|
656 | .then((response) => {
|
657 | let ntpBody;
|
658 |
|
659 | logger.debug(response);
|
660 |
|
661 | if (options.ntp.length > 0 || options.tz) {
|
662 | logger.info('Setting up NTP.');
|
663 |
|
664 | ntpBody = {};
|
665 |
|
666 | if (options.ntp && options.ntp.length > 0) {
|
667 | ntpBody.servers = options.ntp;
|
668 | }
|
669 |
|
670 | if (options.tz) {
|
671 | ntpBody.timezone = options.tz;
|
672 | }
|
673 |
|
674 | return bigIp.modify(
|
675 | '/tm/sys/ntp',
|
676 | ntpBody
|
677 | );
|
678 | }
|
679 |
|
680 | return q();
|
681 | })
|
682 | .then((response) => {
|
683 | logger.debug(response);
|
684 |
|
685 | if (options.dns.length > 0) {
|
686 | logger.info('Setting up DNS.');
|
687 |
|
688 | return bigIp.modify(
|
689 | '/tm/sys/dns',
|
690 | {
|
691 | 'name-servers': options.dns
|
692 | }
|
693 | );
|
694 | }
|
695 |
|
696 | return q();
|
697 | })
|
698 | .then((response) => {
|
699 | logger.debug(response);
|
700 |
|
701 | if (options.hostname) {
|
702 | logger.info('Setting hostname to', options.hostname);
|
703 | return bigIp.onboard.hostname(options.hostname);
|
704 | }
|
705 |
|
706 | return q();
|
707 | })
|
708 | .then((response) => {
|
709 | logger.debug(response);
|
710 |
|
711 | // BIG-IP and BIG-IQ disable their setup gui differently.
|
712 | // We'll take care of BIG-IQ at the end of onboarding.
|
713 | if (bigIp.isBigIp()) {
|
714 | globalSettings.guiSetup = 'disabled';
|
715 | }
|
716 |
|
717 | if (Object.keys(globalSettings).length > 0) {
|
718 | logger.info('Setting global settings.');
|
719 | return bigIp.onboard.globalSettings(globalSettings);
|
720 | }
|
721 |
|
722 | return q();
|
723 | })
|
724 | .then((response) => {
|
725 | logger.debug(response);
|
726 |
|
727 | if (Object.keys(dbVars).length > 0) {
|
728 | logger.info('Setting DB vars.');
|
729 | return bigIp.onboard.setDbVars(dbVars);
|
730 | }
|
731 |
|
732 | return q();
|
733 | })
|
734 | .then((response) => {
|
735 | logger.debug(response);
|
736 |
|
737 | const registrationKey = options.license;
|
738 | const addOnKeys = options.addOn;
|
739 |
|
740 | if (registrationKey || addOnKeys.length > 0) {
|
741 | logger.info('Licensing.');
|
742 |
|
743 | return bigIp.onboard.license(
|
744 | {
|
745 | registrationKey,
|
746 | addOnKeys,
|
747 | overwrite: true
|
748 | }
|
749 | );
|
750 | } else if (options.licensePool) {
|
751 | if (
|
752 | !options.bigIqHost ||
|
753 | !options.bigIqUser ||
|
754 | !(options.bigIqPassword || options.bigIqPasswordUri) ||
|
755 | !options.licensePoolName
|
756 | ) {
|
757 | return q.reject(new Error('Missing parameters for BIG-IQ license pool'));
|
758 | }
|
759 |
|
760 | if (options.revoke) {
|
761 | logger.info('Requesting BIG-IQ to revoke licnse.');
|
762 | return bigIp.onboard.revokeLicenseViaBigIq(
|
763 | options.bigIqHost,
|
764 | options.bigIqUser,
|
765 | options.bigIqPassword || options.bigIqPasswordUri,
|
766 | options.licensePoolName,
|
767 | {
|
768 | passwordIsUri: typeof options.bigIqPasswordUri !== 'undefined',
|
769 | passwordEncrypted: options.bigIqPasswordEncrypted,
|
770 | bigIpMgmtAddress: options.bigIpMgmtAddress,
|
771 | bigIpMgmtPort: options.bigIpMgmtPort,
|
772 | noUnreachable: !options.unreachable
|
773 | }
|
774 | );
|
775 | }
|
776 |
|
777 | logger.info('Getting license from BIG-IQ license pool.');
|
778 | return bigIp.onboard.licenseViaBigIq(
|
779 | options.bigIqHost,
|
780 | options.bigIqUser,
|
781 | options.bigIqPassword || options.bigIqPasswordUri,
|
782 | options.licensePoolName,
|
783 | options.cloud,
|
784 | {
|
785 | passwordIsUri: typeof options.bigIqPasswordUri !== 'undefined',
|
786 | passwordEncrypted: options.bigIqPasswordEncrypted,
|
787 | bigIpMgmtAddress: options.bigIpMgmtAddress,
|
788 | bigIpMgmtPort: options.bigIpMgmtPort,
|
789 | skuKeyword1: options.skuKeyword1,
|
790 | skuKeyword2: options.skuKeyword2,
|
791 | unitOfMeasure: options.unitOfMeasure,
|
792 | tenant: options.tenant,
|
793 | noUnreachable: !options.unreachable
|
794 | }
|
795 | );
|
796 | }
|
797 |
|
798 | return q();
|
799 | })
|
800 | .then((response) => {
|
801 | logger.debug(response);
|
802 |
|
803 | if (Object.keys(provisionModule).length > 0) {
|
804 | logger.info('Provisioning module', provisionModule);
|
805 | return bigIp.onboard.provision(provisionModule);
|
806 | }
|
807 |
|
808 | return q();
|
809 | })
|
810 | .then((response) => {
|
811 | logger.debug(response);
|
812 |
|
813 | if (Object.keys(provisionModules).length > 0) {
|
814 | logger.info('Provisioning modules', provisionModules);
|
815 | return bigIp.onboard.provision(provisionModules);
|
816 | }
|
817 |
|
818 | return q();
|
819 | })
|
820 | .then((response) => {
|
821 | logger.debug(response);
|
822 |
|
823 | if (options.updateSigs) {
|
824 | logger.info('Updating ASM signatures');
|
825 | return bigIp.create(
|
826 | '/tm/asm/tasks/update-signatures',
|
827 | {}
|
828 | );
|
829 | }
|
830 |
|
831 | return q();
|
832 | })
|
833 | .then((response) => {
|
834 | logger.debug(response);
|
835 |
|
836 | const names = Object.keys(createLicensePool);
|
837 | if (names.length > 0 && bigIp.isBigIq()) {
|
838 | const regKey = createLicensePool[names[0]];
|
839 | logger.info('Creating license pool.');
|
840 | return bigIp.onboard.createLicensePool(names[0], regKey);
|
841 | }
|
842 | return q();
|
843 | })
|
844 | .then((response) => {
|
845 | logger.debug(response);
|
846 |
|
847 | const names = Object.keys(createRegKeyPool);
|
848 | if (names.length > 0 && bigIp.isBigIq()) {
|
849 | const regKeyCsv = createRegKeyPool[names[0]];
|
850 | const regKeys = regKeyCsv.split(',');
|
851 | logger.info('Creating reg key pool.');
|
852 | return bigIp.onboard.createRegKeyPool(names[0], regKeys);
|
853 | }
|
854 | return q();
|
855 | })
|
856 | .then((response) => {
|
857 | logger.debug(response);
|
858 |
|
859 | if (options.installIlxPackage) {
|
860 | const packages = options.installIlxPackage;
|
861 | const promises = [];
|
862 |
|
863 | if (packages.length > 0) {
|
864 | packages.forEach((packagePath) => {
|
865 | promises.push(bigIp.onboard.installIlxPackage(packagePath));
|
866 | });
|
867 | return q.all(promises);
|
868 | }
|
869 | }
|
870 | return q();
|
871 | })
|
872 | .then(() => {
|
873 | // Have installed the ilx package(s); strip out args, including --install-ilx-package
|
874 | ARGS_TO_STRIP.push('--install-ilx-package');
|
875 | return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
|
876 | })
|
877 | .then(() => {
|
878 | if (bigIp.isBigIq()) {
|
879 | // Disable the BIG-IQ setup gui. BIG-IP is done
|
880 | // via global settings and is handled with other global
|
881 | // settings above
|
882 | return bigIp.modify(
|
883 | '/shared/system/setup',
|
884 | {
|
885 | isSystemSetup: true,
|
886 | isRootPasswordChanged: true,
|
887 | isAdminPasswordChanged: true
|
888 | }
|
889 | );
|
890 | }
|
891 | return q();
|
892 | })
|
893 | .then((response) => {
|
894 | logger.debug(response);
|
895 | logger.info('Saving config.');
|
896 | return bigIp.save();
|
897 | })
|
898 | .then((response) => {
|
899 | logger.debug(response);
|
900 | logger.info('Waiting for device to be active.');
|
901 | return bigIp.active();
|
902 | })
|
903 | .then((response) => {
|
904 | logger.debug(response);
|
905 | let address;
|
906 | if (options.ping) {
|
907 | address = options.ping === true ? 'f5.com' : options.ping;
|
908 | logger.info('Pinging', address);
|
909 | return bigIp.ping(address);
|
910 | }
|
911 |
|
912 | return q();
|
913 | })
|
914 | .then((response) => {
|
915 | logger.debug(response);
|
916 | if (Object.keys(metrics).length > 0) {
|
917 | logger.info('Sending metrics');
|
918 | metrics.action = 'onboard';
|
919 | metrics.cloudLibsVersion = options.version();
|
920 | return metricsCollector.upload(metrics);
|
921 | }
|
922 |
|
923 | return q();
|
924 | })
|
925 | .then((response) => {
|
926 | logger.debug(response);
|
927 | logger.info('Device onboard complete.');
|
928 | return bigIp.rebootRequired();
|
929 | })
|
930 | .then((response) => {
|
931 | if (response === true) {
|
932 | logger.warn('Reboot required.');
|
933 | rebooting = true;
|
934 | }
|
935 | if (options.forceReboot) {
|
936 | rebooting = true;
|
937 | // After reboot, we just want to send our done signal,
|
938 | // in case any other scripts are waiting on us. So, modify
|
939 | // the saved args for that
|
940 | const forcedRebootArgsToStrip = util.getArgsToStripDuringForcedReboot(options);
|
941 | return util.saveArgs(argv, ARGS_FILE_ID, forcedRebootArgsToStrip);
|
942 | }
|
943 |
|
944 | return q();
|
945 | })
|
946 | .then(() => {
|
947 | if (rebooting) {
|
948 | logger.info('Rebooting and exiting. Will continue after reboot.');
|
949 | return util.reboot(bigIp, { signalOnly: !options.reboot });
|
950 | }
|
951 | return q();
|
952 | })
|
953 | .then(() => {
|
954 | if (!rebooting && provider && options.signalResource) {
|
955 | logger.info('Signalling provider that instance provisioned.');
|
956 | return provider.signalInstanceProvisioned();
|
957 | }
|
958 | return q();
|
959 | })
|
960 | .catch((err) => {
|
961 | let message;
|
962 |
|
963 | if (!err) {
|
964 | message = 'unknown reason';
|
965 | } else {
|
966 | message = err.message;
|
967 | }
|
968 |
|
969 | if (err) {
|
970 | if (err instanceof ActiveError || err.name === 'ActiveError') {
|
971 | logger.warn('Device active check failed.');
|
972 | rebooting = true;
|
973 | return util.reboot(bigIp, { signalOnly: !options.reboot });
|
974 | }
|
975 | }
|
976 |
|
977 | ipc.send(signals.CLOUD_LIBS_ERROR);
|
978 |
|
979 | const error = `Onboard failed: ${message}`;
|
980 | util.logError(error, loggerOptions);
|
981 | util.logAndExit(error, 'error', 1);
|
982 |
|
983 | exiting = true;
|
984 | return q();
|
985 | })
|
986 | .done((response) => {
|
987 | logger.debug(response);
|
988 |
|
989 | if (!options.user) {
|
990 | logger.info('Deleting temporary user.');
|
991 | util.deleteUser(randomUser);
|
992 | }
|
993 |
|
994 | if ((!rebooting || !options.reboot) && !exiting) {
|
995 | ipc.send(options.signal || signals.ONBOARD_DONE);
|
996 | }
|
997 |
|
998 | if (cb) {
|
999 | cb();
|
1000 | }
|
1001 |
|
1002 | if (!rebooting) {
|
1003 | util.deleteArgs(ARGS_FILE_ID);
|
1004 | if (!exiting) {
|
1005 | util.logAndExit('Onboard finished.');
|
1006 | }
|
1007 | } else if (!options.reboot) {
|
1008 | // If we are rebooting, but we were called with --no-reboot, send signal
|
1009 | if (!exiting) {
|
1010 | util.logAndExit('Onboard finished. Reboot required but not rebooting.');
|
1011 | }
|
1012 | } else {
|
1013 | util.logAndExit('Onboard finished. Reboot required.');
|
1014 | }
|
1015 | });
|
1016 |
|
1017 | // If another script has signaled an error, exit, marking ourselves as DONE
|
1018 | ipc.once(signals.CLOUD_LIBS_ERROR)
|
1019 | .then(() => {
|
1020 | ipc.send(options.signal || signals.ONBOARD_DONE);
|
1021 | util.logAndExit('ERROR signaled from other script. Exiting');
|
1022 | });
|
1023 |
|
1024 | // If we reboot due to some other script, exit - otherwise cloud
|
1025 | // providers won't know we're done. If we forced the reboot ourselves,
|
1026 | // we will exit when that call completes.
|
1027 | ipc.once('REBOOT')
|
1028 | .then(() => {
|
1029 | if (!rebooting) {
|
1030 | util.logAndExit('REBOOT signaled. Exiting.');
|
1031 | }
|
1032 | });
|
1033 | } catch (err) {
|
1034 | if (logger) {
|
1035 | logger.error('Onbarding error:', err);
|
1036 | }
|
1037 |
|
1038 | if (cb) {
|
1039 | cb();
|
1040 | }
|
1041 | }
|
1042 | }
|
1043 | };
|
1044 |
|
1045 | module.exports = runner;
|
1046 |
|
1047 | // If we're called from the command line, run
|
1048 | // This allows for test code to call us as a module
|
1049 | if (!module.parent) {
|
1050 | runner.run(process.argv);
|
1051 | }
|
1052 | }());
|