8bf8d04861ac66be4c1f94362fd71025dcc51da8
[oweals/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / zones.js
1 'use strict';
2 'require rpc';
3 'require uci';
4 'require form';
5 'require network';
6 'require firewall';
7 'require tools.widgets as widgets';
8
9 return L.view.extend({
10         callConntrackHelpers: rpc.declare({
11                 object: 'luci',
12                 method: 'getConntrackHelpers',
13                 expect: { result: [] }
14         }),
15
16         load: function() {
17                 return Promise.all([
18                         this.callConntrackHelpers(),
19                         firewall.getDefaults()
20                 ]);
21         },
22
23         render: function(data) {
24                 var ctHelpers = data[0],
25                     fwDefaults = data[1],
26                     m, s, o, inp, out;
27
28                 m = new form.Map('firewall', _('Firewall - Zone Settings'),
29                         _('The firewall creates zones over your network interfaces to control network traffic flow.'));
30
31                 s = m.section(form.TypedSection, 'defaults', _('General Settings'));
32                 s.anonymous = true;
33                 s.addremove = false;
34
35                 o = s.option(form.Flag, 'syn_flood', _('Enable SYN-flood protection'));
36                 o = s.option(form.Flag, 'drop_invalid', _('Drop invalid packets'));
37
38                 var p = [
39                         s.option(form.ListValue, 'input', _('Input')),
40                         s.option(form.ListValue, 'output', _('Output')),
41                         s.option(form.ListValue, 'forward', _('Forward'))
42                 ];
43
44                 for (var i = 0; i < p.length; i++) {
45                         p[i].value('REJECT', _('reject'));
46                         p[i].value('DROP', _('drop'));
47                         p[i].value('ACCEPT', _('accept'));
48                 }
49
50                 /* Netfilter flow offload support */
51
52                 if (L.hasSystemFeature('offloading')) {
53                         s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
54                                 _('Experimental feature. Not fully compatible with QoS/SQM.'));
55
56                         s.anonymous = true;
57                         s.addremove = false;
58
59                         o = s.option(form.Flag, 'flow_offloading',
60                                 _('Software flow offloading'),
61                                 _('Software based offloading for routing/NAT'));
62                         o.optional = true;
63
64                         o = s.option(form.Flag, 'flow_offloading_hw',
65                                 _('Hardware flow offloading'),
66                                 _('Requires hardware NAT support. Implemented at least for mt7621'));
67                         o.optional = true;
68                         o.depends('flow_offloading', '1');
69                 }
70
71
72                 s = m.section(form.GridSection, 'zone', _('Zones'));
73                 s.addremove = true;
74                 s.anonymous = true;
75                 s.sortable  = true;
76
77                 s.tab('general', _('General Settings'));
78                 s.tab('advanced', _('Advanced Settings'));
79                 s.tab('conntrack', _('Conntrack Settings'));
80                 s.tab('extra', _('Extra iptables arguments'));
81
82                 o = s.taboption('general', form.DummyValue, '_generalinfo');
83                 o.rawhtml = true;
84                 o.modalonly = true;
85                 o.cfgvalue = function(section_id) {
86                         var name = uci.get('firewall', section_id, 'name');
87                         if (name == null)
88                                 name = _("this new zone");
89                         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.')
90                                 .replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
91                 };
92
93                 o = s.taboption('general', form.Value, 'name', _('Name'));
94                 o.placeholder = _('Unnamed zone');
95                 o.modalonly = true;
96                 o.rmempty = false;
97                 o.datatype = 'and(uciname,maxlength(11))';
98                 o.write = function(section_id, formvalue) {
99                         var cfgvalue = this.cfgvalue(section_id);
100
101                         if (cfgvalue == null || cfgvalue == '')
102                                 return uci.set('firewall', section_id, 'name', formvalue);
103                         else if (cfgvalue != formvalue)
104                                 return firewall.renameZone(cfgvalue, formvalue);
105                 };
106
107                 o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwardings'));
108                 o.editable = true;
109                 o.modalonly = false;
110                 o.cfgvalue = function(section_id) {
111                         return uci.get('firewall', section_id, 'name');
112                 };
113
114                 var p = [
115                         s.taboption('general', form.ListValue, 'input', _('Input')),
116                         s.taboption('general', form.ListValue, 'output', _('Output')),
117                         s.taboption('general', form.ListValue, 'forward', _('Forward'))
118                 ];
119
120                 for (var i = 0; i < p.length; i++) {
121                         p[i].value('REJECT', _('reject'));
122                         p[i].value('DROP', _('drop'));
123                         p[i].value('ACCEPT', _('accept'));
124                         p[i].editable = true;
125                 }
126
127                 p[0].default = fwDefaults.getInput();
128                 p[1].default = fwDefaults.getOutput();
129                 p[2].default = fwDefaults.getForward();
130
131                 o = s.taboption('general', form.Flag, 'masq', _('Masquerading'));
132                 o.editable = true;
133
134                 o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
135                 o.modalonly = true;
136
137                 o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
138                 o.modalonly = true;
139                 o.multiple = true;
140                 o.write = function(section_id, formvalue) {
141                         var name = uci.get('firewall', section_id, 'name'),
142                             cfgvalue = this.cfgvalue(section_id);
143
144                         if (typeof(cfgvalue) == 'string' && Array.isArray(formvalue) && (cfgvalue == formvalue.join(' ')))
145                                 return;
146
147                         var tasks = [ firewall.getZone(name) ];
148
149                         if (Array.isArray(formvalue))
150                                 for (var i = 0; i < formvalue.length; i++) {
151                                         var netname = formvalue[i];
152                                         tasks.push(network.getNetwork(netname).then(function(net) {
153                                                 return net || network.addNetwork(netname, { 'proto': 'none' });
154                                         }));
155                                 }
156
157                         return Promise.all(tasks).then(function(zone_networks) {
158                                 if (zone_networks[0])
159                                         for (var i = 1; i < zone_networks.length; i++)
160                                                 zone_networks[0].addNetwork(zone_networks[i].getName());
161                         });
162                 };
163                 o.remove = function(section_id) {
164                         return uci.set('firewall', section_id, 'network', ' ');
165                 };
166
167                 o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
168                 o.rawhtml = true;
169                 o.modalonly = true;
170                 o.cfgvalue = function(section_id) {
171                         var name = uci.get('firewall', section_id, 'name');
172                         if (name == null)
173                                 name = _("this new zone");
174                         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.')
175                                 .format(name);
176                 };
177
178                 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.'));
179                 o.modalonly = true;
180                 o.noaliases = true;
181                 o.multiple = true;
182
183                 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.'));
184                 o.datatype = 'neg(cidr)';
185                 o.modalonly = true;
186                 o.multiple = true;
187
188                 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
189                 o.value('', _('IPv4 and IPv6'));
190                 o.value('ipv4', _('IPv4 only'));
191                 o.value('ipv6', _('IPv6 only'));
192                 o.modalonly = true;
193
194                 o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
195                 o.depends('family', '');
196                 o.depends('family', 'ipv4');
197                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
198                 o.placeholder = '0.0.0.0/0';
199                 o.modalonly = true;
200
201                 o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
202                 o.depends('family', '');
203                 o.depends('family', 'ipv4');
204                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
205                 o.placeholder = '0.0.0.0/0';
206                 o.modalonly = true;
207
208                 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.'));
209                 o.modalonly = true;
210
211                 o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
212                 o.default = o.enabled;
213                 o.modalonly = true;
214
215                 o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
216                 o.depends('auto_helper', '0');
217                 o.modalonly = true;
218                 for (var i = 0; i < ctHelpers.length; i++)
219                         o.value(ctHelpers[i].name, '<span class="hide-close">%s (%s)</span><span class="hide-open">%s</span>'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase(), ctHelpers[i].name.toUpperCase()));
220
221                 o = s.taboption('advanced', form.Flag, 'log', _('Enable logging on this zone'));
222                 o.modalonly = true;
223
224                 o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
225                 o.depends('log', '1');
226                 o.placeholder = '10/minute';
227                 o.modalonly = true;
228
229                 o = s.taboption('extra', form.DummyValue, '_extrainfo');
230                 o.rawhtml = true;
231                 o.modalonly = true;
232                 o.cfgvalue = function(section_id) {
233                         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.');
234                 };
235
236                 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.'));
237                 o.modalonly = true;
238                 o.cfgvalue = function(section_id) {
239                         return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
240                 };
241                 o.write = function(section_id, value) {
242                         uci.unset('firewall', section_id, 'extra');
243                         uci.set('firewall', section_id, 'extra_src', value);
244                 };
245
246                 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.'));
247                 o.modalonly = true;
248                 o.cfgvalue = function(section_id) {
249                         return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
250                 };
251                 o.write = function(section_id, value) {
252                         uci.unset('firewall', section_id, 'extra');
253                         uci.set('firewall', section_id, 'extra_dest', value);
254                 };
255
256                 o = s.taboption('general', form.DummyValue, '_forwardinfo');
257                 o.rawhtml = true;
258                 o.modalonly = true;
259                 o.cfgvalue = function(section_id) {
260                         var name = uci.get('firewall', section_id, 'name');
261                         if (name == null)
262                                 name = _("this new zone");
263                         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.')
264                                 .format(name);
265                 };
266
267                 out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
268                 o.nocreate = true;
269                 o.multiple = true;
270                 o.modalonly = true;
271                 o.filter = function(section_id, value) {
272                         return (uci.get('firewall', section_id, 'name') != value);
273                 };
274                 o.cfgvalue = function(section_id) {
275                         var out = (this.option == 'out'),
276                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
277                             fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [],
278                             value = [];
279
280                         for (var i = 0; i < fwds.length; i++)
281                                 value.push(out ? fwds[i].getDestination() : fwds[i].getSource());
282
283                         return value;
284                 };
285                 o.write = o.remove = function(section_id, formvalue) {
286                         var out = (this.option == 'out'),
287                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
288                             fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [];
289
290                         if (formvalue == null)
291                                 formvalue = [];
292
293                         if (Array.isArray(formvalue)) {
294                                 for (var i = 0; i < fwds.length; i++) {
295                                         var cmp = out ? fwds[i].getDestination() : fwds[i].getSource();
296                                         if (!formvalue.filter(function(d) { return d == cmp }).length)
297                                                 zone.deleteForwarding(fwds[i]);
298                                 }
299
300                                 for (var i = 0; i < formvalue.length; i++)
301                                         if (out)
302                                                 zone.addForwardingTo(formvalue[i]);
303                                         else
304                                                 zone.addForwardingFrom(formvalue[i]);
305                         }
306                 };
307
308                 inp = o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
309                 o.nocreate = true;
310                 o.multiple = true;
311                 o.modalonly = true;
312                 o.write = o.remove = out.write;
313                 o.filter = out.filter;
314                 o.cfgvalue = out.cfgvalue;
315
316                 return m.render();
317         }
318 });