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

OpenVpn::toPort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
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('tls-auth %s/ta.key 0', $tlsDir),
170
            sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()),
171
            sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()),
172
            sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1),
173
            sprintf('script-security %d', $profileConfig->getItem('twoFactor') ? 3 : 2),
174
            sprintf('dev %s', $processConfig['dev']),
175
            sprintf('port %d', $processConfig['port']),
176
            sprintf('management %s %d', $processConfig['managementIp'], $processConfig['managementPort']),
177
            sprintf('setenv INSTANCE_ID %s', $instanceId),
178
            sprintf('setenv PROFILE_ID %s', $profileId),
179
            sprintf('proto %s', $processConfig['proto']),
180
            sprintf('local %s', $processConfig['local']),
181
        ];
182
183
        if (!$profileConfig->getItem('enableLog')) {
184
            $serverConfig[] = 'log /dev/null';
185
        }
186
187
        if ('tcp-server' === $processConfig['proto'] || 'tcp6-server' === $processConfig['proto']) {
188
            $serverConfig[] = 'tcp-nodelay';
189
        }
190
191
        if ('udp' === $processConfig['proto'] || 'udp6' === $processConfig['proto']) {
192
            // notify the clients to reconnect when restarting OpenVPN on the server
193
            // OpenVPN server >= 2.4
194
            $serverConfig[] = 'explicit-exit-notify 1';
195
        }
196
197
        if ($profileConfig->getItem('twoFactor')) {
198
            $serverConfig[] = 'auth-gen-token';  // Added in OpenVPN 2.4
199
            $serverConfig[] = 'auth-user-pass-verify /usr/libexec/vpn-server-node-verify-otp via-env';
200
        }
201
202
        // Routes
203
        $serverConfig = array_merge($serverConfig, self::getRoutes($profileConfig));
204
205
        // DNS
206
        $serverConfig = array_merge($serverConfig, self::getDns($profileConfig));
207
208
        // Client-to-client
209
        $serverConfig = array_merge($serverConfig, self::getClientToClient($profileConfig));
210
211
        sort($serverConfig, SORT_STRING);
212
213
        $serverConfig = array_merge(
214
            [
215
                '#',
216
                '# OpenVPN Server Configuration',
217
                '#',
218
                '# ******************************************',
219
                '# * THIS FILE IS GENERATED, DO NOT MODIFY! *',
220
                '# ******************************************',
221
                '#',
222
            ],
223
            $serverConfig
224
        );
225
226
        $configFile = sprintf('%s/%s', $this->vpnConfigDir, $processConfig['configName']);
227
228
        FileIO::writeFile($configFile, implode(PHP_EOL, $serverConfig), 0600);
229
    }
230
231
    private static function getRoutes(ProfileConfig $profileConfig)
232
    {
233
        $routeConfig = [];
234
        if ($profileConfig->getItem('defaultGateway')) {
235
            // For OpenVPN >= 2.4 client only support:
236
            //$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...
237
238
            $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"';
239
            // for Windows clients we need this extra route to mark the TAP adapter as
240
            // trusted and as having "Internet" access to allow the user to set it to
241
            // "Home" or "Work" to allow accessing file shares and printers
242
            // NOTE: this will break OS X tunnelblick because on disconnect it will
243
            // remove all default routes, including the one set before the VPN
244
            // was brought up
245
            //$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...
246
247
            // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway
248
            // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html
249
            $routeConfig[] = 'push "redirect-gateway ipv6"';
250
251
            // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6
252
            // networks where the ::/0 default route already exists
253
            // XXX: no longer needed in OpenVPN 2.4! But not all our clients are
254
            // up to date, e.g. NetAidKit...
255
            $routeConfig[] = 'push "route-ipv6 2000::/3"';
256
        } else {
257
            // there may be some routes specified, push those, and not the default
258
            foreach ($profileConfig->getSection('routes')->toArray() as $route) {
259
                $routeIp = new IP($route);
260
                if (6 === $routeIp->getFamily()) {
261
                    // IPv6
262
                    $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix());
263
                } else {
264
                    // IPv4
265
                    $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask());
266
                }
267
            }
268
        }
269
270
        return $routeConfig;
271
    }
272
273
    private static function getDns(ProfileConfig $profileConfig)
274
    {
275
        // only push DNS if we are the default route
276
        if (!$profileConfig->getItem('defaultGateway')) {
277
            return [];
278
        }
279
280
        $dnsEntries = [];
281
        foreach ($profileConfig->getSection('dns')->toArray() as $dnsAddress) {
282
            // also add DNS6 for OpenVPN >= 2.4beta2
283
            if (false !== strpos($dnsAddress, ':')) {
284
                $dnsEntries[] = sprintf('push "dhcp-option DNS6 %s"', $dnsAddress);
285
                continue;
286
            }
287
            $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress);
288
        }
289
290
        // prevent DNS leakage on Windows
291
        $dnsEntries[] = 'push "block-outside-dns"';
292
293
        return $dnsEntries;
294
    }
295
296
    private static function getClientToClient(ProfileConfig $profileConfig)
297
    {
298
        if (!$profileConfig->getItem('clientToClient')) {
299
            return [];
300
        }
301
302
        $rangeIp = new IP($profileConfig->getItem('range'));
303
        $range6Ip = new IP($profileConfig->getItem('range6'));
304
305
        return [
306
            'client-to-client',
307
            sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()),
308
            sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()),
309
        ];
310
    }
311
312
    private function toPort($instanceNumber, $profileNumber, $processNumber)
313
    {
314
        // convert an instanceNumber, $profileNumber and $processNumber to a management port
315
316
        // instanceId = 6 bits (max 64)
317
        // profileNumber = 4 bits (max 16)
318
        // processNumber = 4 bits  (max 16)
319
        return ($instanceNumber - 1 << 8) | ($profileNumber - 1 << 4) | ($processNumber);
320
    }
321
}
322