database.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. Database Service Scanners
  3. Scans RDS DB Instances and ElastiCache Clusters.
  4. Requirements:
  5. - 5.1: Scan database AWS services using boto3
  6. """
  7. import boto3
  8. from typing import List, Dict, Any
  9. import logging
  10. from app.scanners.base import ResourceData
  11. from app.scanners.utils import retry_with_backoff
  12. logger = logging.getLogger(__name__)
  13. class DatabaseServiceScanner:
  14. """Scanner for database AWS resources"""
  15. @staticmethod
  16. @retry_with_backoff()
  17. def scan_rds_instances(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  18. """
  19. Scan RDS DB Instances in the specified region.
  20. Attributes (vertical layout - one table per instance):
  21. Region, Endpoint, DB instance ID, DB name, Master Username, Port,
  22. DB Engine, DB Version, Instance Type, Storage type, Storage, Multi-AZ,
  23. Security Group, Deletion Protection, Performance Insights Enabled, CloudWatch Logs
  24. """
  25. resources = []
  26. rds_client = session.client('rds')
  27. paginator = rds_client.get_paginator('describe_db_instances')
  28. for page in paginator.paginate():
  29. for db in page.get('DBInstances', []):
  30. db_id = db.get('DBInstanceIdentifier', '')
  31. # Get security groups
  32. security_groups = []
  33. for sg in db.get('VpcSecurityGroups', []):
  34. security_groups.append(sg.get('VpcSecurityGroupId', ''))
  35. # Get CloudWatch logs exports
  36. cw_logs = db.get('EnabledCloudwatchLogsExports', [])
  37. # Get endpoint
  38. endpoint = db.get('Endpoint', {})
  39. endpoint_address = endpoint.get('Address', '')
  40. port = endpoint.get('Port', '')
  41. resources.append(ResourceData(
  42. account_id=account_id,
  43. region=region,
  44. service='rds',
  45. resource_type='DB Instance',
  46. resource_id=db.get('DBInstanceArn', db_id),
  47. name=db_id,
  48. attributes={
  49. 'Region': region,
  50. 'Endpoint': endpoint_address,
  51. 'DB instance ID': db_id,
  52. 'DB name': db.get('DBName', ''),
  53. 'Master Username': db.get('MasterUsername', ''),
  54. 'Port': str(port),
  55. 'DB Engine': db.get('Engine', ''),
  56. 'DB Version': db.get('EngineVersion', ''),
  57. 'Instance Type': db.get('DBInstanceClass', ''),
  58. 'Storage type': db.get('StorageType', ''),
  59. 'Storage': f"{db.get('AllocatedStorage', '')} GB",
  60. 'Multi-AZ': 'Yes' if db.get('MultiAZ') else 'No',
  61. 'Security Group': ', '.join(security_groups),
  62. 'Deletion Protection': 'Yes' if db.get('DeletionProtection') else 'No',
  63. 'Performance Insights Enabled': 'Yes' if db.get('PerformanceInsightsEnabled') else 'No',
  64. 'CloudWatch Logs': ', '.join(cw_logs) if cw_logs else 'N/A'
  65. }
  66. ))
  67. return resources
  68. @staticmethod
  69. @retry_with_backoff()
  70. def scan_elasticache_clusters(session: boto3.Session, account_id: str, region: str) -> List[ResourceData]:
  71. """
  72. Scan ElastiCache Clusters in the specified region.
  73. Attributes (vertical layout - one table per cluster):
  74. Cluster ID, Engine, Engine Version, Node Type, Num Nodes, Status
  75. """
  76. resources = []
  77. elasticache_client = session.client('elasticache')
  78. # Scan cache clusters (Redis/Memcached)
  79. try:
  80. paginator = elasticache_client.get_paginator('describe_cache_clusters')
  81. for page in paginator.paginate(ShowCacheNodeInfo=True):
  82. for cluster in page.get('CacheClusters', []):
  83. cluster_id = cluster.get('CacheClusterId', '')
  84. resources.append(ResourceData(
  85. account_id=account_id,
  86. region=region,
  87. service='elasticache',
  88. resource_type='Cache Cluster',
  89. resource_id=cluster.get('ARN', cluster_id),
  90. name=cluster_id,
  91. attributes={
  92. 'Cluster ID': cluster_id,
  93. 'Engine': cluster.get('Engine', ''),
  94. 'Engine Version': cluster.get('EngineVersion', ''),
  95. 'Node Type': cluster.get('CacheNodeType', ''),
  96. 'Num Nodes': str(cluster.get('NumCacheNodes', 0)),
  97. 'Status': cluster.get('CacheClusterStatus', '')
  98. }
  99. ))
  100. except Exception as e:
  101. logger.warning(f"Failed to scan ElastiCache clusters: {str(e)}")
  102. # Also scan replication groups (Redis cluster mode)
  103. try:
  104. paginator = elasticache_client.get_paginator('describe_replication_groups')
  105. for page in paginator.paginate():
  106. for rg in page.get('ReplicationGroups', []):
  107. rg_id = rg.get('ReplicationGroupId', '')
  108. # Count nodes
  109. num_nodes = 0
  110. for node_group in rg.get('NodeGroups', []):
  111. num_nodes += len(node_group.get('NodeGroupMembers', []))
  112. # Get node type from member clusters
  113. node_type = ''
  114. member_clusters = rg.get('MemberClusters', [])
  115. if member_clusters:
  116. try:
  117. cluster_response = elasticache_client.describe_cache_clusters(
  118. CacheClusterId=member_clusters[0]
  119. )
  120. if cluster_response.get('CacheClusters'):
  121. node_type = cluster_response['CacheClusters'][0].get('CacheNodeType', '')
  122. except Exception:
  123. pass
  124. resources.append(ResourceData(
  125. account_id=account_id,
  126. region=region,
  127. service='elasticache',
  128. resource_type='Cache Cluster',
  129. resource_id=rg.get('ARN', rg_id),
  130. name=rg_id,
  131. attributes={
  132. 'Cluster ID': rg_id,
  133. 'Engine': 'redis',
  134. 'Engine Version': '',
  135. 'Node Type': node_type,
  136. 'Num Nodes': str(num_nodes),
  137. 'Status': rg.get('Status', '')
  138. }
  139. ))
  140. except Exception as e:
  141. logger.warning(f"Failed to scan ElastiCache replication groups: {str(e)}")
  142. return resources