def add_sni_cert(self, hostname, ssl_certfile=None, ssl_keyfile=None, ssl_version=None): try: result = super(KingPhisherServer, self).add_sni_cert(hostname, ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile, ssl_version=ssl_version) except Exception as error: letsencrypt.set_sni_hostname(hostname, ssl_certfile, ssl_keyfile, enabled=False) raise error letsencrypt.set_sni_hostname(hostname, ssl_certfile, ssl_keyfile, enabled=True) return result
def remove_sni_cert(self, hostname): for sni_cert in self.sni_certs: if sni_cert.hostname == hostname: break else: raise ValueError('the specified hostname does not have an sni certificate configuration') result = super(KingPhisherServer, self).remove_sni_cert(hostname) letsencrypt.set_sni_hostname(hostname, sni_cert.certfile, sni_cert.keyfile, enabled=False) return result
def test_get_sni_hostname_config(self): hostname = random_string(16) sni_config = letsencrypt.get_sni_hostname_config(hostname) self.assertIsNone(sni_config) certfile = os.path.join(self.tmp_directory, hostname + '.pem') keyfile = os.path.join(self.tmp_directory, hostname + '-key.pem') letsencrypt.set_sni_hostname(hostname, certfile, keyfile, enabled=False) sni_config = letsencrypt.get_sni_hostname_config(hostname) self.assertIsNone(sni_config) open(certfile, 'wb') open(keyfile, 'wb') sni_config = letsencrypt.get_sni_hostname_config(hostname) os.remove(certfile) os.remove(keyfile) self.assertIsInstance(sni_config, letsencrypt.SNIHostnameConfiguration) self.assertFalse(sni_config.enabled) self.assertEqual(sni_config.certfile, certfile) self.assertEqual(sni_config.keyfile, keyfile)
def server_from_config(config, handler_klass=None, plugin_manager=None): """ Build a server from a provided configuration instance. If *handler_klass* is specified, then the object must inherit from the corresponding KingPhisherServer base class. :param config: Configuration to retrieve settings from. :type config: :py:class:`smoke_zephyr.configuration.Configuration` :param handler_klass: Alternative handler class to use. :type handler_klass: :py:class:`.KingPhisherRequestHandler` :param plugin_manager: The server's plugin manager instance. :type plugin_manager: :py:class:`~king_phisher.server.plugins.ServerPluginManager` :return: A configured server instance. :rtype: :py:class:`.KingPhisherServer` """ handler_klass = (handler_klass or KingPhisherRequestHandler) addresses = get_bind_addresses(config) if not len(addresses): raise errors.KingPhisherError( 'at least one address to listen on must be specified') ssl_certfile = None ssl_keyfile = None if config.has_option('server.ssl_cert'): ssl_certfile = config.get('server.ssl_cert') if not os.access(ssl_certfile, os.R_OK): logger.critical( "setting server.ssl_cert file '{0}' not found".format( ssl_certfile)) raise errors.KingPhisherError( 'invalid ssl configuration, missing certificate file') logger.info("using default ssl cert file '{0}'".format(ssl_certfile)) if config.has_option('server.ssl_key'): ssl_keyfile = config.get('server.ssl_key') if not os.access(ssl_keyfile, os.R_OK): logger.critical( "setting server.ssl_key file '{0}' not found".format( ssl_keyfile)) raise errors.KingPhisherError( 'invalid ssl configuration, missing key file') if any([address.ssl for address in addresses]): ssl_hostnames = get_ssl_hostnames(config) if ssl_certfile is None: if not ssl_hostnames: raise errors.KingPhisherError( 'an ssl certificate must be specified when ssl is enabled') sni_config = ssl_hostnames[0] logger.warning( 'no default certificate file was specified, using ssl host configuration: ' + sni_config.hostname) ssl_certfile = sni_config.certfile ssl_keyfile = sni_config.keyfile else: ssl_certfile = None ssl_keyfile = None ssl_hostnames = [] try: server = KingPhisherServer(config, plugin_manager, handler_klass, addresses=addresses, ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile) except socket.error as error: error_number, error_message = error.args error_message = "socket error #{0} ({1})".format( (error_number or 'NOT-SET'), error_message) if error_number == 98: logger.error('failed to bind server to address (socket error #98)') logger.error(error_message, exc_info=True) raise errors.KingPhisherError(error_message) from None if config.has_option('server.server_header'): server.server_version = config.get('server.server_header') logger.info( "setting the server version to the custom header: '{0}'".format( config.get('server.server_header'))) for hostname, ssl_certfile, ssl_keyfile in ssl_hostnames: sni_config = letsencrypt.get_sni_hostname_config(hostname, config) if sni_config is None: letsencrypt.set_sni_hostname(hostname, ssl_certfile, ssl_keyfile, enabled=True) for hostname, sni_config in letsencrypt.get_sni_hostnames( config, check_files=False).items(): if not sni_config.enabled: continue if not _server_add_sni_cert(server, hostname, sni_config): letsencrypt.set_sni_hostname(hostname, sni_config.certfile, sni_config.keyfile, enabled=False) signals.server_initialized.send(server) return server
def rpc_ssl_letsencrypt_issue(handler, hostname, load=True): """ Issue a certificate with Let's Encrypt. This operation can fail for a wide variety of reasons, check the ``message`` key of the returned dictionary for a string description of what occurred. Successful operation requires that the certbot utility be installed, and the server's Let's Encrypt data path is configured. .. versionadded:: 1.14.0 :param str hostname: The hostname of the certificate to issue. :param bool load: Whether or not to load the certificate once it has been issued. :return: A dictionary containing the results of the operation. :rtype: dict """ config = handler.config result = {'success': False} letsencrypt_config = config.get_if_exists('server.letsencrypt', {}) # step 1: ensure that a letsencrypt configuration is available data_path = letsencrypt_config.get('data_path') if not data_path: result['message'] = 'Let\'s Encrypt is not configured for use.' return result if not os.path.isdir(data_path): rpc_logger.info('creating the letsencrypt data directory') os.mkdir(data_path) # step 2: ensure that SSL is enabled already if not _ssl_is_enabled(handler): result['message'] = 'Can not issue certificates when SSL is not in use.' return result if not advancedhttpserver.g_ssl_has_server_sni: result['message'] = 'Can not issue certificates when SNI is not available.' return result # step 3: ensure that the certbot utility is available bin_path = letsencrypt_config.get('certbot_path') or startup.which('certbot') if not bin_path: result['message'] = 'Can not issue certificates without the certbot utility.' return result # step 4: ensure the hostname looks legit (TM) and hasn't already been issued if re.match(r'^[a-z0-9][a-z0-9-]*(\.[a-z0-9-]+)+$', hostname, flags=re.IGNORECASE) is None: result['message'] = 'Can not issue certificates for invalid hostnames.' return result if letsencrypt.get_sni_hostname_config(hostname, config): result['message'] = 'The specified hostname already has the necessary files.' return result # step 5: determine the web_root path for this hostname and create it if necessary web_root = config.get('server.web_root') if config.get('server.vhost_directories'): web_root = os.path.join(web_root, hostname) if not os.path.isdir(web_root): rpc_logger.info('vhost directory does not exist for hostname: ' + hostname) os.mkdir(web_root) # step 6: issue the certificate with certbot, this starts the subprocess and may take a few seconds with _lend_semaphore(handler): status = letsencrypt.certbot_issue(web_root, hostname, bin_path=bin_path, unified_directory=data_path) if status != os.EX_OK: result['message'] = 'Failed to issue the certificate.' return result # step 7: ensure the necessary files were created sni_config = letsencrypt.get_sni_hostname_config(hostname, config) if sni_config is None: result['message'] = 'The certificate files were not generated.' return result # step 8: store the data in the database so it can be loaded next time the server starts if load: handler.server.add_sni_cert(hostname, ssl_certfile=sni_config.certfile, ssl_keyfile=sni_config.keyfile) else: letsencrypt.set_sni_hostname(hostname, sni_config.certfile, sni_config.certfile, enabled=False) result['success'] = True result['message'] = 'The operation completed successfully.' return result