def prepare(self): # pylint: disable=missing-function-docstring self.outlook = client.GetActiveObject('Outlook.Application') self.mapi = self.outlook.GetNamespace("MAPI") self.account = None for account in self.mapi.Folders: if (account.Name == self.conf('account')): self.account = account break if (not self.account): raise errors.AuthorizationError('Account {} does not exist'.format( self.conf('account')))
def verify_authzr_complete(self): """Verifies that all authorizations have been decided. :returns: Whether all authzr are complete :rtype: bool """ for aauthzr in self.aauthzrs: authzr = aauthzr.authzr if (authzr.body.status != messages.STATUS_VALID and authzr.body.status != messages.STATUS_INVALID): raise errors.AuthorizationError("Incomplete authorizations")
def _report_no_chall_path(challbs): """Logs and raises an error that no satisfiable chall path exists. :param challbs: challenges from the authorization that can't be satisfied """ msg = ("Client with the currently selected authenticator does not support " "any combination of challenges that will satisfy the CA.") if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): msg += (" You may need to use an authenticator " "plugin that can do challenges over DNS.") logger.fatal(msg) raise errors.AuthorizationError(msg)
def handle_authorizations(self, orderr, best_effort=False): """Retrieve all authorizations for challenges. :param acme.messages.OrderResource orderr: must have authorizations filled in :param bool best_effort: Whether or not all authorizations are required (this is useful in renewal) :returns: List of authorization resources :rtype: list :raises .AuthorizationError: If unable to retrieve all authorizations """ aauthzrs = [ AnnotatedAuthzr(authzr, []) for authzr in orderr.authorizations ] self._choose_challenges(aauthzrs) config = zope.component.getUtility(interfaces.IConfig) notify = zope.component.getUtility(interfaces.IDisplay).notification # While there are still challenges remaining... while self._has_challenges(aauthzrs): with error_handler.ExitHandler(self._cleanup_challenges, aauthzrs): resp = self._solve_challenges(aauthzrs) logger.info("Waiting for verification...") if config.debug_challenges: notify( 'Challenges loaded. Press continue to submit to CA. ' 'Pass "-v" for more info about challenges.', pause=True) # Send all Responses - this modifies achalls self._respond(aauthzrs, resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete(aauthzrs) # Only return valid authorizations retVal = [ aauthzr.authzr for aauthzr in aauthzrs if aauthzr.authzr.body.status == messages.STATUS_VALID ] if not retVal: raise errors.AuthorizationError( "Challenges failed for all domains") return retVal
def get_authorizations(self, domains, best_effort=False): """Retrieve all authorizations for challenges. :param list domains: Domains for authorization :param bool best_effort: Whether or not all authorizations are required (this is useful in renewal) :returns: List of authorization resources :rtype: list :raises .AuthorizationError: If unable to retrieve all authorizations """ for domain in domains: self.authzr[domain] = self.acme.request_domain_challenges(domain) self._choose_challenges(domains) config = zope.component.getUtility(interfaces.IConfig) notify = zope.component.getUtility(interfaces.IDisplay).notification # While there are still challenges remaining... while self.achalls: resp = self._solve_challenges() logger.info("Waiting for verification...") if config.debug_challenges: notify( 'Challenges loaded. Press continue to submit to CA. ' 'Pass "-v" for more info about challenges.', pause=True) # Send all Responses - this modifies achalls self._respond(resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete() # Only return valid authorizations retVal = [ authzr for authzr in self.authzr.values() if authzr.body.status == messages.STATUS_VALID ] if not retVal: raise errors.AuthorizationError( "Challenges failed for all domains") return retVal
def _find_updated_challb(self, authzr, achall): # pylint: disable=no-self-use """Find updated challenge body within Authorization Resource. .. warning:: This assumes only one instance of type of challenge in each challenge resource. :param .AuthorizationResource authzr: Authorization Resource :param .AnnotatedChallenge achall: Annotated challenge for which to get status """ for authzr_challb in authzr.body.challenges: if type(authzr_challb.chall) is type(achall.challb.chall): # noqa return authzr_challb raise errors.AuthorizationError( "Target challenge not found in authorization resource")
def verify_authzr_complete(self, aauthzrs): """Verifies that all authorizations have been decided. :param aauthzrs: authorizations and their selected annotated challenges :type aauthzrs: `list` of `AnnotatedAuthzr` :returns: Whether all authzr are complete :rtype: bool """ for aauthzr in aauthzrs: authzr = aauthzr.authzr if (authzr.body.status != messages.STATUS_VALID and authzr.body.status != messages.STATUS_INVALID): raise errors.AuthorizationError("Incomplete authorizations")
def _report_no_chall_path( challbs: List[messages.ChallengeBody]) -> errors.AuthorizationError: """Logs and return a raisable error reporting that no satisfiable chall path exists. :param challbs: challenges from the authorization that can't be satisfied :returns: An authorization error :rtype: certbot.errors.AuthorizationError """ msg = ("Client with the currently selected authenticator does not support " "any combination of challenges that will satisfy the CA.") if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): msg += (" You may need to use an authenticator " "plugin that can do challenges over DNS.") logger.critical(msg) return errors.AuthorizationError(msg)
def _get_chall_pref(self, domain): """Return list of challenge preferences. :param str domain: domain for which you are requesting preferences """ chall_prefs = [] # Make sure to make a copy... plugin_pref = self.auth.get_chall_pref(domain) if self.pref_challs: chall_prefs.extend(pref for pref in self.pref_challs if pref in plugin_pref) if chall_prefs: return chall_prefs raise errors.AuthorizationError( "None of the preferred challenges " "are supported by the selected plugin") chall_prefs.extend(plugin_pref) return chall_prefs
def get_authorizations(self, domains, best_effort=False): """Retrieve all authorizations for challenges. :param list domains: Domains for authorization :param bool best_effort: Whether or not all authorizations are required (this is useful in renewal) :returns: List of authorization resources :rtype: list :raises .AuthorizationError: If unable to retrieve all authorizations """ for domain in domains: self.authzr[domain] = self.acme.request_domain_challenges( domain, self.account.regr.new_authzr_uri) self._choose_challenges(domains) # While there are still challenges remaining... while self.achalls: resp = self._solve_challenges() logger.info("Waiting for verification...") # Send all Responses - this modifies achalls self._respond(resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete() # Only return valid authorizations retVal = [ authzr for authzr in self.authzr.values() if authzr.body.status == messages.STATUS_VALID ] if not retVal: raise errors.AuthorizationError( "Challenges failed for all domains") return retVal
def _get_chall_pref(self, domain): """Return list of challenge preferences. :param str domain: domain for which you are requesting preferences """ chall_prefs = [] # Make sure to make a copy... plugin_pref = self.auth.get_chall_pref(domain) if self.pref_challs: plugin_pref_types = set(chall.typ for chall in plugin_pref) for typ in self.pref_challs: if typ in plugin_pref_types: chall_prefs.append(challenges.Challenge.TYPES[typ]) if chall_prefs: return chall_prefs raise errors.AuthorizationError( "None of the preferred challenges " "are supported by the selected plugin") chall_prefs.extend(plugin_pref) return chall_prefs
def prepare(self): # pylint: disable=missing-function-docstring self.imap = imapclient.IMAPClient( self.conf('host'), port=self.conf('port'), use_uid=False, ssl=True if self.conf('ssl') else False) self.imap.login(self.conf('login'), self.conf('password')) self.imap.select_folder('INBOX') if b'IDLE' not in self.imap.capabilities(): raise errors.AuthorizationError( 'IMAP server does not support IDLE. Cannot continue.') self.__idle(True) method = self.conf('smtp-method') smtp_server = self.conf('smtp-host') if self.conf( 'smtp-host') else self.conf('host') port = self.conf('smtp-port') if self.conf('smtp-port') else self.conf( 'port') login = self.conf('smtp-login') if self.conf( 'smtp-login') else self.conf('login') password = self.conf('smtp-password') if self.conf( 'smtp-password') else self.conf('password') if (method == 'STARTTLS'): context = ssl.create_default_context() port = port if port else 587 self.smtp = SMTP(smtp_server, port=port) self.smtp.ehlo() self.smtp.starttls(context=context) # Secure the connection self.smtp.ehlo() # Can be omitted elif (method == 'SSL'): context = ssl.create_default_context() port = port if port else 465 self.smtp = SMTP_SSL(smtp_server, port=port, context=context) else: port = port if port else 25 self.smtp = SMTP(smtp_server, port=port) self.smtp.login(login, password)
def handle_authorizations(self, orderr, best_effort=False, max_retries=30): """ Retrieve all authorizations, perform all challenges required to validate these authorizations, then poll and wait for the authorization to be checked. :param acme.messages.OrderResource orderr: must have authorizations filled in :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) :param int max_retries: maximum number of retries to poll authorizations :returns: list of all validated authorizations :rtype: List :raises .AuthorizationError: If unable to retrieve all authorizations """ authzrs = orderr.authorizations[:] if not authzrs: raise errors.AuthorizationError('No authorization to handle.') # Retrieve challenges that need to be performed to validate authorizations. achalls = self._choose_challenges(authzrs) if not achalls: return authzrs # Starting now, challenges will be cleaned at the end no matter what. with error_handler.ExitHandler(self._cleanup_challenges, achalls): # To begin, let's ask the authenticator plugin to perform all challenges. try: resps = self.auth.perform(achalls) # If debug is on, wait for user input before starting the verification process. logger.info('Waiting for verification...') config = zope.component.getUtility(interfaces.IConfig) if config.debug_challenges: notify = zope.component.getUtility( interfaces.IDisplay).notification notify( 'Challenges loaded. Press continue to submit to CA. ' 'Pass "-v" for more info about challenges.', pause=True) except errors.AuthorizationError as error: logger.critical('Failure in setting up challenges.') logger.info('Attempting to clean up outstanding challenges...') raise error # All challenges should have been processed by the authenticator. assert len(resps) == len( achalls), 'Some challenges have not been performed.' # Inform the ACME CA server that challenges are available for validation. for achall, resp in zip(achalls, resps): self.acme.answer_challenge(achall.challb, resp) # Wait for authorizations to be checked. self._poll_authorizations(authzrs, max_retries, best_effort) # Keep validated authorizations only. If there is none, no certificate can be issued. authzrs_validated = [ authzr for authzr in authzrs if authzr.body.status == messages.STATUS_VALID ] if not authzrs_validated: raise errors.AuthorizationError('All challenges have failed.') return authzrs_validated
def _report_no_chall_path(): """Logs and raises an error that no satisfiable chall path exists.""" msg = ("Client with the currently selected authenticator does not support " "any combination of challenges that will satisfy the CA.") logger.fatal(msg) raise errors.AuthorizationError(msg)
def _get_active_bigip(self): """sets the active bigip if one is the active unit for all given virtual servers. sets the first bigip if cluster is active/active and auto-sync is turned on. returns false if no bigip is active for all virtual servers (active/active without auto-sync) :raises errors.AuthorizationError: Connection to the BIG-IP failed :raises errors.PluginError: Connection to the BIG-IP failed :return: name of the active bigip :rtype: string """ try: mgmt = ManagementRoot( self.bigips[0], self.username, self.__password, token=True, verify=self.verify_ssl, ) except Exception as e: msg = ( f"Connection to F5 BIG-IP iControl REST API on {self.bigips[0]} failed.{os.linesep}" f"Error raised was {os.linesep}{e}{os.linesep}" "(You most probably need to ensure the username and " "password is correct. Make sure you use the --bigip-username " "and --bigip-password options)") raise errors.AuthorizationError(msg) try: # first check for failover status active_device = "" active_devices = [] devices = mgmt.tm.cm.devices.get_collection() for device in devices: if device.raw["failoverState"] == "active": active_devices.append(device.raw["hostname"]) if len(active_devices) == 0: logger.debug("No active device found") return False if len(active_devices) == 1: active_device = active_devices[0] logger.debug( f"Active device found in A/S cluster: {active_device}") else: # active/active cluster, checking if auto-sync is enabled device_groups = mgmt.tm.cm.device_groups.get_collection() for dg in device_groups: if (dg.raw["type"] == "sync-failover" and dg.raw["autoSync"] == "enabled"): logger.debug( "sync-failover with autoSync enabled, returning first device from list." ) active_devices.append(self.bigips[0]) # active/active cluster, need to find active device for specified virtual servers destinations = [] for vs in self.vs_list: # get destination from vs # extract IP from destination # get virtual address with address=destination # get tg from va # check active device for tgs r = self._split_fullpath(vs) virtual = mgmt.tm.ltm.virtuals.virtual.load(partition=r[0], subPath=r[1], name=r[2]) dst = virtual.raw["destination"] dst = dst[dst.rfind("/") + 1:] if dst.count(".") == 3: # IPv4 address dst = dst.split(":")[0] else: # IPv6 address dst = dst.split(".")[0] if dst not in destinations: destinations.append(dst) tgs = [] virtual_addresses = mgmt.tm.ltm.virtual_address_s.get_collection( ) for va in virtual_addresses: for dst in destinations: if va.raw["address"] == dst: if va.raw["trafficGroup"] not in tgs: tgs.append(va.raw["trafficGroup"]) active_devices = [] for tg in tgs: traffic_group = mgmt.tm.cm.traffic_groups.traffic_group.load( partition=tg.split("/")[1], name=tg.split("/")[2]) traffic_group_stats = traffic_group.stats.load() for item in traffic_group_stats.entries: if (traffic_group_stats.entries[item]["nestedStats"] ["entries"]["failoverState"]["description"] == "active"): if (traffic_group_stats.entries[item] ["nestedStats"]["entries"]["deviceName"] ["description"] not in active_devices): active_devices.append( traffic_group_stats.entries[item] ["nestedStats"]["entries"]["deviceName"] ["description"]) if len(active_devices) == 0: logger.debug("No active device found") return False if len(active_devices) == 1: active_device = active_devices[0].split("/")[2] logger.debug( f"Active device found in A/A cluster: {active_devices[0]}" ) else: logger.debug("No active device found") return False for device in self.bigip_map: if self.bigip_map[device] == active_device: logger.debug( f"Active device {active_device} mapped to {device}") return device except Exception as e: msg = ( f"Connection to F5 BIG-IP iControl REST API failed.{os.linesep}" f"Error raised was {os.linesep}{e}{os.linesep}") raise errors.PluginError(msg)
def __init__( self, bigips, username, password, vs_list, device_group, partition, clientssl_parent, verify_ssl, ): """Initialize a BIG-IP. :param hosts: CSV list of BIG-IP system hostnames or addresses, all have to be in the same cluster :type hosts: string :param username: BIG-IP username :type username: string :param password: BIG-IP password :type password: string :param device_group: Device Group to syncronise configuration :type device_group: string :param partition: BIG-IP partition, defaults to 'Common' :type partition: str, optional :param clientssl_parent: Client SSL parent profile to inherit default values from, defaults to '/Common/clientssl' :type clientssl_parent: str, optional :param verify_ssl: enable or disable SSL verification of the BIG-IP management API, defaults to False :type verify_ssl: bool, optional :raises errors.AuthorizationError: Connection to the BIG-IP failed """ self.bigips = bigips self.bigip_map = {} self.username = username self.__password = password self.token = True self.partition = partition self.vs_list = vs_list self.device_group = device_group self.clientssl_parent = clientssl_parent self.standalone = False self.verify_ssl = verify_ssl if not self.verify_ssl: requests.packages.urllib3.disable_warnings(InsecureRequestWarning) self.bigip_map = self._get_bigip_map() logger.debug(f"bigip map: {self.bigip_map}") self.active_device = self._get_active_bigip() logger.debug(f"active device: {self.active_device}") if self.active_device: try: self.mgmt = ManagementRoot( self.active_device, self.username, self.__password, token=True, verify=self.verify_ssl, ) except Exception as e: msg = ( f"Connection to F5 BIG-IP iControl REST API on {self.active_device} failed.{os.linesep}" f"Error raised was {os.linesep}{e}{os.linesep}" "(You most probably need to ensure the username and" "password is correct. Make sure you use the --bigip-username" "and --bigip-password options)") raise errors.AuthorizationError(msg) else: msg = ( f"No active device found which is responsible for all " "provided virtual servers or auto-sync is disabled. If you run an active/active setup " f"please ensure that all virtual servers are in the same traffic group or auto-sync is enabled{os.linesep}" ) raise errors.PluginError(msg) self.standalone = self._get_cluster_state()
def handle_authorizations( self, orderr: messages.OrderResource, config: configuration.NamespaceConfig, best_effort: bool = False, max_retries: int = 30) -> List[messages.AuthorizationResource]: """ Retrieve all authorizations, perform all challenges required to validate these authorizations, then poll and wait for the authorization to be checked. :param acme.messages.OrderResource orderr: must have authorizations filled in :param certbot.configuration.NamespaceConfig config: current Certbot configuration :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) :param int max_retries: maximum number of retries to poll authorizations :returns: list of all validated authorizations :rtype: List :raises .AuthorizationError: If unable to retrieve all authorizations """ authzrs = orderr.authorizations[:] if not authzrs: raise errors.AuthorizationError('No authorization to handle.') if not self.acme: raise errors.Error( "No ACME client defined, authorizations cannot be handled.") # Retrieve challenges that need to be performed to validate authorizations. achalls = self._choose_challenges(authzrs) if not achalls: return authzrs # Starting now, challenges will be cleaned at the end no matter what. with error_handler.ExitHandler(self._cleanup_challenges, achalls): # To begin, let's ask the authenticator plugin to perform all challenges. try: resps = self.auth.perform(achalls) # If debug is on, wait for user input before starting the verification process. if config.debug_challenges: display_util.notification( 'Challenges loaded. Press continue to submit to CA.\n' + self._debug_challenges_msg(achalls, config), pause=True) except errors.AuthorizationError as error: logger.critical('Failure in setting up challenges.') logger.info('Attempting to clean up outstanding challenges...') raise error # All challenges should have been processed by the authenticator. assert len(resps) == len( achalls), 'Some challenges have not been performed.' # Inform the ACME CA server that challenges are available for validation. for achall, resp in zip(achalls, resps): self.acme.answer_challenge(achall.challb, resp) # Wait for authorizations to be checked. logger.info('Waiting for verification...') self._poll_authorizations(authzrs, max_retries, best_effort) # Keep validated authorizations only. If there is none, no certificate can be issued. authzrs_validated = [ authzr for authzr in authzrs if authzr.body.status == messages.STATUS_VALID ] if not authzrs_validated: raise errors.AuthorizationError('All challenges have failed.') return authzrs_validated raise errors.Error( "An unexpected error occurred while handling the authorizations.")
def _poll_authorizations(self, authzrs: List[messages.AuthorizationResource], max_retries: int, best_effort: bool) -> None: """ Poll the ACME CA server, to wait for confirmation that authorizations have their challenges all verified. The poll may occur several times, until all authorizations are checked (valid or invalid), or after a maximum of retries. """ if not self.acme: raise errors.Error( "No ACME client defined, cannot poll authorizations.") authzrs_to_check: Dict[int, Tuple[ messages.AuthorizationResource, Optional[Response]]] = { index: (authzr, None) for index, authzr in enumerate(authzrs) } authzrs_failed_to_report = [] # Give an initial second to the ACME CA server to check the authorizations sleep_seconds: float = 1 for _ in range(max_retries): # Wait for appropriate time (from Retry-After, initial wait, or no wait) if sleep_seconds > 0: time.sleep(sleep_seconds) # Poll all updated authorizations. authzrs_to_check = { index: self.acme.poll(authzr) for index, (authzr, _) in authzrs_to_check.items() } # Update the original list of authzr with the updated authzrs from server. for index, (authzr, _) in authzrs_to_check.items(): authzrs[index] = authzr # Gather failed authorizations authzrs_failed = [ authzr for authzr, _ in authzrs_to_check.values() if authzr.body.status == messages.STATUS_INVALID ] for authzr_failed in authzrs_failed: logger.info('Challenge failed for domain %s', authzr_failed.body.identifier.value) # Accumulating all failed authzrs to build a consolidated report # on them at the end of the polling. authzrs_failed_to_report.extend(authzrs_failed) # Extract out the authorization already checked for next poll iteration. # Poll may stop here because there is no pending authorizations anymore. authzrs_to_check = { index: (authzr, resp) for index, (authzr, resp) in authzrs_to_check.items() if authzr.body.status == messages.STATUS_PENDING } if not authzrs_to_check: # Polling process is finished, we can leave the loop break # Be merciful with the ACME server CA, check the Retry-After header returned, # and wait this time before polling again in next loop iteration. # From all the pending authorizations, we take the greatest Retry-After value # to avoid polling an authorization before its relevant Retry-After value. # (by construction resp cannot be None at that time, but mypy do not know it). retry_after = max( self.acme.retry_after(resp, 3) for _, resp in authzrs_to_check.values() if resp is not None) sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() # In case of failed authzrs, create a report to the user. if authzrs_failed_to_report: self._report_failed_authzrs(authzrs_failed_to_report) if not best_effort: # Without best effort, having failed authzrs is critical and fail the process. raise errors.AuthorizationError('Some challenges have failed.') if authzrs_to_check: # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. raise errors.AuthorizationError( 'All authorizations were not finalized by the CA.')
def __init__(self, *args, **kwargs): if (not sys.platform.startswith('win32')): raise errors.AuthorizationError( 'MAPI Authenticator only runs in Windows') super(Authenticator, self).__init__(*args, **kwargs)