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 ( 720268...777cc7 )
by François
03:52
created

OpenVpn::writePool()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 39
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 27
nc 4
nop 4
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\Node;
19
20
use SURFnet\VPN\Common\FileIO;
21
use SURFnet\VPN\Common\ProfileConfig;
22
use RuntimeException;
23
use SURFnet\VPN\Common\HttpClient\CaClient;
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
        FileIO::createDir($vpnConfigDir, 0700);
36
        $this->vpnConfigDir = $vpnConfigDir;
37
        FileIO::createDir($vpnTlsDir, 0700);
38
        $this->vpnTlsDir = $vpnTlsDir;
39
    }
40
41
    public function generateKeys(CaClient $caClient, $commonName, $dhSourceFile)
42
    {
43
        $certData = $caClient->addServerCertificate($commonName);
44
45
        $certFileMapping = [
46
            'ca' => sprintf('%s/ca.crt', $this->vpnTlsDir),
47
            'cert' => sprintf('%s/server.crt', $this->vpnTlsDir),
48
            'key' => sprintf('%s/server.key', $this->vpnTlsDir),
49
            'ta' => sprintf('%s/ta.key', $this->vpnTlsDir),
50
        ];
51
52
        foreach ($certFileMapping as $k => $v) {
53
            FileIO::writeFile($v, $certData[$k], 0600);
54
        }
55
56
        // copy the DH parameter file
57
        $dhTargetFile = sprintf('%s/dh.pem', $this->vpnTlsDir);
58
        if (false === copy($dhSourceFile, $dhTargetFile)) {
59
            throw new RuntimeException('unable to copy DH file');
60
        }
61
    }
62
63
    public function writeProfile($instanceNumber, $instanceId, $profileId, ProfileConfig $profileConfig)
64
    {
65
        $range = new IP($profileConfig->v('range'));
66
        $range6 = new IP($profileConfig->v('range6'));
67
        $processCount = $profileConfig->v('processCount');
68
69
        $splitRange = $range->split($processCount);
70
        $splitRange6 = $range6->split($processCount);
71
72
        if ($profileConfig->e('managementIp')) {
73
            $managementIp = $profileConfig->v('managementIp');
74
        } else {
75
            $managementIp = sprintf('127.42.%d.%d', 100 + $instanceNumber, 100 + $profileConfig->v('profileNumber'));
76
        }
77
78
        $processConfig = [
79
            'managementIp' => $managementIp,
80
        ];
81
82
        for ($i = 0; $i < $processCount; ++$i) {
83
            list($proto, $port, $local) = self::determineProtoPortLocal($profileConfig, $i, $managementIp);
84
85
            $processConfig['range'] = $splitRange[$i];
86
            $processConfig['range6'] = $splitRange6[$i];
87
            $processConfig['dev'] = sprintf('tun-%d-%d-%d', $instanceNumber, $profileConfig->v('profileNumber'), $i);
88
            $processConfig['proto'] = $proto;
89
            $processConfig['port'] = $port;
90
            $processConfig['local'] = $local;
91
            $processConfig['managementPort'] = 11940 + $i;
92
            $processConfig['configName'] = sprintf(
93
                'server-%s-%s-%d.conf',
94
                $instanceId,
95
                $profileId,
96
                $i
97
            );
98
99
            $this->writeProcess($instanceId, $profileId, $profileConfig, $processConfig);
100
        }
101
    }
102
103
    private function writeProcess($instanceId, $profileId, ProfileConfig $profileConfig, array $processConfig)
104
    {
105
        $tlsDir = sprintf('/etc/openvpn/tls/%s/%s', $instanceId, $profileId);
106
107
        $rangeIp = new IP($processConfig['range']);
108
        $range6Ip = new IP($processConfig['range6']);
109
110
        // static options
111
        $serverConfig = [
112
            '# OpenVPN Server Configuration',
113
            'verb 3',
114
            'dev-type tun',
115
            sprintf('user %s', $profileConfig->v('_user')),
116
            sprintf('group %s', $profileConfig->v('_group')),
117
            'topology subnet',
118
            'persist-key',
119
            'persist-tun',
120
            'keepalive 10 60',
121
            'comp-lzo no',
122
            'remote-cert-tls client',
123
            'tls-version-min 1.2',
124
            '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',
125
            'auth SHA256',
126
            'cipher AES-256-CBC',
127
            'client-connect /usr/sbin/vpn-server-node-client-connect',
128
            'client-disconnect /usr/sbin/vpn-server-node-client-disconnect',
129
            'push "comp-lzo no"',
130
            'push "explicit-exit-notify 3"',
131
            sprintf('ca %s/ca.crt', $tlsDir),
132
            sprintf('cert %s/server.crt', $tlsDir),
133
            sprintf('key %s/server.key', $tlsDir),
134
            sprintf('dh %s/dh.pem', $tlsDir),
135
            sprintf('tls-auth %s/ta.key 0', $tlsDir),
136
            sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()),
137
            sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()),
138
            sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1),
139
            sprintf('script-security %d', $profileConfig->v('twoFactor') ? 3 : 2),
140
            sprintf('dev %s', $processConfig['dev']),
141
            sprintf('port %d', $processConfig['port']),
142
            sprintf('management %s %d', $processConfig['managementIp'], $processConfig['managementPort']),
143
            sprintf('setenv INSTANCE_ID %s', $instanceId),
144
            sprintf('setenv PROFILE_ID %s', $profileId),
145
            sprintf('proto %s', $processConfig['proto']),
146
            sprintf('local %s', $processConfig['local']),
147
148
            // increase the renegotiation time to 8h from the default of 1h when
149
            // using 2FA, otherwise the user would be asked for the 2FA key every
150
            // hour
151
            sprintf('reneg-sec %d', $profileConfig->v('twoFactor') ? 28800 : 3600),
152
        ];
153
154
        if (!$profileConfig->v('enableLog')) {
155
            $serverConfig[] = 'log /dev/null';
156
        }
157
158
        if ('tcp' === $processConfig['proto']) {
159
            $serverConfig[] = 'tcp-nodelay';
160
        }
161
162
        if ($profileConfig->v('twoFactor')) {
163
            $serverConfig[] = 'auth-user-pass-verify /usr/sbin/vpn-server-node-verify-otp via-env';
164
        }
165
166
        // Routes
167
        $serverConfig = array_merge($serverConfig, self::getRoutes($profileConfig));
168
169
        // DNS
170
        $serverConfig = array_merge($serverConfig, self::getDns($profileConfig));
171
172
        // Client-to-client
173
        $serverConfig = array_merge($serverConfig, self::getClientToClient($profileConfig));
174
175
        sort($serverConfig, SORT_STRING);
176
177
        $configFile = sprintf('%s/%s', $this->vpnConfigDir, $processConfig['configName']);
178
179
        FileIO::writeFile($configFile, implode(PHP_EOL, $serverConfig), 0600);
180
    }
181
182
    private static function getRoutes(ProfileConfig $profileConfig)
183
    {
184
        $routeConfig = [];
185
        if ($profileConfig->v('defaultGateway')) {
186
            $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"';
187
188
            // for Windows clients we need this extra route to mark the TAP adapter as
189
            // trusted and as having "Internet" access to allow the user to set it to
190
            // "Home" or "Work" to allow accessing file shares and printers
191
            // NOTE: this will break OS X tunnelblick because on disconnect it will
192
            // remove all default routes, including the one set before the VPN
193
            // was brought up
194
            //$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...
195
196
            // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway
197
            // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html
198
            $routeConfig[] = 'push "redirect-gateway ipv6"';
199
200
            // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6
201
            // networks where the ::/0 default route already exists
202
            $routeConfig[] = 'push "route-ipv6 2000::/3"';
203
        } else {
204
            // there may be some routes specified, push those, and not the default
205
            foreach ($profileConfig->v('routes') as $route) {
206
                $routeIp = new IP($route);
207
                if (6 === $routeIp->getFamily()) {
208
                    // IPv6
209
                    $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix());
210
                } else {
211
                    // IPv4
212
                    $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask());
213
                }
214
            }
215
        }
216
217
        return $routeConfig;
218
    }
219
220
    private static function getDns(ProfileConfig $profileConfig)
221
    {
222
        // only push DNS if we are the default route
223
        if (!$profileConfig->v('defaultGateway')) {
224
            return [];
225
        }
226
227
        $dnsEntries = [];
228
        foreach ($profileConfig->v('dns') as $dnsAddress) {
229
            $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress);
230
        }
231
232
        // prevent DNS leakage on Windows
233
        $dnsEntries[] = 'push "block-outside-dns"';
234
235
        return $dnsEntries;
236
    }
237
238
    private static function getClientToClient(ProfileConfig $profileConfig)
239
    {
240
        if (!$profileConfig->v('clientToClient')) {
241
            return [];
242
        }
243
244
        $rangeIp = new IP($profileConfig->v('range'));
245
        $range6Ip = new IP($profileConfig->v('range6'));
246
247
        return [
248
            'client-to-client',
249
            sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()),
250
            sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()),
251
        ];
252
    }
253
254
    private static function determineProtoPortLocal(ProfileConfig $profileConfig, $i, $managementIp)
255
    {
256
        $processCount = $profileConfig->v('processCount');
257
        if ($profileConfig->v('dedicatedNode')) {
258
            // we listen on all IPs, both IPv4 and IPv6
259 View Code Duplication
            if (1 === $processCount || $i !== $processCount - 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
260
                // UDP
261
                $proto = 'udp6';
262
                $port = 1194 + $i;
263
                $local = '::';
264
            } else {
265
                // TCP
266
                $proto = 'tcp6-server';
267
                $port = 443;
268
                $local = '::';
269
            }
270 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
271
            // not dedicated, we share with SNI Proxy, we listen on IPv4 only
272
            if (1 === $processCount || $i !== $processCount - 1) {
273
                // UDP
274
                $proto = 'udp';
275
                $port = 1194 + $i;
276
                $local = $profileConfig->v('listen');
277
            } else {
278
                // TCP
279
                $proto = 'tcp-server';
280
                $port = 1194;
281
                $local = $managementIp;
282
            }
283
        }
284
285
        return [$proto, $port, $local];
286
    }
287
}
288