def test_handle_message_loop_with_unexpected_error(self, request_mock): """ Test that the correct logging and error handling occurs when an unexpected error is generated while processing a request. """ data = utils.BytearrayStream(()) kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, 'name') kmip_session._engine = mock.MagicMock() test_exception = Exception("Unexpected error.") kmip_session._engine.process_request = mock.MagicMock( side_effect=test_exception) kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.info.assert_not_called() kmip_session._logger.warning.assert_called_once_with( "An unexpected error occurred while processing request.") kmip_session._logger.exception.assert_called_once_with(test_exception) self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_with_response_too_long(self, request_mock, cert_mock): """ Test that the correct logging and error handling occurs during the message handling loop. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' kmip_engine = engine.KmipEngine() kmip_session = session.KmipSession( kmip_engine, None, None, name='name', enable_tls_client_auth=False ) kmip_session.authenticate = mock.MagicMock() kmip_session.authenticate.return_value = ( 'test', ['group A', 'group B'] ) kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._max_response_size = 0 kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() self.assertTrue(kmip_session._logger.warning.called) kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_with_parse_failure(self): """ Test that the correct logging and error handling occurs during the message handling loop. """ data = utils.BytearrayStream(()) kmip_engine = engine.KmipEngine() kmip_session = session.KmipSession(kmip_engine, None, 'name') kmip_session._get_client_identity = mock.MagicMock() kmip_session._get_client_identity.return_value = 'test' kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.warning.assert_called_once_with( "Failure parsing request message." ) self.assertTrue(kmip_session._logger.exception.called) kmip_session._logger.error.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_with_unexpected_error(self, request_mock, cert_mock): """ Test that the correct logging and error handling occurs when an unexpected error is generated while processing a request. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, None, name='name', enable_tls_client_auth=False) kmip_session.authenticate = mock.MagicMock() kmip_session.authenticate.return_value = ('test', ['group A', 'group B']) kmip_session._engine = mock.MagicMock() test_exception = Exception("Unexpected error.") kmip_session._engine.process_request = mock.MagicMock( side_effect=test_exception) kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.warning.assert_called_once_with( "An unexpected error occurred while processing request.") kmip_session._logger.exception.assert_called_once_with(test_exception) self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_with_authentication_failure( self, request_mock, cert_mock): """ Test that the correct logging and error handling occurs when an authentication error is generated while processing a request. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, None, name='name', enable_tls_client_auth=False) kmip_session.authenticate = mock.MagicMock() kmip_session.authenticate.side_effect = exceptions.PermissionDenied( "Authentication failed.") kmip_session._engine = mock.MagicMock() kmip_session._engine.default_protocol_version = \ kmip_engine.default_protocol_version kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() fake_version = contents.ProtocolVersion(1, 2) fake_credential = objects.Credential( credential_type=enums.CredentialType.USERNAME_AND_PASSWORD, credential_value=objects.UsernamePasswordCredential( username="******", password="******")) fake_header = messages.RequestHeader( protocol_version=fake_version, authentication=contents.Authentication( credentials=[fake_credential])) fake_request = messages.RequestMessage() fake_request.request_header = fake_header fake_request.read = mock.MagicMock() request_mock.return_value = fake_request kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() fake_request.read.assert_called_once_with( data, kmip_version=enums.KMIPVersion.KMIP_1_2) kmip_session.authenticate.assert_called_once_with( "test_certificate", fake_request) kmip_session._logger.warning.assert_called_once_with( "Authentication failed.") kmip_session._engine.build_error_response.assert_called_once_with( fake_version, enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "An error occurred during client authentication. " "See server logs for more information.") kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop(self, request_mock): """ Test that the correct logging and error handling occurs during the message handling loop. """ data = utils.BytearrayStream() # Build a response and use it as a dummy processing result. batch_item = messages.ResponseBatchItem( result_status=contents.ResultStatus(enums.ResultStatus.SUCCESS), result_reason=contents.ResultReason( enums.ResultReason.OBJECT_ARCHIVED), result_message=contents.ResultMessage("Test message.")) batch_items = [batch_item] header = messages.ResponseHeader( protocol_version=contents.ProtocolVersion(1, 0), time_stamp=contents.TimeStamp(int(time.time())), batch_count=contents.BatchCount(len(batch_items))) message = messages.ResponseMessage(response_header=header, batch_items=batch_items) kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, 'name') kmip_session._engine = mock.MagicMock() kmip_session._get_client_identity = mock.MagicMock() kmip_session._get_client_identity.return_value = 'test' kmip_session._engine.process_request = mock.MagicMock( return_value=(message, kmip_session._max_response_size)) kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._connection.shared_ciphers = mock.MagicMock( return_value=[('AES128-SHA256', 'TLSv1/SSLv3', 128), ('AES256-SHA256', 'TLSv1/SSLv3', 256)]) kmip_session._connection.cipher = mock.MagicMock( return_value=('AES128-SHA256', 'TLSv1/SSLv3', 128)) kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.info.assert_not_called() kmip_session._logger.debug.assert_any_call( "Possible session ciphers: 2") kmip_session._logger.debug.assert_any_call( ('AES128-SHA256', 'TLSv1/SSLv3', 128)) kmip_session._logger.debug.assert_any_call( ('AES256-SHA256', 'TLSv1/SSLv3', 256)) kmip_session._logger.debug.assert_any_call( "Session cipher selected: {0}".format( ('AES128-SHA256', 'TLSv1/SSLv3', 128))) kmip_session._logger.warning.assert_not_called() kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_invalid_certificate_extension(self, request_mock, cert_mock, ext_mock): """ Test that the correct logging and error handling occurs when an invalid certificate is encountered while processing a request. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' ext_mock.return_value = [] kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession( kmip_engine, None, None, name='name', enable_tls_client_auth=True ) kmip_session.authenticate = mock.MagicMock() kmip_session._engine = mock.MagicMock() kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.warning( "Failure verifying the client certificate." ) kmip_session._logger.exception.assert_called_once_with( exceptions.PermissionDenied( "The extended key usage extension is not marked for client " "authentication in the client certificate." ) ) kmip_session._engine.build_error_response.assert_called_once_with( contents.ProtocolVersion(1, 0), enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "Error verifying the client certificate. " "See server logs for more information." ) self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop_with_response_too_long(self, request_mock): """ Test that the correct logging and error handling occurs during the message handling loop. """ data = utils.BytearrayStream(()) kmip_engine = engine.KmipEngine() kmip_session = session.KmipSession(kmip_engine, None, 'name') kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._max_response_size = 0 kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.info.assert_not_called() self.assertTrue(kmip_session._logger.warning.called) kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_handle_message_loop(self, request_mock): """ Test that the correct logging and error handling occurs during the message handling loop. """ data = utils.BytearrayStream() # Build a response and use it as a dummy processing result. batch_item = messages.ResponseBatchItem( result_status=contents.ResultStatus(enums.ResultStatus.SUCCESS), result_reason=contents.ResultReason( enums.ResultReason.OBJECT_ARCHIVED), result_message=contents.ResultMessage("Test message.")) batch_items = [batch_item] header = messages.ResponseHeader( protocol_version=contents.ProtocolVersion.create(1, 0), time_stamp=contents.TimeStamp(int(time.time())), batch_count=contents.BatchCount(len(batch_items))) message = messages.ResponseMessage(response_header=header, batch_items=batch_items) kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, 'name') kmip_session._engine = mock.MagicMock() kmip_session._engine.process_request = mock.MagicMock( return_value=(message, kmip_session._max_response_size)) kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.info.assert_not_called() kmip_session._logger.warning.assert_not_called() kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def start(self): """ Prepare the server to start serving connections. Configure the server socket handler and establish a TLS wrapping socket from which all client connections descend. Bind this TLS socket to the specified network address for the server. Raises: NetworkingError: Raised if the TLS socket cannot be bound to the network address. """ self.manager = multiprocessing.Manager() self.policies = self.manager.dict() policies = copy.deepcopy(operation_policy.policies) for policy_name, policy_set in six.iteritems(policies): self.policies[policy_name] = policy_set self.policy_monitor = monitor.PolicyDirectoryMonitor( self.config.settings.get('policy_path'), self.policies, self.live_policies) def interrupt_handler(trigger, frame): self.policy_monitor.stop() signal.signal(signal.SIGINT, interrupt_handler) signal.signal(signal.SIGTERM, interrupt_handler) self.policy_monitor.start() self._engine = engine.KmipEngine( policies=self.policies, database_path=self.config.settings.get('database_path')) self._logger.info("Starting server socket handler.") # Create a TCP stream socket and configure it for immediate reuse. socket.setdefaulttimeout(10) self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._logger.debug("Configured cipher suites: {0}".format( len(self.config.settings.get('tls_cipher_suites')))) for cipher in self.config.settings.get('tls_cipher_suites'): self._logger.debug(cipher) auth_suite_ciphers = self.auth_suite.ciphers.split(':') self._logger.debug("Authentication suite ciphers to use: {0}".format( len(auth_suite_ciphers))) for cipher in auth_suite_ciphers: self._logger.debug(cipher) self._socket = ssl.wrap_socket( self._socket, keyfile=self.config.settings.get('key_path'), certfile=self.config.settings.get('certificate_path'), server_side=True, cert_reqs=ssl.CERT_REQUIRED, ssl_version=self.auth_suite.protocol, ca_certs=self.config.settings.get('ca_path'), do_handshake_on_connect=False, suppress_ragged_eofs=True, ciphers=self.auth_suite.ciphers) try: self._socket.bind((self.config.settings.get('hostname'), int(self.config.settings.get('port')))) except Exception as e: self._logger.exception(e) raise exceptions.NetworkingError( "Server failed to bind socket handler to {0}:{1}".format( self.config.settings.get('hostname'), self.config.settings.get('port'))) else: self._logger.info( "Server successfully bound socket handler to {0}:{1}".format( self.config.settings.get('hostname'), self.config.settings.get('port'))) self._is_serving = True
def __init__( self, hostname=None, port=None, certificate_path=None, key_path=None, ca_path=None, auth_suite=None, config_path='/etc/pykmip/server.conf', log_path='/var/log/pykmip/server.log', policy_path=None, enable_tls_client_auth=None, tls_cipher_suites=None ): """ Create a KmipServer. Settings are loaded initially from the configuration file located at config_path, if specified. All other configuration options listed below, if specified, will override the settings loaded from the configuration file. A rotating file logger will be set up with the base log file located at log_path. The server itself will handle rotating the log files as the logs grow. The server process must have permission to read/write to the specified log directory. The main KmipEngine request processor is created here, along with all information required to manage KMIP client connections and sessions. Args: hostname (string): The host address the server will be bound to (e.g., '127.0.0.1'). Optional, defaults to None. port (int): The port number the server will be bound to (e.g., 5696). Optional, defaults to None. certificate_path (string): The path to the server certificate file (e.g., '/etc/pykmip/certs/server.crt'). Optional, defaults to None. key_path (string): The path to the server certificate key file (e.g., '/etc/pykmip/certs/server.key'). Optional, defaults to None. ca_path (string): The path to the certificate authority (CA) certificate file (e.g., '/etc/pykmip/certs/ca.crt'). Optional, defaults to None. auth_suite (string): A string value indicating the type of authentication suite to use for establishing TLS connections. Accepted values are: 'Basic', 'TLS1.2'. Optional, defaults to None. config_path (string): The path to the server configuration file (e.g., '/etc/pykmip/server.conf'). Optional, defaults to '/etc/pykmip/server.conf'. log_path (string): The path to the base server log file (e.g., '/var/log/pykmip/server.log'). Optional, defaults to '/var/log/pykmip/server.log'. policy_path (string): The path to the filesystem directory containing PyKMIP server operation policy JSON files. Optional, defaults to None. enable_tls_client_auth (boolean): A boolean indicating if the TLS certificate client auth flag should be required for client certificates when establishing a new client session. Optional, defaults to None. tls_cipher_suites (string): A comma-delimited list of cipher suite names (e.g., TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_ 128_CBC_SHA256), indicating which specific cipher suites should be used by the server when establishing a TLS connection with a client. Optional, defaults to None. If None, the default set of TLS cipher suites will be used. """ self._logger = logging.getLogger('kmip.server') self._setup_logging(log_path) self.config = config.KmipServerConfig() self._setup_configuration( config_path, hostname, port, certificate_path, key_path, ca_path, auth_suite, policy_path, enable_tls_client_auth, tls_cipher_suites ) cipher_suites = self.config.settings.get('tls_cipher_suites') if self.config.settings.get('auth_suite') == 'TLS1.2': self.auth_suite = auth.TLS12AuthenticationSuite(cipher_suites) else: self.auth_suite = auth.BasicAuthenticationSuite(cipher_suites) self._engine = engine.KmipEngine( self.config.settings.get('policy_path') ) self._session_id = 1 self._is_serving = False
def __init__(self, hostname=None, port=None, certificate_path=None, key_path=None, ca_path=None, auth_suite=None, config_path='/etc/pykmip/server.conf', log_path='/var/log/pykmip/server.log'): """ Create a KmipServer. Settings are loaded initially from the configuration file located at config_path, if specified. All other configuration options listed below, if specified, will override the settings loaded from the configuration file. A rotating file logger will be set up with the base log file located at log_path. The server itself will handle rotating the log files as the logs grow. The server process must have permission to read/write to the specified log directory. The main KmipEngine request processor is created here, along with all information required to manage KMIP client connections and sessions. Args: hostname (string): The host address the server will be bound to (e.g., '127.0.0.1'). Optional, defaults to None. port (int): The port number the server will be bound to (e.g., 5696). Optional, defaults to None. certificate_path (string): The path to the server certificate file (e.g., '/etc/pykmip/certs/server.crt'). Optional, defaults to None. key_path (string): The path to the server certificate key file (e.g., '/etc/pykmip/certs/server.key'). Optional, defaults to None. ca_path (string): The path to the certificate authority (CA) certificate file (e.g., '/etc/pykmip/certs/ca.crt'). Optional, defaults to None. auth_suite (string): A string value indicating the type of authentication suite to use for establishing TLS connections. Accepted values are: 'Basic', 'TLS1.2'. Optional, defaults to None. config_path (string): The path to the server configuration file (e.g., '/etc/pykmip/server.conf'). Optional, defaults to '/etc/pykmip/server.conf'. log_path (string): The path to the base server log file (e.g., '/var/log/pykmip/server.log'). Optional, defaults to '/var/log/pykmip/server.log'. """ self._logger = logging.getLogger('kmip.server') self._setup_logging(log_path) self.config = config.KmipServerConfig() self._setup_configuration(config_path, hostname, port, certificate_path, key_path, ca_path, auth_suite) if self.config.settings.get('auth_suite') == 'TLS1.2': self.auth_suite = auth.TLS12AuthenticationSuite() else: self.auth_suite = auth.BasicAuthenticationSuite() self._engine = engine.KmipEngine() self._session_id = 1 self._is_serving = False