Last active
June 11, 2021 02:22
-
-
Save runapp/d513f0eaa595368dbb506830f5b73a11 to your computer and use it in GitHub Desktop.
Quickly generate a server and several clients for Wireguard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from typing import List, Tuple | |
from ipaddress import _BaseAddress, _BaseNetwork | |
import io | |
import math | |
import random | |
import argparse | |
import ipaddress | |
import subprocess | |
# region exception types definition | |
class MyError(Exception): | |
pass | |
# region argparse | |
def _arg_parser() -> None: | |
parser = argparse.ArgumentParser( | |
description='Generate Wireguard config files', | |
epilog=f'''Examples: | |
wg-genconf -p 12345 -w 1.2.3.4 10.0.0.123/24 4 | |
Generate 10.0.0.1/24 for gateway, and 10.0.0.2/24 to 10.0.0.5/24 as clients. | |
All configs are written to /etc/wireguard/temp-{{1-5}}.conf . | |
wg-genconf 1.2.3.4 10.0.0.0/24 Alice Bob | |
Generate 10.0.0.1/24 for gateway, 10.0.0.2/24 for Alice and 10.0.0.3/24 for Bob. | |
All configs are written to stdout, with random listen port on gateway. | |
wg-genconf -p 8888 -n 4 -u the_gw_public_key -w 1.2.3.4 10.0.0.0/24 Eve | |
Generate 10.0.0.4/24 for Eve. Mostly used for adding users to an existing configuration. | |
Gateway's extra config is written to /etc/wireguard/temp-0.conf, and Eve's config to /etc/wireguard/temp-3.conf . | |
''', | |
formatter_class=argparse.RawTextHelpFormatter, | |
) | |
parser.add_argument('-p', '--port', type=int, nargs=None, | |
help='Listen port. If not set, use random port in 10000-65534.') | |
parser.add_argument('-w', '--write-to-file', action='store_true', | |
help='Write generated config files to /etc/wireguard/temp-*.conf. If not set, print to screen.') | |
parser.add_argument('-n', '--ip-start-num', type=int, nargs=None, | |
help='Client ip starts at X, usually used when adding clients to existing wg conf.', metavar='X') | |
parser.add_argument('-u', '--pubkey', type=str, nargs=None, | |
help='Specify pubkey manually. Note when set, no gw node header config is generated.', metavar='PUBKEY') | |
parser.add_argument('gw_addr', metavar='gw_public_addr', type=str, | |
help='The public address of gateway, that clients connect to.') | |
parser.add_argument('addr', metavar='vpn_addr', type=str, | |
help='The network of the private subnet.') | |
parser.add_argument('names', metavar='name', type=str, nargs='*', default=None, | |
help='Name of clients. However, for single number, it\'ll be treated as number of clients. See examples.') | |
return parser.parse_args() | |
args = _arg_parser() | |
# region functions | |
def gen_addresses(ADDR: _BaseNetwork, CLIENTS: List[str], ip_startnum=2) -> Tuple[_BaseAddress, List[_BaseAddress]]: | |
gw_addr = ADDR.network_address+1 | |
c_addrs = [] | |
c_addrs_gen = range(ip_startnum, ADDR.num_addresses-2) | |
if len(CLIENTS) > ADDR.num_addresses-3: | |
raise MyError("Subnet capacity too small.") | |
for (i, _) in zip(c_addrs_gen, CLIENTS): | |
c_addrs.append(ADDR.network_address+i) | |
return gw_addr, c_addrs | |
def gen_keypair() -> Tuple[str, str]: | |
try: | |
pri_raw = subprocess.check_output(['wg', 'genkey']) | |
pri = pri_raw.decode('ascii').rstrip() | |
pub = subprocess.check_output(['wg', 'pubkey'], input=pri_raw).decode('ascii').rstrip() | |
except subprocess.CalledProcessError as e: | |
raise MyError(f"Error while generating keys. {e}") | |
return pri, pub | |
def gen_preshared() -> str: | |
try: | |
return subprocess.check_output(['wg', 'genpsk']).decode('ascii').rstrip() | |
except subprocess.CalledProcessError as e: | |
raise MyError(f"Error while generating preshared keys. {e}") | |
def gen_conf(port: int, gw_publicip: str, network: _BaseNetwork, c_names: List[str], write_to_file=False, append_mode_pubkey=None, ip_startnum=2) -> None: | |
gw_addr, c_addrs = gen_addresses(network, c_names, ip_startnum) | |
netmaskbits = network.max_prefixlen - round(math.log(network.num_addresses, 2)) | |
client_conf_files = [] | |
f = io.StringIO() if not write_to_file else open('/etc/wireguard/temp-0.conf', 'w') | |
if not append_mode_pubkey: | |
gw_private, gw_public = gen_keypair() | |
print(f"""[Interface] | |
#Table = off | |
Address = {gw_addr}/{netmaskbits} | |
ListenPort = {port} | |
PrivateKey = {gw_private}""", | |
file=f) | |
else: | |
gw_public = append_mode_pubkey | |
for i, (c_name, c_addr) in enumerate(zip(c_names, c_addrs)): | |
c_private, c_public = gen_keypair() | |
psk = gen_preshared() | |
print(f""" | |
[Peer] | |
# Client {c_name} | |
PublicKey = {c_public} | |
PresharedKey = {psk} | |
AllowedIPs = {c_addr} | |
""", file=f) | |
g = io.StringIO() if not write_to_file else open(f'/etc/wireguard/temp-{c_name}.conf', 'w') | |
client_conf_files.append(g) | |
print(f"""[Interface] | |
#Table = off | |
Address = {c_addr}/{netmaskbits} | |
#ListenPort = {port} | |
PrivateKey = {c_private} | |
[Peer] | |
# Gateway | |
PublicKey = {gw_public} | |
PresharedKey = {psk} | |
AllowedIPs = {network} | |
Endpoint = {gw_publicip}:{port} | |
""", file=g) | |
if not write_to_file: | |
print("#################### Gateway File ####################") | |
print(f.getvalue()) | |
print() | |
for g in client_conf_files: | |
print("#################### Client File ####################") | |
print(g.getvalue()) | |
print() | |
f.close() | |
for g in client_conf_files: | |
g.close() | |
# region main | |
def __main__(): | |
GW_PUBLICIP = args.gw_addr | |
ADDR = ipaddress.ip_network(args.addr, strict=False) | |
PORT = args.port if args.port else random.randint(10000, 65534) | |
IP_START_NUM = args.ip_start_num if args.ip_start_num else 2 | |
CLIENTS = ([str(i-1+IP_START_NUM) for i in range(int(args.names[0]))] | |
if len(args.names) == 1 and args.names[0].isdigit() | |
else args.names | |
) | |
WRITE_TO_FILE = args.write_to_file | |
GW_PUBKEY = args.pubkey | |
if IP_START_NUM < 2: | |
print(f"ip-start-num too small. It should be >=2, rather than {IP_START_NUM}.") | |
exit(1) | |
print(f"args: ADDR={ADDR} PORT={PORT} writetofile={WRITE_TO_FILE} CLIENTS={','.join(CLIENTS)}") | |
if IP_START_NUM or GW_PUBKEY: | |
print(f" GW_PUBKEY={GW_PUBKEY} IP_START_NUM={IP_START_NUM}") | |
try: | |
gen_conf(PORT, GW_PUBLICIP, ADDR, CLIENTS, WRITE_TO_FILE, GW_PUBKEY, IP_START_NUM) | |
except MyError as e: | |
print(e.args[0]) | |
if __name__ == '__main__': | |
__main__() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment