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