def __dns(vpn_opts: ClientOpts, nic: str, reason: str, new_nameservers: str, old_nameservers: str, debug: bool): logger.info(f'Discover DNS with {reason}::{nic}...') _reason = DHCPReason[reason] if not vpn_opts.is_vpn_nic(nic): logger.warn(f'NIC[{nic}] does not belong to VPN service') sys.exit(0) executor = VPNClientExecutor( vpn_opts, adhoc_task=True).require_install().probe(silent=True, log_lvl=logger.INFO) current = executor.storage.get_current(info=True) if not current: current = executor.storage.find(executor.opts.nic_to_account(nic)) if not current: logger.warn(f'Not found any VPN account') sys.exit(ErrorCode.VPN_ACCOUNT_NOT_FOUND) if executor.opts.nic_to_account(nic) != current.account: logger.warn(f'NIC[{nic}] does not meet current VPN account') sys.exit(ErrorCode.VPN_ACCOUNT_NOT_MATCH) if debug: now = datetime.now().isoformat() FileHelper.write_file( FileHelper.tmp_dir().joinpath('vpn_dns'), append=True, content= f"{now}::{reason}::{nic}::{new_nameservers}::{old_nameservers}\n") executor.device.dns_resolver.resolve(executor.vpn_service, _reason, current.hub, new_nameservers, old_nameservers)
def __import(server_opts: ServerOpts, hub_password: str, vpn_opts: ToolOpts, group: str, certs_file: str, output_opts: OutputOpts): executor = VPNAuthExecutor(vpn_opts, server_opts, hub_password) data = JsonHelper.read(certs_file, strict=False) tmp_dir = FileHelper.tmp_dir('vpn_auth') command_file = FileHelper.touch(tmp_dir.joinpath('vpncmd.txt')) vpn_acc = {} for k, v in data.items(): cert_file = tmp_dir.joinpath(f'{k}.cert') FileHelper.write_file(cert_file, v['cert_key']) commands = [ f'CAAdd /{cert_file}', f'UserCreate {k} /GROUP:{group or "none"} /RealName:none /Note:none', f'UserSignedSet {k} /CN:{v["fqdn"]} /SERIAL:{v["serial_number"]}' ] vpn_acc[k] = { 'vpn_server': server_opts.host, 'vpn_port': server_opts.port, 'vpn_hub': server_opts.hub, 'vpn_account': server_opts.hub, 'vpn_auth_type': 'cert', 'vpn_user': k, 'vpn_cert_key': v['cert_key'], 'vpn_private_key': v['private_key'], } FileHelper.write_file(command_file, '\n'.join(commands) + '\n', append=True) executor.exec_command(f'/IN:{command_file}', log_lvl=logger.INFO) logger.sep(logger.INFO) out = output_opts.make_file( f'{server_opts.hub}-{output_opts.to_file("json")}') logger.info(f'Export VPN accounts to {out}...') JsonHelper.dump(out, vpn_acc) logger.done()
def reset_hook(self, vpn_nameserver_hook_conf: Path): logger.info(f'Reset VPN DNS config file...') if FileHelper.is_writable(vpn_nameserver_hook_conf): FileHelper.write_file(vpn_nameserver_hook_conf, mode=0o644, content='') FileHelper.create_symlink(vpn_nameserver_hook_conf, self._dnsmasq_vpn_hook_cfg, force=True) else: FileHelper.rm(self._dnsmasq_vpn_hook_cfg)
def update_hook(self, reason: DHCPReason, priv_root_dns: str, nameservers: list, vpn_nameserver_hook_conf: Path): logger.info( f'Update VPN DNS config file on [{reason.name}][{priv_root_dns}] with nameservers {nameservers}...' ) servers = '\n'.join( [f'server=/{priv_root_dns}/{ns}' for ns in nameservers]) FileHelper.write_file( vpn_nameserver_hook_conf, mode=0o644, content= f'### Generated at [{datetime.now().isoformat()}]\n{servers}\n')
def gen_intermediate_cert(cert_key, private_key, prefix, items, output_opts: OutputOpts, cert_attributes: CertAttributes): """ Generate an Intermediate Signed certificate """ outputs = {} ca_crt, ca_pkey = __load_key(cert_key, private_key) for item in items: outputs[item] = __gen_cert(f'{prefix}.{item}', cert_attributes, ca_crt, ca_pkey) FileHelper.write_file(output_opts.make_file(f"{item}.key"), outputs[item]['private_key']) FileHelper.write_file(output_opts.make_file(f"{item}.crt"), outputs[item]['cert_key']) JsonHelper.dump(output_opts.make_file(f"signed-intermediate-{output_opts.file}.json"), outputs)
def gen_ssh(users, output_opts: OutputOpts): """ Generate SSH key """ output = {} crypto_backend = crypto_default_backend() for user in users: ssh_key = rsa.generate_private_key(backend=crypto_backend, public_exponent=65537, key_size=4096) private_ssh_key = __serialize_private_key(ssh_key) public_ssh_key = ssh_key.public_key() \ .public_bytes(crypto_serialization.Encoding.OpenSSH, crypto_serialization.PublicFormat.OpenSSH) \ .decode(DEFAULT_ENCODING) output[user] = {'private_ssh_key': private_ssh_key, 'public_ssh_key': public_ssh_key} FileHelper.write_file(output_opts.make_file(user + "_ssh"), private_ssh_key) FileHelper.write_file(output_opts.make_file(user + "_ssh.pub"), public_ssh_key) JsonHelper.dump(output_opts.to_fqn_file(".json"), output)
def setup(self, vpn_service: str, origin_resolv_conf: Path, vpn_resolv_conf: Path, vpn_nameserver_hook_conf: Path): if not self._available: logger.error('[dnsmasq] is not yet installed or is corrupted') sys.exit(ErrorCode.MISSING_REQUIREMENT) logger.info('Setup DNS resolver[dnsmasq]...') dnsmasq_vpn_cfg = self._dnsmasq_vpn_cfg(vpn_service) runtime_resolv_cfg = self.adapt_dnsmasq(origin_resolv_conf, vpn_service) dnsmasq_opts = { '{{DNS_RESOLVED_FILE}}': self.__build_dnsmasq_conf('resolv-file', runtime_resolv_cfg), '{{PORT}}': self.__build_dnsmasq_conf('port', self.dnsmasq_options().get('port', None)), '{{CACHE_SIZE}}': self.__build_dnsmasq_conf( 'cache-size', self.dnsmasq_options().get('cache_size', None)) } logger.debug( f'Add [dnsmasq] config for {vpn_service}[{dnsmasq_vpn_cfg}]...') FileHelper.copy(self.resource_dir.joinpath(self.DNSMASQ_CONFIG_TMPL), dnsmasq_vpn_cfg, force=True) FileHelper.replace_in_file(dnsmasq_vpn_cfg, dnsmasq_opts, backup='') FileHelper.chmod(dnsmasq_vpn_cfg, mode=0o0644) logger.debug( f'Symlink [dnsmasq] VPN nameserver runtime configuration [{vpn_nameserver_hook_conf}]...' ) FileHelper.create_symlink(vpn_nameserver_hook_conf, self._dnsmasq_vpn_hook_cfg, force=True) logger.info(f'Generate System DNS config file from VPN service...') FileHelper.write_file(vpn_resolv_conf, self.__dnsmasq_resolv(vpn_service), mode=0o0644) FileHelper.create_symlink(vpn_resolv_conf, DNSResolver.DNS_SYSTEM_FILE, force=True) self.service.enable(self.config.identity)
def create_config(self, vpn_service: str, auto_connman_dhcp: bool): if self.is_connman(): FileHelper.write_file(self.connman_dhcp, str(auto_connman_dhcp)) return if not FileHelper.is_readable(self.origin_resolv_cfg): logger.info( f'Backup System DNS config file to [{self.origin_resolv_cfg}]...' ) FileHelper.backup(DNSResolver.DNS_SYSTEM_FILE, self.origin_resolv_cfg, remove=False) if not FileHelper.is_readable(self.origin_resolv_cfg): logger.error( f'Not found origin DNS config file [{self.origin_resolv_cfg}]') sys.exit(ErrorCode.FILE_CORRUPTED) if not FileHelper.is_readable(self.vpn_hook_cfg): FileHelper.touch(self.vpn_hook_cfg, 0o0644) self._resolver().setup(vpn_service, self.origin_resolv_cfg, self.vpn_resolv_cfg, self.vpn_hook_cfg) self._resolver().restart(_all=True)
def gen_root_cert(output_opts: OutputOpts, cert_attributes: CertAttributes): """ Generate Root Certification """ crypto_backend = crypto_default_backend() algorithm = hashes.SHA512() now = datetime.datetime.utcnow() private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=crypto_backend) subject = issuer = cert_attributes.create_x509_attributes() crt = x509.CertificateBuilder() \ .subject_name(subject).issuer_name(issuer).public_key(private_key.public_key()) \ .serial_number(x509.random_serial_number()).not_valid_before(now) \ .not_valid_after(now + datetime.timedelta(days=cert_attributes.valid_days)) \ .sign(private_key, algorithm, crypto_backend) root_private_key = __serialize_private_key(private_key) root_cert_key = crt.public_bytes(encoding=crypto_serialization.Encoding.PEM).decode(DEFAULT_ENCODING) output = {'private_key': root_private_key, 'cert_key': root_cert_key, 'serial_number': f'{crt.serial_number:0>40X}'} FileHelper.write_file(output_opts.to_fqn_file("key"), root_private_key) FileHelper.write_file(output_opts.to_fqn_file("crt"), root_cert_key) JsonHelper.dump(output_opts.to_fqn_file("json"), output)
def gen_signed_cert(cert_key, private_key, intermediate_code: str, fn: str, items: list, dump_to_file: bool, seq: bool, quantity: int, start_from: int, prefix: str, length: int, output_opts: OutputOpts, cert_attributes: CertAttributes): """ Generate Signed certificate """ if not seq and not items: raise RuntimeError('Must provide singed certification name') if seq: if quantity <= 0 or start_from <= 0 or length <= 0: raise RuntimeError('Invalid value in sequence mode. All of [quantity, start_from, length] must be > 0') seq_format = f'0{length}d' items = [f'{prefix}{x:{seq_format}}' for x in range(start_from, start_from + quantity)] outputs = {} ca_crt, ca_pkey = __load_key(cert_key, private_key) for item in items: outputs[item] = __gen_cert(f'{item}.{fn}.{intermediate_code}', cert_attributes, ca_crt, ca_pkey) if dump_to_file: FileHelper.write_file(output_opts.make_file(f"{item}.key"), outputs[item]['private_key']) FileHelper.write_file(output_opts.make_file(f"{item}.crt"), outputs[item]['cert_key']) JsonHelper.dump(output_opts.make_file(f"{intermediate_code}-{output_opts.file}.json"), outputs)
def export_env(self): FileHelper.write_file(VpnDirectory.PROFILE_D_ENV, f'export {AppEnv.VPN_HOME_ENV}="{self.vpn_dir}"', mode=0o0644)