luci-app-firewall: filter alias interfaces in zone device selection
[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
88                         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.')
89                                 .replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
90                 };
91
92                 o = s.taboption('general', form.Value, 'name', _('Name'));
93                 o.placeholder = _('Unnamed zone');
94                 o.modalonly = true;
95                 o.datatype = 'and(uciname,maxlength(11))';
96                 o.write = function(section_id, formvalue) {
97                         var cfgvalue = this.cfgvalue(section_id);
98
99                         if (cfgvalue != formvalue)
100                                 return firewall.renameZone(cfgvalue, formvalue);
101                 };
102
103                 o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwardings'));
104                 o.editable = true;
105                 o.modalonly = false;
106                 o.cfgvalue = function(section_id) {
107                         return uci.get('firewall', section_id, 'name');
108                 };
109
110                 var p = [
111                         s.taboption('general', form.ListValue, 'input', _('Input')),
112                         s.taboption('general', form.ListValue, 'output', _('Output')),
113                         s.taboption('general', form.ListValue, 'forward', _('Forward'))
114                 ];
115
116                 for (var i = 0; i < p.length; i++) {
117                         p[i].value('REJECT', _('reject'));
118                         p[i].value('DROP', _('drop'));
119                         p[i].value('ACCEPT', _('accept'));
120                         p[i].editable = true;
121                 }
122
123                 p[0].default = fwDefaults.getInput();
124                 p[1].default = fwDefaults.getOutput();
125                 p[2].default = fwDefaults.getForward();
126
127                 o = s.taboption('general', form.Flag, 'masq', _('Masquerading'));
128                 o.editable = true;
129
130                 o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
131                 o.modalonly = true;
132
133                 o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
134                 o.modalonly = true;
135                 o.multiple = true;
136                 o.write = function(section_id, formvalue) {
137                         var name = uci.get('firewall', section_id, 'name'),
138                             cfgvalue = this.cfgvalue(section_id);
139
140                         if (typeof(cfgvalue) == 'string' && Array.isArray(formvalue) && (cfgvalue == formvalue.join(' ')))
141                                 return;
142
143                         var tasks = [ firewall.getZone(name) ];
144
145                         if (Array.isArray(formvalue))
146                                 for (var i = 0; i < formvalue.length; i++) {
147                                         var netname = formvalue[i];
148                                         tasks.push(network.getNetwork(netname).then(function(net) {
149                                                 return net || network.addNetwork(netname, { 'proto': 'none' });
150                                         }));
151                                 }
152
153                         return Promise.all(tasks).then(function(zone_networks) {
154                                 if (zone_networks[0])
155                                         for (var i = 1; i < zone_networks.length; i++)
156                                                 zone_networks[0].addNetwork(zone_networks[i].getName());
157                         });
158                 };
159                 o.remove = function(section_id) {
160                         return uci.set('firewall', section_id, 'network', ' ');
161                 };
162
163                 o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
164                 o.rawhtml = true;
165                 o.modalonly = true;
166                 o.cfgvalue = function(section_id) {
167                         var name = uci.get('firewall', section_id, 'name');
168
169                         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.')
170                                 .format(name);
171                 };
172
173                 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.'));
174                 o.modalonly = true;
175                 o.noaliases = true;
176                 o.multiple = true;
177
178                 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.'));
179                 o.datatype = 'neg(cidr)';
180                 o.modalonly = true;
181                 o.multiple = true;
182
183                 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
184                 o.value('', _('IPv4 and IPv6'));
185                 o.value('ipv4', _('IPv4 only'));
186                 o.value('ipv6', _('IPv6 only'));
187                 o.modalonly = true;
188
189                 o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
190                 o.depends('family', '');
191                 o.depends('family', 'ipv4');
192                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
193                 o.placeholder = '0.0.0.0/0';
194                 o.modalonly = true;
195
196                 o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
197                 o.depends('family', '');
198                 o.depends('family', 'ipv4');
199                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
200                 o.placeholder = '0.0.0.0/0';
201                 o.modalonly = true;
202
203                 o = s.taboption('conntrack', form.Flag, 'conntrack', _('Force connection tracking'), _('Prevent the installation of <em>NOTRACK</em> rules which would bypass connection tracking.'));
204                 o.modalonly = true;
205
206                 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.'));
207                 o.modalonly = true;
208
209                 o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
210                 o.default = o.enabled;
211                 o.modalonly = true;
212
213                 o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
214                 o.depends('auto_helper', '0');
215                 o.modalonly = true;
216                 for (var i = 0; i < ctHelpers.length; i++)
217                         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()));
218
219                 o = s.taboption('advanced', form.Flag, 'log', _('Enable logging on this zone'));
220                 o.modalonly = true;
221
222                 o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
223                 o.depends('log', '1');
224                 o.placeholder = '10/minute';
225                 o.modalonly = true;
226
227                 o = s.taboption('extra', form.DummyValue, '_extrainfo');
228                 o.rawhtml = true;
229                 o.modalonly = true;
230                 o.cfgvalue = function(section_id) {
231                         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.');
232                 };
233
234                 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.'));
235                 o.modalonly = true;
236                 o.cfgvalue = function(section_id) {
237                         return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
238                 };
239                 o.write = function(section_id, value) {
240                         uci.unset('firewall', section_id, 'extra');
241                         uci.set('firewall', section_id, 'extra_src', value);
242                 };
243
244                 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.'));
245                 o.modalonly = true;
246                 o.cfgvalue = function(section_id) {
247                         return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
248                 };
249                 o.write = function(section_id, value) {
250                         uci.unset('firewall', section_id, 'extra');
251                         uci.set('firewall', section_id, 'extra_dest', value);
252                 };
253
254                 o = s.taboption('general', form.DummyValue, '_forwardinfo');
255                 o.rawhtml = true;
256                 o.modalonly = true;
257                 o.cfgvalue = function(section_id) {
258                         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.')
259                                 .format(uci.get('firewall', section_id, 'name'));
260                 };
261
262                 out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
263                 o.nocreate = true;
264                 o.multiple = true;
265                 o.modalonly = true;
266                 o.filter = function(section_id, value) {
267                         return (uci.get('firewall', section_id, 'name') != value);
268                 };
269                 o.cfgvalue = function(section_id) {
270                         var out = (this.option == 'out'),
271                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
272                             fwds = zone.getForwardingsBy(out ? 'src' : 'dest'),
273                             value = [];
274
275                         for (var i = 0; i < fwds.length; i++)
276                                 value.push(out ? fwds[i].getDestination() : fwds[i].getSource());
277
278                         return value;
279                 };
280                 o.write = o.remove = function(section_id, formvalue) {
281                         var out = (this.option == 'out'),
282                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
283                             fwds = zone.getForwardingsBy(out ? 'src' : 'dest');
284
285                         if (formvalue == null)
286                                 formvalue = [];
287
288                         if (Array.isArray(formvalue)) {
289                                 for (var i = 0; i < fwds.length; i++) {
290                                         var cmp = out ? fwds[i].getDestination() : fwds[i].getSource();
291                                         if (!formvalue.filter(function(d) { return d == cmp }).length)
292                                                 zone.deleteForwarding(fwds[i]);
293                                 }
294
295                                 for (var i = 0; i < formvalue.length; i++)
296                                         if (out)
297                                                 zone.addForwardingTo(formvalue[i]);
298                                         else
299                                                 zone.addForwardingFrom(formvalue[i]);
300                         }
301                 };
302
303                 inp = o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
304                 o.nocreate = true;
305                 o.multiple = true;
306                 o.modalonly = true;
307                 o.write = o.remove = out.write;
308                 o.filter = out.filter;
309                 o.cfgvalue = out.cfgvalue;
310
311                 return m.render();
312         }
313 });