def _write_worker(audit_key, audit_version, plugin_key, plugin, input_queue, worker_type): """Worker function for store and alert plugins. Arguments: audit_key (str): Audit key name in configuration audit_version (str): Audit version string. plugin_key (str): Plugin key name in configuration. plugin (object): Store plugin or alert plugin object. input_queue (multiprocessing.Queue): Queue to read records from. worker_type (str): Either ``'store'`` or ``'alert'``. """ worker_name = audit_key + '_' + plugin_key _log.info('%s: Started', worker_name) while True: record = input_queue.get() if record is None: plugin.done() break record['com'] = util.merge_dicts(record.get('com', {}), { 'audit_key': audit_key, 'audit_version': audit_version, 'target_key': plugin_key, 'target_class': type(plugin).__name__, 'target_worker': worker_name, 'target_type': worker_type, }) plugin.write(record) _log.info('%s: Stopped', worker_name)
def cloud_worker(audit_key, audit_version, plugin_key, plugin, output_queues): """Worker function for cloud plugins. This function expects the ``plugin`` object to implement a ``read`` method that yields records. This function calls this ``read`` method to retrieve records and puts each record into each queue in ``output_queues``. Arguments: audit_key (str): Audit key name in configuration. audit_version (str): Audit version string. plugin_key (str): Plugin key name in configuration. plugin (object): Cloud plugin object. output_queues (list): List of :class:`multiprocessing.Queue` objects to write records to. """ worker_name = audit_key + '_' + plugin_key _log.info('%s: Started', worker_name) for record in plugin.read(): record['com'] = util.merge_dicts(record.get('com', {}), { 'audit_key': audit_key, 'audit_version': audit_version, 'origin_key': plugin_key, 'origin_class': type(plugin).__name__, 'origin_worker': worker_name, 'origin_type': 'cloud', }) for q in output_queues: q.put(record) plugin.done() _log.info('%s: Stopped', worker_name)
def _get_sql_db_tde_disabled_event(com, ext): """Generate SQL DB disabled TDE event. Arguments: com (dict): SQL DB record `com` bucket ext (dict): SQL DB record `ext` bucket Returns: dict: An event record representing SQL DB with disabled TDE """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ('{} SQL DB {} has TDE disabled.'.format( friendly_cloud_type, reference)) recommendation = ('Check {} SQL DB {} and enable TDE.'.format( friendly_cloud_type, reference)) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'sql_db_tde_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'sql_db_tde_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating sql_db_tde_event; %r', event_record) yield event_record
def _get_log_profile_missing_event(com, ext): """Create an event record for missing log profile. Arguments: com (dict): The `com` bucket of a ``log_profile_missing`` record. ext (dict): The `ext` bucket of a ``log_profile_missing`` record. Returns: dict: An event record representing log profile missing for a subscription. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ('{} subscription {} has log profile missing.'.format( friendly_cloud_type, reference)) recommendation = ( 'Check {} subscription {} and create a log profile.'.format( friendly_cloud_type, reference)) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'log_profile_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'log_profile_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating log_profile_event; %r', event_record) yield event_record
def _process_vm_instance_view(vm, vm_iv, vm_index, subscription_id): """Process virtual machine record and yeild them. Arguments: vm (VirtualMachine): Virtual Machine Descriptor vm_iv (VirtualMachineInstanceView): Virtual Machine Instance view subscription_id (str): Subscription ID. Yields: dict: An Azure record of type ``vm_instance_view``. """ raw_record = vm.as_dict() raw_record['instance_view'] = vm_iv.as_dict() record = { 'raw': raw_record, 'ext': { 'cloud_type': 'azure', 'record_type': 'vm_instance_view', 'subscription_id': subscription_id, }, 'com': { 'cloud_type': 'azure', 'record_type': 'compute', 'reference': raw_record.get('id') } } record['ext'] = util.merge_dicts( record['ext'], _get_normalized_vm_statuses(vm_iv), _get_normalized_vm_disk_encryption_status(vm, vm_iv)) _log.info('Found vm_instance_view #%d; subscription_id: %s; name: %s', vm_index, subscription_id, record['raw'].get('name')) yield record
def _process_postgres_server_details(self, sub, server, configuration, derived_configurations): """Process Postgres server details and configuration and yield them. Arguments: server (dict): Raw Postgres server record. configuration (list): Raw Postgres server configuration. derived_configurations (dict): Derived Postgres server configuration. Yields: dict: An Azure record of type ``rdbms``. """ server['configuration'] = configuration ssl_enforcement = server.get('raw', {}).get('ssl_enforcement') ssl_connection_enabled = (ssl_enforcement == 'Enabled') record = { 'raw': server, 'ext': { 'cloud_type': 'azure', 'record_type': 'postgresql_server', 'subscription_id': sub.get('subscription_id'), 'subscription_name': sub.get('display_name'), 'subscription_state': sub.get('state'), }, 'com': { 'cloud_type': 'azure', 'record_type': 'rdbms', 'reference': server.get('id'), 'tls_enforced': ssl_connection_enabled, } } record['ext'] = util.merge_dicts(record['ext'], derived_configurations) yield record
def test_merge_dicts_immutability_simple(self): a = {'a': 1} b = {'b': 2} c = util.merge_dicts(a, b) c['c'] = 3 self.assertEqual(a, {'a': 1}) self.assertEqual(b, {'b': 2})
def test_merge_dicts_immutability_nested(self): a = {'a': [1, 2, 3]} b = {'a': [4, 5, 6]} c = util.merge_dicts(a, b) c['a'].append(7) self.assertEqual(a, {'a': [1, 2, 3]}) self.assertEqual(b, {'a': [4, 5, 6]})
def event_worker(audit_key, audit_version, plugin_key, plugin, input_queue, output_queues): """Worker function for event plugins. This function expects the ``plugin`` object to implement a ``eval`` method that accepts a single record as a parameter and yields one or more records, and a ``done`` method to perform cleanup work in the end. This function gets records from ``input_queue`` and passes each record to the ``eval`` method of ``plugin``. Then it puts each record yielded by the ``eval`` method into each queue in ``output_queues``. When there are no more records in the ``input_queue``, i.e., once ``None`` is found in the ``input_queue``, this function calls the ``done`` method of the ``plugin`` to indicate that record processing is over. Arguments: audit_key (str): Audit key name in configuration. audit_version (str): Audit version string. plugin_key (str): Plugin key name in configuration. plugin (object): Store plugin object. input_queue (multiprocessing.Queue): Queue to read records from. output_queues (list): List of :class:`multiprocessing.Queue` objects to write records to. """ worker_name = audit_key + '_' + plugin_key _log.info('%s: Started', worker_name) while True: record = input_queue.get() if record is None: try: plugin.done() except Exception as e: _log.exception('%s: Failed; done() error: %s: %s', worker_name, type(e).__name__, e) break try: for event_record in plugin.eval(record): event_record['com'] = \ util.merge_dicts(event_record.get('com', {}), { 'audit_key': audit_key, 'audit_version': audit_version, 'origin_key': plugin_key, 'origin_class': type(plugin).__name__, 'origin_worker': worker_name, 'origin_type': 'event', }) for q in output_queues: q.put(event_record) except Exception as e: _log.exception('%s: Failed; eval() error: %s: %s', worker_name, type(e).__name__, e) _log.info('%s: Stopped', worker_name)
def _get_normalized_firewall_rule(firewall_record, rule_index, rule, project_index, project, key_file_path): """Create a normalized firewall rule record. Arguments: firewall_record (dict): Firewall record generated by this plugin. rule_index (int): Index of a firewall rule (for logging only). rule (dict): Raw allowed or denied rule in ``firewall``. project_index (int): Project index (for logging only). project (dict): GCP project object (for logging only). key_file_path (str): Path to key file (for logging only). Returns: dict: A normalized firewall rule record with ``com`` bucket populated with firewall rule properties in common notation. """ raw_firewall = firewall_record.get('raw', {}) record = { 'raw': rule, # Preserve the extended properties from firewall record. 'ext': util.merge_dicts( firewall_record.get('ext'), { # Set extended properties specific to a firewall rule. 'record_type': 'firewall_rule', 'firewall_id': raw_firewall.get('id'), 'firewall_link': raw_firewall.get('selfLink'), }), 'com': { 'cloud_type': 'gcp', 'record_type': 'firewall_rule', 'reference': raw_firewall.get('selfLink'), 'enabled': not raw_firewall.get('disabled'), 'direction': _get_normalized_firewall_direction(raw_firewall), 'source_addresses': raw_firewall.get('sourceRanges'), 'protocol': _get_normalized_firewall_protocol(rule), # If the 'ports' key is missing in an allowed/denied rule, # it means all ports are allowed/denied. 'destination_ports': rule.get('ports', ['0-65535']) } } _log.info( 'Found firewall_rule #%d; %s, %s; %s', rule_index, raw_firewall.get('name'), rule.get('IPProtocol'), util.outline_gcp_project(project_index, project, None, key_file_path)) return record
def _get_azure_vm_data_disk_encryption_event(com, ext, raw): """Evaluate Azure VM for unencrypted data disks. Arguments: com (dict): Virtual machine record `com` bucket ext (dict): Virtual machine record `ext` bucket raw (dict): Virtual machine record `raw` bucket Returns: dict: An event record representing unencrypted data disk """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) os_disk_name = raw.get('storage_profile').get('os_disk').get('name') instance_view = raw.get('instance_view') if instance_view is None: return for disk in instance_view.get('disks'): # If it is an OS disk skip and continue if disk.get('name') == os_disk_name: continue if disk.get('encryption_settings') is not None and \ disk['encryption_settings'][0]['enabled']: continue reference = com.get('reference') description = ( '{} virtual machine {} has unencrypted data disk {}' .format(friendly_cloud_type, reference, disk.get('name')) ) recommendation = ( 'Check {} virtual machine {} and encrypt data disk {}' .format(friendly_cloud_type, reference, disk.get('name')) ) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, { 'record_type': 'vm_data_disk_encryption_event' }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'vm_data_disk_encryption_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating vm_data_disk_encryption_event; %r', event_record) yield event_record
def _write_worker(audit_key, audit_version, plugin_key, plugin_config, input_queue, worker_type): """Worker function for store and alert plugins. Arguments: audit_key (str): Audit key name in configuration audit_version (str): Audit version string. plugin_key (str): Plugin key name in configuration. plugin_config (dict): Store or alert plugin config dictionary. input_queue (multiprocessing.Queue): Queue to read records from. worker_type (str): Either ``'store'`` or ``'alert'``. """ worker_name = audit_key + '_' + plugin_key _log.info('%s_worker: %s: Started', worker_type, worker_name) try: plugin = util.load_plugin(plugin_config) except Exception as e: _log.exception('%s_worker: %s: Failed; error: %s: %s', worker_type, worker_name, type(e).__name__, e) _log.info('%s_worker: %s: Stopped', worker_type, worker_name) return while plugin is not None: try: record = input_queue.get() if record is None: _log.info('%s_worker: %s: Stopping', worker_type, worker_name) plugin.done() break record['com'] = util.merge_dicts( record.get('com', {}), { 'audit_key': audit_key, 'audit_version': audit_version, 'target_key': plugin_key, 'target_class': type(plugin).__name__, 'target_worker': worker_name, 'target_type': worker_type, }) plugin.write(record) except Exception as e: _log.exception('%s_worker: %s: Failed; error: %s: %s', worker_type, worker_name, type(e).__name__, e) _log.info('%s_worker: %s: Stopped', worker_type, worker_name)
def _get_normalized_rdbms_record(rdbms_record): """Normalize records of type `rdbms`. Arguments: rdbms_record (dict): RDBMS record generated by this plugin. Yields: dict: A normalized rdbms record. """ ssl_enforcement = rdbms_record.get('raw', {}).get('ssl_enforcement') ssl_connection_enabled = (ssl_enforcement == 'Enabled') normalized_rdbms_record = { 'raw': rdbms_record.get('raw', {}), 'ext': util.merge_dicts(rdbms_record.get('ext'), { 'reference': rdbms_record.get('raw', {}).get('id'), }), 'com': util.merge_dicts(rdbms_record.get('com'), { 'reference': rdbms_record.get('raw', {}).get('id'), 'tls_enforced': ssl_connection_enabled, }), } yield normalized_rdbms_record
def eval(self, record): """Evaluate record to check for multiples of ``n``. If ``record['raw']['data']`` is a multiple of ``n`` (the parameter with which this plugin was initialized with), then generate an event record. Otherwise, do nothing. If ``record['raw']['data]`` is missing, i.e., the key named ``raw`` or ``data`` does not exist, then its record number is assumed to be ``1``. This is a mock example of a event plugin. In actual event plugins, this method would typically check for security issues in the ``record``. Arguments: record (dict): Record to evaluate. Yields: dict: Event record if evaluation rule matches the input record. """ # If record data value is a multiple of self._n, generate an # event record. data = record.get('raw', {}).get('data', 1) description = '{} is a multiple of {}.'.format(data, self._n) recommendation = ( 'Divide {} by {} and confirm that the remainder is 0.'.format( data, self._n)) if data % self._n == 0: yield { 'ext': util.merge_dicts(record.get('ext', {}), { 'record_type': 'mock_event', 'data': data, 'n': self._n, }), 'com': { 'record_type': 'mock_event', 'description': description, 'recommendation': recommendation, } }
def _get_az_storage_account_secure_transfer_event(com, ext): """Evaluate Azure storage account to check if insecure transfer enabled. Arguments: com (dict): Azure storage account record `com` bucket. ext (dict): Azure storage account record `ext` bucket. Returns: dict: An event record representing storage accounts with secure transfer enabled set to false. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} storage account {} does not require secure transfer.' .format(friendly_cloud_type, reference) ) recommendation = ( 'Check {} storage account {} and ensure that it requires ' 'secure transfer.'.format(friendly_cloud_type, reference) ) event_record = { # Preserve the properties from the storage account # record because they provide useful context to # locate the storage account that led to the event. 'ext': util.merge_dicts(ext, { 'record_type': 'storage_account_secure_transfer_event' }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'storage_account_secure_transfer_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating storage_account_secure_transfer_event; %r', event_record) yield event_record
def _get_normalized_firewall_rule(firewall_record, rule): """Create a normalized firewall rule record. Arguments: firewall_record (dict): Firewall record generated by this plugin. rule (dict): Raw allowed or denied rule in ``firewall``. Returns: dict: A normalized firewall rule record with ``com`` bucket populated with firewall rule properties in common notation. """ raw_firewall = firewall_record.get('raw', {}) record = { 'raw': rule, # Preserve the extended properties from firewall record. 'ext': util.merge_dicts( firewall_record.get('ext'), { # Set extended properties specific to a firewall rule. 'record_type': 'firewall_rule', 'firewall_id': raw_firewall.get('id'), 'firewall_link': raw_firewall.get('selfLink'), }), 'com': { 'cloud_type': 'gcp', 'record_type': 'firewall_rule', 'reference': raw_firewall.get('selfLink'), 'enabled': not raw_firewall.get('disabled'), 'direction': _get_normalized_firewall_direction(raw_firewall), 'source_addresses': raw_firewall.get('sourceRanges'), 'protocol': _get_normalized_firewall_protocol(rule), # If the 'ports' key is missing in an allowed/denied rule, # it means all ports are allowed/denied. 'destination_ports': rule.get('ports', ['0-65535']) } } return record
def _get_az_storage_account_default_network_access_event(com, ext): """Generate Azure storage account default network access event. Arguments: com (dict): Azure storage account record `com` bucket. ext (dict): Azure storage account record `ext` bucket. Returns: dict: An event record representing storage accounts with default network access set to allowed. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} storage account {} has default network access set to allowed.'. format(friendly_cloud_type, reference)) recommendation = ( 'Check {} storage account {} and set default network access to deny.'. format(friendly_cloud_type, reference)) event_record = { # Preserve the properties from the storage account # record because they provide useful context to # locate the storage account that led to the event. 'ext': util.merge_dicts( ext, {'record_type': 'storage_account_default_network_access_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'storage_account_default_network_access_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating storage_account_default_network_access_event; %r', event_record) yield event_record
def _process_vm_instance_view(vm, vm_iv, subscription_id): """Process virtual machine record and yeild them. Arguments: vm (VirtualMachine): Virtual Machine Descriptor vm_iv (VirtualMachineInstanceView): Virtual Machine Instance view subscription_id (str): Subscription ID. Yields: dict: An Azure record of type ``vm_instance_view``. """ try: raw_record = vm.as_dict() raw_record['instance_view'] = vm_iv.as_dict() record = { 'raw': raw_record, 'ext': { 'cloud_type': 'azure', 'record_type': 'vm_instance_view', 'subscription_id': subscription_id, }, 'com': { 'cloud_type': 'azure', 'record_type': None, 'reference': raw_record.get('id') } } record['ext'] = util.merge_dicts( record['ext'], _get_normalized_vm_statuses(vm_iv), _get_normalized_vm_disk_encryption_status(vm, vm_iv) ) _log.info('Found virtual_machine; subscription_id: %s; name: %s', subscription_id, record['raw'].get('name')) yield record except CloudError as e: _log.error('Failed to fetch details for virtual_machine; ' 'subscription_id: %s; error: %s: %s', subscription_id, type(e).__name__, e)
def _get_postgres_log_retention_days_event(com, ext, min_log_retention_days): """Generate event for Postgres log retention days set below minimum. Arguments: com (dict): Postgres record `com` bucket ext (dict): Postgres record `ext` bucket min_log_retention_days (int): Minimum log retention days Returns: dict: An event record representing Postgres server with log retention days set below desired minimum. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) friendly_rdbms_type = util.friendly_string(ext.get('record_type')) reference = com.get('reference') description = ( '{} {} {} has log retention days set below desired minimum value.'. format(friendly_cloud_type, friendly_rdbms_type, reference)) recommendation = ( 'Check {} {} {} and set log retention days to minimum of {} days.'. format(friendly_cloud_type, friendly_rdbms_type, reference, min_log_retention_days)) event_record = { # Preserve the extended properties from the RDBMS # record because they provide useful context to # locate the RDBMS that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'postgres_log_retention_days_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'postgres_log_retention_days_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating postgres_log_retention_days_event; %r', event_record) yield event_record
def _get_postgres_log_disconnections_disabled_event(com, ext): """Generate event for Postgres log disconnections disabled. Arguments: com (dict): Postgres record `com` bucket ext (dict): Postgres record `ext` bucket Returns: dict: An event record representing Postgres server with log disconnections disabled """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) friendly_rdbms_type = util.friendly_string(ext.get('record_type')) reference = com.get('reference') description = ( '{} {} {} has log disconnections disabled.' .format(friendly_cloud_type, friendly_rdbms_type, reference) ) recommendation = ( 'Check {} {} {} and enable log disconnections.' .format(friendly_cloud_type, friendly_rdbms_type, reference) ) event_record = { # Preserve the extended properties from the RDBMS # record because they provide useful context to # locate the RDBMS that led to the event. 'ext': util.merge_dicts(ext, { 'record_type': 'postgres_log_disconnections_event' }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'postgres_log_disconnections_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating postgres_log_disconnections_event; %r', event_record) yield event_record
def _get_azure_vm_blacklisted_extension_event(com, ext, blacklisted): """Evaluate Azure VM for blacklisted extensions. Arguments: com (dict): Virtual machine record `com` bucket ext (dict): Virtual machine record `ext` bucket blacklisted (list): Added blacklisted extension list Returns: dict: An event record representing VM with blacklisted extenstions """ if not blacklisted: return friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} virtual machine {} has blacklisted extensions {}'.format( friendly_cloud_type, reference, util.friendly_list(blacklisted))) recommendation = ( 'Check {} virtual machine {} and remove blacklisted extensions {}'. format(friendly_cloud_type, reference, util.friendly_list(blacklisted))) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'vm_blacklisted_extension_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'vm_blacklisted_extension_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating vm_blacklisted_extension_event; %r', event_record) yield event_record
def _get_log_profile_missing_location_event(com, ext, missing_locations): """Generate log profile missing category type event. Arguments: com (dict): Log profile record `com` bucket ext (dict): Log profile record `ext` bucket missing_locations (set): Missing location set Returns: dict: An event record representing log profile which is not enabled for all locations. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ('{} log profile {} does not include locations {}.'.format( friendly_cloud_type, reference, util.friendly_list(missing_locations))) recommendation = ( 'Check {} log profile {} and enable locations {}.'.format( friendly_cloud_type, reference, util.friendly_list(missing_locations))) event_record = { # Preserve the extended properties from the log profile # record because they provide useful context to locate # the log profile that led to the event. 'ext': util.merge_dicts( ext, { 'record_type': 'log_profile_missing_location_event', 'missing_locations': missing_locations }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'log_profile_missing_location_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating log_profile_missing_location_event; %r', event_record) return event_record
def _get_azure_web_app_https_event(com, ext): """Generate Web App HTTPS event. Arguments: com (dict): Azure web app record `com` bucket. ext (dict): Azure web app record `ext` bucket. Returns: dict: An event record representing web apps not using HTTPS only traffic. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} web app {} has HTTPS only traffic disabled.' .format(friendly_cloud_type, reference) ) recommendation = ( 'Check {} web app {} and enable HTTPS only traffic.' .format(friendly_cloud_type, reference) ) event_record = { # Preserve the extended properties from the web app # record because they provide useful context to # locate the web app that led to the event. 'ext': util.merge_dicts(ext, { 'record_type': 'web_app_https_event' }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'web_app_https_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating web_app_https_event; %r', event_record) yield event_record
def _get_azure_vm_os_disk_encryption_event(com, ext, raw): """Evaluate Azure VM for unencrypted OS disks. Arguments: com (dict): Virtual machine record `com` bucket ext (dict): Virtual machine record `ext` bucket raw (dict): Virtual machine record `raw` bucket Returns: dict: An event record representing unencrypted OS disk """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) os_disk_name = raw.get('storage_profile').get('os_disk').get('name') reference = com.get('reference') description = ( '{} virtual machine {} has unencrypted OS disk {}' .format(friendly_cloud_type, reference, os_disk_name) ) recommendation = ( 'Check {} virtual machine {} and encrypt OS disk {}' .format(friendly_cloud_type, reference, os_disk_name) ) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, { 'record_type': 'vm_os_disk_encryption_event' }), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'vm_os_disk_encryption_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating vm_os_disk_encryption_event; %r', event_record) yield event_record
def _get_log_profile_retention_event(com, ext, min_retention_days): """Generate log profile retention event. Arguments: com (dict): Log profile record `com` bucket ext (dict): Log profile record `ext` bucket min_retention_days (int): Minimum required retention days. Returns: dict: An event record representing log profile with retention policy configured for less number of days than required. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} log profile {} has log retention set to less than {} days.'.format( friendly_cloud_type, reference, min_retention_days)) recommendation = ( 'Check {} log profile {} and set log retention to more than {} days.'. format(friendly_cloud_type, reference, min_retention_days)) event_record = { # Preserve the extended properties from the virtual # machine record because they provide useful context to # locate the virtual machine that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'log_profile_retention_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'log_profile_retention_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating log_profile_retention_event; %r', event_record) yield event_record
def _process_vm_instance_view(vm_index, vm, vm_iv, sub_index, sub, tenant): """Process virtual machine record and yeild them. Arguments: vm_index (int): Virtual machine index (for logging only). vm (dict): Raw virtual machine record. vm_iv (dict): Raw virtual machine instance view record. sub_index (int): Subscription index (for logging only). sub (Subscription): Azure subscription object. tenant (str): Azure tenant ID. Yields: dict: An Azure record of type ``vm_instance_view``. """ vm['instance_view'] = vm_iv record = { 'raw': vm, 'ext': { 'cloud_type': 'azure', 'record_type': 'vm_instance_view', 'subscription_id': sub.get('subscription_id'), 'subscription_name': sub.get('display_name'), 'subscription_state': sub.get('state'), }, 'com': { 'cloud_type': 'azure', 'record_type': 'compute', 'reference': vm.get('id') } } record['ext'] = util.merge_dicts( record['ext'], _get_normalized_vm_statuses(vm_iv), _get_normalized_vm_disk_encryption_status(vm, vm_iv), _get_vm_extension_list(vm_iv)) _log.info('Found vm_instance_view #%d: %s; %s', vm_index, vm.get('name'), util.outline_az_sub(sub_index, sub, tenant)) return record
def _get_key_vault_secret_no_expiry_event(com, ext): """Generate Key Vault secret expiry event. Arguments: com (dict): Key Vault secret record `com` bucket. ext (dict): Key Vault secret record `ext` bucket. Returns: dict: An event record representing Key Vault secret with no expiry set. """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ( '{} Key Vault secret {} has has no expiration date set.'.format( friendly_cloud_type, reference)) recommendation = ( 'Check {} Key Vault secret {} and set expiration date.'.format( friendly_cloud_type, reference)) event_record = { # Preserve the extended properties from the key vault # secret record because they provide useful context to # locate the key vault secret that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'key_vault_secret_no_expiry_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'key_vault_secret_no_expiry_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating key_vault_secret_no_expiry_event; %r', event_record) yield event_record
def _get_azure_web_app_tls_event(com, ext, min_tls_version): """Evaluate Azure web app config for insecure min TLS version. Arguments: com (dict): Azure web app record `com` bucket ext (dict): Azure web app record `ext` bucket min_tls_version (float): Minimum required TLS version Returns: dict: An event record representing web apps not using a minimum TLS version """ friendly_cloud_type = util.friendly_string(com.get('cloud_type')) reference = com.get('reference') description = ('{} web app {} has insecure minimum TLS version.'.format( friendly_cloud_type, reference)) recommendation = ( 'Check {} web app {} and ensure the minimum TLS version is set to {}.'. format(friendly_cloud_type, reference, str(min_tls_version))) event_record = { # Preserve the extended properties from the web app # record because they provide useful context to # locate the web app that led to the event. 'ext': util.merge_dicts(ext, {'record_type': 'web_app_tls_event'}), 'com': { 'cloud_type': com.get('cloud_type'), 'record_type': 'web_app_tls_event', 'reference': reference, 'description': description, 'recommendation': recommendation, } } _log.info('Generating web_app_tls_event; %r', event_record) yield event_record
def test_merge_second_dict_empty(self): a = {'a': 1} b = {} c = util.merge_dicts(a, b) self.assertEqual(c, {'a': 1})
def test_merge_dicts_nested(self): a = {'a': 1, 'b': {'c': 2, 'd': 3}} b = {'a': 1, 'b': {'d': 4, 'e': 5}} c = util.merge_dicts(a, b) self.assertEqual(c, {'a': 1, 'b': {'c': 2, 'd': 4, 'e': 5}})