|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* eduVPN - End-user friendly VPN. |
|
5
|
|
|
* |
|
6
|
|
|
* Copyright: 2016-2017, The Commons Conservancy eduVPN Programme |
|
7
|
|
|
* SPDX-License-Identifier: AGPL-3.0+ |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
namespace SURFnet\VPN\Node; |
|
11
|
|
|
|
|
12
|
|
|
use SURFnet\VPN\Common\ProfileConfig; |
|
13
|
|
|
|
|
14
|
|
|
class Firewall |
|
15
|
|
|
{ |
|
16
|
|
|
public static function getFirewall4(array $configList, FirewallConfig $firewallConfig, $asArray = false) |
|
17
|
|
|
{ |
|
18
|
|
|
return self::getFirewall($configList, $firewallConfig, 4, $asArray); |
|
19
|
|
|
} |
|
20
|
|
|
|
|
21
|
|
|
public static function getFirewall6(array $configList, FirewallConfig $firewallConfig, $asArray = false) |
|
22
|
|
|
{ |
|
23
|
|
|
return self::getFirewall($configList, $firewallConfig, 6, $asArray); |
|
24
|
|
|
} |
|
25
|
|
|
|
|
26
|
|
|
private static function getFirewall(array $configList, FirewallConfig $firewallConfig, $inetFamily, $asArray) |
|
27
|
|
|
{ |
|
28
|
|
|
$firewall = []; |
|
29
|
|
|
|
|
30
|
|
|
// NAT |
|
31
|
|
|
$firewall = array_merge( |
|
32
|
|
|
$firewall, |
|
33
|
|
|
[ |
|
34
|
|
|
'*nat', |
|
35
|
|
|
':PREROUTING ACCEPT [0:0]', |
|
36
|
|
|
':INPUT ACCEPT [0:0]', |
|
37
|
|
|
':OUTPUT ACCEPT [0:0]', |
|
38
|
|
|
':POSTROUTING ACCEPT [0:0]', |
|
39
|
|
|
] |
|
40
|
|
|
); |
|
41
|
|
|
// add all instances |
|
42
|
|
|
foreach ($configList as $instanceConfig) { |
|
43
|
|
|
$firewall = array_merge($firewall, self::getNat($instanceConfig, $inetFamily)); |
|
44
|
|
|
} |
|
45
|
|
|
$firewall[] = 'COMMIT'; |
|
46
|
|
|
|
|
47
|
|
|
// FILTER |
|
48
|
|
|
$firewall = array_merge( |
|
49
|
|
|
$firewall, |
|
50
|
|
|
[ |
|
51
|
|
|
'*filter', |
|
52
|
|
|
':INPUT ACCEPT [0:0]', |
|
53
|
|
|
':FORWARD ACCEPT [0:0]', |
|
54
|
|
|
':OUTPUT ACCEPT [0:0]', |
|
55
|
|
|
] |
|
56
|
|
|
); |
|
57
|
|
|
|
|
58
|
|
|
// INPUT |
|
59
|
|
|
$firewall = array_merge($firewall, self::getInputChain($inetFamily, $firewallConfig)); |
|
60
|
|
|
|
|
61
|
|
|
// FORWARD |
|
62
|
|
|
$firewall = array_merge( |
|
63
|
|
|
$firewall, |
|
64
|
|
|
[ |
|
65
|
|
|
sprintf('-A FORWARD -p %s -j ACCEPT', 4 === $inetFamily ? 'icmp' : 'ipv6-icmp'), |
|
66
|
|
|
'-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT', |
|
67
|
|
|
] |
|
68
|
|
|
); |
|
69
|
|
|
|
|
70
|
|
|
// add all instances |
|
71
|
|
|
foreach ($configList as $instanceConfig) { |
|
72
|
|
|
$firewall = array_merge($firewall, self::getForwardChain($instanceConfig, $inetFamily)); |
|
73
|
|
|
} |
|
74
|
|
|
$firewall[] = sprintf('-A FORWARD -j REJECT --reject-with %s', 4 === $inetFamily ? 'icmp-host-prohibited' : 'icmp6-adm-prohibited'); |
|
75
|
|
|
$firewall[] = 'COMMIT'; |
|
76
|
|
|
|
|
77
|
|
|
$firewall = array_merge( |
|
78
|
|
|
[ |
|
79
|
|
|
'#', |
|
80
|
|
|
'# VPN Firewall Configuration', |
|
81
|
|
|
'#', |
|
82
|
|
|
'# ******************************************', |
|
83
|
|
|
'# * THIS FILE IS GENERATED, DO NOT MODIFY! *', |
|
84
|
|
|
'# ******************************************', |
|
85
|
|
|
'#', |
|
86
|
|
|
], |
|
87
|
|
|
$firewall |
|
88
|
|
|
); |
|
89
|
|
|
|
|
90
|
|
|
if ($asArray) { |
|
91
|
|
|
return $firewall; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
return implode(PHP_EOL, $firewall).PHP_EOL; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
private static function getNat(array $instanceConfig, $inetFamily) |
|
98
|
|
|
{ |
|
99
|
|
|
$nat = []; |
|
100
|
|
|
|
|
101
|
|
|
foreach ($instanceConfig['profileList'] as $profileId => $profileData) { |
|
102
|
|
|
$profileConfig = new ProfileConfig($profileData); |
|
103
|
|
|
if ($profileConfig->getItem('useNat')) { |
|
104
|
|
|
if (4 === $inetFamily) { |
|
105
|
|
|
// get the IPv4 range |
|
106
|
|
|
$srcNet = $profileConfig->getItem('range'); |
|
107
|
|
|
} else { |
|
108
|
|
|
// get the IPv6 range |
|
109
|
|
|
$srcNet = $profileConfig->getItem('range6'); |
|
110
|
|
|
} |
|
111
|
|
|
// -i (--in-interface) cannot be specified for POSTROUTING |
|
112
|
|
|
$nat[] = sprintf('-A POSTROUTING -s %s -o %s -j MASQUERADE', $srcNet, $profileConfig->getItem('extIf')); |
|
113
|
|
|
} |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
return $nat; |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
private static function getInputChain($inetFamily, FirewallConfig $firewallConfig) |
|
120
|
|
|
{ |
|
121
|
|
|
$inputChain = [ |
|
122
|
|
|
'-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT', |
|
123
|
|
|
sprintf('-A INPUT -p %s -j ACCEPT', 4 === $inetFamily ? 'icmp' : 'ipv6-icmp'), |
|
124
|
|
|
'-A INPUT -i lo -j ACCEPT', |
|
125
|
|
|
]; |
|
126
|
|
|
|
|
127
|
|
|
// add trusted interfaces |
|
128
|
|
|
if ($firewallConfig->getSection('inputChain')->hasSection('trustedInterfaces')) { |
|
129
|
|
|
foreach ($firewallConfig->getSection('inputChain')->getSection('trustedInterfaces')->toArray() as $trustedIf) { |
|
130
|
|
|
$inputChain[] = sprintf('-A INPUT -i %s -j ACCEPT', $trustedIf); |
|
131
|
|
|
} |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
$udpPorts = $firewallConfig->getSection('inputChain')->getSection('udp')->toArray(); |
|
135
|
|
|
$tcpPorts = $firewallConfig->getSection('inputChain')->getSection('tcp')->toArray(); |
|
136
|
|
|
|
|
137
|
|
View Code Duplication |
foreach ($udpPorts as $udpPort) { |
|
|
|
|
|
|
138
|
|
|
if (!is_array($udpPort)) { |
|
139
|
|
|
$inputChain[] = sprintf( |
|
140
|
|
|
'-A INPUT -m state --state NEW -m udp -p udp --dport %s -j ACCEPT', |
|
141
|
|
|
$udpPort |
|
142
|
|
|
); |
|
143
|
|
|
|
|
144
|
|
|
continue; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
foreach ($udpPort['src'] as $src) { |
|
148
|
|
|
$ipSource = new IP($src); |
|
149
|
|
|
if ($inetFamily === $ipSource->getFamily()) { |
|
150
|
|
|
$inputChain[] = sprintf( |
|
151
|
|
|
'-A INPUT -m state --state NEW -m udp -p udp --source %s --dport %s -j ACCEPT', |
|
152
|
|
|
$src, |
|
153
|
|
|
$udpPort['port'] |
|
154
|
|
|
); |
|
155
|
|
|
} |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
View Code Duplication |
foreach ($tcpPorts as $tcpPort) { |
|
|
|
|
|
|
160
|
|
|
if (!is_array($tcpPort)) { |
|
161
|
|
|
$inputChain[] = sprintf( |
|
162
|
|
|
'-A INPUT -m state --state NEW -m tcp -p tcp --dport %s -j ACCEPT', |
|
163
|
|
|
$tcpPort |
|
164
|
|
|
); |
|
165
|
|
|
|
|
166
|
|
|
continue; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
foreach ($tcpPort['src'] as $src) { |
|
170
|
|
|
$ipSource = new IP($src); |
|
171
|
|
|
if ($inetFamily === $ipSource->getFamily()) { |
|
172
|
|
|
$inputChain[] = sprintf( |
|
173
|
|
|
'-A INPUT -m state --state NEW -m tcp -p tcp --source %s --dport %s -j ACCEPT', |
|
174
|
|
|
$src, |
|
175
|
|
|
$tcpPort['port'] |
|
176
|
|
|
); |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
$inputChain[] = sprintf('-A INPUT -j REJECT --reject-with %s', 4 === $inetFamily ? 'icmp-host-prohibited' : 'icmp6-adm-prohibited'); |
|
182
|
|
|
|
|
183
|
|
|
return $inputChain; |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
private static function getForwardChain(array $instanceConfig, $inetFamily) |
|
187
|
|
|
{ |
|
188
|
|
|
$forwardChain = []; |
|
189
|
|
|
|
|
190
|
|
|
$instanceNumber = $instanceConfig['instanceNumber']; |
|
191
|
|
|
foreach ($instanceConfig['profileList'] as $profileId => $profileData) { |
|
192
|
|
|
$profileConfig = new ProfileConfig($profileData); |
|
193
|
|
|
$profileNumber = $profileConfig->getItem('profileNumber'); |
|
194
|
|
|
|
|
195
|
|
|
if (4 === $inetFamily && $profileConfig->getItem('reject4')) { |
|
196
|
|
|
// IPv4 forwarding is disabled |
|
197
|
|
|
continue; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
if (6 === $inetFamily && $profileConfig->getItem('reject6')) { |
|
201
|
|
|
// IPv6 forwarding is disabled |
|
202
|
|
|
continue; |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
if (4 === $inetFamily) { |
|
206
|
|
|
// get the IPv4 range |
|
207
|
|
|
$srcNet = $profileConfig->getItem('range'); |
|
208
|
|
|
} else { |
|
209
|
|
|
// get the IPv6 range |
|
210
|
|
|
$srcNet = $profileConfig->getItem('range6'); |
|
211
|
|
|
} |
|
212
|
|
|
$forwardChain[] = sprintf('-N vpn-%s-%s', $instanceNumber, $profileNumber); |
|
213
|
|
|
|
|
214
|
|
|
$forwardChain[] = sprintf('-A FORWARD -i tun-%s-%s+ -s %s -j vpn-%s-%s', $instanceNumber, $profileNumber, $srcNet, $instanceNumber, $profileNumber); |
|
215
|
|
|
|
|
216
|
|
|
// merge outgoing forwarding firewall rules to prevent certain |
|
217
|
|
|
// traffic |
|
218
|
|
|
$forwardChain = array_merge($forwardChain, self::getForwardFirewall($instanceNumber, $profileNumber, $profileConfig, $inetFamily)); |
|
219
|
|
|
|
|
220
|
|
|
if ($profileConfig->getItem('clientToClient')) { |
|
221
|
|
|
// allow client-to-client |
|
222
|
|
|
$forwardChain[] = sprintf('-A vpn-%s-%s -o tun-%s-%s+ -d %s -j ACCEPT', $instanceNumber, $profileNumber, $instanceNumber, $profileNumber, $srcNet); |
|
223
|
|
|
} |
|
224
|
|
|
if ($profileConfig->getItem('defaultGateway')) { |
|
225
|
|
|
// allow traffic to all outgoing destinations |
|
226
|
|
|
$forwardChain[] = sprintf('-A vpn-%s-%s -o %s -j ACCEPT', $instanceNumber, $profileNumber, $profileConfig->getItem('extIf')); |
|
227
|
|
|
} else { |
|
228
|
|
|
// only allow certain traffic to the external interface |
|
229
|
|
|
foreach ($profileConfig->getSection('routes')->toArray() as $route) { |
|
230
|
|
|
$routeIp = new IP($route); |
|
231
|
|
|
if ($inetFamily === $routeIp->getFamily()) { |
|
232
|
|
|
$forwardChain[] = sprintf('-A vpn-%s-%s -o %s -d %s -j ACCEPT', $instanceNumber, $profileNumber, $profileConfig->getItem('extIf'), $route); |
|
233
|
|
|
} |
|
234
|
|
|
} |
|
235
|
|
|
} |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
return $forwardChain; |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
private static function getForwardFirewall($instanceNumber, $profileNumber, ProfileConfig $profileConfig, $inetFamily) |
|
242
|
|
|
{ |
|
243
|
|
|
$forwardFirewall = []; |
|
244
|
|
|
if ($profileConfig->getItem('blockSmb')) { |
|
245
|
|
|
// drop SMB outgoing traffic |
|
246
|
|
|
// @see https://medium.com/@ValdikSS/deanonymizing-windows-users-and-capturing-microsoft-and-vpn-accounts-f7e53fe73834 |
|
247
|
|
|
foreach (['tcp', 'udp'] as $proto) { |
|
248
|
|
|
$forwardFirewall[] = sprintf( |
|
249
|
|
|
'-A vpn-%s-%s -o %s -m multiport -p %s --dports 137:139,445 -j REJECT --reject-with %s', |
|
250
|
|
|
$instanceNumber, |
|
251
|
|
|
$profileNumber, |
|
252
|
|
|
$profileConfig->getItem('extIf'), |
|
253
|
|
|
$proto, |
|
254
|
|
|
4 === $inetFamily ? 'icmp-host-prohibited' : 'icmp6-adm-prohibited'); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
return $forwardFirewall; |
|
259
|
|
|
} |
|
260
|
|
|
} |
|
261
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.