eduvpn /
vpn-server-node
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 RuntimeException; |
||
| 13 | use SURFnet\VPN\Common\FileIO; |
||
| 14 | use SURFnet\VPN\Common\HttpClient\ServerClient; |
||
| 15 | use SURFnet\VPN\Common\ProfileConfig; |
||
| 16 | |||
| 17 | class OpenVpn |
||
| 18 | { |
||
| 19 | /** @var string */ |
||
| 20 | private $vpnConfigDir; |
||
| 21 | |||
| 22 | /** @var string */ |
||
| 23 | private $vpnTlsDir; |
||
| 24 | |||
| 25 | public function __construct($vpnConfigDir, $vpnTlsDir) |
||
| 26 | { |
||
| 27 | FileIO::createDir($vpnConfigDir, 0700); |
||
| 28 | $this->vpnConfigDir = $vpnConfigDir; |
||
| 29 | FileIO::createDir($vpnTlsDir, 0700); |
||
| 30 | $this->vpnTlsDir = $vpnTlsDir; |
||
| 31 | } |
||
| 32 | |||
| 33 | public function generateKeys(ServerClient $serverClient, $commonName) |
||
| 34 | { |
||
| 35 | $certData = $serverClient->post('add_server_certificate', ['common_name' => $commonName]); |
||
| 36 | |||
| 37 | $certFileMapping = [ |
||
| 38 | 'ca' => sprintf('%s/ca.crt', $this->vpnTlsDir), |
||
| 39 | 'certificate' => sprintf('%s/server.crt', $this->vpnTlsDir), |
||
| 40 | 'private_key' => sprintf('%s/server.key', $this->vpnTlsDir), |
||
| 41 | 'ta' => sprintf('%s/ta.key', $this->vpnTlsDir), |
||
| 42 | ]; |
||
| 43 | |||
| 44 | foreach ($certFileMapping as $k => $v) { |
||
| 45 | FileIO::writeFile($v, $certData[$k], 0600); |
||
| 46 | } |
||
| 47 | } |
||
| 48 | |||
| 49 | public function writeProfile($instanceNumber, $instanceId, $profileId, ProfileConfig $profileConfig) |
||
| 50 | { |
||
| 51 | $range = new IP($profileConfig->getItem('range')); |
||
| 52 | $range6 = new IP($profileConfig->getItem('range6')); |
||
| 53 | $processCount = count($profileConfig->getItem('vpnProtoPorts')); |
||
| 54 | |||
| 55 | $splitRange = $range->split($processCount); |
||
| 56 | $splitRange6 = $range6->split($processCount); |
||
| 57 | |||
| 58 | $managementIp = $profileConfig->getItem('managementIp'); |
||
| 59 | $profileNumber = $profileConfig->getItem('profileNumber'); |
||
| 60 | |||
| 61 | $processConfig = [ |
||
| 62 | 'managementIp' => $managementIp, |
||
| 63 | ]; |
||
| 64 | |||
| 65 | for ($i = 0; $i < $processCount; ++$i) { |
||
| 66 | list($proto, $port) = self::getProtoPort($profileConfig->getItem('vpnProtoPorts'), $profileConfig->getItem('listen'))[$i]; |
||
| 67 | $processConfig['range'] = $splitRange[$i]; |
||
| 68 | $processConfig['range6'] = $splitRange6[$i]; |
||
| 69 | $processConfig['dev'] = sprintf('tun-%d-%d-%d', $instanceNumber, $profileConfig->getItem('profileNumber'), $i); |
||
| 70 | $processConfig['proto'] = $proto; |
||
| 71 | $processConfig['port'] = $port; |
||
| 72 | $processConfig['local'] = $profileConfig->getItem('listen'); |
||
| 73 | $processConfig['managementPort'] = 11940 + $this->toPort($instanceNumber, $profileNumber, $i); |
||
| 74 | $processConfig['configName'] = sprintf( |
||
| 75 | '%s-%s-%d.conf', |
||
| 76 | $instanceId, |
||
| 77 | $profileId, |
||
| 78 | $i |
||
| 79 | ); |
||
| 80 | |||
| 81 | $this->writeProcess($instanceId, $profileId, $profileConfig, $processConfig); |
||
| 82 | } |
||
| 83 | } |
||
| 84 | |||
| 85 | private static function getFamilyProto($listenAddress, $proto) |
||
| 86 | { |
||
| 87 | $v6 = false !== strpos($listenAddress, ':'); |
||
| 88 | if ('udp' === $proto) { |
||
| 89 | return $v6 ? 'udp6' : 'udp'; |
||
| 90 | } |
||
| 91 | if ('tcp' === $proto) { |
||
| 92 | return $v6 ? 'tcp6-server' : 'tcp-server'; |
||
| 93 | } |
||
| 94 | |||
| 95 | throw new RuntimeException('only "tcp" and "udp" are supported as protocols'); |
||
| 96 | } |
||
| 97 | |||
| 98 | private static function getProtoPort(array $vpnProcesses, $listenAddress) |
||
| 99 | { |
||
| 100 | $convertedPortProto = []; |
||
| 101 | |||
| 102 | foreach ($vpnProcesses as $vpnProcess) { |
||
| 103 | list($proto, $port) = explode('/', $vpnProcess); |
||
| 104 | $convertedPortProto[] = [self::getFamilyProto($listenAddress, $proto), $port]; |
||
| 105 | } |
||
| 106 | |||
| 107 | return $convertedPortProto; |
||
| 108 | } |
||
| 109 | |||
| 110 | private function writeProcess($instanceId, $profileId, ProfileConfig $profileConfig, array $processConfig) |
||
| 111 | { |
||
| 112 | $tlsDir = sprintf('tls/%s/%s', $instanceId, $profileId); |
||
| 113 | |||
| 114 | $rangeIp = new IP($processConfig['range']); |
||
| 115 | $range6Ip = new IP($processConfig['range6']); |
||
| 116 | |||
| 117 | // static options |
||
| 118 | $serverConfig = [ |
||
| 119 | 'verb 3', |
||
| 120 | 'dev-type tun', |
||
| 121 | sprintf('user %s', $profileConfig->getItem('_user')), |
||
| 122 | sprintf('group %s', $profileConfig->getItem('_group')), |
||
| 123 | 'topology subnet', |
||
| 124 | 'persist-key', |
||
| 125 | 'persist-tun', |
||
| 126 | 'keepalive 10 60', |
||
| 127 | 'comp-lzo no', |
||
| 128 | 'remote-cert-tls client', |
||
| 129 | 'tls-version-min 1.2', |
||
| 130 | 'tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384', |
||
| 131 | 'auth SHA256', |
||
| 132 | 'dh none', // Only ECDHE |
||
| 133 | |||
| 134 | // 2.4 only clients: 'ncp-ciphers AES-256-GCM', |
||
| 135 | // 2.4 only clients: 'cipher AES-256-GCM', // also should update the client config to set this, but ncp overrides --cipher |
||
| 136 | 'cipher AES-256-CBC', |
||
| 137 | 'client-connect /usr/libexec/vpn-server-node-client-connect', |
||
| 138 | 'client-disconnect /usr/libexec/vpn-server-node-client-disconnect', |
||
| 139 | 'push "comp-lzo no"', |
||
| 140 | 'push "explicit-exit-notify 3"', |
||
| 141 | |||
| 142 | // we probably do NOT want this, it is up to the client to decide |
||
| 143 | // about this! |
||
| 144 | //'push "persist-key"', |
||
| 145 | //'push "persist-tun"', |
||
| 146 | |||
| 147 | sprintf('ca %s/ca.crt', $tlsDir), |
||
| 148 | sprintf('cert %s/server.crt', $tlsDir), |
||
| 149 | sprintf('key %s/server.key', $tlsDir), |
||
| 150 | sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()), |
||
| 151 | sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()), |
||
| 152 | sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1), |
||
| 153 | sprintf('script-security %d', $profileConfig->getItem('twoFactor') ? 3 : 2), |
||
| 154 | sprintf('dev %s', $processConfig['dev']), |
||
| 155 | sprintf('port %d', $processConfig['port']), |
||
| 156 | sprintf('management %s %d', $processConfig['managementIp'], $processConfig['managementPort']), |
||
| 157 | sprintf('setenv INSTANCE_ID %s', $instanceId), |
||
| 158 | sprintf('setenv PROFILE_ID %s', $profileId), |
||
| 159 | sprintf('proto %s', $processConfig['proto']), |
||
| 160 | sprintf('local %s', $processConfig['local']), |
||
| 161 | ]; |
||
| 162 | |||
| 163 | if (!$profileConfig->getItem('enableLog')) { |
||
| 164 | $serverConfig[] = 'log /dev/null'; |
||
| 165 | } |
||
| 166 | |||
| 167 | if ('tcp-server' === $processConfig['proto'] || 'tcp6-server' === $processConfig['proto']) { |
||
| 168 | $serverConfig[] = 'tcp-nodelay'; |
||
| 169 | } |
||
| 170 | |||
| 171 | if ('udp' === $processConfig['proto'] || 'udp6' === $processConfig['proto']) { |
||
| 172 | // notify the clients to reconnect when restarting OpenVPN on the server |
||
| 173 | // OpenVPN server >= 2.4 |
||
| 174 | $serverConfig[] = 'explicit-exit-notify 1'; |
||
| 175 | } |
||
| 176 | |||
| 177 | if ($profileConfig->getItem('twoFactor')) { |
||
| 178 | $serverConfig[] = 'auth-gen-token'; // Added in OpenVPN 2.4 |
||
| 179 | $serverConfig[] = 'auth-user-pass-verify /usr/libexec/vpn-server-node-verify-otp via-env'; |
||
| 180 | } |
||
| 181 | |||
| 182 | if ($profileConfig->getItem('tlsCrypt')) { |
||
| 183 | $serverConfig[] = sprintf('tls-crypt %s/ta.key', $tlsDir); |
||
| 184 | } else { |
||
| 185 | $serverConfig[] = sprintf('tls-auth %s/ta.key 0', $tlsDir); |
||
| 186 | } |
||
| 187 | |||
| 188 | // Routes |
||
| 189 | $serverConfig = array_merge($serverConfig, self::getRoutes($profileConfig)); |
||
| 190 | |||
| 191 | // DNS |
||
| 192 | $serverConfig = array_merge($serverConfig, self::getDns($profileConfig)); |
||
| 193 | |||
| 194 | // Client-to-client |
||
| 195 | $serverConfig = array_merge($serverConfig, self::getClientToClient($profileConfig)); |
||
| 196 | |||
| 197 | sort($serverConfig, SORT_STRING); |
||
| 198 | |||
| 199 | $serverConfig = array_merge( |
||
| 200 | [ |
||
| 201 | '#', |
||
| 202 | '# OpenVPN Server Configuration', |
||
| 203 | '#', |
||
| 204 | '# ******************************************', |
||
| 205 | '# * THIS FILE IS GENERATED, DO NOT MODIFY! *', |
||
| 206 | '# ******************************************', |
||
| 207 | '#', |
||
| 208 | ], |
||
| 209 | $serverConfig |
||
| 210 | ); |
||
| 211 | |||
| 212 | $configFile = sprintf('%s/%s', $this->vpnConfigDir, $processConfig['configName']); |
||
| 213 | |||
| 214 | FileIO::writeFile($configFile, implode(PHP_EOL, $serverConfig), 0600); |
||
| 215 | } |
||
| 216 | |||
| 217 | private static function getRoutes(ProfileConfig $profileConfig) |
||
| 218 | { |
||
| 219 | $routeConfig = []; |
||
| 220 | if ($profileConfig->getItem('defaultGateway')) { |
||
| 221 | // For OpenVPN >= 2.4 client only support: |
||
| 222 | //$routeConfig[] = 'push "redirect-gateway def1 ipv6"'; |
||
|
0 ignored issues
–
show
|
|||
| 223 | |||
| 224 | $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"'; |
||
| 225 | // for Windows clients we need this extra route to mark the TAP adapter as |
||
| 226 | // trusted and as having "Internet" access to allow the user to set it to |
||
| 227 | // "Home" or "Work" to allow accessing file shares and printers |
||
| 228 | // NOTE: this will break OS X tunnelblick because on disconnect it will |
||
| 229 | // remove all default routes, including the one set before the VPN |
||
| 230 | // was brought up |
||
| 231 | //$routeConfig[] = 'push "route 0.0.0.0 0.0.0.0"'; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
63% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 232 | |||
| 233 | // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway |
||
| 234 | // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html |
||
| 235 | $routeConfig[] = 'push "redirect-gateway ipv6"'; |
||
| 236 | |||
| 237 | // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6 |
||
| 238 | // networks where the ::/0 default route already exists |
||
| 239 | // XXX: no longer needed in OpenVPN 2.4! But not all our clients are |
||
| 240 | // up to date, e.g. NetAidKit... |
||
| 241 | $routeConfig[] = 'push "route-ipv6 2000::/3"'; |
||
| 242 | } else { |
||
| 243 | // there may be some routes specified, push those, and not the default |
||
| 244 | foreach ($profileConfig->getSection('routes')->toArray() as $route) { |
||
| 245 | $routeIp = new IP($route); |
||
| 246 | if (6 === $routeIp->getFamily()) { |
||
| 247 | // IPv6 |
||
| 248 | $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix()); |
||
| 249 | } else { |
||
| 250 | // IPv4 |
||
| 251 | $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask()); |
||
| 252 | } |
||
| 253 | } |
||
| 254 | } |
||
| 255 | |||
| 256 | return $routeConfig; |
||
| 257 | } |
||
| 258 | |||
| 259 | private static function getDns(ProfileConfig $profileConfig) |
||
| 260 | { |
||
| 261 | // only push DNS if we are the default route |
||
| 262 | if (!$profileConfig->getItem('defaultGateway')) { |
||
| 263 | return []; |
||
| 264 | } |
||
| 265 | |||
| 266 | $dnsEntries = []; |
||
| 267 | foreach ($profileConfig->getSection('dns')->toArray() as $dnsAddress) { |
||
| 268 | // also add DNS6 for OpenVPN >= 2.4beta2 |
||
| 269 | if (false !== strpos($dnsAddress, ':')) { |
||
| 270 | $dnsEntries[] = sprintf('push "dhcp-option DNS6 %s"', $dnsAddress); |
||
| 271 | continue; |
||
| 272 | } |
||
| 273 | $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress); |
||
| 274 | } |
||
| 275 | |||
| 276 | // prevent DNS leakage on Windows |
||
| 277 | $dnsEntries[] = 'push "block-outside-dns"'; |
||
| 278 | |||
| 279 | return $dnsEntries; |
||
| 280 | } |
||
| 281 | |||
| 282 | private static function getClientToClient(ProfileConfig $profileConfig) |
||
| 283 | { |
||
| 284 | if (!$profileConfig->getItem('clientToClient')) { |
||
| 285 | return []; |
||
| 286 | } |
||
| 287 | |||
| 288 | $rangeIp = new IP($profileConfig->getItem('range')); |
||
| 289 | $range6Ip = new IP($profileConfig->getItem('range6')); |
||
| 290 | |||
| 291 | return [ |
||
| 292 | 'client-to-client', |
||
| 293 | sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()), |
||
| 294 | sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()), |
||
| 295 | ]; |
||
| 296 | } |
||
| 297 | |||
| 298 | private function toPort($instanceNumber, $profileNumber, $processNumber) |
||
| 299 | { |
||
| 300 | // convert an instanceNumber, $profileNumber and $processNumber to a management port |
||
| 301 | |||
| 302 | // instanceId = 6 bits (max 64) |
||
| 303 | // profileNumber = 4 bits (max 16) |
||
| 304 | // processNumber = 4 bits (max 16) |
||
| 305 | return ($instanceNumber - 1 << 8) | ($profileNumber - 1 << 4) | ($processNumber); |
||
| 306 | } |
||
| 307 | } |
||
| 308 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.