aws-inventory-manager 0.17.12__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.
Files changed (152) hide show
  1. aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
  3. aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
  4. aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.17.12.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 +4046 -0
  15. src/cloudtrail/__init__.py +5 -0
  16. src/cloudtrail/query.py +642 -0
  17. src/config_service/__init__.py +21 -0
  18. src/config_service/collector.py +346 -0
  19. src/config_service/detector.py +256 -0
  20. src/config_service/resource_type_mapping.py +328 -0
  21. src/cost/__init__.py +5 -0
  22. src/cost/analyzer.py +226 -0
  23. src/cost/explorer.py +209 -0
  24. src/cost/reporter.py +237 -0
  25. src/delta/__init__.py +5 -0
  26. src/delta/calculator.py +206 -0
  27. src/delta/differ.py +185 -0
  28. src/delta/formatters.py +272 -0
  29. src/delta/models.py +154 -0
  30. src/delta/reporter.py +234 -0
  31. src/matching/__init__.py +6 -0
  32. src/matching/config.py +52 -0
  33. src/matching/normalizer.py +450 -0
  34. src/matching/prompts.py +33 -0
  35. src/models/__init__.py +21 -0
  36. src/models/config_diff.py +135 -0
  37. src/models/cost_report.py +87 -0
  38. src/models/deletion_operation.py +104 -0
  39. src/models/deletion_record.py +97 -0
  40. src/models/delta_report.py +122 -0
  41. src/models/efs_resource.py +80 -0
  42. src/models/elasticache_resource.py +90 -0
  43. src/models/group.py +318 -0
  44. src/models/inventory.py +133 -0
  45. src/models/protection_rule.py +123 -0
  46. src/models/report.py +288 -0
  47. src/models/resource.py +111 -0
  48. src/models/security_finding.py +102 -0
  49. src/models/snapshot.py +122 -0
  50. src/restore/__init__.py +20 -0
  51. src/restore/audit.py +175 -0
  52. src/restore/cleaner.py +461 -0
  53. src/restore/config.py +209 -0
  54. src/restore/deleter.py +976 -0
  55. src/restore/dependency.py +254 -0
  56. src/restore/safety.py +115 -0
  57. src/security/__init__.py +0 -0
  58. src/security/checks/__init__.py +0 -0
  59. src/security/checks/base.py +56 -0
  60. src/security/checks/ec2_checks.py +88 -0
  61. src/security/checks/elasticache_checks.py +149 -0
  62. src/security/checks/iam_checks.py +102 -0
  63. src/security/checks/rds_checks.py +140 -0
  64. src/security/checks/s3_checks.py +95 -0
  65. src/security/checks/secrets_checks.py +96 -0
  66. src/security/checks/sg_checks.py +142 -0
  67. src/security/cis_mapper.py +97 -0
  68. src/security/models.py +53 -0
  69. src/security/reporter.py +174 -0
  70. src/security/scanner.py +87 -0
  71. src/snapshot/__init__.py +6 -0
  72. src/snapshot/capturer.py +453 -0
  73. src/snapshot/filter.py +259 -0
  74. src/snapshot/inventory_storage.py +236 -0
  75. src/snapshot/report_formatter.py +250 -0
  76. src/snapshot/reporter.py +189 -0
  77. src/snapshot/resource_collectors/__init__.py +5 -0
  78. src/snapshot/resource_collectors/apigateway.py +140 -0
  79. src/snapshot/resource_collectors/backup.py +136 -0
  80. src/snapshot/resource_collectors/base.py +81 -0
  81. src/snapshot/resource_collectors/cloudformation.py +55 -0
  82. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  83. src/snapshot/resource_collectors/codebuild.py +69 -0
  84. src/snapshot/resource_collectors/codepipeline.py +82 -0
  85. src/snapshot/resource_collectors/dynamodb.py +65 -0
  86. src/snapshot/resource_collectors/ec2.py +240 -0
  87. src/snapshot/resource_collectors/ecs.py +215 -0
  88. src/snapshot/resource_collectors/efs_collector.py +102 -0
  89. src/snapshot/resource_collectors/eks.py +200 -0
  90. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  91. src/snapshot/resource_collectors/elb.py +126 -0
  92. src/snapshot/resource_collectors/eventbridge.py +156 -0
  93. src/snapshot/resource_collectors/glue.py +199 -0
  94. src/snapshot/resource_collectors/iam.py +188 -0
  95. src/snapshot/resource_collectors/kms.py +111 -0
  96. src/snapshot/resource_collectors/lambda_func.py +139 -0
  97. src/snapshot/resource_collectors/rds.py +109 -0
  98. src/snapshot/resource_collectors/route53.py +86 -0
  99. src/snapshot/resource_collectors/s3.py +105 -0
  100. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  101. src/snapshot/resource_collectors/sns.py +68 -0
  102. src/snapshot/resource_collectors/sqs.py +82 -0
  103. src/snapshot/resource_collectors/ssm.py +160 -0
  104. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  105. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  106. src/snapshot/resource_collectors/waf.py +159 -0
  107. src/snapshot/storage.py +351 -0
  108. src/storage/__init__.py +21 -0
  109. src/storage/audit_store.py +419 -0
  110. src/storage/database.py +294 -0
  111. src/storage/group_store.py +763 -0
  112. src/storage/inventory_store.py +320 -0
  113. src/storage/resource_store.py +416 -0
  114. src/storage/schema.py +339 -0
  115. src/storage/snapshot_store.py +363 -0
  116. src/utils/__init__.py +12 -0
  117. src/utils/export.py +305 -0
  118. src/utils/hash.py +60 -0
  119. src/utils/logging.py +63 -0
  120. src/utils/pagination.py +41 -0
  121. src/utils/paths.py +51 -0
  122. src/utils/progress.py +41 -0
  123. src/utils/unsupported_resources.py +306 -0
  124. src/web/__init__.py +5 -0
  125. src/web/app.py +97 -0
  126. src/web/dependencies.py +69 -0
  127. src/web/routes/__init__.py +1 -0
  128. src/web/routes/api/__init__.py +18 -0
  129. src/web/routes/api/charts.py +156 -0
  130. src/web/routes/api/cleanup.py +186 -0
  131. src/web/routes/api/filters.py +253 -0
  132. src/web/routes/api/groups.py +305 -0
  133. src/web/routes/api/inventories.py +80 -0
  134. src/web/routes/api/queries.py +202 -0
  135. src/web/routes/api/resources.py +393 -0
  136. src/web/routes/api/snapshots.py +314 -0
  137. src/web/routes/api/views.py +260 -0
  138. src/web/routes/pages.py +198 -0
  139. src/web/services/__init__.py +1 -0
  140. src/web/templates/base.html +955 -0
  141. src/web/templates/components/navbar.html +31 -0
  142. src/web/templates/components/sidebar.html +104 -0
  143. src/web/templates/pages/audit_logs.html +86 -0
  144. src/web/templates/pages/cleanup.html +279 -0
  145. src/web/templates/pages/dashboard.html +227 -0
  146. src/web/templates/pages/diff.html +175 -0
  147. src/web/templates/pages/error.html +30 -0
  148. src/web/templates/pages/groups.html +721 -0
  149. src/web/templates/pages/queries.html +246 -0
  150. src/web/templates/pages/resources.html +2429 -0
  151. src/web/templates/pages/snapshot_detail.html +271 -0
  152. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,177 @@
1
+ """Rate limiter utility using token bucket algorithm."""
2
+
3
+ import logging
4
+ import time
5
+ from threading import Lock
6
+ from typing import Dict, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ # Service-specific rate limits (calls per second)
12
+ # Based on AWS API throttling limits
13
+ SERVICE_RATE_LIMITS: Dict[str, float] = {
14
+ "iam": 5.0, # IAM has strict rate limits (global service)
15
+ "cloudformation": 2.0, # CloudFormation is particularly slow
16
+ "sts": 10.0, # STS is also rate-limited
17
+ "default": 10.0, # Conservative default for other services
18
+ }
19
+
20
+
21
+ class RateLimiter:
22
+ """Token bucket rate limiter for controlling API call frequency.
23
+
24
+ This prevents hitting AWS API rate limits by throttling client-side
25
+ before the request is even made.
26
+ """
27
+
28
+ def __init__(self, rate: float):
29
+ """Initialize rate limiter.
30
+
31
+ Args:
32
+ rate: Maximum number of calls per second
33
+ """
34
+ self.rate = rate
35
+ self.tokens = rate
36
+ self.last_update = time.time()
37
+ self.lock = Lock()
38
+
39
+ logger.debug(f"Initialized rate limiter with rate {rate} calls/sec")
40
+
41
+ def acquire(self, blocking: bool = True) -> bool:
42
+ """Acquire permission to make an API call.
43
+
44
+ This method will block until a token is available (if blocking=True)
45
+ or return immediately (if blocking=False).
46
+
47
+ Args:
48
+ blocking: If True, wait until token available. If False, return immediately.
49
+
50
+ Returns:
51
+ True if token acquired, False if blocking=False and no token available
52
+ """
53
+ with self.lock:
54
+ now = time.time()
55
+ elapsed = now - self.last_update
56
+
57
+ # Refill tokens based on elapsed time
58
+ self.tokens = min(self.rate, self.tokens + elapsed * self.rate)
59
+ self.last_update = now
60
+
61
+ if self.tokens >= 1:
62
+ # Token available
63
+ self.tokens -= 1
64
+ return True
65
+ else:
66
+ # No token available
67
+ if not blocking:
68
+ return False
69
+
70
+ # Calculate how long to sleep
71
+ sleep_time = (1 - self.tokens) / self.rate
72
+ logger.debug(f"Rate limiter sleeping for {sleep_time:.3f}s")
73
+
74
+ # Sleep outside the lock to allow other threads
75
+ time.sleep(sleep_time)
76
+
77
+ # Acquire token after sleeping
78
+ with self.lock:
79
+ self.tokens = 0
80
+ self.last_update = time.time()
81
+ return True
82
+
83
+ def try_acquire(self) -> bool:
84
+ """Try to acquire a token without blocking.
85
+
86
+ Returns:
87
+ True if token acquired, False otherwise
88
+ """
89
+ return self.acquire(blocking=False)
90
+
91
+
92
+ class ServiceRateLimiter:
93
+ """Manages rate limiters for different AWS services."""
94
+
95
+ def __init__(self, rate_limits: Optional[Dict[str, float]] = None):
96
+ """Initialize service rate limiter.
97
+
98
+ Args:
99
+ rate_limits: Dictionary mapping service names to rates (optional)
100
+ """
101
+ self.rate_limits = rate_limits or SERVICE_RATE_LIMITS
102
+ self._limiters: Dict[str, RateLimiter] = {}
103
+ self._lock = Lock()
104
+
105
+ def get_limiter(self, service_name: str) -> RateLimiter:
106
+ """Get or create a rate limiter for a service.
107
+
108
+ Args:
109
+ service_name: AWS service name (e.g., 'iam', 'ec2')
110
+
111
+ Returns:
112
+ RateLimiter instance for the service
113
+ """
114
+ with self._lock:
115
+ if service_name not in self._limiters:
116
+ rate = self.rate_limits.get(service_name, self.rate_limits["default"])
117
+ self._limiters[service_name] = RateLimiter(rate)
118
+ logger.debug(f"Created rate limiter for {service_name} ({rate} calls/sec)")
119
+
120
+ return self._limiters[service_name]
121
+
122
+ def acquire(self, service_name: str, blocking: bool = True) -> bool:
123
+ """Acquire permission to call an AWS service API.
124
+
125
+ Args:
126
+ service_name: AWS service name
127
+ blocking: Whether to block until token available
128
+
129
+ Returns:
130
+ True if token acquired
131
+ """
132
+ limiter = self.get_limiter(service_name)
133
+ return limiter.acquire(blocking=blocking)
134
+
135
+ def try_acquire(self, service_name: str) -> bool:
136
+ """Try to acquire permission without blocking.
137
+
138
+ Args:
139
+ service_name: AWS service name
140
+
141
+ Returns:
142
+ True if token acquired, False otherwise
143
+ """
144
+ return self.acquire(service_name, blocking=False)
145
+
146
+
147
+ # Global service rate limiter instance
148
+ _global_limiter: Optional[ServiceRateLimiter] = None
149
+
150
+
151
+ def get_global_rate_limiter() -> ServiceRateLimiter:
152
+ """Get the global service rate limiter instance.
153
+
154
+ Returns:
155
+ Global ServiceRateLimiter instance
156
+ """
157
+ global _global_limiter
158
+ if _global_limiter is None:
159
+ _global_limiter = ServiceRateLimiter()
160
+ return _global_limiter
161
+
162
+
163
+ def rate_limited_call(service_name: str, func, *args, **kwargs): # type: ignore[no-untyped-def]
164
+ """Execute a function with rate limiting applied.
165
+
166
+ Args:
167
+ service_name: AWS service name for rate limiting
168
+ func: Function to call
169
+ *args: Positional arguments to pass to func
170
+ **kwargs: Keyword arguments to pass to func
171
+
172
+ Returns:
173
+ Result of func(*args, **kwargs)
174
+ """
175
+ limiter = get_global_rate_limiter()
176
+ limiter.acquire(service_name)
177
+ return func(*args, **kwargs)
src/cli/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """CLI module for AWS Baseline Snapshot tool."""
2
+
3
+ __all__ = ["app", "cli_main"]
4
+
5
+
6
+ def __getattr__(name: str):
7
+ """Lazy import to avoid RuntimeWarning when running with python -m."""
8
+ if name in ("app", "cli_main"):
9
+ from .main import app, cli_main
10
+
11
+ return app if name == "app" else cli_main
12
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
src/cli/config.py ADDED
@@ -0,0 +1,130 @@
1
+ """Configuration loader for CLI."""
2
+
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import yaml
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class Config:
14
+ """Application configuration manager."""
15
+
16
+ def __init__(self) -> None:
17
+ """Initialize configuration with defaults."""
18
+ self.snapshot_dir: str = ".snapshots"
19
+ self.storage_path: Optional[str] = None # Runtime override for snapshot storage path
20
+ self.regions: List[str] = [] # Empty means all enabled regions
21
+ self.resource_types: List[str] = [] # Empty means all supported types
22
+ self.aws_profile: Optional[str] = None
23
+ self.parallel_workers: int = 10
24
+ self.auto_compress_mb: int = 10
25
+ self.log_level: str = "INFO"
26
+
27
+ @classmethod
28
+ def load(cls, config_file: Optional[str] = None) -> "Config":
29
+ """Load configuration from file and environment variables.
30
+
31
+ Priority (highest to lowest):
32
+ 1. Environment variables
33
+ 2. Config file
34
+ 3. Defaults
35
+
36
+ Args:
37
+ config_file: Path to config file (optional)
38
+
39
+ Returns:
40
+ Config instance
41
+ """
42
+ config = cls()
43
+
44
+ # Try to load from config file
45
+ config_path = None
46
+ if config_file:
47
+ config_path = Path(config_file)
48
+ else:
49
+ # Try current directory first
50
+ config_path = Path(".aws-baseline.yaml")
51
+ if not config_path.exists():
52
+ # Try home directory
53
+ config_path = Path.home() / ".aws-baseline.yaml"
54
+
55
+ if config_path and config_path.exists():
56
+ config._load_from_file(config_path)
57
+
58
+ # Override with environment variables
59
+ config._load_from_env()
60
+
61
+ return config
62
+
63
+ def _load_from_file(self, config_path: Path) -> None:
64
+ """Load configuration from YAML file.
65
+
66
+ Args:
67
+ config_path: Path to config file
68
+ """
69
+ try:
70
+ with open(config_path, "r") as f:
71
+ data = yaml.safe_load(f)
72
+
73
+ if not data:
74
+ return
75
+
76
+ self.snapshot_dir = data.get("snapshot_dir", self.snapshot_dir)
77
+ self.regions = data.get("regions", self.regions)
78
+ self.aws_profile = data.get("aws_profile", self.aws_profile)
79
+ self.parallel_workers = data.get("parallel_workers", self.parallel_workers)
80
+ self.auto_compress_mb = data.get("auto_compress_mb", self.auto_compress_mb)
81
+
82
+ # Handle resource_types (include/exclude)
83
+ resource_types_config = data.get("resource_types", {})
84
+ if isinstance(resource_types_config, dict):
85
+ self.resource_types = resource_types_config.get("include", [])
86
+ elif isinstance(resource_types_config, list):
87
+ self.resource_types = resource_types_config
88
+
89
+ logger.info(f"Loaded configuration from {config_path}")
90
+
91
+ except Exception as e:
92
+ logger.warning(f"Could not load config file {config_path}: {e}")
93
+
94
+ def _load_from_env(self) -> None:
95
+ """Load configuration from environment variables."""
96
+ # AWS_BASELINE_SNAPSHOT_DIR
97
+ snapshot_dir = os.getenv("AWS_BASELINE_SNAPSHOT_DIR")
98
+ if snapshot_dir:
99
+ self.snapshot_dir = snapshot_dir
100
+
101
+ # AWS_BASELINE_LOG_LEVEL
102
+ log_level = os.getenv("AWS_BASELINE_LOG_LEVEL")
103
+ if log_level:
104
+ self.log_level = log_level
105
+
106
+ # AWS_PROFILE
107
+ aws_profile = os.getenv("AWS_PROFILE")
108
+ if aws_profile:
109
+ self.aws_profile = aws_profile
110
+
111
+ # AWS_REGION (single region from env)
112
+ aws_region = os.getenv("AWS_REGION")
113
+ if aws_region:
114
+ self.regions = [aws_region]
115
+
116
+ def to_dict(self) -> Dict[str, Any]:
117
+ """Convert configuration to dictionary.
118
+
119
+ Returns:
120
+ Configuration as dictionary
121
+ """
122
+ return {
123
+ "snapshot_dir": self.snapshot_dir,
124
+ "regions": self.regions,
125
+ "resource_types": self.resource_types,
126
+ "aws_profile": self.aws_profile,
127
+ "parallel_workers": self.parallel_workers,
128
+ "auto_compress_mb": self.auto_compress_mb,
129
+ "log_level": self.log_level,
130
+ }