Project

General

Profile

Download (11.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * wg.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
7
 * All rights reserved.
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 * http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21

    
22
require_once("globals.inc");
23
require_once('config.inc');
24

    
25
$wgbinpath = '/usr/local/bin/wg';
26

    
27
// Gnerate a preshared key
28
function gerneratePSK() {
29
	global $wgbinpath;
30

    
31
	return exec("{$wgbinpath} genpsk");
32
}
33

    
34
// Return the next available wWreguard port
35
function next_wg_port() {
36
	global $config;
37

    
38
	init_config_arr(array('wireguard', 'tunnel'));
39
	$tunnels = &$config['wireguard']['tunnel'];
40

    
41
	for ($idx=51820; $idx<65535; $idx++) {
42
		// Check to see if the port is already in use
43
		$found = false;
44
		foreach ($tunnels as $tunnel) {
45
			if ($tunnel['interface']['listenport'] == $idx) {
46
				$found = true;
47
				break;
48
			}
49
		}
50

    
51
		// If not, it can be used
52
		if (!$found) {
53
			return $idx;
54
		}
55
	}
56

    
57
	return 51820;
58
}
59

    
60
// Validate the user's input and return error messages if not acceptable
61
function wg_validate_post($pconfig) {
62
	$input_errors = array();
63

    
64
	// Check the addresses
65
	$addrs = explode(",", $pconfig['interface']['address']);
66

    
67
	foreach ($addrs as $addr) {
68
		$addr = trim($addr);
69

    
70
		if (!is_subnet($addr)) {
71
			$input_errors[] = sprintf(gettext(
72
			    '%1$s is not a valid CIDR address'), $addr);
73
		}
74

    
75
		$iflist = get_configured_interface_list_by_realif();
76

    
77
		$skip = '';
78
		if (!empty($iflist[$pconfig['name']])) {
79
			$skip = $iflist[$pconfig['name']];
80
		}
81

    
82
		$a = explode("/", $addr);
83
		$conflicts = where_is_ipaddr_configured($a[0], $skip, true,
84
		    true, $a[1]);
85

    
86
		if (!empty($conflicts)) {
87
			foreach ($conflicts as $conflict) {
88
				$input_errors[] = sprintf(gettext(
89
				    '%1$s is already configured on this ' .
90
				    'firewall: %2$s (%3$s)'), $addr,
91
					strtoupper($conflict['if']),
92
					$conflict['ip_or_subnet']);
93
			}
94
		}
95
	}
96

    
97
	// Check listen port
98
	$lport = $pconfig['interface']['listenport'];
99
	if (empty($lport) || $lport > 65535 || $lport < 512 ) {
100
		$input_errors[] = gettext("Invalid interface listen port.");
101
	}
102

    
103
	// Check keys
104
	if (empty($pconfig['interface']['privatekey']) ||
105
	    empty($pconfig['interface']['publickey'])) {
106
		$input_errors[] = gettext("Both a public and private key " .
107
		    "must be specified");
108
	}
109

    
110
	// Now the peers
111
	if (isset($pconfig['peers']['wgpeer'])) {
112
		$idx = 0;
113
		foreach ($pconfig['peers']['wgpeer'] as $peer) {
114
			$input_errors = array_merge($input_errors,
115
			    wg_validate_peer($idx, $peer));
116
			$idx++;
117
		}
118
	}
119

    
120
	return $input_errors;
121
}
122

    
123
// Valildate a peer
124
function wg_validate_peer($idx, $peer) {
125
	$input_errors = array();
126

    
127
	// Check remote port
128
	$rport = $peer['port'];
129
	if (!empty($rport) && ($rport > 65535 || $rport < 512 )) {
130
		$input_errors[] = sprintf(gettext(
131
		    'Peer %1$s: Invalid remote port. (%2$s)'), $idx, $rport);
132
	}
133

    
134
	// Check key
135
	if (empty($peer['publickey'])) {
136
		$input_errors[] = sprintf(gettext(
137
		    'Peer %1$s: A public key must be specified'), $idx);
138
	}
139

    
140
	// Endpoint
141
	$ep = trim($peer['endpoint']);
142
	if (!empty($ep) && !is_hostname($ep) && !is_ipaddr($ep)) {
143
		$input_errors[] = sprintf(gettext(
144
		    'Peer %1$s: Endpoint must be a valid IPv4 or IPv6 ' .
145
		    'adress or hostname.'), $idx);
146
	}
147

    
148
	// Allowed IPs
149
	if (!empty($peer['allowedips'])) {
150
		foreach (explode(",", $peer['allowedips']) as $ip) {
151
			if (!is_subnet(trim($ip))) {
152
				$input_errors[] = sprintf(gettext(
153
				    'Peer %1$s: Address %2$s is not a valid ' .
154
				    'IPv4 or IPv6 CIDR subnet aadress.'),
155
					$idx, $ip);
156
			}
157
		}
158
	}
159

    
160
	return $input_errors;
161
}
162

    
163
// Setup all WireGuard tunnels
164
function wg_configure() {
165
	global $config;
166

    
167
	if (!is_array($config["wireguard"]["tunnel"])) {
168
		return;
169
	}
170

    
171
	if (platform_booting()) {
172
		echo gettext("Configuring WireGuard Tunnels... ");
173
	}
174

    
175
	wg_create_config_files();
176

    
177
	foreach ($config["wireguard"]["tunnel"] as $tunnel) {
178
		if (isset($tunnel['enabled']) && $tunnel['enabled'] == 'yes') {
179
			wg_configure_if($tunnel['name']);
180
		}
181
	}
182

    
183
	if (platform_booting()) {
184
		echo gettext("done.") . "\n";
185
	}
186
}
187

    
188
// Setup WireGuard tunnel
189
function wg_configure_if($wg_ifname) {
190
	global $config, $g;
191

    
192
	if (!is_array($config["wireguard"]["tunnel"])) {
193
		return;
194
	}
195

    
196
	unset($tunnel);
197
	foreach ($config["wireguard"]["tunnel"] as $tun) {
198
		if (isset($tun['enabled']) && $tun['enabled'] == 'yes' &&
199
		    $tun['name'] == $wg_ifname) {
200
			$tunnel = $tun;
201
			break;
202
		}
203
	}
204

    
205
	if (!is_array($tunnel)) {
206
		return;
207
	}
208

    
209
	echo $tunnel['name'] . " ";
210
	wg_destroy_if($tunnel['name']);
211
	//$if = pfSense_interface_create($tunnel['name']);
212
	$conf_path = $g["wg_conf_path"] . "/" . $tunnel['name'] . ".conf";
213
	/* XXX - workaround while pfSense-module is fixed */
214
	mwexec("/sbin/ifconfig " . escapeshellarg($tunnel['name']) . " create");
215
	mwexec("/usr/local/bin/wg setconf " . escapeshellarg($tunnel['name']) .
216
	    " " . escapeshellarg($conf_path));
217
	$ip4_first = true;
218
	$ip6_first = true;
219
	foreach (explode(",", $tunnel['interface']['address']) as $addr) {
220
		if (strstr($addr, "/") == false) {
221
			continue;
222
		}
223
		list($ip, $mask) = explode("/", trim($addr));
224
		if (is_ipaddrv4($ip)) {
225
			mwexec("/sbin/ifconfig " .
226
			    escapeshellarg($tunnel['name']) . " inet " .
227
			    escapeshellarg($ip) . " netmask " .
228
			    escapeshellarg(gen_subnet_mask($mask)) .
229
			    ($ip4_first ? "" : " alias"));
230
			$ip4_first = false;
231
		} elseif (is_ipaddrv6($ip)) {
232
			mwexec("/sbin/ifconfig " .
233
			    escapeshellarg($tunnel['name']) . " inet6 " .
234
			    escapeshellarg($ip) . " netmask " .
235
			    escapeshellarg(gen_subnet_mask($mask)) .
236
			    ($ip6_first ? "" : " alias"));
237
			$ip6_first = false;
238
		}
239
	}
240

    
241
	system_routing_configure(
242
	    convert_real_interface_to_friendly_interface_name($wg_ifname));
243
}
244

    
245
// Remove WireGuard tunnel
246
function wg_destroy_if($wg_ifname) {
247
	return (pfSense_interface_destroy($wg_ifname));
248
}
249

    
250
// Generate private key
251
function genKeyPair($json = false) {
252
	global $wgbinpath;
253

    
254
	$privkey = exec("{$wgbinpath} genkey");
255
	$pubkey = genPubKey($privkey);
256

    
257
	$res = array('privkey' => $privkey, 'pubkey' => $pubkey);
258
	return $json ? json_encode($res) : $res;
259
}
260

    
261
// Compose the public key from a provided private key
262
function genPubKey($privkey) {
263
	global $wgbinpath;
264

    
265
	return (exec("echo {$privkey} | {$wgbinpath} pubkey"));
266
}
267

    
268
// Return the next wireGuard interface name that is not currently in use
269
function nextFreeWGInterfaceName() {
270
	global $config;
271

    
272
	$tunnels = $config['wireguard']['tunnel'];
273
	$num = count($tunnels);
274

    
275
	for( $idx=0; $idx<$num; $idx++) {
276
		if ($idx != intval(substr($tunnels[$idx]['name'], 2))) {
277
			return $idx;
278
		}
279
	}
280

    
281
	return $idx;
282
}
283

    
284
function deleteTunnel($tunidx) {
285
	global $config, $g;
286

    
287
	if ($config['wireguard']['tunnel'][$tunidx]) {
288
		unset($ifname);
289
		unset($conf_path);
290
		if (isset($config['wireguard']['tunnel'][$tunidx]['name'])) {
291
			$ifname =
292
			    $config['wireguard']['tunnel'][$tunidx]['name'];
293
		}
294
		if (isset($ifname)) {
295
			$conf_path = $g["wg_conf_path"] . "/" . $ifname .
296
			    ".conf";
297
		}
298
		// Delete the tunnel configuration entry
299
		unset($config['wireguard']['tunnel'][$tunidx]);
300
		write_config("WireGuard tunnel {$index} updated.");
301

    
302
		// Delete the wg?.conf file
303
		if (isset($conf_path) && is_file($conf_path)) {
304
			unlink($conf_path);
305
		}
306

    
307
		// Destroy the deleted tunnel
308
		wg_destroy_if($ifname);
309
	}
310
}
311

    
312
// Write new tunnel values to the configuration system
313
function wg_do_post($post, $json = false) {
314
	global $config;
315

    
316
	init_config_arr(array('wireguard', 'tunnel'));
317

    
318
	$input_errors = array();
319

    
320
	$index = $post['index'];
321

    
322
	$pconfig = &$config['wireguard']['tunnel'][$index];
323

    
324
	$pconfig['name'] = "wg" . $index;
325
	$pconfig['enabled'] = empty($post['enabled']) ? 'no':'yes';
326
	$pconfig['descr'] = $post['descr'];
327

    
328
	// Interface section
329
	$pconfig['interface']['address'] = $post['address'];
330
	$pconfig['interface']['listenport'] = $post['listenport'];
331
	$pconfig['interface']['privatekey'] = $post['privatekey'];
332
	$pconfig['interface']['publickey'] = $post['publickey'];
333

    
334
	if (isset($post['endpoint0'])) {
335
		// Peers section
336
		$pconfig['peers'] = array();
337
		$pconfig['peers']['wgpeer'] = array();
338

    
339
		$idx = 0;
340

    
341
		for (;;) {
342
			if (!$post['publickeyp'.$idx] &&
343
			    !$post['endpoint'.$idx] &&
344
			    !$post['allowedips'.$idx] &&
345
			    !$post['descr'.$idx] &&
346
			    !$post['persistentkeepalive'.$idx]) {
347
				break;
348
			}
349

    
350
			$peer = array();
351
			$peer['publickey'] = $post['publickeyp' . $idx];
352
			$peer['endpoint'] = $post['endpoint' . $idx];
353
			$peer['allowedips'] = $post['allowedips' . $idx];
354
			$peer['descr'] = $post['descp' . $idx];
355
			$peer['persistentkeepalive'] =
356
			    $post['persistentkeepalive' . $idx];
357
			$peer['presharedkey'] = $post['presharedkey' . $idx];
358
			$peer['port'] = $post['port' . $idx];
359
			$peer['peerwgaddr'] = $post['peerwgaddr' . $idx];
360
			$peer['peernwks'] = $post['peernwks' . $idx];
361

    
362
			$pconfig['peers']['wgpeer'][] = $peer;
363
			$idx++;
364
		}
365
	} else {
366
		unset($pconfig['peers']);
367
	}
368

    
369
	$input_errors = wg_validate_post($pconfig);
370

    
371
	if (!$input_errors) {
372
		$config['wireguard']['tunnel'][$index] = $pconfig;
373
		write_config("WireGuard tunnel {$index} updated.");
374
	}
375

    
376
	return(array('input_errors' => $input_errors, 'pconfig' => $pconfig));
377
}
378

    
379
/*
380
 * Read the WireGuard configurations from config.xml and create a number of
381
 * *.conf files for wg-quick to read
382
 */
383
function wg_create_config_files($clean = true) {
384
	global $config, $g;
385

    
386
	if ($config["wireguard"]["tunnel"]) {
387
		$wg_tunnels = $config["wireguard"]["tunnel"];
388
		$cfgpath = $g["wg_conf_path"];
389

    
390
		if (!file_exists($cfgpath)) {
391
			mkdir($cfgpath, 0700, true);
392
		} else {
393
			chmod($cfgpath, 0700);
394
		}
395

    
396
		if ($clean) {
397
			delete_wg_configs();
398
		}
399

    
400
		foreach ($wg_tunnels as $tunnel) {
401
			if (!empty($tunnel['enabled']) &&
402
			    $tunnel['enabled'] == 'yes') {
403
				make_wg_conf($tunnel);
404
			}
405
		}
406
	}
407
}
408

    
409
// Write each file
410
function make_wg_conf($tunnel) {
411
	global $g;
412

    
413
	$txt = "# This WireGuard config file has been created automatically. " .
414
	    "Do not edit!\n";
415

    
416
	$txt .= "# Description: {$tunnel['descr']}\n\n";
417

    
418
	// Process Interfaces section
419
	$txt .= "[Interface]\n";
420

    
421
	if (!empty($tunnel["interface"]["privatekey"])) {
422
		$txt .= "PrivateKey = {$tunnel["interface"]["privatekey"]}\n";
423
	}
424

    
425
	if (!empty($tunnel["interface"]["listenport"])) {
426
		$txt .= "ListenPort = {$tunnel["interface"]["listenport"]}\n";
427
	}
428

    
429
	$txt .= "\n";
430

    
431
	// Process peers section
432
	if (is_array($tunnel['peers']) && is_array($tunnel['peers']['wgpeer']) &&
433
	    count($tunnel['peers']['wgpeer']) > 0) {
434
		foreach ($tunnel['peers']['wgpeer'] as $peer) {
435
			$txt .= "# Peer: {$peer['descr']}\n";
436
			$txt .= "[Peer]\n";
437

    
438
			if (!empty($peer["publickey"])) {
439
				$txt .= "PublicKey = {$peer['publickey']}\n";
440
			}
441

    
442
			if (!empty($peer["endpoint"]) &&
443
			    !empty($peer["port"])) {
444
				$txt .= "EndPoint = " .
445
				    "{$peer['endpoint']}:{$peer['port']}\n";
446
			}
447

    
448
			if (!empty($peer["allowedips"])) {
449
				$txt .= "AllowedIPs = {$peer['allowedips']}\n";
450
			}
451

    
452
			if (!empty($peer["persistentkeepalive"])) {
453
				$txt .= "PersistentKeepalive = " .
454
				    "{$peer['persistentkeepalive']}\n";
455
			}
456

    
457
			if (!empty($peer["presharedkey"])) {
458
				$txt .= "PresharedKey = " .
459
				    "{$peer['presharedkey']}\n";
460
			}
461

    
462
			$txt .= "\n";
463
		}
464
	}
465

    
466
	file_put_contents($g["wg_conf_path"] . "/" . $tunnel['name'] . ".conf",
467
	    $txt);
468
	chmod($g["wg_conf_path"] . "/" . $tunnel['name'] . ".conf", 0600);
469
}
470

    
471
// Remove all wg config files from the conf directory
472
function delete_wg_configs() {
473
	global $g;
474

    
475
	unlink_if_exists($g["wg_conf_path"] . "/*.conf");
476
}
477

    
478
/* Check if at least one tunnel is enabled */
479
function is_wg_enabled() {
480
	global $config;
481

    
482
	init_config_arr(array('wireguard', 'tunnel'));
483

    
484
	foreach ($config['wireguard']['tunnel'] as $tunnel) {
485
		if (empty($tunnel['enabled'])) {
486
			continue;
487
		}
488
		return true;
489
	}
490

    
491
	return false;
492
}
(5-5/5)