luci-app-firewall: zones.js: fix HTML display in ct helper selection
[project/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / zones.js
1 'use strict';
2 'require view';
3 'require rpc';
4 'require uci';
5 'require form';
6 'require network';
7 'require firewall';
8 'require tools.firewall as fwtool';
9 'require tools.widgets as widgets';
10
11 return view.extend({
12 callConntrackHelpers: rpc.declare({
13 object: 'luci',
14 method: 'getConntrackHelpers',
15 expect: { result: [] }
16 }),
17
18 load: function() {
19 return Promise.all([
20 this.callConntrackHelpers(),
21 firewall.getDefaults()
22 ]);
23 },
24
25 render: function(data) {
26 if (fwtool.checkLegacySNAT())
27 return fwtool.renderMigration();
28 else
29 return this.renderZones(data);
30 },
31
32 renderZones: function(data) {
33 var ctHelpers = data[0],
34 fwDefaults = data[1],
35 m, s, o, inp, out;
36
37 m = new form.Map('firewall', _('Firewall - Zone Settings'),
38 _('The firewall creates zones over your network interfaces to control network traffic flow.'));
39
40 s = m.section(form.TypedSection, 'defaults', _('General Settings'));
41 s.anonymous = true;
42 s.addremove = false;
43
44 o = s.option(form.Flag, 'synflood_protect', _('Enable SYN-flood protection'));
45 o.cfgvalue = function(section_id) {
46 var val = uci.get('firewall', section_id, 'synflood_protect');
47 return (val != null) ? val : uci.get('firewall', section_id, 'syn_flood');
48 };
49 o.write = function(section_id, value) {
50 uci.unset('firewall', section_id, 'syn_flood');
51 uci.set('firewall', section_id, 'synflood_protect', value);
52 };
53 o.remove = function(section_id) {
54 uci.unset('firewall', section_id, 'syn_flood');
55 uci.unset('firewall', section_id, 'synflood_protect');
56 };
57
58 o = s.option(form.Flag, 'drop_invalid', _('Drop invalid packets'));
59
60 var p = [
61 s.option(form.ListValue, 'input', _('Input')),
62 s.option(form.ListValue, 'output', _('Output')),
63 s.option(form.ListValue, 'forward', _('Forward'))
64 ];
65
66 for (var i = 0; i < p.length; i++) {
67 p[i].value('REJECT', _('reject'));
68 p[i].value('DROP', _('drop'));
69 p[i].value('ACCEPT', _('accept'));
70 }
71
72 /* Netfilter flow offload support */
73
74 if (L.hasSystemFeature('offloading')) {
75 s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
76 _('Experimental feature. Not fully compatible with QoS/SQM.'));
77
78 s.anonymous = true;
79 s.addremove = false;
80
81 o = s.option(form.Flag, 'flow_offloading',
82 _('Software flow offloading'),
83 _('Software based offloading for routing/NAT'));
84 o.optional = true;
85
86 o = s.option(form.Flag, 'flow_offloading_hw',
87 _('Hardware flow offloading'),
88 _('Requires hardware NAT support. Implemented at least for mt7621'));
89 o.optional = true;
90 o.depends('flow_offloading', '1');
91 }
92
93
94 s = m.section(form.GridSection, 'zone', _('Zones'));
95 s.addremove = true;
96 s.anonymous = true;
97 s.sortable = true;
98
99 s.handleRemove = function(section_id, ev) {
100 return firewall.deleteZone(section_id).then(L.bind(function() {
101 return this.super('handleRemove', [section_id, ev]);
102 }, this));
103 };
104
105 s.tab('general', _('General Settings'));
106 s.tab('advanced', _('Advanced Settings'));
107 s.tab('conntrack', _('Conntrack Settings'));
108 s.tab('extra', _('Extra iptables arguments'));
109
110 o = s.taboption('general', form.DummyValue, '_generalinfo');
111 o.rawhtml = true;
112 o.modalonly = true;
113 o.cfgvalue = function(section_id) {
114 var name = uci.get('firewall', section_id, 'name');
115 if (name == null)
116 name = _("this new zone");
117 return _('This section defines common properties of %q. The <em>input</em> and <em>output</em> options set the default policies for traffic entering and leaving this zone while the <em>forward</em> option describes the policy for forwarded traffic between different networks within the zone. <em>Covered networks</em> specifies which available networks are members of this zone.')
118 .replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
119 };
120
121 o = s.taboption('general', form.Value, 'name', _('Name'));
122 o.placeholder = _('Unnamed zone');
123 o.modalonly = true;
124 o.rmempty = false;
125 o.datatype = 'and(uciname,maxlength(11))';
126 o.write = function(section_id, formvalue) {
127 var cfgvalue = this.cfgvalue(section_id);
128
129 if (cfgvalue == null || cfgvalue == '')
130 return uci.set('firewall', section_id, 'name', formvalue);
131 else if (cfgvalue != formvalue)
132 return firewall.renameZone(cfgvalue, formvalue);
133 };
134
135 o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwardings'));
136 o.editable = true;
137 o.modalonly = false;
138 o.cfgvalue = function(section_id) {
139 return uci.get('firewall', section_id, 'name');
140 };
141
142 var p = [
143 s.taboption('general', form.ListValue, 'input', _('Input')),
144 s.taboption('general', form.ListValue, 'output', _('Output')),
145 s.taboption('general', form.ListValue, 'forward', _('Forward'))
146 ];
147
148 for (var i = 0; i < p.length; i++) {
149 p[i].value('REJECT', _('reject'));
150 p[i].value('DROP', _('drop'));
151 p[i].value('ACCEPT', _('accept'));
152 p[i].editable = true;
153 }
154
155 p[0].default = fwDefaults.getInput();
156 p[1].default = fwDefaults.getOutput();
157 p[2].default = fwDefaults.getForward();
158
159 o = s.taboption('general', form.Flag, 'masq', _('Masquerading'));
160 o.editable = true;
161
162 o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
163 o.modalonly = true;
164
165 o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
166 o.modalonly = true;
167 o.multiple = true;
168 o.cfgvalue = function(section_id) {
169 return uci.get('firewall', section_id, 'network');
170 };
171 o.write = function(section_id, formvalue) {
172 var name = uci.get('firewall', section_id, 'name'),
173 cfgvalue = this.cfgvalue(section_id),
174 oldNetworks = L.toArray(cfgvalue),
175 newNetworks = L.toArray(formvalue);
176
177 oldNetworks.sort();
178 newNetworks.sort();
179
180 if (oldNetworks.join(' ') == newNetworks.join(' '))
181 return;
182
183 var tasks = [ firewall.getZone(name) ];
184
185 if (Array.isArray(formvalue))
186 for (var i = 0; i < newNetworks.length; i++) {
187 var netname = newNetworks[i];
188 tasks.push(network.getNetwork(netname).then(L.bind(function(netname, net) {
189 return net || network.addNetwork(netname, { 'proto': 'none' });
190 }, this, netname)));
191 }
192
193 return Promise.all(tasks).then(function(zone_networks) {
194 if (zone_networks[0]) {
195 zone_networks[0].clearNetworks();
196 for (var i = 1; i < zone_networks.length; i++)
197 zone_networks[0].addNetwork(zone_networks[i].getName());
198 }
199 });
200 };
201
202 o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
203 o.rawhtml = true;
204 o.modalonly = true;
205 o.cfgvalue = function(section_id) {
206 var name = uci.get('firewall', section_id, 'name');
207 if (name == null)
208 name = _("this new zone");
209 return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
210 .format(name);
211 };
212
213 o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Covered devices'), _('Use this option to classify zone traffic by raw, non-<em>uci</em> managed network devices.'));
214 o.modalonly = true;
215 o.noaliases = true;
216 o.multiple = true;
217
218 o = s.taboption('advanced', form.DynamicList, 'subnet', _('Covered subnets'), _('Use this option to classify zone traffic by source or destination subnet instead of networks or devices.'));
219 o.datatype = 'neg(cidr)';
220 o.modalonly = true;
221 o.multiple = true;
222
223 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
224 o.value('', _('IPv4 and IPv6'));
225 o.value('ipv4', _('IPv4 only'));
226 o.value('ipv6', _('IPv6 only'));
227 o.modalonly = true;
228
229 o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
230 o.depends('family', '');
231 o.depends('family', 'ipv4');
232 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
233 o.placeholder = '0.0.0.0/0';
234 o.modalonly = true;
235
236 o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
237 o.depends('family', '');
238 o.depends('family', 'ipv4');
239 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
240 o.placeholder = '0.0.0.0/0';
241 o.modalonly = true;
242
243 o = s.taboption('conntrack', form.Flag, 'masq_allow_invalid', _('Allow "invalid" traffic'), _('Do not install extra rules to reject forwarded traffic with conntrack state <em>invalid</em>. This may be required for complex asymmetric route setups.'));
244 o.modalonly = true;
245
246 o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
247 o.default = o.enabled;
248 o.modalonly = true;
249
250 o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
251 o.depends('auto_helper', '0');
252 o.modalonly = true;
253 for (var i = 0; i < ctHelpers.length; i++)
254 o.value(ctHelpers[i].name, E('<span><span class="hide-close">%s (%s)</span><span class="hide-open">%s</span></span>'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase(), ctHelpers[i].name.toUpperCase())));
255
256 o = s.taboption('advanced', form.Flag, 'log', _('Enable logging on this zone'));
257 o.modalonly = true;
258
259 o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
260 o.depends('log', '1');
261 o.placeholder = '10/minute';
262 o.modalonly = true;
263
264 o = s.taboption('extra', form.DummyValue, '_extrainfo');
265 o.rawhtml = true;
266 o.modalonly = true;
267 o.cfgvalue = function(section_id) {
268 return _('Passing raw iptables arguments to source and destination traffic classification rules allows to match packets based on other criteria than interfaces or subnets. These options should be used with extreme care as invalid values could render the firewall ruleset broken, completely exposing all services.');
269 };
270
271 o = s.taboption('extra', form.Value, 'extra_src', _('Extra source arguments'), _('Additional raw <em>iptables</em> arguments to classify zone source traffic, e.g. <code>-p tcp --sport 443</code> to only match inbound HTTPS traffic.'));
272 o.modalonly = true;
273 o.cfgvalue = function(section_id) {
274 return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
275 };
276 o.write = function(section_id, value) {
277 uci.unset('firewall', section_id, 'extra');
278 uci.set('firewall', section_id, 'extra_src', value);
279 };
280
281 o = s.taboption('extra', form.Value, 'extra_dest', _('Extra destination arguments'), _('Additional raw <em>iptables</em> arguments to classify zone destination traffic, e.g. <code>-p tcp --dport 443</code> to only match outbound HTTPS traffic.'));
282 o.modalonly = true;
283 o.cfgvalue = function(section_id) {
284 return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
285 };
286 o.write = function(section_id, value) {
287 uci.unset('firewall', section_id, 'extra');
288 uci.set('firewall', section_id, 'extra_dest', value);
289 };
290
291 o = s.taboption('general', form.DummyValue, '_forwardinfo');
292 o.rawhtml = true;
293 o.modalonly = true;
294 o.cfgvalue = function(section_id) {
295 var name = uci.get('firewall', section_id, 'name');
296 if (name == null)
297 name = _("this new zone");
298 return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
299 .format(name);
300 };
301
302 out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
303 o.nocreate = true;
304 o.multiple = true;
305 o.modalonly = true;
306 o.filter = function(section_id, value) {
307 return (uci.get('firewall', section_id, 'name') != value);
308 };
309 o.cfgvalue = function(section_id) {
310 var out = (this.option == 'out'),
311 zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
312 fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [],
313 value = [];
314
315 for (var i = 0; i < fwds.length; i++)
316 value.push(out ? fwds[i].getDestination() : fwds[i].getSource());
317
318 return value;
319 };
320 o.write = o.remove = function(section_id, formvalue) {
321 var out = (this.option == 'out'),
322 zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
323 fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [];
324
325 if (formvalue == null)
326 formvalue = [];
327
328 if (Array.isArray(formvalue)) {
329 for (var i = 0; i < fwds.length; i++) {
330 var cmp = out ? fwds[i].getDestination() : fwds[i].getSource();
331 if (!formvalue.filter(function(d) { return d == cmp }).length)
332 zone.deleteForwarding(fwds[i]);
333 }
334
335 for (var i = 0; i < formvalue.length; i++)
336 if (out)
337 zone.addForwardingTo(formvalue[i]);
338 else
339 zone.addForwardingFrom(formvalue[i]);
340 }
341 };
342
343 inp = o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
344 o.nocreate = true;
345 o.multiple = true;
346 o.modalonly = true;
347 o.write = o.remove = out.write;
348 o.filter = out.filter;
349 o.cfgvalue = out.cfgvalue;
350
351 return m.render();
352 }
353 });