UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * Copyright 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('./cryptoUtil');
20const util = require('./util');
21const q = require('q');
22const url = require('url');
23
24const MIN_PASSPHRASE_LENGTH = 16;
25const SPECIALS = ['!', '"', '#', '$', '%', '&', '(', ')', '*', '+', '-', '.', '/', '?', '^', '<', '>', '-'];
26const ASCII_UPPER_CASE_LOW = 65;
27const ASCII_UPPER_CASE_HIGH = 90;
28const ASCII_LOWER_CASE_LOW = 97;
29const ASCII_LOWER_CASE_HIGH = 122;
30const ASCII_NUMBER_LOW = 48;
31const ASCII_NUMBER_HIGH = 57;
32
33const STATUS_ACTIVATING_AUTOMATIC = 'ACTIVATING_AUTOMATIC';
34const STATUS_NEEDS_EULA = 'ACTIVATING_AUTOMATIC_NEED_EULA_ACCEPT';
35const STATUS_ACCEPT_EULA = 'ACTIVATING_AUTOMATIC_EULA_ACCEPTED';
36const STATUS_EULA_ACCEPTED = 'ACTIVATING_AUTOMATIC_EULA_ACCEPTED';
37const STATUS_READY = 'READY';
38const STATUS_LICENSED = 'LICENSED';
39
40/**
41 * Functions that only pertain to BIG-IQ onboarding, not BIG-IP
42 *
43 * @mixin
44 */
45const bigIqOnboardMixins = {
46 /**
47 * Creates a license pool
48 *
49 * @param {String} name - The name to use for the pool
50 * @param {String} regKey - The reg key to use for the pool
51 *
52 * @returns {Promise} A promise which is resolved when the pool is created and
53 * all licenses are activated.
54 */
55 createLicensePool(name, regKey) {
56 const INITIAL_ACTIVATION_PATH = '/cm/device/licensing/pool/initial-activation';
57
58 const trimmedRegKey = regKey.trim();
59
60 function waitForLicensed(licenseReference) {
61 const path = url.parse(licenseReference.link).path;
62 const prefix = '/mgmt';
63 return this.core.list(path.substr(prefix.length), null, util.NO_RETRY, { silent: true })
64 .then((response) => {
65 // To keep things interesting, this API returns different things
66 // based on the license type.
67 // Purchased pool uses state: LICENSED and utility uses status: READY
68 const status = response.state || response.status;
69 this.logger.silly('license state', status);
70 if (status === STATUS_LICENSED || status === STATUS_READY) {
71 this.logger.silly('pool ready');
72 return q();
73 }
74 return q.reject();
75 })
76 .catch((err) => {
77 this.logger.debug(
78 'got error waiting for licensed',
79 err && err.message ? err.message : err
80 );
81 return q.reject(err);
82 });
83 }
84
85 this.logger.silly('creating license pool', name, trimmedRegKey);
86 return this.core.create(
87 INITIAL_ACTIVATION_PATH,
88 {
89 name,
90 regKey: trimmedRegKey,
91 status: STATUS_ACTIVATING_AUTOMATIC
92 },
93 null,
94 null,
95 { silent: true } // there is a private key in the response
96 )
97 .then(() => {
98 return doCommonLicensePoolCreation.call(this, INITIAL_ACTIVATION_PATH, [trimmedRegKey]);
99 })
100 .then((responses) => {
101 this.logger.silly('waiting for licensed');
102 return util.tryUntil(
103 this,
104 util.DEFAULT_RETRY,
105 waitForLicensed,
106 [responses[0].licenseReference]
107 );
108 })
109 .then(() => {
110 this.logger.silly('license pool created');
111 q();
112 })
113 .catch((err) => {
114 q.reject(err);
115 });
116 },
117
118 /**
119 * Creates a license pool
120 *
121 * @param {String} name - The name to use for the pool
122 * @param {String[]} regKeyList - A list of reg keys to add to the pool
123 *
124 * @returns {Promise} A promise which is resolved when the pool is created and
125 * all licenses are activated.
126 */
127 createRegKeyPool(name, regKeyList) {
128 const REG_KEY_POOL_PATH = '/cm/device/licensing/pool/regkey/licenses';
129 const trimmedRegKeys = [];
130
131 let pollingPrefix;
132
133 function addRegKeys(regKeys) {
134 const promises = [];
135 regKeys.forEach((regKey) => {
136 promises.push(
137 this.core.create(
138 pollingPrefix,
139 {
140 regKey,
141 description: regKey,
142 status: STATUS_ACTIVATING_AUTOMATIC
143 },
144 null,
145 null,
146 { silent: true }
147 )
148 );
149 });
150 return q.all(promises);
151 }
152
153 regKeyList.forEach((regKey) => {
154 trimmedRegKeys.push(regKey.trim());
155 });
156
157 this.logger.silly('creating reg key pool', name);
158 return this.core.create(
159 REG_KEY_POOL_PATH,
160 {
161 name
162 }
163 )
164 .then((response) => {
165 const uuid = response.id;
166 pollingPrefix = `${REG_KEY_POOL_PATH}/${uuid}/offerings`;
167 this.logger.silly('pool created, adding reg keys');
168 return addRegKeys.call(this, trimmedRegKeys);
169 })
170 .then(() => {
171 return doCommonLicensePoolCreation.call(this, pollingPrefix, trimmedRegKeys);
172 })
173 .then(() => {
174 this.logger.silly('reg key pool created');
175 return q();
176 })
177 .catch((err) => {
178 q.reject(err);
179 });
180 },
181
182 /**
183 * Determines if primary key is already set
184 *
185 * @returns {Promise} A Promise which is resolved with true or false
186 * based on whether or not the primary key is set
187 */
188 isPrimaryKeySet() {
189 return this.core.list('/cm/shared/secure-storage/primarykey')
190 .then((response) => {
191 return q(response.isMkSet);
192 });
193 },
194
195 /**
196 * Sets the passphrase for the primary key (which, in turn, generates a new primary key)
197 *
198 * @param {String} passphrase - Passphrase for primary key
199 *
200 * @returns {Promise} A promise which is resolved when the operation is complete
201 * or rejected if an error occurs.
202 */
203 setPrimaryPassphrase(passphrase) {
204 return this.core.create(
205 '/cm/shared/secure-storage/primarykey',
206 { passphrase }
207 );
208 },
209
210 /**
211 * Sets the passphrase for the primary key to a random value
212 *
213 * @returns {Promise} A promise which is resolved when the operation is complete
214 * or rejected if an error occurs.
215 */
216 setRandomPrimaryPassphrase() {
217 // get random bytes of minimum length
218 return cryptoUtil.generateRandomBytes(MIN_PASSPHRASE_LENGTH, 'base64')
219 .then((data) => {
220 let passphrase = data;
221
222 // make sure there is at least one special char, number, lower case, and upper case
223 const index = cryptoUtil.generateRandomIntInRange(0, SPECIALS.length - 1);
224 passphrase += SPECIALS[index];
225
226 let asciiCode = cryptoUtil.generateRandomIntInRange(ASCII_NUMBER_LOW, ASCII_NUMBER_HIGH);
227 passphrase += String.fromCharCode(asciiCode);
228
229 asciiCode = cryptoUtil.generateRandomIntInRange(ASCII_LOWER_CASE_LOW, ASCII_LOWER_CASE_HIGH);
230 passphrase += String.fromCharCode(asciiCode);
231
232 asciiCode = cryptoUtil.generateRandomIntInRange(ASCII_UPPER_CASE_LOW, ASCII_UPPER_CASE_HIGH);
233 passphrase += String.fromCharCode(asciiCode);
234
235 return this.setPrimaryPassphrase(passphrase);
236 });
237 }
238};
239
240function pollRegKeys(pollingPath, regKeys) {
241 const promises = [];
242
243 regKeys.forEach((regKey) => {
244 promises.push(
245 this.core.list(`${pollingPath}/${regKey}`, null, null, { silent: true })
246 );
247 });
248
249 return q.all(promises);
250}
251
252function waitForEulas(pollingPath, regKeys) {
253 const eulas = [];
254
255 return pollRegKeys.call(this, pollingPath, regKeys)
256 .then((responses) => {
257 for (let i = 0; i < responses.length; i++) {
258 this.logger.silly('current status', regKeys[i], responses[i].status);
259 if (responses[i].status === STATUS_NEEDS_EULA) {
260 this.logger.silly('got eula for', regKeys[i]);
261 eulas.push(
262 {
263 regKey: regKeys[i],
264 eulaText: responses[i].eulaText
265 }
266 );
267 } else {
268 this.logger.silly('still waiting for eula for', regKeys[i]);
269 return q.reject();
270 }
271 }
272 return q(eulas);
273 })
274 .catch((err) => {
275 this.logger.debug('still waiting for eulas', err && err.message ? err.message : err);
276 return q.reject(err);
277 });
278}
279
280function waitForEulasAccepted(pollingPath, regKeys) {
281 return pollRegKeys.call(this, pollingPath, regKeys)
282 .then((responses) => {
283 for (let i = 0; i < responses.length; i++) {
284 this.logger.silly('current status', regKeys[i], responses[i].status);
285 if (responses[i].status === STATUS_EULA_ACCEPTED) {
286 this.logger.silly('eula accepted for', regKeys[i]);
287 } else {
288 this.logger.silly('eula not yet accepted for', regKeys[i]);
289 return q.reject();
290 }
291 }
292 return q(responses);
293 })
294 .catch((err) => {
295 this.logger.debug('not all eulas accepted', err && err.message ? err.message : err);
296 return q.reject(err);
297 });
298}
299
300function waitForLicensesReady(pollingPath, regKeys) {
301 return pollRegKeys.call(this, pollingPath, regKeys)
302 .then((responses) => {
303 for (let i = 0; i < responses.length; i++) {
304 this.logger.silly('current status', regKeys[i], responses[i].status);
305
306 if (responses[i].status === STATUS_READY) {
307 this.logger.silly('license ready', regKeys[i]);
308 } else {
309 this.logger.silly('license not yet ready for', regKeys[i]);
310 return q.reject();
311 }
312 }
313 return q(responses);
314 })
315 .catch((err) => {
316 this.logger.debug(
317 'not all licenses are ready',
318 err && err.message ? err.message : err
319 );
320 return q.reject(err);
321 });
322}
323
324function doCommonLicensePoolCreation(pollingPath, regKeys) {
325 this.logger.silly('waiting for eulas');
326 return util.tryUntil(
327 this,
328 util.QUICK_BUT_LONG_RETRY,
329 waitForEulas,
330 [pollingPath, regKeys]
331 )
332 .then((responses) => {
333 this.logger.silly('accepting eulas');
334 const promises = [];
335 responses.forEach((response) => {
336 promises.push(
337 this.core.modify(
338 `${pollingPath}/${response.regKey}`,
339 {
340 status: STATUS_ACCEPT_EULA,
341 eulaText: response.eulaText
342 },
343 null,
344 null,
345 { silent: true }
346 )
347 );
348 });
349 return q.all(promises);
350 })
351 .then(() => {
352 this.logger.silly('waiting for eulas accepted');
353 return util.tryUntil(
354 this,
355 util.QUICK_BUT_LONG_RETRY,
356 waitForEulasAccepted,
357 [pollingPath, regKeys]
358 );
359 })
360 .then(() => {
361 this.logger.silly('waiting for licenses ready');
362 return util.tryUntil(
363 this,
364 util.DEFAULT_RETRY,
365 waitForLicensesReady,
366 [pollingPath, regKeys]
367 );
368 });
369}
370
371module.exports = bigIqOnboardMixins;