initial commit

This commit is contained in:
Patrick Neff 2024-05-02 11:43:19 +02:00
commit 4d3e204a2d
5 changed files with 319 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.conf
*.png
*.ini

64
flake.lock Normal file
View File

@ -0,0 +1,64 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1713537308,
"narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1680978846,
"narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
"owner": "nix-systems",
"repo": "x86_64-linux",
"rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "x86_64-linux",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

29
flake.nix Normal file
View File

@ -0,0 +1,29 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/x86_64-linux";
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
};
};
outputs = {
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
};
in {
devShells.default = pkgs.mkShellNoCC {
name = "wireguard config";
packages = with pkgs; [
wireguard-tools
qrencode
python3
];
};
});
}

166
generate_config.py Executable file
View File

@ -0,0 +1,166 @@
#!/usr/bin/env python
import subprocess
import argparse
import configparser
import sys
from pathlib import Path
from string import Template
from typing import Iterable, TypedDict, NamedTuple
TEMPLATE_STR = """[Interface]
PrivateKey = $private_key
Address = $address
DNS = $dns
[Peer]
PublicKey = $remote_public_key
PresharedKey = $psk
AllowedIPs = $allowed_ips
Endpoint = $endpoint"""
TEMPLATE = Template(TEMPLATE_STR)
ENDPOINT = "127.0.0.1:51820"
PUBLIC_KEY = "REPLACE_WITH_SERVER_PUBLIC_KEY"
DNS = "127.0.0.1"
class ConfigArgs(TypedDict):
remote_public_key: str
public_key: str
private_key: str
psk: str
address: str
allowed_ips: str
endpoint: str
dns: str
class Config(NamedTuple):
public_key: str
psk: str
config: str
def main():
# Query for connection info (Public key from server, Address, AllowedIPs, etc...)
args = parse_args()
config = load_config(args.config)
if args.outfile and args.outfile.exists():
print("The file {args.outfile} already exists. Do you want to overwrite? y/N ", end="")
overwrite_input = input()
overwrite = overwrite_input.strip()[0].lower() == 'y'
if not overwrite:
sys.exit(0)
address = args.address
allowed_ips = args.allowed_ips
public_key = args.public_key or config["wireguard"]["public_key"]
endpoint = args.endpoint or config["wireguard"]["endpoint"]
dns = args.dns or config["wireguard"]["dns"]
psk = args.psk
public_key, psk, config = generate_config(
address,
allowed_ips,
public_key,
endpoint,
dns,
psk,
)
if not args.quiet:
print_config(public_key, psk, config)
if args.outfile:
save_config_file(args.outfile, config)
def print_config(public_key: str, psk: str, config: str):
print(f"### Public Key:\n{public_key}", end="\n\n")
print(f"### PSK:\n{psk}", end="\n\n")
print(f"### Wireguard Config:\n{config}", end="\n\n")
def generate_config(
address: str,
allowed_ips: Iterable[str],
remote_public_key: str,
endpoint: str,
dns: str,
psk: str | None,
) -> Config:
allowed_ips = ", ".join(allowed_ips)
private_key = get_private_key()
public_key = get_public_key(private_key)
psk = psk or get_psk()
config = {
"private_key": private_key,
"public_key": public_key,
"remote_public_key": remote_public_key,
"psk": psk,
"address": address,
"allowed_ips": allowed_ips,
"endpoint": endpoint,
"dns": dns,
}
config_string = TEMPLATE.substitute(**config)
return Config(public_key, psk, config_string)
def get_private_key() -> str:
result = subprocess.run(["wg", "genkey"], stdout=subprocess.PIPE)
private_key = result.stdout.strip()
return private_key.decode("utf-8")
def get_public_key(private_key: str) -> str:
process = subprocess.Popen(
["wg", "pubkey"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
result = process.communicate(input=private_key.encode("utf-8"))
public_key = result[0].strip()
return public_key.decode("utf-8")
def get_psk() -> str:
result = subprocess.run(["wg", "genpsk"], stdout=subprocess.PIPE)
psk = result.stdout.strip()
return psk.decode("utf-8")
def save_config_file(filename: Path, config: str):
filename.touch()
filename.chmod(0o600)
filename.write_text(config)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="WireGuard Config File Generator")
parser.add_argument("address", help="The remote wireguard server hostname or address")
parser.add_argument("allowed_ips", nargs="+", help="The list of allowed IP ranges for the routed network")
parser.add_argument("-c", "--config", default="config.ini", type=Path, help="The config file to use for server configuration")
parser.add_argument("-o", "--outfile", type=Path, help="The file to write to")
parser.add_argument("-q", "--quiet", default=False, action="store_true", help="Suppress all output")
parser.add_argument("-p", "--psk", help="The Pre-Shared Key to use")
parser.add_argument("-e", "--endpoint", help="The Wireguard server to use")
parser.add_argument("-d", "--dns", help="The DNS server to use")
parser.add_argument("-P", "--public-key", help="The Servers public key to use")
return parser.parse_args()
def load_config(filename: Path) -> configparser.ConfigParser:
config = configparser.ConfigParser()
if not filename.exists():
config["wireguard"] = {
"endpoint": ENDPOINT,
"public_key": PUBLIC_KEY,
"dns": DNS,
}
with filename.open("w") as fp:
config.write(fp)
config.read(filename)
return config
if __name__ == "__main__":
main()

57
generate_qr_code.py Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python
import subprocess
import argparse
import sys
from pathlib import Path
def main():
args = parse_args()
config_file = Path(args.config)
config = config_file.read_text().strip()
if args.outfile and args.outfile.exists():
print("The file {args.outfile} already exists. Do you want to overwrite? y/N ", end="")
overwrite_input = input()
overwrite = overwrite_input.strip()[0].lower() == 'y'
if not overwrite:
sys.exit(0)
if not args.quiet:
print_qr_code(config)
if args.outfile:
save_qr_code_image(args.outfile, config)
def print_qr_code(config: str):
print("### QR Code:")
print(get_qr_code(config))
def get_qr_code(config: str) -> str:
process = subprocess.Popen(
["qrencode", "-t", "utf8"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
result = process.communicate(input=config.encode("utf-8"))
return result[0].decode("utf-8")
def save_qr_code_image(filename: Path, config: str):
process = subprocess.Popen(
["qrencode", "-t", "png", "-o", filename], stdin=subprocess.PIPE
)
process.communicate(input=config.encode("utf-8"))
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="WireGuard QR Code Generator")
parser.add_argument("config", help="The WireGuard config file to generate a qr code for")
parser.add_argument("-o", "--outfile", type=Path, help="The ouput file for the generated PNG image")
parser.add_argument("-q", "--quiet", action="store_true", help="Suppress the output of the UTF-8 QR Code")
return parser.parse_args()
if __name__ == "__main__":
main()