aws-inventory-manager 0.13.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aws-inventory-manager might be problematic. Click here for more details.

Files changed (145) hide show
  1. aws_inventory_manager-0.13.2.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
  3. aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
  4. aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.13.2.dist-info/top_level.txt +1 -0
  7. src/__init__.py +3 -0
  8. src/aws/__init__.py +11 -0
  9. src/aws/client.py +128 -0
  10. src/aws/credentials.py +191 -0
  11. src/aws/rate_limiter.py +177 -0
  12. src/cli/__init__.py +12 -0
  13. src/cli/config.py +130 -0
  14. src/cli/main.py +3626 -0
  15. src/config_service/__init__.py +21 -0
  16. src/config_service/collector.py +346 -0
  17. src/config_service/detector.py +256 -0
  18. src/config_service/resource_type_mapping.py +328 -0
  19. src/cost/__init__.py +5 -0
  20. src/cost/analyzer.py +226 -0
  21. src/cost/explorer.py +209 -0
  22. src/cost/reporter.py +237 -0
  23. src/delta/__init__.py +5 -0
  24. src/delta/calculator.py +206 -0
  25. src/delta/differ.py +185 -0
  26. src/delta/formatters.py +272 -0
  27. src/delta/models.py +154 -0
  28. src/delta/reporter.py +234 -0
  29. src/models/__init__.py +21 -0
  30. src/models/config_diff.py +135 -0
  31. src/models/cost_report.py +87 -0
  32. src/models/deletion_operation.py +104 -0
  33. src/models/deletion_record.py +97 -0
  34. src/models/delta_report.py +122 -0
  35. src/models/efs_resource.py +80 -0
  36. src/models/elasticache_resource.py +90 -0
  37. src/models/group.py +318 -0
  38. src/models/inventory.py +133 -0
  39. src/models/protection_rule.py +123 -0
  40. src/models/report.py +288 -0
  41. src/models/resource.py +111 -0
  42. src/models/security_finding.py +102 -0
  43. src/models/snapshot.py +122 -0
  44. src/restore/__init__.py +20 -0
  45. src/restore/audit.py +175 -0
  46. src/restore/cleaner.py +461 -0
  47. src/restore/config.py +209 -0
  48. src/restore/deleter.py +976 -0
  49. src/restore/dependency.py +254 -0
  50. src/restore/safety.py +115 -0
  51. src/security/__init__.py +0 -0
  52. src/security/checks/__init__.py +0 -0
  53. src/security/checks/base.py +56 -0
  54. src/security/checks/ec2_checks.py +88 -0
  55. src/security/checks/elasticache_checks.py +149 -0
  56. src/security/checks/iam_checks.py +102 -0
  57. src/security/checks/rds_checks.py +140 -0
  58. src/security/checks/s3_checks.py +95 -0
  59. src/security/checks/secrets_checks.py +96 -0
  60. src/security/checks/sg_checks.py +142 -0
  61. src/security/cis_mapper.py +97 -0
  62. src/security/models.py +53 -0
  63. src/security/reporter.py +174 -0
  64. src/security/scanner.py +87 -0
  65. src/snapshot/__init__.py +6 -0
  66. src/snapshot/capturer.py +451 -0
  67. src/snapshot/filter.py +259 -0
  68. src/snapshot/inventory_storage.py +236 -0
  69. src/snapshot/report_formatter.py +250 -0
  70. src/snapshot/reporter.py +189 -0
  71. src/snapshot/resource_collectors/__init__.py +5 -0
  72. src/snapshot/resource_collectors/apigateway.py +140 -0
  73. src/snapshot/resource_collectors/backup.py +136 -0
  74. src/snapshot/resource_collectors/base.py +81 -0
  75. src/snapshot/resource_collectors/cloudformation.py +55 -0
  76. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  77. src/snapshot/resource_collectors/codebuild.py +69 -0
  78. src/snapshot/resource_collectors/codepipeline.py +82 -0
  79. src/snapshot/resource_collectors/dynamodb.py +65 -0
  80. src/snapshot/resource_collectors/ec2.py +240 -0
  81. src/snapshot/resource_collectors/ecs.py +215 -0
  82. src/snapshot/resource_collectors/efs_collector.py +102 -0
  83. src/snapshot/resource_collectors/eks.py +200 -0
  84. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  85. src/snapshot/resource_collectors/elb.py +126 -0
  86. src/snapshot/resource_collectors/eventbridge.py +156 -0
  87. src/snapshot/resource_collectors/iam.py +188 -0
  88. src/snapshot/resource_collectors/kms.py +111 -0
  89. src/snapshot/resource_collectors/lambda_func.py +139 -0
  90. src/snapshot/resource_collectors/rds.py +109 -0
  91. src/snapshot/resource_collectors/route53.py +86 -0
  92. src/snapshot/resource_collectors/s3.py +105 -0
  93. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  94. src/snapshot/resource_collectors/sns.py +68 -0
  95. src/snapshot/resource_collectors/sqs.py +82 -0
  96. src/snapshot/resource_collectors/ssm.py +160 -0
  97. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  98. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  99. src/snapshot/resource_collectors/waf.py +159 -0
  100. src/snapshot/storage.py +351 -0
  101. src/storage/__init__.py +21 -0
  102. src/storage/audit_store.py +419 -0
  103. src/storage/database.py +294 -0
  104. src/storage/group_store.py +749 -0
  105. src/storage/inventory_store.py +320 -0
  106. src/storage/resource_store.py +413 -0
  107. src/storage/schema.py +288 -0
  108. src/storage/snapshot_store.py +346 -0
  109. src/utils/__init__.py +12 -0
  110. src/utils/export.py +305 -0
  111. src/utils/hash.py +60 -0
  112. src/utils/logging.py +63 -0
  113. src/utils/pagination.py +41 -0
  114. src/utils/paths.py +51 -0
  115. src/utils/progress.py +41 -0
  116. src/utils/unsupported_resources.py +306 -0
  117. src/web/__init__.py +5 -0
  118. src/web/app.py +97 -0
  119. src/web/dependencies.py +69 -0
  120. src/web/routes/__init__.py +1 -0
  121. src/web/routes/api/__init__.py +18 -0
  122. src/web/routes/api/charts.py +156 -0
  123. src/web/routes/api/cleanup.py +186 -0
  124. src/web/routes/api/filters.py +253 -0
  125. src/web/routes/api/groups.py +305 -0
  126. src/web/routes/api/inventories.py +80 -0
  127. src/web/routes/api/queries.py +202 -0
  128. src/web/routes/api/resources.py +379 -0
  129. src/web/routes/api/snapshots.py +314 -0
  130. src/web/routes/api/views.py +260 -0
  131. src/web/routes/pages.py +198 -0
  132. src/web/services/__init__.py +1 -0
  133. src/web/templates/base.html +949 -0
  134. src/web/templates/components/navbar.html +31 -0
  135. src/web/templates/components/sidebar.html +104 -0
  136. src/web/templates/pages/audit_logs.html +86 -0
  137. src/web/templates/pages/cleanup.html +279 -0
  138. src/web/templates/pages/dashboard.html +227 -0
  139. src/web/templates/pages/diff.html +175 -0
  140. src/web/templates/pages/error.html +30 -0
  141. src/web/templates/pages/groups.html +721 -0
  142. src/web/templates/pages/queries.html +246 -0
  143. src/web/templates/pages/resources.html +2251 -0
  144. src/web/templates/pages/snapshot_detail.html +271 -0
  145. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,105 @@
1
+ """S3 resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class S3Collector(BaseResourceCollector):
11
+ """Collector for AWS S3 buckets.
12
+
13
+ Note: S3 is a global service but buckets are accessed via regional endpoints.
14
+ We only collect once (in us-east-1) to avoid duplicates.
15
+ """
16
+
17
+ @property
18
+ def service_name(self) -> str:
19
+ return "s3"
20
+
21
+ @property
22
+ def is_global_service(self) -> bool:
23
+ # S3 is global, so only collect once
24
+ return True
25
+
26
+ def collect(self) -> List[Resource]:
27
+ """Collect S3 buckets.
28
+
29
+ Returns:
30
+ List of S3 bucket resources
31
+ """
32
+ resources = []
33
+ client = self._create_client()
34
+
35
+ try:
36
+ # List all buckets
37
+ response = client.list_buckets()
38
+
39
+ for bucket in response.get("Buckets", []):
40
+ bucket_name = bucket["Name"]
41
+ creation_date = bucket.get("CreationDate")
42
+
43
+ # Get bucket location to determine region
44
+ try:
45
+ location_response = client.get_bucket_location(Bucket=bucket_name)
46
+ location = location_response.get("LocationConstraint")
47
+ # None means us-east-1
48
+ bucket_region = location if location else "us-east-1"
49
+ except Exception as e:
50
+ self.logger.debug(f"Could not get location for bucket {bucket_name}: {e}")
51
+ bucket_region = "unknown"
52
+
53
+ # Get bucket tags
54
+ tags = {}
55
+ try:
56
+ tag_response = client.get_bucket_tagging(Bucket=bucket_name)
57
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("TagSet", [])}
58
+ except client.exceptions.NoSuchTagSet:
59
+ # Bucket has no tags
60
+ pass
61
+ except Exception as e:
62
+ self.logger.debug(f"Could not get tags for bucket {bucket_name}: {e}")
63
+
64
+ # Get additional bucket configuration for config hash
65
+ bucket_config = {
66
+ "Name": bucket_name,
67
+ "CreationDate": creation_date,
68
+ "Region": bucket_region,
69
+ }
70
+
71
+ # Try to get versioning, encryption, etc.
72
+ try:
73
+ versioning = client.get_bucket_versioning(Bucket=bucket_name)
74
+ bucket_config["Versioning"] = versioning.get("Status", "Disabled")
75
+ except Exception:
76
+ pass
77
+
78
+ try:
79
+ encryption = client.get_bucket_encryption(Bucket=bucket_name)
80
+ bucket_config["Encryption"] = encryption.get("ServerSideEncryptionConfiguration")
81
+ except Exception:
82
+ # No encryption configured
83
+ pass
84
+
85
+ # Build ARN
86
+ arn = f"arn:aws:s3:::{bucket_name}"
87
+
88
+ # Create resource
89
+ resource = Resource(
90
+ arn=arn,
91
+ resource_type="AWS::S3::Bucket",
92
+ name=bucket_name,
93
+ region=bucket_region,
94
+ tags=tags,
95
+ config_hash=compute_config_hash(bucket_config),
96
+ created_at=creation_date,
97
+ raw_config=bucket_config,
98
+ )
99
+ resources.append(resource)
100
+
101
+ except Exception as e:
102
+ self.logger.error(f"Error collecting S3 buckets: {e}")
103
+
104
+ self.logger.debug(f"Collected {len(resources)} S3 buckets")
105
+ return resources
@@ -0,0 +1,70 @@
1
+ """Secrets Manager resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class SecretsManagerCollector(BaseResourceCollector):
11
+ """Collector for AWS Secrets Manager resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "secretsmanager"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect Secrets Manager secrets.
19
+
20
+ Returns:
21
+ List of Secrets Manager secret resources
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_secrets")
28
+ for page in paginator.paginate():
29
+ for secret in page.get("SecretList", []):
30
+ secret_name = secret["Name"]
31
+ secret_arn = secret["ARN"]
32
+
33
+ # Get full secret details (but not the actual secret value)
34
+ try:
35
+ secret_details = client.describe_secret(SecretId=secret_arn)
36
+ # Use detailed info for config hash
37
+ config_data = secret_details
38
+ except Exception as e:
39
+ self.logger.debug(f"Could not get details for secret {secret_name}: {e}")
40
+ config_data = secret
41
+
42
+ # Extract tags
43
+ tags = {}
44
+ for tag in secret.get("Tags", []):
45
+ tags[tag["Key"]] = tag["Value"]
46
+
47
+ # Extract creation date
48
+ created_at = secret.get("CreatedDate")
49
+
50
+ # Create resource (without secret value for security)
51
+ # Remove sensitive fields if present
52
+ safe_config = {k: v for k, v in config_data.items() if k not in ["SecretString", "SecretBinary"]}
53
+
54
+ resource = Resource(
55
+ arn=secret_arn,
56
+ resource_type="AWS::SecretsManager::Secret",
57
+ name=secret_name,
58
+ region=self.region,
59
+ tags=tags,
60
+ config_hash=compute_config_hash(safe_config),
61
+ created_at=created_at,
62
+ raw_config=safe_config,
63
+ )
64
+ resources.append(resource)
65
+
66
+ except Exception as e:
67
+ self.logger.error(f"Error collecting Secrets Manager secrets in {self.region}: {e}")
68
+
69
+ self.logger.debug(f"Collected {len(resources)} Secrets Manager secrets in {self.region}")
70
+ return resources
@@ -0,0 +1,68 @@
1
+ """SNS resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class SNSCollector(BaseResourceCollector):
11
+ """Collector for AWS SNS resources (topics)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "sns"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect SNS resources.
19
+
20
+ Returns:
21
+ List of SNS topics
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_topics")
28
+ for page in paginator.paginate():
29
+ for topic in page.get("Topics", []):
30
+ topic_arn = topic["TopicArn"]
31
+
32
+ # Get topic name from ARN
33
+ topic_name = topic_arn.split(":")[-1]
34
+
35
+ # Get topic attributes
36
+ try:
37
+ attrs_response = client.get_topic_attributes(TopicArn=topic_arn)
38
+ attributes = attrs_response.get("Attributes", {})
39
+
40
+ # Get tags
41
+ tags = {}
42
+ try:
43
+ tag_response = client.list_tags_for_resource(ResourceArn=topic_arn)
44
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("Tags", [])}
45
+ except Exception as e:
46
+ self.logger.debug(f"Could not get tags for SNS topic {topic_name}: {e}")
47
+
48
+ # Create resource
49
+ resource = Resource(
50
+ arn=topic_arn,
51
+ resource_type="AWS::SNS::Topic",
52
+ name=topic_name,
53
+ region=self.region,
54
+ tags=tags,
55
+ config_hash=compute_config_hash(attributes),
56
+ created_at=None, # SNS topics don't expose creation date
57
+ raw_config=attributes,
58
+ )
59
+ resources.append(resource)
60
+
61
+ except Exception as e:
62
+ self.logger.debug(f"Could not get attributes for SNS topic {topic_name}: {e}")
63
+
64
+ except Exception as e:
65
+ self.logger.error(f"Error collecting SNS topics in {self.region}: {e}")
66
+
67
+ self.logger.debug(f"Collected {len(resources)} SNS topics in {self.region}")
68
+ return resources
@@ -0,0 +1,82 @@
1
+ """SQS resource collector."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import List
5
+
6
+ from ...models.resource import Resource
7
+ from ...utils.hash import compute_config_hash
8
+ from .base import BaseResourceCollector
9
+
10
+
11
+ class SQSCollector(BaseResourceCollector):
12
+ """Collector for AWS SQS resources (queues)."""
13
+
14
+ @property
15
+ def service_name(self) -> str:
16
+ return "sqs"
17
+
18
+ def collect(self) -> List[Resource]:
19
+ """Collect SQS resources.
20
+
21
+ Returns:
22
+ List of SQS queues
23
+ """
24
+ resources = []
25
+ account_id = self._get_account_id()
26
+ client = self._create_client()
27
+
28
+ try:
29
+ # List all queue URLs
30
+ response = client.list_queues()
31
+ queue_urls = response.get("QueueUrls", [])
32
+
33
+ for queue_url in queue_urls:
34
+ try:
35
+ # Get queue attributes
36
+ attrs_response = client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"])
37
+ attributes = attrs_response.get("Attributes", {})
38
+
39
+ # Get queue name from URL
40
+ queue_name = queue_url.split("/")[-1]
41
+
42
+ # Build ARN
43
+ queue_arn = attributes.get("QueueArn", f"arn:aws:sqs:{self.region}:{account_id}:{queue_name}")
44
+
45
+ # Get tags
46
+ tags = {}
47
+ try:
48
+ tag_response = client.list_queue_tags(QueueUrl=queue_url)
49
+ tags = tag_response.get("Tags", {})
50
+ except Exception as e:
51
+ self.logger.debug(f"Could not get tags for SQS queue {queue_name}: {e}")
52
+
53
+ # Convert CreatedTimestamp from epoch seconds to datetime
54
+ created_at = None
55
+ created_timestamp = attributes.get("CreatedTimestamp")
56
+ if created_timestamp:
57
+ try:
58
+ created_at = datetime.fromtimestamp(int(created_timestamp), tz=timezone.utc)
59
+ except (ValueError, OSError) as e:
60
+ self.logger.debug(f"Could not parse CreatedTimestamp for {queue_name}: {e}")
61
+
62
+ # Create resource
63
+ resource = Resource(
64
+ arn=queue_arn,
65
+ resource_type="AWS::SQS::Queue",
66
+ name=queue_name,
67
+ region=self.region,
68
+ tags=tags,
69
+ config_hash=compute_config_hash(attributes),
70
+ created_at=created_at,
71
+ raw_config=attributes,
72
+ )
73
+ resources.append(resource)
74
+
75
+ except Exception as e:
76
+ self.logger.debug(f"Could not get attributes for SQS queue {queue_url}: {e}")
77
+
78
+ except Exception as e:
79
+ self.logger.error(f"Error collecting SQS queues in {self.region}: {e}")
80
+
81
+ self.logger.debug(f"Collected {len(resources)} SQS queues in {self.region}")
82
+ return resources
@@ -0,0 +1,160 @@
1
+ """Systems Manager resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class SSMCollector(BaseResourceCollector):
11
+ """Collector for AWS Systems Manager resources (Parameter Store, Documents)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "ssm"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect Systems Manager resources.
19
+
20
+ Collects:
21
+ - Parameter Store parameters
22
+ - SSM Documents (custom documents only)
23
+
24
+ Returns:
25
+ List of Systems Manager resources
26
+ """
27
+ resources = []
28
+
29
+ # Collect Parameter Store parameters
30
+ resources.extend(self._collect_parameters())
31
+
32
+ # Collect SSM Documents
33
+ resources.extend(self._collect_documents())
34
+
35
+ self.logger.debug(f"Collected {len(resources)} Systems Manager resources in {self.region}")
36
+ return resources
37
+
38
+ def _collect_parameters(self) -> List[Resource]:
39
+ """Collect Parameter Store parameters.
40
+
41
+ Returns:
42
+ List of Parameter resources
43
+ """
44
+ resources = []
45
+ client = self._create_client()
46
+
47
+ try:
48
+ paginator = client.get_paginator("describe_parameters")
49
+ for page in paginator.paginate():
50
+ for param in page.get("Parameters", []):
51
+ param_name = param["Name"]
52
+ param_type = param["Type"]
53
+
54
+ # Build ARN
55
+ # Get account ID from session
56
+ sts_client = self.session.client("sts")
57
+ account_id = sts_client.get_caller_identity()["Account"]
58
+ arn = f"arn:aws:ssm:{self.region}:{account_id}:parameter{param_name}"
59
+
60
+ # Get tags
61
+ tags = {}
62
+ try:
63
+ tag_response = client.list_tags_for_resource(ResourceType="Parameter", ResourceId=param_name)
64
+ for tag in tag_response.get("TagList", []):
65
+ tags[tag["Key"]] = tag["Value"]
66
+ except Exception as e:
67
+ self.logger.debug(f"Could not get tags for parameter {param_name}: {e}")
68
+
69
+ # Get parameter details (metadata only, not the actual value for SecureString)
70
+ try:
71
+ # For SecureString, we don't want to expose the value
72
+ if param_type == "SecureString":
73
+ # Use describe_parameters data only
74
+ config = param
75
+ else:
76
+ # For String and StringList, we can get the value
77
+ param_details = client.get_parameter(Name=param_name, WithDecryption=False)
78
+ config = {
79
+ **param,
80
+ "Value": param_details["Parameter"].get("Value"),
81
+ }
82
+ except Exception as e:
83
+ self.logger.debug(f"Could not get details for parameter {param_name}: {e}")
84
+ config = param
85
+
86
+ # Extract last modified date as creation date proxy
87
+ created_at = param.get("LastModifiedDate")
88
+
89
+ # Create resource
90
+ resource = Resource(
91
+ arn=arn,
92
+ resource_type="AWS::SSM::Parameter",
93
+ name=param_name,
94
+ region=self.region,
95
+ tags=tags,
96
+ config_hash=compute_config_hash(config),
97
+ created_at=created_at,
98
+ raw_config=config,
99
+ )
100
+ resources.append(resource)
101
+
102
+ except Exception as e:
103
+ self.logger.error(f"Error collecting SSM parameters in {self.region}: {e}")
104
+
105
+ return resources
106
+
107
+ def _collect_documents(self) -> List[Resource]:
108
+ """Collect SSM Documents (custom documents only).
109
+
110
+ Returns:
111
+ List of SSM Document resources
112
+ """
113
+ resources = []
114
+ client = self._create_client()
115
+
116
+ try:
117
+ # Only collect custom documents (not AWS-owned)
118
+ paginator = client.get_paginator("list_documents")
119
+ for page in paginator.paginate(Filters=[{"Key": "Owner", "Values": ["Self"]}]): # Only user-owned documents
120
+ for doc in page.get("DocumentIdentifiers", []):
121
+ doc_name = doc["Name"]
122
+
123
+ # Get document details
124
+ try:
125
+ doc_details = client.describe_document(Name=doc_name)["Document"]
126
+
127
+ # Build ARN
128
+ arn = doc_details.get("DocumentArn", doc_name)
129
+
130
+ # Get tags
131
+ tags = {}
132
+ for tag in doc_details.get("Tags", []):
133
+ tags[tag["Key"]] = tag["Value"]
134
+
135
+ # Extract creation date
136
+ created_at = doc_details.get("CreatedDate")
137
+
138
+ # Create resource (without the actual document content to keep size manageable)
139
+ config = {k: v for k, v in doc_details.items() if k != "Content"}
140
+
141
+ resource = Resource(
142
+ arn=arn,
143
+ resource_type="AWS::SSM::Document",
144
+ name=doc_name,
145
+ region=self.region,
146
+ tags=tags,
147
+ config_hash=compute_config_hash(config),
148
+ created_at=created_at,
149
+ raw_config=config,
150
+ )
151
+ resources.append(resource)
152
+
153
+ except Exception as e:
154
+ self.logger.debug(f"Error processing document {doc_name}: {e}")
155
+ continue
156
+
157
+ except Exception as e:
158
+ self.logger.error(f"Error collecting SSM documents in {self.region}: {e}")
159
+
160
+ return resources
@@ -0,0 +1,74 @@
1
+ """Step Functions resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class StepFunctionsCollector(BaseResourceCollector):
11
+ """Collector for AWS Step Functions resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "stepfunctions"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect Step Functions state machines.
19
+
20
+ Returns:
21
+ List of Step Functions state machine resources
22
+ """
23
+ resources = []
24
+ client = self._create_client("stepfunctions")
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_state_machines")
28
+ for page in paginator.paginate():
29
+ for state_machine in page.get("stateMachines", []):
30
+ sm_arn = state_machine["stateMachineArn"]
31
+ sm_name = state_machine["name"]
32
+
33
+ try:
34
+ # Get detailed state machine info
35
+ sm_details = client.describe_state_machine(stateMachineArn=sm_arn)
36
+
37
+ # Get tags
38
+ tags = {}
39
+ try:
40
+ tag_response = client.list_tags_for_resource(resourceArn=sm_arn)
41
+ for tag in tag_response.get("tags", []):
42
+ tags[tag["key"]] = tag["value"]
43
+ except Exception as e:
44
+ self.logger.debug(f"Could not get tags for state machine {sm_name}: {e}")
45
+
46
+ # Extract creation date
47
+ created_at = sm_details.get("creationDate")
48
+
49
+ # Remove the definition for config hash (can be large)
50
+ # but keep key metadata
51
+ config = {k: v for k, v in sm_details.items() if k != "definition"}
52
+
53
+ # Create resource
54
+ resource = Resource(
55
+ arn=sm_arn,
56
+ resource_type="AWS::StepFunctions::StateMachine",
57
+ name=sm_name,
58
+ region=self.region,
59
+ tags=tags,
60
+ config_hash=compute_config_hash(config),
61
+ created_at=created_at,
62
+ raw_config=config,
63
+ )
64
+ resources.append(resource)
65
+
66
+ except Exception as e:
67
+ self.logger.debug(f"Error processing state machine {sm_name}: {e}")
68
+ continue
69
+
70
+ except Exception as e:
71
+ self.logger.error(f"Error collecting Step Functions state machines in {self.region}: {e}")
72
+
73
+ self.logger.debug(f"Collected {len(resources)} Step Functions state machines in {self.region}")
74
+ return resources
@@ -0,0 +1,79 @@
1
+ """VPC Endpoints resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class VPCEndpointsCollector(BaseResourceCollector):
11
+ """Collector for VPC Endpoints (Interface and Gateway endpoints)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "vpc-endpoints"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect VPC Endpoints.
19
+
20
+ Collects both Interface and Gateway VPC endpoints.
21
+
22
+ Returns:
23
+ List of VPC Endpoint resources
24
+ """
25
+ resources = []
26
+ client = self._create_client("ec2")
27
+
28
+ try:
29
+ paginator = client.get_paginator("describe_vpc_endpoints")
30
+ for page in paginator.paginate():
31
+ for endpoint in page.get("VpcEndpoints", []):
32
+ endpoint_id = endpoint["VpcEndpointId"]
33
+ service_name = endpoint.get("ServiceName", "unknown")
34
+ endpoint_type = endpoint.get("VpcEndpointType", "Unknown")
35
+
36
+ # Extract tags
37
+ tags = {}
38
+ for tag in endpoint.get("Tags", []):
39
+ tags[tag["Key"]] = tag["Value"]
40
+
41
+ # Get account ID for ARN
42
+ sts_client = self.session.client("sts")
43
+ account_id = sts_client.get_caller_identity()["Account"]
44
+
45
+ # Build ARN
46
+ arn = f"arn:aws:ec2:{self.region}:{account_id}:vpc-endpoint/{endpoint_id}"
47
+
48
+ # Extract creation date
49
+ created_at = endpoint.get("CreationTimestamp")
50
+
51
+ # Determine resource type based on endpoint type
52
+ if endpoint_type == "Interface":
53
+ resource_type = "AWS::EC2::VPCEndpoint::Interface"
54
+ elif endpoint_type == "Gateway":
55
+ resource_type = "AWS::EC2::VPCEndpoint::Gateway"
56
+ else:
57
+ resource_type = f"AWS::EC2::VPCEndpoint::{endpoint_type}"
58
+
59
+ # Use service name as the friendly name
60
+ name = f"{endpoint_id} ({service_name.split('.')[-1]})"
61
+
62
+ # Create resource
63
+ resource = Resource(
64
+ arn=arn,
65
+ resource_type=resource_type,
66
+ name=name,
67
+ region=self.region,
68
+ tags=tags,
69
+ config_hash=compute_config_hash(endpoint),
70
+ created_at=created_at,
71
+ raw_config=endpoint,
72
+ )
73
+ resources.append(resource)
74
+
75
+ except Exception as e:
76
+ self.logger.error(f"Error collecting VPC endpoints in {self.region}: {e}")
77
+
78
+ self.logger.debug(f"Collected {len(resources)} VPC endpoints in {self.region}")
79
+ return resources