GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 485b85...f48305 )
by François
02:31
created

OpenVpn::__construct()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 8.8571
cc 5
eloc 9
nc 7
nop 2
1
<?php
2
/**
3
 *  Copyright (C) 2016 SURFnet.
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as
7
 *  published by the Free Software Foundation, either version 3 of the
8
 *  License, or (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
namespace SURFnet\VPN\Server\Config;
19
20
use SURFnet\VPN\Server\InstanceConfig;
21
use SURFnet\VPN\Server\PoolConfig;
22
use SURFnet\VPN\Server\IP;
23
use RuntimeException;
24
25
class OpenVpn
26
{
27
    /** @var string */
28
    private $vpnConfigDir;
29
30
    /** @var string */
31
    private $vpnTlsDir;
32
33
    public function __construct($vpnConfigDir, $vpnTlsDir)
34
    {
35
        if (!@file_exists($vpnConfigDir)) {
36
            if (false === @mkdir($vpnConfigDir, 0700, true)) {
37
                throw new RuntimeException(sprintf('unable to create directory "%s"', $vpnConfigDir));
38
            }
39
        }
40
        $this->vpnConfigDir = $vpnConfigDir;
41
        $this->vpnTlsDir = $vpnTlsDir;
42
        if (!@file_exists($vpnTlsDir)) {
43
            if (false === @mkdir($vpnTlsDir, 0700, true)) {
44
                throw new RuntimeException(sprintf('unable to create directory "%s"', $vpnTlsDir));
45
            }
46
        }
47
    }
48
49
    public function generateKeys($apiUri, $userName, $userPass, $commonName, $dhLength)
50
    {
51
        $postData = [
52
            'common_name' => $commonName,
53
            'cert_type' => 'server',
54
        ];
55
56
        $apiUri = sprintf('%s/certificate/', $apiUri);
57
58
        $ch = curl_init($apiUri);
59
        $optionsSet = curl_setopt_array(
60
            $ch,
61
            [
62
                CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
63
                CURLOPT_USERPWD => sprintf('%s:%s', $userName, $userPass),
64
                CURLOPT_POST => true,
65
                CURLOPT_POSTFIELDS => http_build_query($postData),
66
                CURLOPT_RETURNTRANSFER => true,
67
            ]
68
        );
69
70
        if (!$optionsSet) {
71
            throw new RuntimeException('unable to set all cURL options');
72
        }
73
74
        $response = curl_exec($ch);
75
        curl_close($ch);
76
        if (false === $response) {
77
            throw new RuntimeException(sprintf('cURL request error: %s', curl_error($ch)));
78
        }
79
80
        $configData = json_decode($response, true);
81
        $certData = $configData['data']['certificate'];
82
83
        $certFileMapping = [
84
            'ca' => sprintf('%s/ca.crt', $this->vpnTlsDir),
85
            'cert' => sprintf('%s/server.crt', $this->vpnTlsDir),
86
            'key' => sprintf('%s/server.key', $this->vpnTlsDir),
87
            'ta' => sprintf('%s/ta.key', $this->vpnTlsDir),
88
        ];
89
90
        foreach ($certFileMapping as $k => $v) {
91
            if (false === @file_put_contents($v, $certData[$k])) {
92
                throw new RuntimeException(sprintf('unable to write "%s"', $v));
93
            }
94
        }
95
96
        // generate the DH params
97
        $dhFile = sprintf('%s/dh.pem', $this->vpnTlsDir);
98
        $cmd = sprintf('/usr/bin/openssl dhparam -out %s %d >/dev/null 2>/dev/null', $dhFile, $dhLength);
99
        $output = [];
100
        $returnValue = -1;
101
        exec($cmd, $output, $returnValue);
102
        if (0 !== $returnValue) {
103
            throw new RuntimeException('unable to generate DH');
104
        }
105
    }
106
107
    public function write($instanceId, InstanceConfig $instanceConfig)
108
    {
109
        $instanceNumber = $instanceConfig->instanceNumber();
110
        foreach ($instanceConfig->pools() as $poolNumber => $poolId) {
111
            $poolConfig = $instanceConfig->pool($poolId);
112
            $poolConfig->s('instanceId', $instanceId);
113
            $poolConfig->s('poolId', $poolId);
114
            $this->writePool($instanceNumber, $poolNumber, $poolConfig);
115
        }
116
    }
117
118
    private function writePool($instanceNumber, $poolNumber, PoolConfig $poolConfig)
119
    {
120
        $range = new IP($poolConfig->v('range'));
121
        $range6 = new IP($poolConfig->v('range6'));
122
        $processCount = $poolConfig->getProcessCount();
123
124
        $splitRange = $range->split($processCount);
125
        $splitRange6 = $range6->split($processCount);
126
127
        $poolConfig->s('managementIp', sprintf('127.42.%d.%d', 100 + $instanceNumber, 100 + $poolNumber));
128
129
        for ($i = 0; $i < $processCount; ++$i) {
130
            // protocol is udp unless it is the last process when there is
131
            // not just one process
132
            if (1 === $processCount || $i !== $processCount - 1) {
133
                $proto = 'udp';
134
                $port = 1194 + $i;
135
            } else {
136
                $proto = 'tcp';
137
                $port = 1194;
138
            }
139
140
            $poolConfig->s('range', $splitRange[$i]);
141
            $poolConfig->s('range6', $splitRange6[$i]);
142
            $poolConfig->s('dev', sprintf('tun-%d-%d-%d', $instanceNumber, $poolNumber, $i));
143
            $poolConfig->s('proto', $proto);
144
            $poolConfig->s('port', $port);
145
            $poolConfig->s('managementPort', 11940 + $i);
146
            $poolConfig->s(
147
                'configName',
148
                sprintf(
149
                    'server-%s-%s-%d.conf',
150
                    $poolConfig->v('instanceId'),
151
                    $poolConfig->v('poolId'),
152
                    $i
153
                )
154
            );
155
156
            $this->writeProcess($poolConfig);
157
        }
158
    }
159
160
    private function writeProcess(PoolConfig $poolConfig)
161
    {
162
        $tlsDir = sprintf('/etc/openvpn/tls/%s', $poolConfig->v('instanceId'));
163
164
        $rangeIp = new IP($poolConfig->v('range'));
165
        $range6Ip = new IP($poolConfig->v('range6'));
166
167
        // static options
168
        $serverConfig = [
169
            '# OpenVPN Server Configuration',
170
            'verb 3',
171
            'dev-type tun',
172
            'user openvpn',
173
            'group openvpn',
174
            'topology subnet',
175
            'persist-key',
176
            'persist-tun',
177
            'keepalive 10 60',
178
            'comp-lzo no',
179
            'remote-cert-tls client',
180
            'tls-version-min 1.2',
181
            'tls-cipher TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA',
182
            'auth SHA256',
183
            'cipher AES-256-CBC',
184
            'client-connect /usr/bin/vpn-server-api-client-connect',
185
            'client-disconnect /usr/bin/vpn-server-api-client-disconnect',
186
            'push "comp-lzo no"',
187
            'push "explicit-exit-notify 3"',
188
            sprintf('ca %s/ca.crt', $tlsDir),
189
            sprintf('cert %s/server.crt', $tlsDir),
190
            sprintf('key %s/server.key', $tlsDir),
191
            sprintf('dh %s/dh.pem', $tlsDir),
192
            sprintf('tls-auth %s/ta.key 0', $tlsDir),
193
            sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()),
194
            sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()),
195
            sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1),
196
            sprintf('script-security %d', $poolConfig->v('twoFactor', false) ? 3 : 2),
197
            sprintf('dev %s', $poolConfig->v('dev')),
198
            sprintf('port %d', $poolConfig->v('port')),
199
            sprintf('management %s %d', $poolConfig->v('managementIp'), $poolConfig->v('managementPort')),
200
            sprintf('setenv INSTANCE_ID %s', $poolConfig->v('instanceId')),
201
            sprintf('setenv POOL_ID %s', $poolConfig->v('poolId')),
202
            sprintf('proto %s', 'tcp' === $poolConfig->v('proto') ? 'tcp-server' : 'udp'),
203
            sprintf('local %s', 'tcp' === $poolConfig->v('proto') ? $poolConfig->v('managementIp') : $poolConfig->v('listen', '0.0.0.0')),
204
205
            // increase the renegotiation time to 8h from the default of 1h when
206
            // using 2FA, otherwise the user would be asked for the 2FA key every
207
            // hour
208
            sprintf('reneg-sec %d', $poolConfig->v('twoFactor', false) ? 28800 : 3600),
209
        ];
210
211
        if (!$poolConfig->v('enableLog', false)) {
212
            $serverConfig[] = 'log /dev/null';
213
        }
214
215
        if ('tcp' === $poolConfig->v('proto')) {
216
            $serverConfig[] = 'tcp-nodelay';
217
        }
218
219
        if ($poolConfig->v('twoFactor', false)) {
220
            $serverConfig[] = 'auth-user-pass-verify /usr/bin/vpn-server-api-verify-otp via-env';
221
        }
222
223
        // Routes
224
        $serverConfig = array_merge($serverConfig, self::getRoutes($poolConfig));
225
226
        // DNS
227
        $serverConfig = array_merge($serverConfig, self::getDns($poolConfig));
228
229
        // Client-to-client
230
        $serverConfig = array_merge($serverConfig, self::getClientToClient($poolConfig));
231
232
        sort($serverConfig, SORT_STRING);
233
234
        $configFile = sprintf('%s/%s', $this->vpnConfigDir, $poolConfig->v('configName'));
235
236
        if (false === @file_put_contents($configFile, implode(PHP_EOL, $serverConfig))) {
237
            throw new RuntimeException(sprintf('unable to write configuration file "%s"', $configFile));
238
        }
239
    }
240
241
    private static function getRoutes(PoolConfig $poolConfig)
242
    {
243
        $routeConfig = [];
244
        if ($poolConfig->v('defaultGateway', false)) {
245
            $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"';
246
247
            // for Windows clients we need this extra route to mark the TAP adapter as
248
            // trusted and as having "Internet" access to allow the user to set it to
249
            // "Home" or "Work" to allow accessing file shares and printers
250
            // NOTE: this will break OS X tunnelblick because on disconnect it will
251
            // remove all default routes, including the one set before the VPN
252
            // was brought up
253
            //$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...
254
255
            // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway
256
            // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html
257
            $routeConfig[] = 'push "redirect-gateway ipv6"';
258
259
            // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6
260
            // networks where the ::/0 default route already exists
261
            $routeConfig[] = 'push "route-ipv6 2000::/3"';
262
        } else {
263
            // there may be some routes specified, push those, and not the default
264
            foreach ($poolConfig->v('routes', []) as $route) {
265
                $routeIp = new IP($route);
266
                if (6 === $routeIp->getFamily()) {
267
                    // IPv6
268
                    $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix());
269
                } else {
270
                    // IPv4
271
                    $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask());
272
                }
273
            }
274
        }
275
276
        return $routeConfig;
277
    }
278
279
    private static function getDns(PoolConfig $poolConfig)
280
    {
281
        // only push DNS if we are the default route
282
        if (!$poolConfig->v('defaultGateway', false)) {
283
            return [];
284
        }
285
286
        $dnsEntries = [];
287
        foreach ($poolConfig->v('dns', []) as $dnsAddress) {
288
            $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress);
289
        }
290
291
        // prevent DNS leakage on Windows
292
        $dnsEntries[] = 'push "block-outside-dns"';
293
294
        return $dnsEntries;
295
    }
296
297
    private static function getClientToClient(PoolConfig $poolConfig)
298
    {
299
        if (!$poolConfig->v('clientToClient', false)) {
300
            return [];
301
        }
302
303
        $rangeIp = new IP($poolConfig->v('range'));
304
        $range6Ip = new IP($poolConfig->v('range6'));
305
306
        return [
307
            'client-to-client',
308
            sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()),
309
            sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()),
310
        ];
311
    }
312
}
313