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 ( e6167a...90a395 )
by François
02:04
created

OpenVpn::getDns()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 1
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
        if ('auto' === $managementIp = $profileConfig->getItem('managementIp')) {
74
            $managementIp = sprintf('10.42.%d.%d', 100 + $instanceNumber, 100 + $profileConfig->getItem('profileNumber'));
75
        }
76
77
        $processConfig = [
78
            'managementIp' => $managementIp,
79
        ];
80
81
        for ($i = 0; $i < $processCount; ++$i) {
82
            list($proto, $port) = self::getProtoPort($profileConfig->getItem('vpnProtoPorts'), $profileConfig->getItem('listen'))[$i];
83
            $processConfig['range'] = $splitRange[$i];
84
            $processConfig['range6'] = $splitRange6[$i];
85
            $processConfig['dev'] = sprintf('tun-%d-%d-%d', $instanceNumber, $profileConfig->getItem('profileNumber'), $i);
86
            $processConfig['proto'] = $proto;
87
            $processConfig['port'] = $port;
88
            $processConfig['local'] = $profileConfig->getItem('listen');
89
            $processConfig['managementPort'] = 11940 + $i;
90
            $processConfig['configName'] = sprintf(
91
                '%s-%s-%d.conf',
92
                $instanceId,
93
                $profileId,
94
                $i
95
            );
96
97
            $this->writeProcess($instanceId, $profileId, $profileConfig, $processConfig);
98
        }
99
    }
100
101
    private static function getFamilyProto($listenAddress, $proto)
102
    {
103
        $v6 = false !== strpos($listenAddress, ':');
104
        if ('udp' === $proto) {
105
            return $v6 ? 'udp6' : 'udp';
106
        }
107
        if ('tcp' === $proto) {
108
            return $v6 ? 'tcp6-server' : 'tcp-server';
109
        }
110
111
        throw new RuntimeException('only "tcp" and "udp" are supported as protocols');
112
    }
113
114
    private static function getProtoPort(array $vpnProcesses, $listenAddress)
115
    {
116
        $convertedPortProto = [];
117
118
        foreach ($vpnProcesses as $vpnProcess) {
119
            list($proto, $port) = explode('/', $vpnProcess);
120
            $convertedPortProto[] = [self::getFamilyProto($listenAddress, $proto), $port];
121
        }
122
123
        return $convertedPortProto;
124
    }
125
126
    private function writeProcess($instanceId, $profileId, ProfileConfig $profileConfig, array $processConfig)
127
    {
128
        $tlsDir = sprintf('tls/%s/%s', $instanceId, $profileId);
129
130
        $rangeIp = new IP($processConfig['range']);
131
        $range6Ip = new IP($processConfig['range6']);
132
133
        // static options
134
        $serverConfig = [
135
            '# OpenVPN Server Configuration',
136
            'verb 3',
137
            'dev-type tun',
138
            sprintf('user %s', $profileConfig->getItem('_user')),
139
            sprintf('group %s', $profileConfig->getItem('_group')),
140
            'topology subnet',
141
            'persist-key',
142
            'persist-tun',
143
            'keepalive 10 60',
144
            'comp-lzo no',
145
            'remote-cert-tls client',
146
            'tls-version-min 1.2',
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
            'auth SHA256',
149
            'cipher AES-256-CBC',
150
            'client-connect /usr/libexec/vpn-server-node-client-connect',
151
            'client-disconnect /usr/libexec/vpn-server-node-client-disconnect',
152
            'push "comp-lzo no"',
153
            'push "explicit-exit-notify 3"',
154
155
            // we probably do NOT want this, it is up to the client to decide
156
            // about this!
157
            //'push "persist-key"',
158
            //'push "persist-tun"',
159
160
            sprintf('ca %s/ca.crt', $tlsDir),
161
            sprintf('cert %s/server.crt', $tlsDir),
162
            sprintf('key %s/server.key', $tlsDir),
163
            sprintf('dh %s/dh.pem', $tlsDir),
164
            sprintf('tls-auth %s/ta.key 0', $tlsDir),
165
            sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()),
166
            sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()),
167
            sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1),
168
            sprintf('script-security %d', $profileConfig->getItem('twoFactor') ? 3 : 2),
169
            sprintf('dev %s', $processConfig['dev']),
170
            sprintf('port %d', $processConfig['port']),
171
            sprintf('management %s %d', $processConfig['managementIp'], $processConfig['managementPort']),
172
            sprintf('setenv INSTANCE_ID %s', $instanceId),
173
            sprintf('setenv PROFILE_ID %s', $profileId),
174
            sprintf('proto %s', $processConfig['proto']),
175
            sprintf('local %s', $processConfig['local']),
176
        ];
177
178
        if (!$profileConfig->getItem('enableLog')) {
179
            $serverConfig[] = 'log /dev/null';
180
        }
181
182
        if ('tcp-server' === $processConfig['proto'] || 'tcp6-server' === $processConfig['proto']) {
183
            $serverConfig[] = 'tcp-nodelay';
184
        }
185
186
        if ('udp' === $processConfig['proto'] || 'udp6' === $processConfig['proto']) {
187
            // notify the clients to reconnect when restarting OpenVPN on the server
188
            // OpenVPN server >= 2.4
189
            $serverConfig[] = 'explicit-exit-notify 1';
190
        }
191
192
        if ($profileConfig->getItem('twoFactor')) {
193
            $serverConfig[] = 'auth-gen-token';  // Added in OpenVPN 2.4
194
            $serverConfig[] = 'auth-user-pass-verify /usr/libexec/vpn-server-node-verify-otp via-env';
195
        }
196
197
        // Routes
198
        $serverConfig = array_merge($serverConfig, self::getRoutes($profileConfig));
199
200
        // DNS
201
        $serverConfig = array_merge($serverConfig, self::getDns($profileConfig));
202
203
        // Client-to-client
204
        $serverConfig = array_merge($serverConfig, self::getClientToClient($profileConfig));
205
206
        sort($serverConfig, SORT_STRING);
207
208
        $configFile = sprintf('%s/%s', $this->vpnConfigDir, $processConfig['configName']);
209
210
        FileIO::writeFile($configFile, implode(PHP_EOL, $serverConfig), 0600);
211
    }
212
213
    private static function getRoutes(ProfileConfig $profileConfig)
214
    {
215
        $routeConfig = [];
216
        if ($profileConfig->getItem('defaultGateway')) {
217
            $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"';
218
219
            // for Windows clients we need this extra route to mark the TAP adapter as
220
            // trusted and as having "Internet" access to allow the user to set it to
221
            // "Home" or "Work" to allow accessing file shares and printers
222
            // NOTE: this will break OS X tunnelblick because on disconnect it will
223
            // remove all default routes, including the one set before the VPN
224
            // was brought up
225
            //$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...
226
227
            // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway
228
            // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html
229
            $routeConfig[] = 'push "redirect-gateway ipv6"';
230
231
            // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6
232
            // networks where the ::/0 default route already exists
233
            // XXX: no longer needed in OpenVPN 2.4! But not all our clients are
234
            // up to date, e.g. NetAidKit...
235
            $routeConfig[] = 'push "route-ipv6 2000::/3"';
236
        } else {
237
            // there may be some routes specified, push those, and not the default
238
            foreach ($profileConfig->getSection('routes')->toArray() as $route) {
239
                $routeIp = new IP($route);
240
                if (6 === $routeIp->getFamily()) {
241
                    // IPv6
242
                    $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix());
243
                } else {
244
                    // IPv4
245
                    $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask());
246
                }
247
            }
248
        }
249
250
        return $routeConfig;
251
    }
252
253
    private static function getDns(ProfileConfig $profileConfig)
254
    {
255
        // only push DNS if we are the default route
256
        if (!$profileConfig->getItem('defaultGateway')) {
257
            return [];
258
        }
259
260
        $dnsEntries = [];
261
        foreach ($profileConfig->getSection('dns')->toArray() as $dnsAddress) {
262
            // also add DNS6 for OpenVPN >= 2.4beta2
263
            if (false !== strpos($dnsAddress, ':')) {
264
                $dnsEntries[] = sprintf('push "dhcp-option DNS6 %s"', $dnsAddress);
265
                continue;
266
            }
267
            $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress);
268
        }
269
270
        // prevent DNS leakage on Windows
271
        $dnsEntries[] = 'push "block-outside-dns"';
272
273
        return $dnsEntries;
274
    }
275
276
    private static function getClientToClient(ProfileConfig $profileConfig)
277
    {
278
        if (!$profileConfig->getItem('clientToClient')) {
279
            return [];
280
        }
281
282
        $rangeIp = new IP($profileConfig->getItem('range'));
283
        $range6Ip = new IP($profileConfig->getItem('range6'));
284
285
        return [
286
            'client-to-client',
287
            sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()),
288
            sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()),
289
        ];
290
    }
291
}
292