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 ( 2f0f7e...aab97a )
by François
02:32
created

OpenVpn::writeProcess()   D

Complexity

Conditions 9
Paths 32

Size

Total Lines 110
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 110
rs 4.8196
c 0
b 0
f 0
cc 9
eloc 66
nc 32
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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