iam-policy-validator 1.14.0__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 (106) hide show
  1. iam_policy_validator-1.14.0.dist-info/METADATA +782 -0
  2. iam_policy_validator-1.14.0.dist-info/RECORD +106 -0
  3. iam_policy_validator-1.14.0.dist-info/WHEEL +4 -0
  4. iam_policy_validator-1.14.0.dist-info/entry_points.txt +2 -0
  5. iam_policy_validator-1.14.0.dist-info/licenses/LICENSE +21 -0
  6. iam_validator/__init__.py +27 -0
  7. iam_validator/__main__.py +11 -0
  8. iam_validator/__version__.py +9 -0
  9. iam_validator/checks/__init__.py +45 -0
  10. iam_validator/checks/action_condition_enforcement.py +1442 -0
  11. iam_validator/checks/action_resource_matching.py +472 -0
  12. iam_validator/checks/action_validation.py +67 -0
  13. iam_validator/checks/condition_key_validation.py +88 -0
  14. iam_validator/checks/condition_type_mismatch.py +257 -0
  15. iam_validator/checks/full_wildcard.py +62 -0
  16. iam_validator/checks/mfa_condition_check.py +105 -0
  17. iam_validator/checks/policy_size.py +114 -0
  18. iam_validator/checks/policy_structure.py +556 -0
  19. iam_validator/checks/policy_type_validation.py +331 -0
  20. iam_validator/checks/principal_validation.py +708 -0
  21. iam_validator/checks/resource_validation.py +135 -0
  22. iam_validator/checks/sensitive_action.py +438 -0
  23. iam_validator/checks/service_wildcard.py +98 -0
  24. iam_validator/checks/set_operator_validation.py +153 -0
  25. iam_validator/checks/sid_uniqueness.py +146 -0
  26. iam_validator/checks/trust_policy_validation.py +509 -0
  27. iam_validator/checks/utils/__init__.py +17 -0
  28. iam_validator/checks/utils/action_parser.py +149 -0
  29. iam_validator/checks/utils/policy_level_checks.py +190 -0
  30. iam_validator/checks/utils/sensitive_action_matcher.py +293 -0
  31. iam_validator/checks/utils/wildcard_expansion.py +86 -0
  32. iam_validator/checks/wildcard_action.py +58 -0
  33. iam_validator/checks/wildcard_resource.py +374 -0
  34. iam_validator/commands/__init__.py +31 -0
  35. iam_validator/commands/analyze.py +549 -0
  36. iam_validator/commands/base.py +48 -0
  37. iam_validator/commands/cache.py +393 -0
  38. iam_validator/commands/completion.py +471 -0
  39. iam_validator/commands/download_services.py +255 -0
  40. iam_validator/commands/post_to_pr.py +86 -0
  41. iam_validator/commands/query.py +485 -0
  42. iam_validator/commands/validate.py +830 -0
  43. iam_validator/core/__init__.py +13 -0
  44. iam_validator/core/access_analyzer.py +671 -0
  45. iam_validator/core/access_analyzer_report.py +640 -0
  46. iam_validator/core/aws_fetcher.py +29 -0
  47. iam_validator/core/aws_service/__init__.py +21 -0
  48. iam_validator/core/aws_service/cache.py +108 -0
  49. iam_validator/core/aws_service/client.py +205 -0
  50. iam_validator/core/aws_service/fetcher.py +641 -0
  51. iam_validator/core/aws_service/parsers.py +149 -0
  52. iam_validator/core/aws_service/patterns.py +51 -0
  53. iam_validator/core/aws_service/storage.py +291 -0
  54. iam_validator/core/aws_service/validators.py +380 -0
  55. iam_validator/core/check_registry.py +679 -0
  56. iam_validator/core/cli.py +134 -0
  57. iam_validator/core/codeowners.py +245 -0
  58. iam_validator/core/condition_validators.py +626 -0
  59. iam_validator/core/config/__init__.py +81 -0
  60. iam_validator/core/config/aws_api.py +35 -0
  61. iam_validator/core/config/aws_global_conditions.py +160 -0
  62. iam_validator/core/config/category_suggestions.py +181 -0
  63. iam_validator/core/config/check_documentation.py +390 -0
  64. iam_validator/core/config/condition_requirements.py +258 -0
  65. iam_validator/core/config/config_loader.py +670 -0
  66. iam_validator/core/config/defaults.py +739 -0
  67. iam_validator/core/config/principal_requirements.py +421 -0
  68. iam_validator/core/config/sensitive_actions.py +672 -0
  69. iam_validator/core/config/service_principals.py +132 -0
  70. iam_validator/core/config/wildcards.py +127 -0
  71. iam_validator/core/constants.py +149 -0
  72. iam_validator/core/diff_parser.py +325 -0
  73. iam_validator/core/finding_fingerprint.py +131 -0
  74. iam_validator/core/formatters/__init__.py +27 -0
  75. iam_validator/core/formatters/base.py +147 -0
  76. iam_validator/core/formatters/console.py +68 -0
  77. iam_validator/core/formatters/csv.py +171 -0
  78. iam_validator/core/formatters/enhanced.py +481 -0
  79. iam_validator/core/formatters/html.py +672 -0
  80. iam_validator/core/formatters/json.py +33 -0
  81. iam_validator/core/formatters/markdown.py +64 -0
  82. iam_validator/core/formatters/sarif.py +251 -0
  83. iam_validator/core/ignore_patterns.py +297 -0
  84. iam_validator/core/ignore_processor.py +309 -0
  85. iam_validator/core/ignored_findings.py +400 -0
  86. iam_validator/core/label_manager.py +197 -0
  87. iam_validator/core/models.py +404 -0
  88. iam_validator/core/policy_checks.py +220 -0
  89. iam_validator/core/policy_loader.py +785 -0
  90. iam_validator/core/pr_commenter.py +780 -0
  91. iam_validator/core/report.py +942 -0
  92. iam_validator/integrations/__init__.py +28 -0
  93. iam_validator/integrations/github_integration.py +1821 -0
  94. iam_validator/integrations/ms_teams.py +442 -0
  95. iam_validator/sdk/__init__.py +220 -0
  96. iam_validator/sdk/arn_matching.py +382 -0
  97. iam_validator/sdk/context.py +222 -0
  98. iam_validator/sdk/exceptions.py +48 -0
  99. iam_validator/sdk/helpers.py +177 -0
  100. iam_validator/sdk/policy_utils.py +451 -0
  101. iam_validator/sdk/query_utils.py +454 -0
  102. iam_validator/sdk/shortcuts.py +283 -0
  103. iam_validator/utils/__init__.py +35 -0
  104. iam_validator/utils/cache.py +105 -0
  105. iam_validator/utils/regex.py +205 -0
  106. iam_validator/utils/terminal.py +22 -0
@@ -0,0 +1,255 @@
1
+ """Download AWS service definitions command."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ import logging
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+
10
+ import httpx
11
+ from rich.console import Console
12
+ from rich.progress import BarColumn, Progress, TaskID, TextColumn, TimeRemainingColumn
13
+
14
+ from iam_validator.commands.base import Command
15
+ from iam_validator.core.config import AWS_SERVICE_REFERENCE_BASE_URL
16
+
17
+ logger = logging.getLogger(__name__)
18
+ console = Console()
19
+
20
+ BASE_URL = AWS_SERVICE_REFERENCE_BASE_URL
21
+ DEFAULT_OUTPUT_DIR = Path("aws_services")
22
+
23
+
24
+ class DownloadServicesCommand(Command):
25
+ """Download all AWS service definition JSON files."""
26
+
27
+ @property
28
+ def name(self) -> str:
29
+ return "sync-services"
30
+
31
+ @property
32
+ def help(self) -> str:
33
+ return "Sync/download all AWS service definitions for offline use"
34
+
35
+ @property
36
+ def epilog(self) -> str:
37
+ return """
38
+ Examples:
39
+ # Sync all AWS service definitions to default directory (aws_services/)
40
+ iam-validator sync-services
41
+
42
+ # Sync to a custom directory
43
+ iam-validator sync-services --output-dir /path/to/backup
44
+
45
+ # Limit concurrent downloads
46
+ iam-validator sync-services --max-concurrent 5
47
+
48
+ # Enable verbose output
49
+ iam-validator sync-services --log-level debug
50
+
51
+ Directory structure:
52
+ aws_services/
53
+ _manifest.json # Metadata about the download
54
+ _services.json # List of all services
55
+ s3.json # Individual service definitions
56
+ ec2.json
57
+ iam.json
58
+ ...
59
+
60
+ This command is useful for:
61
+ - Creating offline backups of AWS service definitions
62
+ - Avoiding API rate limiting during development
63
+ - Ensuring consistent service definitions across environments
64
+ """
65
+
66
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
67
+ """Add sync-services command arguments."""
68
+ parser.add_argument(
69
+ "--output-dir",
70
+ type=Path,
71
+ default=DEFAULT_OUTPUT_DIR,
72
+ help=f"Output directory for downloaded files (default: {DEFAULT_OUTPUT_DIR})",
73
+ )
74
+
75
+ parser.add_argument(
76
+ "--max-concurrent",
77
+ type=int,
78
+ default=10,
79
+ help="Maximum number of concurrent downloads (default: 10)",
80
+ )
81
+
82
+ async def execute(self, args: argparse.Namespace) -> int:
83
+ """Execute the sync-services command."""
84
+ output_dir = args.output_dir
85
+ max_concurrent = args.max_concurrent
86
+
87
+ try:
88
+ await self._download_all_services(output_dir, max_concurrent)
89
+ return 0
90
+ except Exception as e:
91
+ console.print(f"[red]Error:[/red] {e}")
92
+ logger.error(f"Download failed: {e}", exc_info=True)
93
+ return 1
94
+
95
+ async def _download_services_list(self, client: httpx.AsyncClient) -> list[dict]:
96
+ """Download the list of all AWS services.
97
+
98
+ Args:
99
+ client: HTTP client for making requests
100
+
101
+ Returns:
102
+ List of service info dictionaries
103
+ """
104
+ console.print(f"[cyan]Fetching services list from {BASE_URL}...[/cyan]")
105
+
106
+ try:
107
+ response = await client.get(BASE_URL, timeout=30.0)
108
+ response.raise_for_status()
109
+ services = response.json()
110
+
111
+ console.print(f"[green]✓[/green] Found {len(services)} AWS services")
112
+ return services
113
+ except Exception as e:
114
+ logger.error(f"Failed to fetch services list: {e}")
115
+ raise
116
+
117
+ async def _download_service_detail(
118
+ self,
119
+ client: httpx.AsyncClient,
120
+ service_name: str,
121
+ service_url: str,
122
+ semaphore: asyncio.Semaphore,
123
+ progress: Progress,
124
+ task_id: TaskID,
125
+ ) -> tuple[str, dict | None]:
126
+ """Download detailed JSON for a single service.
127
+
128
+ Args:
129
+ client: HTTP client for making requests
130
+ service_name: Name of the service
131
+ service_url: URL to fetch service details
132
+ semaphore: Semaphore to limit concurrent requests
133
+ progress: Progress bar instance
134
+ task_id: Progress task ID
135
+
136
+ Returns:
137
+ Tuple of (service_name, service_data) or (service_name, None) if failed
138
+ """
139
+ async with semaphore:
140
+ try:
141
+ logger.debug(f"Downloading {service_name}...")
142
+ response = await client.get(service_url, timeout=30.0)
143
+ response.raise_for_status()
144
+ data = response.json()
145
+ logger.debug(f"✓ Downloaded {service_name}")
146
+ progress.update(task_id, advance=1)
147
+ return service_name, data
148
+ except Exception as e:
149
+ logger.error(f"✗ Failed to download {service_name}: {e}")
150
+ progress.update(task_id, advance=1)
151
+ return service_name, None
152
+
153
+ async def _download_all_services(self, output_dir: Path, max_concurrent: int = 10) -> None:
154
+ """Download all AWS service definitions.
155
+
156
+ Args:
157
+ output_dir: Directory to save the downloaded files
158
+ max_concurrent: Maximum number of concurrent downloads
159
+ """
160
+ # Create output directory
161
+ output_dir.mkdir(parents=True, exist_ok=True)
162
+ console.print(f"[cyan]Output directory:[/cyan] {output_dir.absolute()}\n")
163
+
164
+ # Create HTTP client with connection pooling
165
+ async with httpx.AsyncClient(
166
+ limits=httpx.Limits(max_connections=max_concurrent, max_keepalive_connections=5),
167
+ timeout=httpx.Timeout(30.0),
168
+ ) as client:
169
+ # Download services list
170
+ services = await self._download_services_list(client)
171
+
172
+ # Save services list (underscore prefix for easy discovery at top of directory)
173
+ services_file = output_dir / "_services.json"
174
+ with open(services_file, "w") as f:
175
+ json.dump(services, f, indent=2)
176
+ console.print(f"[green]✓[/green] Saved services list to {services_file}\n")
177
+
178
+ # Download all service details with rate limiting and progress bar
179
+ semaphore = asyncio.Semaphore(max_concurrent)
180
+ tasks = []
181
+
182
+ # Set up progress bar
183
+ with Progress(
184
+ TextColumn("[progress.description]{task.description}"),
185
+ BarColumn(),
186
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
187
+ TextColumn("({task.completed}/{task.total})"),
188
+ TimeRemainingColumn(),
189
+ console=console,
190
+ ) as progress:
191
+ task_id = progress.add_task(
192
+ "[cyan]Downloading service definitions...", total=len(services)
193
+ )
194
+
195
+ for item in services:
196
+ service_name = item.get("service")
197
+ service_url = item.get("url")
198
+
199
+ if service_name and service_url:
200
+ task = self._download_service_detail(
201
+ client, service_name, service_url, semaphore, progress, task_id
202
+ )
203
+ tasks.append(task)
204
+
205
+ # Download all services concurrently
206
+ results = await asyncio.gather(*tasks)
207
+
208
+ # Save individual service files
209
+ successful = 0
210
+ failed = 0
211
+
212
+ console.print("\n[cyan]Saving service definitions...[/cyan]")
213
+
214
+ for service_name, data in results:
215
+ if data is not None:
216
+ # Normalize filename (lowercase, safe characters)
217
+ filename = f"{service_name.lower().replace(' ', '_')}.json"
218
+ service_file = output_dir / filename
219
+
220
+ with open(service_file, "w") as f:
221
+ json.dump(data, f, indent=2)
222
+
223
+ successful += 1
224
+ else:
225
+ failed += 1
226
+
227
+ # Create manifest with metadata
228
+ manifest = {
229
+ "download_date": datetime.now(timezone.utc).isoformat(),
230
+ "total_services": len(services),
231
+ "successful_downloads": successful,
232
+ "failed_downloads": failed,
233
+ "base_url": BASE_URL,
234
+ }
235
+
236
+ manifest_file = output_dir / "_manifest.json"
237
+ with open(manifest_file, "w") as f:
238
+ json.dump(manifest, f, indent=2)
239
+
240
+ # Print summary
241
+ console.print(f"\n{'=' * 60}")
242
+ console.print("[bold cyan]Download Summary:[/bold cyan]")
243
+ console.print(f" Total services: {len(services)}")
244
+ console.print(f" [green]Successful:[/green] {successful}")
245
+ if failed > 0:
246
+ console.print(f" [red]Failed:[/red] {failed}")
247
+ console.print(f" Output directory: {output_dir.absolute()}")
248
+ console.print(f" Manifest: {manifest_file}")
249
+ console.print(f"{'=' * 60}")
250
+
251
+ if failed > 0:
252
+ console.print(
253
+ "\n[yellow]Warning:[/yellow] Some services failed to download. "
254
+ "Check the logs for details."
255
+ )
@@ -0,0 +1,86 @@
1
+ """Post-to-PR command for IAM Policy Validator."""
2
+
3
+ import argparse
4
+
5
+ from iam_validator.commands.base import Command
6
+ from iam_validator.core.pr_commenter import post_report_to_pr
7
+
8
+
9
+ class PostToPRCommand(Command):
10
+ """Command to post a validation report to a GitHub PR."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ return "post-to-pr"
15
+
16
+ @property
17
+ def help(self) -> str:
18
+ return "Post a JSON report to a GitHub PR"
19
+
20
+ @property
21
+ def epilog(self) -> str:
22
+ return """
23
+ Examples:
24
+ # Post report with line comments
25
+ iam-validator post-to-pr --report report.json
26
+
27
+ # Post only summary comment
28
+ iam-validator post-to-pr --report report.json --no-review
29
+
30
+ # Post only line comments (no summary)
31
+ iam-validator post-to-pr --report report.json --no-summary
32
+ """
33
+
34
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
35
+ """Add post-to-pr command arguments."""
36
+ parser.add_argument(
37
+ "--report",
38
+ "-r",
39
+ required=True,
40
+ help="Path to JSON report file",
41
+ )
42
+
43
+ parser.add_argument(
44
+ "--create-review",
45
+ action="store_true",
46
+ default=True,
47
+ help="Create line-specific review comments (default: True)",
48
+ )
49
+
50
+ parser.add_argument(
51
+ "--no-review",
52
+ action="store_false",
53
+ dest="create_review",
54
+ help="Don't create line-specific review comments",
55
+ )
56
+
57
+ parser.add_argument(
58
+ "--add-summary",
59
+ action="store_true",
60
+ default=True,
61
+ help="Add summary comment (default: True)",
62
+ )
63
+
64
+ parser.add_argument(
65
+ "--no-summary",
66
+ action="store_false",
67
+ dest="add_summary",
68
+ help="Don't add summary comment",
69
+ )
70
+
71
+ parser.add_argument(
72
+ "--config",
73
+ "-c",
74
+ help="Path to configuration file (for fail_on_severity setting)",
75
+ )
76
+
77
+ async def execute(self, args: argparse.Namespace) -> int:
78
+ """Execute the post-to-pr command."""
79
+ success = await post_report_to_pr(
80
+ args.report,
81
+ create_review=args.create_review,
82
+ add_summary=args.add_summary,
83
+ config_path=getattr(args, "config", None),
84
+ )
85
+
86
+ return 0 if success else 1