- Activate wg interface explicitly if nmcli import does not do it automatically (Arch) - Apply DNS via hydraveil-resolvectl wrapper instead of resolvectl directly (avoids polkit auth dialog) - Add __is_resolved_active() helper to check systemd-resolved before applying DNS - Wait for socket.getaddrinfo to confirm DNS is routable before await_connection() - Replace subprocess test_connection with requests direct call + socket.getaddrinfo (respects systemd-resolved on all distros) - Use sudo install instead of pkexec install for wg.conf copy (uses sudoers, more reliable) - Add fallback to pkexec if sudo not available
139 lines
6.1 KiB
Python
139 lines
6.1 KiB
Python
from core.Constants import Constants
|
|
from core.Errors import ProfileDeletionError, ProfileModificationError, CommandNotFoundError
|
|
from core.models.BaseProfile import BaseProfile
|
|
from core.models.system.SystemConnection import SystemConnection
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
|
|
@dataclass
|
|
class SystemProfile(BaseProfile):
|
|
connection: Optional[SystemConnection]
|
|
|
|
def get_system_config_path(self):
|
|
filepath = self.__get_system_config_path(self.id)
|
|
the_id = self.id
|
|
return filepath
|
|
|
|
def save(self):
|
|
if 'location' in self._get_dirty_keys():
|
|
self.__delete_wireguard_configuration()
|
|
super().save()
|
|
|
|
def attach_wireguard_configuration(self, wireguard_configuration):
|
|
wireguard_configuration_file_backup_path = f'{self.get_config_path()}/wg.conf.bak'
|
|
with open(wireguard_configuration_file_backup_path, 'w') as wireguard_configuration_file:
|
|
wireguard_configuration_file.write(wireguard_configuration)
|
|
wireguard_configuration_is_attached = False
|
|
failed_attempt_count = 0
|
|
# Try sudo first (configured via sudoers by installer), fall back to pkexec
|
|
install_cmd = 'sudo' if shutil.which('sudo') else 'pkexec'
|
|
if install_cmd == 'pkexec' and shutil.which('pkexec') is None:
|
|
raise CommandNotFoundError('pkexec')
|
|
while not wireguard_configuration_is_attached and failed_attempt_count < 3:
|
|
process = subprocess.Popen((install_cmd, 'install', '-D', wireguard_configuration_file_backup_path, self.get_wireguard_configuration_path(), '-o', 'root', '-m', '744'))
|
|
wireguard_configuration_is_attached = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
|
if not wireguard_configuration_is_attached:
|
|
failed_attempt_count += 1
|
|
if not wireguard_configuration_is_attached:
|
|
raise ProfileModificationError('The WireGuard configuration could not be attached.')
|
|
|
|
def get_wireguard_configuration_path(self):
|
|
filepath = f'{self.get_system_config_path()}/wg.conf'
|
|
return filepath
|
|
|
|
def has_wireguard_configuration(self):
|
|
filepath = f'{self.get_system_config_path()}/wg.conf'
|
|
if os.path.isdir(os.path.dirname(filepath)):
|
|
return os.path.isfile(filepath)
|
|
else:
|
|
return False
|
|
|
|
def address_security_incident(self):
|
|
super().address_security_incident()
|
|
self.__delete_wireguard_configuration()
|
|
|
|
def delete(self):
|
|
try:
|
|
self.__delete_wireguard_configuration()
|
|
except ProfileModificationError:
|
|
raise ProfileDeletionError('The WireGuard configuration could not be deleted.')
|
|
if shutil.which('pkexec') is None:
|
|
raise CommandNotFoundError('pkexec')
|
|
|
|
try:
|
|
process = subprocess.run(('pkexec', 'rm', '-rf', self.get_system_config_path()))
|
|
completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
|
if not completed_successfully:
|
|
raise ProfileDeletionError('The profile could not be deleted.')
|
|
except:
|
|
print("skipping the delete of the WG folder.")
|
|
|
|
super().delete()
|
|
|
|
def attach_operator_proxy_session(self, operator_proxy_session):
|
|
from core.models.OperatorProxySession import OperatorProxySession
|
|
operator_proxy_session_file_contents = f'{operator_proxy_session.to_json(indent=4)}\n'
|
|
os.makedirs(self.get_config_path(), exist_ok=True)
|
|
operator_proxy_session_file_path = self.get_operator_proxy_session_path()
|
|
with open(operator_proxy_session_file_path, 'w') as operator_proxy_session_file:
|
|
operator_proxy_session_file.write(operator_proxy_session_file_contents)
|
|
if operator_proxy_session.location_country_code and operator_proxy_session.location_city_code:
|
|
try:
|
|
from core.models.orm_models.Location import Location
|
|
from core.models.manage.session_management import get_session
|
|
from sqlalchemy import select
|
|
session = get_session()
|
|
loc = session.execute(
|
|
select(Location).where(
|
|
(Location.country_code == operator_proxy_session.location_country_code) &
|
|
(Location.code == operator_proxy_session.location_city_code)
|
|
)
|
|
).scalar_one_or_none()
|
|
if loc:
|
|
self.location = loc
|
|
self.save()
|
|
except Exception:
|
|
pass
|
|
|
|
def get_operator_proxy_session_path(self):
|
|
return f'{self.get_config_path()}/operator_proxy_session.json'
|
|
|
|
def get_operator_proxy_session(self):
|
|
try:
|
|
config_file_contents = open(self.get_operator_proxy_session_path(), 'r').read()
|
|
except FileNotFoundError:
|
|
return None
|
|
try:
|
|
data = json.loads(config_file_contents)
|
|
except ValueError:
|
|
return None
|
|
from core.models.OperatorProxySession import OperatorProxySession
|
|
return OperatorProxySession.from_dict(data)
|
|
|
|
def has_operator_proxy_session(self):
|
|
return os.path.isfile(self.get_operator_proxy_session_path())
|
|
|
|
def __delete_wireguard_configuration(self):
|
|
if self.has_wireguard_configuration():
|
|
if shutil.which('pkexec') is None:
|
|
raise CommandNotFoundError('pkexec')
|
|
|
|
try:
|
|
process = subprocess.run(('pkexec', 'rm', '-rf', self.get_wireguard_configuration_path()), check=True)
|
|
completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
|
except subprocess.CalledProcessError as e:
|
|
completed_successfully = True
|
|
except:
|
|
completed_successfully = True
|
|
|
|
if not completed_successfully:
|
|
raise ProfileModificationError('The WireGuard configuration could not be deleted.')
|
|
|
|
@staticmethod
|
|
def __get_system_config_path(id: int):
|
|
config_path = f'{Constants.HV_SYSTEM_PROFILE_CONFIG_PATH}/{str(id)}'
|
|
return config_path
|