monitoring.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """
  2. Monitoring and Management Service Scanners
  3. Scans SNS Topics, CloudWatch Log Groups, EventBridge Rules,
  4. CloudTrail Trails, and Config Recorders.
  5. Requirements:
  6. - 5.1: Scan monitoring and management AWS services using boto3
  7. """
  8. import boto3
  9. from typing import List, Dict, Any
  10. import logging
  11. from app.scanners.base import ResourceData
  12. from app.scanners.utils import retry_with_backoff
  13. logger = logging.getLogger(__name__)
  14. class MonitoringServiceScanner:
  15. """Scanner for monitoring and management AWS resources"""
  16. @staticmethod
  17. @retry_with_backoff()
  18. def scan_sns_topics(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  19. """
  20. Scan SNS Topics in the specified region.
  21. Attributes (horizontal layout):
  22. Topic Name, Topic Display Name, Subscription Protocol, Subscription Endpoint
  23. """
  24. resources = []
  25. sns_client = session.client('sns')
  26. try:
  27. paginator = sns_client.get_paginator('list_topics')
  28. for page in paginator.paginate():
  29. for topic in page.get('Topics', []):
  30. topic_arn = topic.get('TopicArn', '')
  31. topic_name = topic_arn.split(':')[-1] if topic_arn else ''
  32. # Get topic attributes
  33. display_name = ''
  34. try:
  35. attrs_response = sns_client.get_topic_attributes(TopicArn=topic_arn)
  36. attrs = attrs_response.get('Attributes', {})
  37. display_name = attrs.get('DisplayName', '')
  38. except Exception as e:
  39. logger.debug(f"Failed to get topic attributes: {str(e)}")
  40. # Get subscriptions
  41. subscriptions = []
  42. try:
  43. sub_paginator = sns_client.get_paginator('list_subscriptions_by_topic')
  44. for sub_page in sub_paginator.paginate(TopicArn=topic_arn):
  45. for sub in sub_page.get('Subscriptions', []):
  46. protocol = sub.get('Protocol', '')
  47. endpoint = sub.get('Endpoint', '')
  48. subscriptions.append({
  49. 'protocol': protocol,
  50. 'endpoint': endpoint
  51. })
  52. except Exception as e:
  53. logger.debug(f"Failed to get subscriptions: {str(e)}")
  54. # Create one entry per subscription, or one entry if no subscriptions
  55. if subscriptions:
  56. for sub in subscriptions:
  57. resources.append(ResourceData(
  58. account_id=account_id,
  59. region=region,
  60. service='sns',
  61. resource_type='Topic',
  62. resource_id=topic_arn,
  63. name=topic_name,
  64. attributes={
  65. 'Topic Name': topic_name,
  66. 'Topic Display Name': display_name,
  67. 'Subscription Protocol': sub['protocol'],
  68. 'Subscription Endpoint': sub['endpoint']
  69. }
  70. ))
  71. else:
  72. resources.append(ResourceData(
  73. account_id=account_id,
  74. region=region,
  75. service='sns',
  76. resource_type='Topic',
  77. resource_id=topic_arn,
  78. name=topic_name,
  79. attributes={
  80. 'Topic Name': topic_name,
  81. 'Topic Display Name': display_name,
  82. 'Subscription Protocol': 'N/A',
  83. 'Subscription Endpoint': 'N/A'
  84. }
  85. ))
  86. except Exception as e:
  87. logger.warning(f"Failed to scan SNS topics: {str(e)}")
  88. return resources
  89. @staticmethod
  90. @retry_with_backoff()
  91. def scan_cloudwatch_log_groups(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  92. """
  93. Scan CloudWatch Log Groups in the specified region.
  94. Attributes (horizontal layout):
  95. Log Group Name, Retention Days, Stored Bytes, KMS Encryption
  96. """
  97. resources = []
  98. logs_client = session.client('logs')
  99. try:
  100. paginator = logs_client.get_paginator('describe_log_groups')
  101. for page in paginator.paginate():
  102. for log_group in page.get('logGroups', []):
  103. log_group_name = log_group.get('logGroupName', '')
  104. # Get retention in days
  105. retention = log_group.get('retentionInDays')
  106. retention_str = str(retention) if retention else 'Never Expire'
  107. # Get stored bytes
  108. stored_bytes = log_group.get('storedBytes', 0)
  109. stored_str = f"{stored_bytes / (1024*1024):.2f} MB" if stored_bytes else '0 MB'
  110. # Check KMS encryption
  111. kms_key = log_group.get('kmsKeyId', '')
  112. kms_encrypted = 'Yes' if kms_key else 'No'
  113. resources.append(ResourceData(
  114. account_id=account_id,
  115. region=region,
  116. service='cloudwatch',
  117. resource_type='Log Group',
  118. resource_id=log_group.get('arn', log_group_name),
  119. name=log_group_name,
  120. attributes={
  121. 'Log Group Name': log_group_name,
  122. 'Retention Days': retention_str,
  123. 'Stored Bytes': stored_str,
  124. 'KMS Encryption': kms_encrypted
  125. }
  126. ))
  127. except Exception as e:
  128. logger.warning(f"Failed to scan CloudWatch log groups: {str(e)}")
  129. return resources
  130. @staticmethod
  131. @retry_with_backoff()
  132. def scan_eventbridge_rules(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  133. """
  134. Scan EventBridge Rules in the specified region.
  135. Attributes (horizontal layout):
  136. Name, Description, Event Bus, State
  137. """
  138. resources = []
  139. events_client = session.client('events')
  140. try:
  141. # List event buses first
  142. buses_response = events_client.list_event_buses()
  143. event_buses = [bus.get('Name', 'default') for bus in buses_response.get('EventBuses', [])]
  144. # If no buses found, use default
  145. if not event_buses:
  146. event_buses = ['default']
  147. for bus_name in event_buses:
  148. try:
  149. paginator = events_client.get_paginator('list_rules')
  150. for page in paginator.paginate(EventBusName=bus_name):
  151. for rule in page.get('Rules', []):
  152. rule_name = rule.get('Name', '')
  153. resources.append(ResourceData(
  154. account_id=account_id,
  155. region=region,
  156. service='eventbridge',
  157. resource_type='Rule',
  158. resource_id=rule.get('Arn', rule_name),
  159. name=rule_name,
  160. attributes={
  161. 'Name': rule_name,
  162. 'Description': rule.get('Description', ''),
  163. 'Event Bus': bus_name,
  164. 'State': rule.get('State', '')
  165. }
  166. ))
  167. except Exception as e:
  168. logger.debug(f"Failed to list rules for bus {bus_name}: {str(e)}")
  169. except Exception as e:
  170. logger.warning(f"Failed to scan EventBridge rules: {str(e)}")
  171. return resources
  172. @staticmethod
  173. @retry_with_backoff()
  174. def scan_cloudtrail_trails(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  175. """
  176. Scan CloudTrail Trails in the specified region.
  177. Attributes (horizontal layout):
  178. Name, Multi-Region Trail, Log File Validation, KMS Encryption
  179. """
  180. resources = []
  181. cloudtrail_client = session.client('cloudtrail')
  182. try:
  183. response = cloudtrail_client.describe_trails()
  184. for trail in response.get('trailList', []):
  185. trail_name = trail.get('Name', '')
  186. # Only include trails that are in this region or are multi-region
  187. trail_region = trail.get('HomeRegion', '')
  188. is_multi_region = trail.get('IsMultiRegionTrail', False)
  189. if trail_region != region and not is_multi_region:
  190. continue
  191. resources.append(ResourceData(
  192. account_id=account_id,
  193. region=region,
  194. service='cloudtrail',
  195. resource_type='Trail',
  196. resource_id=trail.get('TrailARN', trail_name),
  197. name=trail_name,
  198. attributes={
  199. 'Name': trail_name,
  200. 'Multi-Region Trail': 'Yes' if is_multi_region else 'No',
  201. 'Log File Validation': 'Yes' if trail.get('LogFileValidationEnabled') else 'No',
  202. 'KMS Encryption': 'Yes' if trail.get('KmsKeyId') else 'No'
  203. }
  204. ))
  205. except Exception as e:
  206. logger.warning(f"Failed to scan CloudTrail trails: {str(e)}")
  207. return resources
  208. @staticmethod
  209. @retry_with_backoff()
  210. def scan_config_recorders(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  211. """
  212. Scan AWS Config Recorders in the specified region.
  213. Attributes (horizontal layout):
  214. Name, Regional Resources, Global Resources, Retention period
  215. """
  216. resources = []
  217. config_client = session.client('config')
  218. try:
  219. response = config_client.describe_configuration_recorders()
  220. for recorder in response.get('ConfigurationRecorders', []):
  221. recorder_name = recorder.get('name', '')
  222. # Get recording group settings
  223. recording_group = recorder.get('recordingGroup', {})
  224. all_supported = recording_group.get('allSupported', False)
  225. include_global = recording_group.get('includeGlobalResourceTypes', False)
  226. # Get retention period
  227. retention_period = 'N/A'
  228. try:
  229. retention_response = config_client.describe_retention_configurations()
  230. for retention in retention_response.get('RetentionConfigurations', []):
  231. retention_period = f"{retention.get('RetentionPeriodInDays', 'N/A')} days"
  232. break
  233. except Exception:
  234. pass
  235. resources.append(ResourceData(
  236. account_id=account_id,
  237. region=region,
  238. service='config',
  239. resource_type='Config',
  240. resource_id=recorder_name,
  241. name=recorder_name,
  242. attributes={
  243. 'Name': recorder_name,
  244. 'Regional Resources': 'Yes' if all_supported else 'No',
  245. 'Global Resources': 'Yes' if include_global else 'No',
  246. 'Retention period': retention_period
  247. }
  248. ))
  249. except Exception as e:
  250. logger.warning(f"Failed to scan Config recorders: {str(e)}")
  251. return resources