????
Current Path : /usr/lib/Acronis/BackupAndRecovery/ |
Current File : //usr/lib/Acronis/BackupAndRecovery/create_hosting_panel_client.py |
#!/usr/sbin/acropsh from argparse import ArgumentParser import subprocess import base64 import json import uuid import sys import os import xml.etree.ElementTree as ET try: from urllib.parse import urlencode from urllib.request import urlopen, Request from urllib.error import HTTPError except ImportError: from urllib import urlencode from urllib2 import urlopen, Request, HTTPError class ClientCreator: def __init__(self, cred1, cred2, bearer_token, ve_ids): self.cred1 = cred1 self.cred2 = cred2 self.bearer_token = bearer_token self.group_id = '' self.tenant_id = '' self.ve_ids = ve_ids self.headers = {} self.dc_url = '' self.mc_url = '' def run(self): print('\nConnecting to the Cyber Protect service...') self.identify_url_and_group() if not self.bearer_token: self.dc_login() print('Successfully authenticated with the Cyber Protection service.') else: self.headers = {'Authorization': 'Bearer {}'.format(self.bearer_token), 'Content-Type': 'application/json'} self.group_to_tenant() self.id_vz_ve_to_process() self.process_api_clients() def identify_url_and_group(self): parts = subprocess.check_output(['aakore', 'info']).decode('utf-8').strip().split('cloud server: ')[1].split(' ') self.dc_url = parts[0] self.mc_url = '{}/api/2/'.format(self.dc_url) self.group_id = parts[-1] def dc_login(self): is_client_auth = False try: uuid.UUID(str(self.cred1)) is_client_auth = True except: pass try: if not is_client_auth: payload = {'grant_type': 'password', 'username': self.cred1, 'password': self.cred2} login_response = urlopen(Request('{}idp/token'.format(self.mc_url), data=urlencode(payload).encode('utf-8'))) else: payload = {'grant_type': 'client_credentials'} req = Request('{}idp/token'.format(self.mc_url), data=urlencode(payload).encode('utf-8')) req.add_header("Authorization", "Basic {}".format(base64.b64encode('{}:{}'.format(self.cred1, self.cred2).encode('utf-8')).decode('utf-8'))) login_response = urlopen(req) except HTTPError as e: if 'totp_required' in e.read().decode('utf-8'): totp_code = input( '\nThis account is protected with two-factor authentication. Please enter the TOTP code: ') login_response = urlopen(Request('{}idp/token'.format(self.mc_url), data=urlencode( {'grant_type': 'password', 'username': self.cred1, 'password': self.cred2, 'totp_code': totp_code} ).encode('utf-8'))) if login_response.getcode() != 200: print( "Failed to obtain an access token. Please check your credentials and try again.") sys.exit() else: raise e except Exception as e: print("Failed to create an access token: {}".format(e)) sys.exit() if login_response.getcode() != 200: if json.loads(login_response.read().decode('utf-8')).get("error") == "totp_required": totp_code = input( '\nThis account is protected with two-factor authentication. Please enter the TOTP code: ') login_response = urlopen(Request('{}idp/token'.format(self.mc_url), data=urlencode( {'grant_type': 'password', 'username': self.username, 'password': self.password, 'totp_code': totp_code} ).encode('utf-8'))) if login_response.getcode() != 200: print( "Failed to obtain an access token. Please check your credentials and try again.") sys.exit() else: print("Failed to obtain an access token. Please check your credentials and try again.") sys.exit() self.headers = {'Authorization': 'Bearer {}'.format(json.loads(login_response.read().decode('utf-8')).get('access_token', '')), 'Content-Type': 'application/json'} def group_to_tenant(self): group_req = Request('{}/api/1/groups/{}'.format(self.dc_url, self.group_id), None, self.headers) self.tenant_id = json.loads(urlopen(group_req).read().decode('utf-8')).get('uuid') def id_vz_ve_to_process(self): if self.ve_ids: self.ve_ids = self.ve_ids.split(',') return else: self.ve_ids = [] if os.path.isdir("/vz/root/"): try: for line in subprocess.check_output(['prlctl', 'list', '-o', 'uuid', '-H']).decode('utf-8').splitlines(): self.ve_ids.append(line.strip().strip('{}')) except FileNotFoundError: return def process_api_clients(self): if self.ve_ids: for ve in self.ve_ids: try: self.process_api_client(ve) except Exception as err: print("Failed to create client for VE {}: {}".format(ve, err)) else: try: self.process_api_client() except Exception as err: print("Failed to create an agent client or save it to file. {}".format(err)) def process_api_client(self, ve_id=None): plugin_srv_path = self.get_secret_file_path(ve_id) if not plugin_srv_path: print('Unable to detect a hosting control panel. Skipping...') return client_path = os.path.join(plugin_srv_path, 'client_secret.json') res_id = ve_id if ve_id else self.get_res_id_from_agent_config() client_id_from_existing_sw_pol = self.client_id_from_existing_sw_policy(res_id) client_data = self.get_client_creds(client_path, ve_id) if client_data: client_id = client_data.get('client_id') if client_id: if client_id_from_existing_sw_pol and client_id == client_id_from_existing_sw_pol: if self.client_exists(client_id_from_existing_sw_pol): if self.client_is_authorizable(client_data): return else: self.recreate_and_save(client_id, client_path, ve_id) return else: if self.client_exists(client_id) and self.is_hosting_panel_agent_type(client_id): self.delete_client_from_cloud(client_id) if client_id_from_existing_sw_pol and self.client_exists(client_id_from_existing_sw_pol): self.recreate_and_save(client_id_from_existing_sw_pol, client_path, ve_id) return self.create_client_and_save(client_path, ve_id, client_id_from_existing_sw_pol) def get_client_creds(self, client_path, ve_id=None): if not ve_id: try: with open(client_path) as client_file: return json.load(client_file) except: return {} else: try: return json.loads(subprocess.check_output(['prlctl', 'exec', ve_id, 'cat', client_path, '2>/dev/null']).decode('utf-8').strip()) except subprocess.CalledProcessError: return {} def client_exists(self, client_id): client_request = Request('{}/api/2/clients/{}'.format(self.dc_url, client_id), None, self.headers) try: return urlopen(client_request).getcode() == 200 except HTTPError: return False def client_is_authorizable(self, client_data): payload = {'grant_type': 'client_credentials'} req = Request('{}idp/token'.format(self.mc_url), data=urlencode(payload).encode('utf-8')) req.add_header("Authorization", "Basic {}".format(base64.b64encode('{}:{}'.format(client_data.get('client_id'), client_data.get('client_secret')).encode('utf-8')).decode('utf-8'))) try: return urlopen(req).getcode() == 200 except HTTPError: return False def create_client_and_save(self, path, ve_id=None, client_id=None): client_data = self.create_client(ve_id, client_id) try: self.save_client(client_data, path, ve_id) except Exception as e: self.delete_client_from_cloud(client_data['client_id']) raise e def recreate_and_save(self, client_id, path, ve_id=None): self.delete_client_from_cloud(client_id) self.create_client_and_save(client_id, path, ve_id) def check_file_exists(self, path, ve_id=None): if not ve_id: return os.path.exists(path) try: subprocess.check_output(['prlctl', 'exec', ve_id, 'ls', path]) return True except subprocess.CalledProcessError: return False def get_res_id_from_agent_config(self): tree = ET.parse('/etc/Acronis/BackupAndRecovery.config') root = tree.getroot() return root.findall(".//value[@name='InstanceID']")[0].text.replace('"','') def compile_client_name(self, ve_id=None): if not ve_id: return "Instance ID: {}".format(self.get_res_id_from_agent_config()) else: return "Virtuozzo Instance ID: {}".format(ve_id.upper()) def is_hosting_panel_agent_type(self, client_id): client_request = Request('{}/api/2/clients/{}'.format(self.dc_url, client_id), None, self.headers) client_response = urlopen(client_request) parsed_response = json.loads(client_response.read().decode('utf-8')) return parsed_response.get('data', {}).get('agent_type', '') == 'hosting_panel' def client_id_from_existing_sw_policy(self, ve_id): if not ve_id: return None app_request = Request( '{}/api/policy_management/v4/applications?context_id={}&policy_type=policy.backup.machine'.format(self.dc_url, ve_id), None, self.headers ) app_response = urlopen(app_request) parsed_app_response = json.loads(app_response.read().decode('utf-8')) for comp_app in parsed_app_response.get('items', []): for app in comp_app: if 'policy.backup.' in app.get('policy', {}).get('type', ''): pol_request = Request( '{}/api/policy_management/v4/policies/{}'.format(self.dc_url, app.get('policy', {}).get('id')), None, self.headers ) pol_response = urlopen(pol_request) parsed_pol_response = json.loads(pol_response.read().decode('utf-8')) policy = parsed_pol_response.get('policy', [])[0] if 'client_id' in policy: return policy['client_id'] return None def create_client(self, ve_id=None, client_id=None): creation_request_body = { "type": "agent", "tenant_id": self.tenant_id, "data": { "client_name": self.compile_client_name(ve_id), "agent_type": "hosting_panel" }, "token_endpoint_auth_method": "client_secret_basic" } if client_id: creation_request_body['client_id'] = client_id creation_request = Request( '{}/bc/api/account_server/v2/clients'.format(self.dc_url), json.dumps(creation_request_body).encode('utf-8'), self.headers ) creation_response = urlopen(creation_request) if creation_response.getcode() == 201: parsed_response = json.loads(creation_response.read().decode('utf-8')) return {key:parsed_response[key] for key in ['client_id', 'client_secret']} else: raise Exception("Internal error occurred while creating an agent client: {}".format( creation_response.getcode())) def delete_client_from_cloud(self, client_id): client_deletion_request = Request('{}/api/2/clients/{}'.format(self.dc_url, client_id), None, self.headers) client_deletion_request.get_method = lambda: 'DELETE' client_deletion_response = urlopen(client_deletion_request) if client_deletion_response.getcode() != 204: print("Failed to delete API client {} from the Cyber Protection service.".format(client_id)) def get_secret_file_path(self, ve): paths = [ '/usr/local/cpanel/base/3rdparty/acronisbackup/srv', '/usr/local/directadmin/plugins/acronisbackup/srv', '/usr/local/psa/var/modules/acronis-backup/srv' ] if not ve: for path in paths: if os.path.isdir(path): return path else: for path in paths: try: subprocess.check_output(['prlctl', 'exec', ve, 'ls', path, '>/dev/null', '2>&1']) return path except subprocess.CalledProcessError: pass return None def save_client(self, client_data, path, ve_id): client_data['url'] = self.dc_url client_secret_file_content = json.dumps(client_data, sort_keys=True, separators=(',', ':')) if not ve_id: try: with open(path, 'w') as f: f.write(client_secret_file_content) os.chmod(path, 0o660) except Exception as e: raise Exception( "Failed to save credentials to file {}".format(path)) else: try: subprocess.check_output(['prlctl', 'exec', ve_id, 'echo', "'{}'".format(client_secret_file_content), '>', path]) except Exception as e: raise Exception( "Failed to save credentials for VE {}".format(ve_id)) if self.check_file_exists(path, ve_id): print("Agent client was successfully created{}".format(' for ' + ve_id if ve_id else '.')) def main(args): cred1 = '' cred2 = os.environ.get('ACRONIS_HOSTING_SECRET', '') bearer_token = os.environ.get('ACRONIS_HOSTING_BEARER_TOKEN', '') if args.username: cred1 = args.username if not cred2 and args.password_file: with open(args.password_file) as f: cred2 = f.readline().strip() if args.client_id: cred1 = args.client_id if not cred2 and args.secret_file: with open(args.secret_file) as f: cred2 = f.readline().strip() if not cred1 and not cred2 and not bearer_token: print('Authentication with the Cyber Protection service is required. \ Please use either --username and --password-file or --client-id and --secret-file.') sys.exit() cl_creator = ClientCreator(cred1, cred2, bearer_token, args.ve_id) cl_creator.run() print('Done! Good bye.') def parse_arguments(): parser = ArgumentParser( description='This script generates unique API Clients necessary to ensure the connectivity of hosting control panel integrations with Acronis Cyber Protect Cloud. Make sure to run this script on the host where the protection agent is installed. This applies to both agent-based and agentless deployments.') parser.add_argument('-u', '--username', help='Account name (login) used during the protection agent installation and registration process.') parser.add_argument('-p', '--password-file', help='Path to the file with the password.') parser.add_argument('-i', '--client-id', help='Client ID used during the protection agent installation and registration process.') parser.add_argument('-s', '--secret-file', help='Path to the file with the Client Secret.') parser.add_argument('-c', '--ve-id', help='Comma-separated list of virtual environment (container, virtual machine) IDs. If not defined, all virtual environments will be processed.') return parser.parse_args() if __name__ == '__main__': main(parse_arguments())