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