예제 #1
0
 def do_install(self,
                service_opts: UnixServiceOpts,
                auto_startup: bool = False,
                auto_connman_dhcp: bool = False):
     FileHelper.mkdirs(self.opts.vpn_dir.parent)
     FileHelper.unpack_archive(ClientOpts.get_resource(ClientOpts.VPN_ZIP),
                               self.opts.vpn_dir)
     FileHelper.mkdirs([self.opts.vpn_dir, self.opts.runtime_dir])
     FileHelper.chmod(self.opts.runtime_dir, mode=0o0755)
     FileHelper.chmod([
         os.path.join(self.opts.vpn_dir, p) for p in ('vpnclient', 'vpncmd')
     ],
                      mode=0o0755)
     _, cmd = EnvHelper.build_executable_command()
     svc_opts = self._standard_service_opt(service_opts)
     self.device.unix_service.create(
         svc_opts, {
             '{{WORKING_DIR}}': f'{self.opts.vpn_dir}',
             '{{VPN_DESC}}': svc_opts.service_name,
             '{{START_CMD}}': f'{cmd} start --vpn-dir {self.opts.vpn_dir}',
             '{{STOP_CMD}}': f'{cmd} stop --vpn-dir {self.opts.vpn_dir}'
         }, auto_startup)
     self._dump_cache_service(svc_opts)
     self.device.ip_resolver.add_hook(svc_opts.service_name, {
         '{{WORKING_DIR}}': f'{self.opts.vpn_dir}',
         '{{VPN_CLIENT_CLI}}': cmd
     })
     self.device.dns_resolver.create_config(svc_opts.service_name,
                                            auto_connman_dhcp)
     self.storage.empty()
     self.opts.export_env()
예제 #2
0
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)
예제 #3
0
파일: about.py 프로젝트: play-iot/iot-vpn
def show(vpn_opts: VpnOpts,
         version: str,
         sha: str,
         show_brand=False,
         show_license=False,
         is_json=False):
    if show_brand and not is_json:
        brand = FileHelper.read_file_by_line(
            vpn_opts.get_resource('banner.txt'))
        brand and print(brand)

    ver = {
        "vpn_version": vpn_opts.get_vpn_version(Versions.VPN_VERSION),
        "cli_version": version,
        "hash_version": sha
    }
    if is_json:
        print(JsonHelper.to_json(ver))
    else:
        logger.info(f'VPN version : {ver.get("vpn_version")}')
        logger.info(f'CLI version : {ver.get("cli_version")}')
        logger.info(f'Hash version: {ver.get("hash_version")}')
        logger.sep(logger.INFO, 58)
        if show_license:
            print(
                FileHelper.read_file_by_line(
                    vpn_opts.get_resource('LICENSE_BUNDLE.md'),
                    fallback_if_not_exists=''))
예제 #4
0
 def _check_pid(pid_file: str, log_lvl=logger.TRACE) -> int:
     try:
         logger.log(log_lvl, f'Read PID file {pid_file}')
         pid = FileHelper.read_file_by_line(pid_file)
         pid = int(pid)
         if pid and pid > 0 and SystemHelper.is_pid_exists(pid):
             return pid
     except Exception as _:
         FileHelper.rm(pid_file)
     return 0
예제 #5
0
 def remove(self, svc_opts: UnixServiceOpts, force: bool = False):
     service_fqn = self.to_service_fqn(svc_opts.service_dir,
                                       svc_opts.service_name)
     self.stop(svc_opts.service_name)
     self.disable(svc_opts.service_name)
     if force and FileHelper.is_exists(service_fqn):
         logger.info(f'Remove System service [{svc_opts.service_name}]...')
         FileHelper.rm(service_fqn)
     SystemHelper.exec_command("systemctl daemon-reload",
                               silent=True,
                               log_lvl=logger.INFO)
예제 #6
0
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)
예제 #7
0
 def restore_config(self, vpn_service: str, keep_dnsmasq=True):
     if not keep_dnsmasq:
         logger.debug(
             f'Remove dnsmasq vpn hook config [{self._dnsmasq_vpn_hook_cfg}]'
         )
         FileHelper.rm(self._dnsmasq_vpn_hook_cfg)
         logger.debug(
             f'Remove dnsmasq vpn config [{self._dnsmasq_vpn_cfg(vpn_service)}]'
         )
         FileHelper.rm(self._dnsmasq_vpn_cfg(vpn_service))
     if self._resolver:
         self._resolver.restore_config(vpn_service, keep_dnsmasq)
예제 #8
0
 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')
예제 #9
0
 def cleanup_config(self, vpn_service: str, keep_dnsmasq=True):
     if self.is_connman():
         return
     resolver = self._resolver()
     if keep_dnsmasq:
         resolver.reset_hook(self.vpn_hook_cfg)
     elif FileHelper.is_readable(self.origin_resolv_cfg):
         logger.info(f'Restore System DNS config file...')
         FileHelper.backup(self.origin_resolv_cfg,
                           DNSResolver.DNS_SYSTEM_FILE)
         FileHelper.rm(self.vpn_resolv_cfg)
     resolver.restore_config(vpn_service, keep_dnsmasq)
     resolver.restart(_all=not keep_dnsmasq, keep_dnsmasq=keep_dnsmasq)
예제 #10
0
 def change_host_name(hostname: str, log_lvl=logger.DEBUG):
     prev_regex = re.escape('127.0.1.1') + r'\s+' + re.escape(
         socket.gethostname()) + r'.*'
     SystemHelper.exec_command(f'hostnamectl set-hostname {hostname}',
                               log_lvl=logger.down_lvl(log_lvl))
     FileHelper.replace_in_file('/etc/hosts',
                                {prev_regex: f'127.0.1.1    {hostname}'},
                                regex=True)
     SystemHelper.exec_command(f'hostnamectl', silent=True, log_lvl=log_lvl)
     logger.sep(level=log_lvl, quantity=20)
     SystemHelper.exec_command(f'cat /etc/hosts',
                               silent=True,
                               log_lvl=log_lvl)
     logger.sep(level=log_lvl)
예제 #11
0
 def adapt_dnsmasq(self, origin_resolv_conf: Path,
                   vpn_service: str) -> Optional[Path]:
     content = FileHelper.read_file_by_line(self.config.main_cfg)
     resolv = TextHelper.awk(next(
         iter(TextHelper.grep(content, r'dnsmasq_resolv=.+')), None),
                             sep='=',
                             pos=1)
     return Path(resolv or self.config.runtime_resolv)
예제 #12
0
 def do_uninstall(self,
                  keep_vpn: bool = True,
                  keep_dnsmasq: bool = True,
                  service_opts: UnixServiceOpts = None,
                  log_lvl: int = logger.INFO):
     vpn_service = self._standard_service_opt(service_opts).service_name
     logger.info(f'Uninstall VPN service [{vpn_service}]...')
     self.do_delete([a.account for a in self.storage.list()],
                    force_stop=True,
                    log_lvl=log_lvl)
     if not keep_vpn:
         logger.log(log_lvl, f'Remove VPN Client [{self.opts.vpn_dir}]...')
         self.device.ip_resolver.remove_hook(vpn_service)
         self.opts.remove_env()
         FileHelper.rm(self.opts.vpn_dir)
     self.device.dns_resolver.cleanup_config(vpn_service,
                                             keep_dnsmasq=keep_dnsmasq)
예제 #13
0
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)
예제 #14
0
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)
예제 #15
0
 def __validate_nameservers(self,
                            reason: DHCPReason,
                            new_ns: str = None,
                            old_ns: str = None) -> Optional[list]:
     if reason.is_ignore():
         return None
     if reason is DHCPReason.RENEW and new_ns == old_ns and FileHelper.is_readable(
             self.vpn_hook_cfg):
         return None
     nameservers = old_ns if reason.is_unreachable() else new_ns
     return [ns for ns in nameservers.split(',')
             if ns][0:2] if nameservers else None
예제 #16
0
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)
예제 #17
0
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()
예제 #18
0
 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)
예제 #19
0
 def create_config(self, vpn_acc: str, replacements: dict):
     config_file = self._to_config_file(vpn_acc)
     logger.log(self.log_lvl,
                f'Create DHCP client VPN config[{config_file}]...')
     FileHelper.copy(self.resource_dir.joinpath(self.DHCLIENT_CONFIG_TMPL),
                     config_file,
                     force=True)
     FileHelper.replace_in_file(config_file, replacements, backup='')
     FileHelper.chmod(config_file, mode=0o0644)
예제 #20
0
 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)
예제 #21
0
 def add_hook(self, service_name: str, replacements: dict):
     exit_hook_file = self._to_hook_file(service_name)
     logger.log(self.log_lvl,
                f'Create DHCP client VPN hook[{exit_hook_file}]...')
     FileHelper.copy(self.resource_dir.joinpath(
         self.DHCLIENT_EXIT_HOOK_TMPL),
                     exit_hook_file,
                     force=True)
     FileHelper.replace_in_file(exit_hook_file, replacements, backup='')
     FileHelper.chmod(exit_hook_file, mode=0o0744)
예제 #22
0
 def restore_config(self, backup_dir: Path, keep_backup: bool):
     logger.info(
         f'Restore VPN configuration [{backup_dir}] to [{self.opts.vpn_dir}]...'
     )
     FileHelper.copy(backup_dir.joinpath(self.opts.VPN_CONFIG_FILE),
                     self.opts.config_file,
                     force=True)
     FileHelper.copy(backup_dir.joinpath(self.opts.RUNTIME_FOLDER),
                     self.opts.runtime_dir,
                     force=True)
     FileHelper.rm(backup_dir, force=not keep_backup)
예제 #23
0
 def _common_adapt_dnsmasq(self, vpn_service: str):
     identity = self.config.identity
     logger.debug(
         f'Adapt [{identity}] DNS resolver service to compatible with [dnsmasq] and [{vpn_service}]...'
     )
     FileHelper.mkdirs(self.config.config_dir)
     FileHelper.copy(self.resource_dir.joinpath(f'dnsmasq-{identity}.conf'),
                     self.config.to_fqn_cfg(self.DNSMASQ_TUNED_CFG), True)
     FileHelper.chmod(self.config.to_fqn_cfg(self.DNSMASQ_TUNED_CFG),
                      mode=0o0644)
     return self.config.runtime_resolv
예제 #24
0
 def backup_config(self):
     backup_dir = self.opts.backup_dir()
     logger.info(
         f'Backup VPN configuration [{self.opts.vpn_dir}] to [{backup_dir}] ...'
     )
     FileHelper.mkdirs(backup_dir)
     FileHelper.copy(self.opts.config_file, backup_dir, force=True)
     FileHelper.copy(self.opts.runtime_dir,
                     backup_dir.joinpath(self.opts.RUNTIME_FOLDER),
                     force=True)
     default_acc = self.storage.get_default()
     current_acc = self.storage.get_current()
     svc_opt = self._standard_service_opt()
     return default_acc, current_acc, svc_opt, backup_dir
예제 #25
0
 def create(self,
            svc_opts: UnixServiceOpts,
            replacements: dict,
            auto_startup: bool = False):
     service_fqn = self.to_service_fqn(svc_opts.service_dir,
                                       svc_opts.service_name)
     logger.info(
         f'Add new service [{svc_opts.service_name}] in [{service_fqn}]...')
     FileHelper.copy(self.resource_dir.joinpath(Systemd.SERVICE_FILE_TMPL),
                     service_fqn,
                     force=True)
     FileHelper.replace_in_file(service_fqn, replacements, backup='')
     FileHelper.chmod(service_fqn, mode=0o0644)
     SystemHelper.exec_command("systemctl daemon-reload",
                               silent=True,
                               log_lvl=logger.INFO)
     if auto_startup:
         self.enable(svc_opts.service_name)
예제 #26
0
 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)
예제 #27
0
 def remove_hook(self, service_name: str):
     exit_hook_file = self._to_hook_file(service_name)
     logger.log(self.log_lvl,
                f'Remove DHCP client VPN hook[{exit_hook_file}]...')
     FileHelper.rm(exit_hook_file, force=True)
예제 #28
0
 def _common_remove_dnsmasq(self, vpn_service: str, keep_dnsmasq: bool):
     if not keep_dnsmasq:
         cfg = self.config.to_fqn_cfg(self.DNSMASQ_TUNED_CFG)
         logger.debug(
             f'Remove [dnsmasq] and [{vpn_service}] plugin[{cfg}]...')
         FileHelper.rm(cfg)
예제 #29
0
 def is_enable_connman_dhcp(self) -> bool:
     yes_ = ('true', 't', 'yes', '1')
     return self.is_connman() and FileHelper.read_file_by_line(
         self.connman_dhcp, fallback_if_not_exists='0').lower() in yes_
예제 #30
0
 def adapt_dnsmasq(self, origin_resolv_conf: Path,
                   vpn_service: str) -> Optional[Path]:
     return FileHelper.get_target_link(origin_resolv_conf) or self.config.runtime_resolv if \
         FileHelper.is_readable(self.config.runtime_resolv) else origin_resolv_conf