Print this page
Add "log": to fwadm JSON for cfwlog tag.
| Split |
Close |
| Expand all |
| Collapse all |
--- old/src/fw/node_modules/fwrule/rule.js
+++ new/src/fw/node_modules/fwrule/rule.js
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License, Version 1.0 only
6 6 * (the "License"). You may not use this file except in compliance
7 7 * with the License.
8 8 *
9 9 * You can obtain a copy of the license at http://smartos.org/CDDL
10 10 *
11 11 * See the License for the specific language governing permissions
12 12 * and limitations under the License.
13 13 *
14 14 * When distributing Covered Code, include this CDDL HEADER in each
15 15 * file.
16 16 *
17 17 * If applicable, add the following below this CDDL HEADER, with the
18 18 * fields enclosed by brackets "[]" replaced with your own identifying
19 19 * information: Portions Copyright [yyyy] [name of copyright owner]
20 20 *
21 21 * CDDL HEADER END
22 22 *
23 23 * Copyright (c) 2018, Joyent, Inc. All rights reserved.
24 24 *
25 25 *
26 26 * fwadm: firewall rule model
27 27 */
28 28
29 29 'use strict';
30 30
31 31 var mod_net = require('net');
32 32 var mod_uuid = require('uuid');
33 33 var sprintf = require('extsprintf').sprintf;
34 34 var util = require('util');
35 35 var validators = require('./validators');
36 36 var verror = require('verror');
37 37
38 38
39 39
40 40 // --- Globals
41 41
42 42
43 43
|
↓ open down ↓ |
43 lines elided |
↑ open up ↑ |
44 44 var DIRECTIONS = ['to', 'from'];
45 45 // Exported fields that can be in the serialized rule:
46 46 var FIELDS = [
47 47 'created_by',
48 48 'description',
49 49 'enabled',
50 50 'global',
51 51 'owner_uuid',
52 52 'rule',
53 53 'uuid',
54 - 'version'
54 + 'version',
55 + 'log'
55 56 ];
56 57 // Maximum number of targets per side:
57 58 var MAX_TARGETS_PER_SIDE = 24;
58 59 // Maximum number of protocol targets:
59 60 var MAX_PROTOCOL_TARGETS = 24;
60 61 // Minimum version for using a larger list of protocol targets:
61 62 var MINVER_LGPROTOTARG = 4;
62 63 // The old maximum number of protocol targets:
63 64 var OLD_MAX_PORTS = 8;
64 65 var STRING_PROPS = ['created_by', 'description'];
65 66 var TARGET_TYPES = ['wildcard', 'ip', 'subnet', 'tag', 'vm'];
66 67
67 68 var icmpr = /^icmp6?$/;
68 69
69 70 // --- Internal functions
70 71
71 72
72 73 /**
73 74 * Safely check if an object has a property
74 75 */
75 76 function hasOwnProperty(obj, prop) {
76 77 return Object.prototype.hasOwnProperty.call(obj, prop);
77 78 }
78 79
79 80
80 81 /**
81 82 * Calls callback for all of the firewall target types
82 83 */
83 84 function forEachTarget(obj, callback) {
84 85 DIRECTIONS.forEach(function (dir) {
85 86 if (!hasOwnProperty(obj, dir)) {
86 87 return;
87 88 }
88 89
89 90 TARGET_TYPES.forEach(function (type) {
90 91 var name = type + 's';
91 92 if (!hasOwnProperty(obj[dir], name)) {
92 93 return;
93 94 }
94 95
95 96 callback(dir, type, name, obj[dir][name]);
96 97 });
97 98 });
98 99 }
99 100
100 101
101 102 /**
102 103 * Sorts a list of ICMP types (with optional codes)
103 104 */
104 105 function icmpTypeSort(types) {
105 106 return types.map(function (type) {
106 107 return type.toString().split(':');
107 108 }).sort(function (a, b) {
108 109 var aTot = (Number(a[0]) << 8) + (a.length === 1 ? 0 : Number(a[1]));
109 110 var bTot = (Number(b[0]) << 8) + (a.length === 1 ? 0 : Number(b[1]));
110 111 return aTot - bTot;
111 112 }).map(function (typeArr) {
112 113 return typeArr.join(':');
113 114 });
114 115 }
115 116
116 117
117 118 /**
118 119 * Adds a tag to an object
119 120 */
120 121 function addTag(obj, tag, val) {
121 122 if (!hasOwnProperty(obj, tag)) {
122 123 obj[tag] = {};
123 124 }
124 125
125 126 if (val === undefined || val === null) {
126 127 obj[tag].all = true;
127 128 return;
128 129 }
129 130
130 131 if (!hasOwnProperty(obj[tag], 'values')) {
131 132 obj[tag].values = {};
132 133 }
133 134
134 135 obj[tag].values[val] = true;
135 136 }
136 137
137 138
138 139 /**
139 140 * Creates a list of tags based on an object populated by addTag() above
140 141 */
141 142 function tagList(obj) {
142 143 var tags = [];
143 144 Object.keys(obj).sort().forEach(function (tag) {
144 145 if (hasOwnProperty(obj[tag], 'all')) {
145 146 tags.push(tag);
146 147 } else {
147 148 Object.keys(obj[tag].values).sort().forEach(function (val) {
148 149 tags.push([tag, val]);
149 150 });
150 151 }
151 152 });
152 153 return tags;
153 154 }
154 155
155 156
156 157 /**
157 158 * The following characters are allowed to come after an escape, and get
158 159 * escaped when producing rule text.
159 160 *
160 161 * Parentheses don't need to be escaped with newer parsers, but will cause
161 162 * errors with older parsers which expect them to be escaped. We therefore
162 163 * always escape them when generating rule text, to make sure we don't
163 164 * cause issues for older parsers.
164 165 */
165 166 var escapes = {
166 167 '"': '"',
167 168 'b': '\b',
168 169 'f': '\f',
169 170 'n': '\n',
170 171 'r': '\r',
171 172 't': '\t',
172 173 '/': '/',
173 174 '(': '(',
174 175 ')': ')',
175 176 '\\': '\\'
176 177 };
177 178
178 179
179 180 /**
180 181 * When producing text versions of a rule, we escape Unicode whitespace
181 182 * characters. These characters don't need to be escaped, but we do so
182 183 * to reduce the chance that an operator will look at a rule and mistake
183 184 * any of them for the ASCII space character (\u0020), or not see them
184 185 * because they're non-printing.
185 186 */
186 187 var unescapes = {
187 188 // Things that need to be escaped for the fwrule parser
188 189 '"': '"',
189 190 '(': '(',
190 191 ')': ')',
191 192 '\\': '\\',
192 193
193 194 // Special ASCII characters we don't want to print
194 195 '\u0000': 'u0000', // null (NUL)
195 196 '\u0001': 'u0001', // start of heading (SOH)
196 197 '\u0002': 'u0002', // start of text (STX)
197 198 '\u0003': 'u0003', // end of text (ETX)
198 199 '\u0004': 'u0004', // end of transmission (EOT)
199 200 '\u0005': 'u0005', // enquiry (ENQ)
200 201 '\u0006': 'u0006', // acknowledgement (ACK)
201 202 '\u0007': 'u0007', // bell (BEL)
202 203 '\u0008': 'b', // backspace (BS)
203 204 '\u0009': 't', // horizontal tab (HT)
204 205 '\u000A': 'n', // newline (NL)
205 206 '\u000B': 'u000B', // vertical tab (VT)
206 207 '\u000C': 'f', // form feed/next page (NP)
207 208 '\u000D': 'r', // carriage return (CR)
208 209 '\u000E': 'u000E', // shift out (SO)
209 210 '\u000F': 'u000F', // shift in (SI)
210 211 '\u0010': 'u0010', // data link escape (DLE)
211 212 '\u0011': 'u0011', // device control 1 (DC1)/XON
212 213 '\u0012': 'u0012', // device control 2 (DC2)
213 214 '\u0013': 'u0013', // device control 3 (DC3)/XOFF
214 215 '\u0014': 'u0014', // device control 4 (DC4)
215 216 '\u0015': 'u0015', // negative acknowledgement (NAK)
216 217 '\u0016': 'u0016', // synchronous idle (SYN)
217 218 '\u0017': 'u0017', // end of transmission block (ETB)
218 219 '\u0018': 'u0018', // cancel (CAN)
219 220 '\u0019': 'u0019', // end of medium (EM)
220 221 '\u001A': 'u001A', // substitute (SUB)
221 222 '\u001B': 'u001B', // escape (ESC)
222 223 '\u001C': 'u001C', // file separator (FS)
223 224 '\u001D': 'u001D', // group separator (GS)
224 225 '\u001E': 'u001E', // record separator (RS)
225 226 '\u001F': 'u001F', // unit separator (US)
226 227 '\u007F': 'u007F', // delete (DEL)
227 228
228 229 // Unicode whitespace characters
229 230 '\u0085': 'u0085', // next line
230 231 '\u00A0': 'u00A0', // non-breaking space
231 232 '\u1680': 'u1680', // ogham space mark
232 233 '\u180E': 'u180E', // mongolian vowel separator
233 234 '\u2000': 'u2000', // en quad
234 235 '\u2001': 'u2001', // em quad
235 236 '\u2002': 'u2002', // en space
236 237 '\u2003': 'u2003', // em space
237 238 '\u2004': 'u2004', // three-per-em space
238 239 '\u2005': 'u2005', // four-per-em space
239 240 '\u2006': 'u2006', // six-per-em space
240 241 '\u2007': 'u2007', // figure space
241 242 '\u2008': 'u2008', // punctuation space
242 243 '\u2009': 'u2009', // thin space
243 244 '\u200A': 'u200A', // hair space
244 245 '\u200B': 'u200B', // zero width space
245 246 '\u200C': 'u200C', // zero width non-joiner
246 247 '\u200D': 'u200D', // zero width joiner
247 248 '\u2028': 'u2028', // line separator
248 249 '\u2029': 'u2029', // paragraph separator
249 250 '\u202F': 'u202F', // narrow no-break space
250 251 '\u205F': 'u205F', // medium mathematical space
251 252 '\u2060': 'u2060', // word joiner
252 253 '\u3000': 'u3000', // ideographic space
253 254 '\uFEFF': 'uFEFF' // zero width no-break space
254 255 };
255 256
256 257
257 258 /**
258 259 * Unescape a string that's been escaped so that it can be used
259 260 * in a firewall rule.
260 261 */
261 262 function tagUnescape(ostr) {
262 263 var nstr = '';
263 264 var len = ostr.length;
264 265
265 266 for (var cur = 0; cur < len; cur += 1) {
266 267 var val = ostr[cur];
267 268 if (val === '\\') {
268 269 var escaped = ostr[cur + 1];
269 270 if (escaped === 'u') {
270 271 nstr += String.fromCharCode(
271 272 parseInt(ostr.substring(cur + 2, cur + 6), 16));
272 273 cur += 5;
273 274 } else if (escapes[escaped] !== undefined) {
274 275 nstr += escapes[escaped];
275 276 cur += 1;
276 277 } else {
277 278 throw new Error('Invalid escape sequence "\\' + escaped + '"!');
278 279 }
279 280 } else {
280 281 nstr += val;
281 282 }
282 283 }
283 284
284 285 return nstr;
285 286 }
286 287
287 288
288 289 /**
289 290 * Escape a string so that it can be placed, quoted, into a
290 291 * firewall rule.
291 292 */
292 293 function tagEscape(ostr) {
293 294 var nstr = '';
294 295 var len = ostr.length;
295 296
296 297 for (var cur = 0; cur < len; cur += 1) {
297 298 var val = ostr[cur];
298 299 if (unescapes[val] !== undefined) {
299 300 nstr += '\\' + unescapes[val];
300 301 } else {
301 302 nstr += val;
302 303 }
303 304 }
304 305
305 306 return nstr;
306 307 }
307 308
308 309
309 310 /**
310 311 * Quotes a string in case it contains non-alphanumeric
311 312 * characters or keywords for firewall rules.
312 313 */
313 314 function quote(str) {
314 315 return '"' + tagEscape(str) + '"';
315 316 }
316 317
317 318
318 319
319 320 // --- Firewall object and methods
320 321
321 322
322 323
323 324 /**
324 325 * Firewall rule constructor
325 326 */
326 327 function FwRule(data, opts) {
327 328 var errs = [];
328 329 var parsed;
329 330
330 331 if (!opts) {
331 332 opts = {};
332 333 }
333 334
334 335 // -- validation --
335 336
336 337 if (!data.rule && !data.parsed) {
337 338 errs.push(new validators.InvalidParamError('rule',
338 339 'No rule specified'));
339 340 } else {
340 341 try {
341 342 parsed = data.parsed || require('./').parse(data.rule, opts);
342 343 } catch (err) {
343 344 errs.push(err);
344 345 }
345 346 }
346 347
347 348 if (hasOwnProperty(data, 'uuid')) {
348 349 if (!validators.validateUUID(data.uuid)) {
349 350 errs.push(new validators.InvalidParamError('uuid',
350 351 'Invalid rule UUID'));
351 352 }
352 353
353 354 this.uuid = data.uuid;
354 355 } else {
355 356 this.uuid = mod_uuid.v4();
356 357 }
357 358
358 359 this.version = data.version || generateVersion();
359 360
360 361 if (hasOwnProperty(data, 'owner_uuid')) {
361 362 if (!validators.validateUUID(data.owner_uuid)) {
362 363 errs.push(new validators.InvalidParamError('owner_uuid',
363 364 'Invalid owner UUID'));
364 365 }
365 366 this.owner_uuid = data.owner_uuid;
366 367 } else {
367 368 // No owner: this rule will affect all VMs
368 369 this.global = true;
369 370 }
370 371
371 372 if (hasOwnProperty(data, 'enabled')) {
|
↓ open down ↓ |
307 lines elided |
↑ open up ↑ |
372 373 if (!validators.bool(data.enabled)) {
373 374 errs.push(new validators.InvalidParamError('enabled',
374 375 'enabled must be true or false'));
375 376 }
376 377
377 378 this.enabled = data.enabled;
378 379 } else {
379 380 this.enabled = false;
380 381 }
381 382
383 + if (hasOwnProperty(data, 'log')) {
384 + if (!validators.bool(data.log)) {
385 + errs.push(new validators.InvalidParamError('log',
386 + 'log must be true or false'));
387 + }
388 +
389 + this.log = data.log;
390 + } else {
391 + this.log = false;
392 + }
393 +
382 394 for (var s in STRING_PROPS) {
383 395 var str = STRING_PROPS[s];
384 396 if (hasOwnProperty(data, str)) {
385 397 try {
386 398 validators.validateString(str, data[str]);
387 399 this[str] = data[str];
388 400 } catch (valErr) {
389 401 errs.push(valErr);
390 402 }
391 403 }
392 404 }
393 405
394 406 if (opts.enforceGlobal) {
395 407 if (hasOwnProperty(data, 'global') && !validators.bool(data.global)) {
396 408 errs.push(new validators.InvalidParamError('global',
397 409 'global must be true or false'));
398 410 }
399 411
400 412 if (hasOwnProperty(data, 'global') &&
401 413 hasOwnProperty(data, 'owner_uuid') && data.global) {
402 414 errs.push(new validators.InvalidParamError('global',
403 415 'cannot specify both global and owner_uuid'));
404 416 }
405 417
406 418 if (!hasOwnProperty(data, 'global') &&
407 419 !hasOwnProperty(data, 'owner_uuid')) {
408 420 errs.push(new validators.InvalidParamError('owner_uuid',
409 421 'owner_uuid required'));
410 422 }
411 423 }
412 424
413 425 if (errs.length !== 0) {
414 426 if (errs.length === 1) {
415 427 throw errs[0];
416 428 }
417 429
418 430 throw new verror.MultiError(errs);
419 431 }
420 432
421 433 // -- translate into the internal rule format --
422 434
423 435 var d;
424 436 var dir;
425 437
426 438 this.action = parsed.action;
427 439 this.priority = parsed.priority || 0;
428 440 this.protocol = parsed.protocol.name;
429 441
430 442 switch (this.protocol) {
431 443 case 'icmp':
432 444 case 'icmp6':
433 445 this.types = icmpTypeSort(parsed.protocol.targets);
434 446 this.protoTargets = this.types;
435 447 break;
436 448 case 'ah':
437 449 case 'esp':
438 450 this.protoTargets = parsed.protocol.targets;
439 451 break;
440 452 case 'tcp':
441 453 case 'udp':
442 454 this.ports = parsed.protocol.targets.sort(function (a, b) {
443 455 var first = hasOwnProperty(a, 'start') ? a.start : a;
444 456 var second = hasOwnProperty(b, 'start') ? b.start : b;
445 457 return Number(first) - Number(second);
446 458 });
447 459 this.protoTargets = this.ports;
448 460 break;
449 461 default:
450 462 throw new validators.InvalidParamError('rule',
451 463 'unknown protocol "%s"', this.protocol);
452 464 }
453 465
454 466 if (opts.maxVersion < MINVER_LGPROTOTARG) {
455 467 if (this.protoTargets.length > OLD_MAX_PORTS) {
456 468 throw new validators.InvalidParamError('rule',
457 469 'maximum of %d %s allowed', OLD_MAX_PORTS,
458 470 icmpr.test(this.protocol) ? 'types' : 'ports');
459 471 }
460 472 } else if (this.protoTargets.length > MAX_PROTOCOL_TARGETS) {
461 473 throw new validators.InvalidParamError('rule',
462 474 'maximum of %d %s allowed', MAX_PROTOCOL_TARGETS,
463 475 icmpr.test(this.protocol) ? 'types' : 'ports');
464 476 }
465 477
466 478 this.from = {};
467 479 this.to = {};
468 480
469 481 this.allVMs = false;
470 482 this.ips = {};
471 483 this.tags = {};
472 484 this.vms = {};
473 485 this.subnets = {};
474 486 this.wildcards = {};
475 487
476 488 var dirs = {
477 489 'to': {},
478 490 'from': {}
479 491 };
480 492 var numTargets;
481 493
482 494 for (d in DIRECTIONS) {
483 495 dir = DIRECTIONS[d];
484 496 numTargets = 0;
485 497 for (var j in parsed[dir]) {
486 498 var target = parsed[dir][j];
487 499 var targetName;
488 500 var name = target[0] + 's';
489 501
490 502 numTargets++;
491 503 if (!hasOwnProperty(dirs[dir], name)) {
492 504 dirs[dir][name] = {};
493 505 }
494 506
495 507 if (name === 'tags') {
496 508 var targetVal = null;
497 509 if (typeof (target[1]) === 'string') {
498 510 targetName = target[1];
499 511 } else {
500 512 targetName = target[1][0];
501 513 targetVal = target[1][1];
502 514 }
503 515
504 516 addTag(this[name], targetName, targetVal);
505 517 addTag(dirs[dir][name], targetName, targetVal);
506 518
507 519 } else {
508 520 targetName = target[1];
509 521 this[name][targetName] = target[1];
510 522 dirs[dir][name][targetName] = target[1];
511 523 }
512 524 }
513 525
514 526 if (numTargets > MAX_TARGETS_PER_SIDE) {
515 527 throw new validators.InvalidParamError('rule',
516 528 'maximum of %d targets allowed per side',
517 529 MAX_TARGETS_PER_SIDE);
518 530 }
519 531 }
520 532
521 533 // Now dedup and sort
522 534 for (d in DIRECTIONS) {
523 535 dir = DIRECTIONS[d];
524 536 for (var t in TARGET_TYPES) {
525 537 var type = TARGET_TYPES[t] + 's';
526 538 if (hasOwnProperty(dirs[dir], type)) {
527 539 if (type === 'tags') {
528 540 this[dir][type] = tagList(dirs[dir][type]);
529 541
530 542 } else {
531 543 this[dir][type] = Object.keys(dirs[dir][type]).sort();
532 544 }
533 545 } else {
534 546 this[dir][type] = [];
535 547 }
536 548 }
537 549 }
538 550
539 551 this.ips = Object.keys(this.ips).sort();
540 552 this.tags = tagList(this.tags);
541 553 this.vms = Object.keys(this.vms).sort();
542 554 this.subnets = Object.keys(this.subnets).sort();
543 555 this.wildcards = Object.keys(this.wildcards).sort();
544 556
545 557 if (this.wildcards.length !== 0 && this.wildcards.indexOf('vmall') !== -1) {
546 558 this.allVMs = true;
547 559 }
548 560
549 561 // Check for rules that obviously don't make sense
550 562 if (this.protocol === 'icmp') {
551 563 this.ips.map(function (ip) {
552 564 if (!mod_net.isIPv4(ip)) {
553 565 throw new validators.InvalidParamError('rule',
554 566 'rule affects ICMPv4 but contains a non-IPv4 address');
555 567 }
556 568 });
557 569 this.subnets.map(function (subnet) {
558 570 if (!mod_net.isIPv4(subnet.split('/')[0])) {
559 571 throw new validators.InvalidParamError('rule',
560 572 'rule affects ICMPv4 but contains a non-IPv4 subnet');
561 573 }
562 574 });
563 575 } else if (this.protocol === 'icmp6') {
564 576 this.ips.map(function (ip) {
565 577 if (!mod_net.isIPv6(ip)) {
566 578 throw new validators.InvalidParamError('rule',
567 579 'rule affects ICMPv6 but contains a non-IPv6 address');
568 580 }
569 581 });
570 582 this.subnets.map(function (subnet) {
571 583 if (!mod_net.isIPv6(subnet.split('/')[0])) {
572 584 throw new validators.InvalidParamError('rule',
573 585 'rule affects ICMPv6 but contains a non-IPv6 subnet');
574 586 }
575 587 });
576 588 }
577 589
578 590 // Final check: does this rule actually contain targets that can actually
579 591 // affect VMs?
580 592 if (!this.allVMs && this.tags.length === 0 && this.vms.length === 0) {
581 593 throw new validators.InvalidParamError('rule',
582 594 'rule does not affect VMs');
583 595 }
584 596 }
585 597
586 598
587 599 /**
588 600 * Returns the internal representation of the rule
|
↓ open down ↓ |
197 lines elided |
↑ open up ↑ |
589 601 */
590 602 FwRule.prototype.raw = function () {
591 603 var raw = {
592 604 action: this.action,
593 605 enabled: this.enabled,
594 606 from: this.from,
595 607 priority: this.priority,
596 608 protocol: this.protocol,
597 609 to: this.to,
598 610 uuid: this.uuid,
599 - version: this.version
611 + version: this.version,
612 + log: this.log
600 613 };
601 614
602 615 if (this.owner_uuid) {
603 616 raw.owner_uuid = this.owner_uuid;
604 617 }
605 618
606 619 switch (this.protocol) {
607 620 case 'icmp':
608 621 case 'icmp6':
609 622 raw.types = this.types;
610 623 break;
611 624 case 'ah':
612 625 case 'esp':
613 626 break;
614 627 case 'tcp':
615 628 case 'udp':
616 629 raw.ports = this.ports;
617 630 break;
618 631 default:
619 632 throw new Error('unknown protocol: ' + this.protocol);
620 633 }
621 634
622 635 for (var s in STRING_PROPS) {
623 636 var str = STRING_PROPS[s];
624 637 if (hasOwnProperty(this, str)) {
625 638 raw[str] = this[str];
626 639 }
627 640 }
628 641
629 642 return raw;
630 643 };
631 644
632 645
633 646 /**
634 647 * Returns the serialized version of the rule, suitable for storing
635 648 *
636 649 * @param fields {Array}: fields to return (optional)
637 650 */
638 651 FwRule.prototype.serialize = function (fields) {
639 652 var ser = {};
640 653 if (!fields) {
641 654 fields = FIELDS;
642 655 }
643 656
644 657 for (var f in fields) {
645 658 var field = fields[f];
646 659 if (field === 'rule') {
647 660 ser.rule = this.text();
648 661 } else if (field === 'global') {
649 662 // Only display the global flag if true
650 663 if (this.global) {
651 664 ser.global = true;
652 665 }
653 666 } else {
654 667 if (hasOwnProperty(this, field)) {
655 668 ser[field] = this[field];
656 669 }
657 670 }
658 671 }
659 672
660 673 return ser;
661 674 };
662 675
663 676
664 677 /**
665 678 * Returns the text of the rule
666 679 */
667 680 FwRule.prototype.text = function () {
668 681 var containsRange;
669 682 var ports;
670 683 var protoTxt;
671 684 var prioTxt = '';
672 685 var targets = {
673 686 from: [],
674 687 to: []
675 688 };
676 689
677 690 forEachTarget(this, function (dir, type, _, arr) {
678 691 for (var i in arr) {
679 692 var txt;
680 693 if (type === 'tag') {
681 694 txt = util.format('%s %s', type,
682 695 typeof (arr[i]) === 'string' ? quote(arr[i])
683 696 : (quote(arr[i][0]) + ' = ' + quote(arr[i][1])));
684 697 } else {
685 698 txt = util.format('%s %s', type, arr[i]);
686 699 }
687 700
688 701 if (type === 'wildcard') {
689 702 txt = arr[i] === 'vmall' ? 'all vms' : arr[i];
690 703 }
691 704 targets[dir].push(txt);
692 705 }
693 706 });
694 707
695 708 // Protocol-specific text: different for ICMP rather than TCP/UDP
696 709 switch (this.protocol) {
697 710 case 'icmp':
698 711 case 'icmp6':
699 712 protoTxt = util.format(' %sTYPE %s%s',
700 713 this.types.length > 1 ? '(' : '',
701 714 this.types.map(function (type) {
702 715 return type.toString().split(':');
703 716 }).map(function (code) {
704 717 return code[0] + (code.length === 1 ? '' : ' CODE ' + code[1]);
705 718 }).join(' AND TYPE '),
706 719 this.types.length > 1 ? ')' : ''
707 720 );
708 721 break;
709 722 case 'ah':
710 723 case 'esp':
711 724 protoTxt = '';
712 725 break;
713 726 case 'tcp':
714 727 case 'udp':
715 728 ports = this.ports.map(function (port) {
716 729 if (hasOwnProperty(port, 'start') &&
717 730 hasOwnProperty(port, 'end')) {
718 731 /*
719 732 * We only output PORTS when we have a range, since we don't
720 733 * distinguish PORTS 1, 2 from (PORT 1 AND PORT 2) after
721 734 * parsing.
722 735 */
723 736 containsRange = true;
724 737 return port.start + ' - ' + port.end;
725 738 } else {
726 739 return port;
727 740 }
728 741 });
729 742 if (containsRange) {
730 743 protoTxt = util.format(' PORTS %s', ports.join(', '));
731 744 } else {
732 745 protoTxt = util.format(' %sPORT %s%s',
733 746 ports.length > 1 ? '(' : '',
734 747 ports.join(' AND PORT '),
735 748 ports.length > 1 ? ')' : ''
736 749 );
737 750 }
738 751 break;
739 752 default:
740 753 throw new Error('unknown protocol: ' + this.protocol);
741 754 }
742 755
743 756 if (this.priority > 0) {
744 757 prioTxt += ' PRIORITY ' + this.priority.toString();
745 758 }
746 759
747 760 return util.format('FROM %s%s%s TO %s%s%s %s %s%s%s',
748 761 targets.from.length > 1 ? '(' : '',
749 762 targets.from.join(' OR '),
750 763 targets.from.length > 1 ? ')' : '',
751 764 targets.to.length > 1 ? '(' : '',
752 765 targets.to.join(' OR '),
753 766 targets.to.length > 1 ? ')' : '',
754 767 this.action.toUpperCase(),
755 768 this.protocol.toLowerCase(),
756 769 protoTxt,
757 770 prioTxt
758 771 );
759 772 };
760 773
761 774
762 775 /**
763 776 * Returns the string representation of the rule
764 777 */
765 778 FwRule.prototype.toString = function () {
766 779 return util.format('[%s,%s%s] %s', this.uuid, this.enabled,
767 780 (this.owner_uuid ? ',' + this.owner_uuid : ''),
768 781 this.text());
769 782 };
770 783
771 784
772 785
773 786 // --- Exported functions
774 787
775 788
776 789
777 790 /**
778 791 * Creates a new firewall rule from the payload
779 792 */
780 793 function createRule(payload, opts) {
781 794 return new FwRule(payload, opts);
782 795 }
783 796
784 797
785 798 function generateVersion() {
786 799 return Date.now(0) + '.' + sprintf('%06d', process.pid);
787 800 }
788 801
789 802 module.exports = {
790 803 create: createRule,
791 804 generateVersion: generateVersion,
792 805 tagEscape: tagEscape,
793 806 tagUnescape: tagUnescape,
794 807 DIRECTIONS: DIRECTIONS,
795 808 FIELDS: FIELDS,
796 809 FwRule: FwRule,
797 810 TARGET_TYPES: TARGET_TYPES
798 811 };
|
↓ open down ↓ |
189 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX