add make_client.py
This commit is contained in:
parent
16e9aeca9b
commit
7834aed9b3
226
make_client.py
Normal file
226
make_client.py
Normal file
@ -0,0 +1,226 @@
|
||||
import sys
|
||||
import subprocess
|
||||
import itertools
|
||||
import time
|
||||
import os
|
||||
from pprint import pprint
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
from dataclasses import dataclass
|
||||
|
||||
class Filenames(NamedTuple):
|
||||
ssl_config: Path
|
||||
cfg_tmpl: Path
|
||||
ca: Path
|
||||
req: Path
|
||||
key: Path
|
||||
cert: Path
|
||||
config: Path
|
||||
|
||||
@classmethod
|
||||
def for_client(cls, name: str):
|
||||
return cls(
|
||||
Path(os.environ.get("KEY_CONFIG", "openssl-1.0.0.cnf")),
|
||||
Path("template.ovpn"),
|
||||
Path("keys") / "ca.crt",
|
||||
Path("keys") / f"{name}.csr",
|
||||
Path("keys") / f"{name}.key",
|
||||
Path("keys") / f"{name}.crt",
|
||||
Path("config") / f"{name}.ovpn"
|
||||
)
|
||||
|
||||
def is_config_exists(self):
|
||||
return self.config.exists()
|
||||
|
||||
def is_cert_exists(self):
|
||||
return self.cert.exists()
|
||||
|
||||
@dataclass
|
||||
class CertFiles:
|
||||
_encoding = "oem"
|
||||
_req_days = "3650"
|
||||
|
||||
ssl_config_filename: str
|
||||
cfg_tmpl_filename: str
|
||||
ca_filename: str
|
||||
req_filename: str
|
||||
key_filename: str
|
||||
cert_filename: str
|
||||
config_filename: str
|
||||
|
||||
@classmethod
|
||||
def from_files(cls, files: Filenames):
|
||||
return cls(
|
||||
str(files.ssl_config),
|
||||
str(files.cfg_tmpl),
|
||||
str(files.ca),
|
||||
str(files.req),
|
||||
str(files.key),
|
||||
str(files.cert),
|
||||
str(files.config)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _readfile(cls, filename) -> str:
|
||||
result = ""
|
||||
if Path(filename).exists():
|
||||
with open(filename, encoding=cls._encoding, mode="r") as f:
|
||||
result = f.read()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _writefile(cls, filename, text) -> str:
|
||||
with open(filename, encoding=cls._encoding, mode="w") as f:
|
||||
f.write(text)
|
||||
|
||||
@property
|
||||
def cfg_tmpl(self):
|
||||
return self._readfile(self.cfg_tmpl_filename)
|
||||
|
||||
@property
|
||||
def ca(self):
|
||||
return self._readfile(self.ca_filename)
|
||||
|
||||
@property
|
||||
def req(self):
|
||||
return self._readfile(self.req_filename)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._readfile(self.key_filename)
|
||||
|
||||
@property
|
||||
def cert(self):
|
||||
return self._readfile(self.cert_filename)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return self._readfile(self.config_filename)
|
||||
|
||||
@config.setter
|
||||
def config(self, value):
|
||||
self._writefile(self.config_filename, value)
|
||||
|
||||
def request(self):
|
||||
if self.req:
|
||||
return
|
||||
print("request", end="... ")
|
||||
cmd = f"openssl req -days {self._req_days} -nodes -new -keyout {self.key_filename} -out {self.req_filename} -config {self.ssl_config_filename} -batch"
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def sign(self):
|
||||
if self.cert:
|
||||
return
|
||||
print("sign", end="... ")
|
||||
cmd = f"openssl ca -days {self._req_days} -out {self.cert_filename} -in {self.req_filename} -config {self.ssl_config_filename} -batch"
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def render_client_config(self):
|
||||
self.config = self.cfg_tmpl.replace("{{ca}}", self.ca.rstrip("\n")).replace("{{cert}}", self.cert.rstrip("\n")).replace("{{key}}", self.key.rstrip("\n"))
|
||||
|
||||
def make_validator(valid_keys=None):
|
||||
valid = valid_keys or []
|
||||
all_keys_is_valid = len(valid) == 0
|
||||
valid = set(valid)
|
||||
def validate_pair(ob):
|
||||
try:
|
||||
if not (len(ob) == 2):
|
||||
print("Unexpected result:", ob, file=sys.stderr)
|
||||
raise ValueError
|
||||
except:
|
||||
return False
|
||||
return all_keys_is_valid or ob[0] in valid
|
||||
return validate_pair
|
||||
|
||||
def consume(iter):
|
||||
try:
|
||||
while True: next(iter)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
def get_environment_from_batch_command(env_cmd, initial=None, /, valid_keys=None, encoding = "oem"):
|
||||
"""
|
||||
Take a command (either a single command or list of arguments)
|
||||
and return the environment created after running that command.
|
||||
Note that if the command must be a batch file or .cmd file, or the
|
||||
changes to the environment will not be captured.
|
||||
If initial is supplied, it is used as the initial environment passed
|
||||
to the child process.
|
||||
"""
|
||||
if not isinstance(env_cmd, (list, tuple)):
|
||||
env_cmd = [env_cmd]
|
||||
# construct the command that will alter the environment
|
||||
env_cmd = subprocess.list2cmdline(env_cmd)
|
||||
# create a tag so we can tell in the output when the proc is done
|
||||
tag = 'Done running command'
|
||||
# construct a cmd.exe command to do accomplish this
|
||||
cmd = 'cmd.exe /s /c "{env_cmd} && echo "{tag}" && set"'.format(**vars())
|
||||
# launch the process
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial)
|
||||
# parse the output sent to stdout
|
||||
lines = proc.stdout
|
||||
# consume whatever output occurs until the tag is reached
|
||||
consume(itertools.takewhile(lambda l: tag not in l.decode(encoding), lines))
|
||||
# define a way to handle each KEY=VALUE line
|
||||
handle_line = lambda l: tuple(map(lambda x: x.strip(), l.decode(encoding).rstrip().split('=',1)))
|
||||
# parse key/values into pairs
|
||||
pairs = map(handle_line, lines)
|
||||
# make sure the pairs are valid
|
||||
valid_pairs = filter(make_validator(valid_keys), pairs)
|
||||
# construct a dictionary of the pairs
|
||||
result = dict(valid_pairs)
|
||||
# let the process finish
|
||||
proc.communicate()
|
||||
return result
|
||||
|
||||
def load_vars():
|
||||
valid_keys = "HOME,KEY_CONFIG,KEY_DIR,DH_KEY_SIZE,KEY_SIZE,KEY_COUNTRY,KEY_PROVINCE,KEY_CITY,KEY_ORG,KEY_EMAIL,KEY_OU,PKCS11_MODULE_PATH,PKCS11_PIN".split(",")
|
||||
envs = get_environment_from_batch_command("vars.bat", valid_keys=valid_keys)
|
||||
os.environ.update(envs)
|
||||
print("loaded from vars:")
|
||||
pprint(envs)
|
||||
print("---\n\n")
|
||||
|
||||
def set_name_vars(name: str):
|
||||
os.environ.update({
|
||||
"KEY_CN": name,
|
||||
"KEY_NAME": name
|
||||
})
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='make_client',
|
||||
description='Generate key and make client config',
|
||||
epilog='. . .')
|
||||
parser.add_argument('client_name')
|
||||
parser.add_argument('-e', '--email')
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
load_vars()
|
||||
set_name_vars(args.client_name)
|
||||
|
||||
filenames = Filenames.for_client(args.client_name)
|
||||
if filenames.is_config_exists():
|
||||
print(f"file already exists: {filenames.config}", file=sys.stderr)
|
||||
return
|
||||
|
||||
if args.email:
|
||||
os.environ["KEY_EMAIL"] = args.email
|
||||
|
||||
print("build client:", args.client_name)
|
||||
certs = CertFiles.from_files(filenames)
|
||||
if not filenames.is_cert_exists():
|
||||
print("make", end="... ")
|
||||
certs.request()
|
||||
certs.sign()
|
||||
print("ok")
|
||||
|
||||
print("render", end="...")
|
||||
certs.render_client_config()
|
||||
print("done")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user