iam-policy-validator 1.7.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.

Potentially problematic release.


This version of iam-policy-validator might be problematic. Click here for more details.

Files changed (83) hide show
  1. iam_policy_validator-1.7.0.dist-info/METADATA +1057 -0
  2. iam_policy_validator-1.7.0.dist-info/RECORD +83 -0
  3. iam_policy_validator-1.7.0.dist-info/WHEEL +4 -0
  4. iam_policy_validator-1.7.0.dist-info/entry_points.txt +2 -0
  5. iam_policy_validator-1.7.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 +7 -0
  9. iam_validator/checks/__init__.py +43 -0
  10. iam_validator/checks/action_condition_enforcement.py +884 -0
  11. iam_validator/checks/action_resource_matching.py +441 -0
  12. iam_validator/checks/action_validation.py +72 -0
  13. iam_validator/checks/condition_key_validation.py +92 -0
  14. iam_validator/checks/condition_type_mismatch.py +259 -0
  15. iam_validator/checks/full_wildcard.py +71 -0
  16. iam_validator/checks/mfa_condition_check.py +112 -0
  17. iam_validator/checks/policy_size.py +147 -0
  18. iam_validator/checks/policy_type_validation.py +305 -0
  19. iam_validator/checks/principal_validation.py +776 -0
  20. iam_validator/checks/resource_validation.py +138 -0
  21. iam_validator/checks/sensitive_action.py +254 -0
  22. iam_validator/checks/service_wildcard.py +107 -0
  23. iam_validator/checks/set_operator_validation.py +157 -0
  24. iam_validator/checks/sid_uniqueness.py +170 -0
  25. iam_validator/checks/utils/__init__.py +1 -0
  26. iam_validator/checks/utils/policy_level_checks.py +143 -0
  27. iam_validator/checks/utils/sensitive_action_matcher.py +294 -0
  28. iam_validator/checks/utils/wildcard_expansion.py +87 -0
  29. iam_validator/checks/wildcard_action.py +67 -0
  30. iam_validator/checks/wildcard_resource.py +135 -0
  31. iam_validator/commands/__init__.py +25 -0
  32. iam_validator/commands/analyze.py +531 -0
  33. iam_validator/commands/base.py +48 -0
  34. iam_validator/commands/cache.py +392 -0
  35. iam_validator/commands/download_services.py +255 -0
  36. iam_validator/commands/post_to_pr.py +86 -0
  37. iam_validator/commands/validate.py +600 -0
  38. iam_validator/core/__init__.py +14 -0
  39. iam_validator/core/access_analyzer.py +671 -0
  40. iam_validator/core/access_analyzer_report.py +640 -0
  41. iam_validator/core/aws_fetcher.py +940 -0
  42. iam_validator/core/check_registry.py +607 -0
  43. iam_validator/core/cli.py +134 -0
  44. iam_validator/core/condition_validators.py +626 -0
  45. iam_validator/core/config/__init__.py +81 -0
  46. iam_validator/core/config/aws_api.py +35 -0
  47. iam_validator/core/config/aws_global_conditions.py +160 -0
  48. iam_validator/core/config/category_suggestions.py +104 -0
  49. iam_validator/core/config/condition_requirements.py +155 -0
  50. iam_validator/core/config/config_loader.py +472 -0
  51. iam_validator/core/config/defaults.py +523 -0
  52. iam_validator/core/config/principal_requirements.py +421 -0
  53. iam_validator/core/config/sensitive_actions.py +672 -0
  54. iam_validator/core/config/service_principals.py +95 -0
  55. iam_validator/core/config/wildcards.py +124 -0
  56. iam_validator/core/constants.py +74 -0
  57. iam_validator/core/formatters/__init__.py +27 -0
  58. iam_validator/core/formatters/base.py +147 -0
  59. iam_validator/core/formatters/console.py +59 -0
  60. iam_validator/core/formatters/csv.py +170 -0
  61. iam_validator/core/formatters/enhanced.py +440 -0
  62. iam_validator/core/formatters/html.py +672 -0
  63. iam_validator/core/formatters/json.py +33 -0
  64. iam_validator/core/formatters/markdown.py +63 -0
  65. iam_validator/core/formatters/sarif.py +251 -0
  66. iam_validator/core/models.py +327 -0
  67. iam_validator/core/policy_checks.py +656 -0
  68. iam_validator/core/policy_loader.py +396 -0
  69. iam_validator/core/pr_commenter.py +424 -0
  70. iam_validator/core/report.py +872 -0
  71. iam_validator/integrations/__init__.py +28 -0
  72. iam_validator/integrations/github_integration.py +815 -0
  73. iam_validator/integrations/ms_teams.py +442 -0
  74. iam_validator/sdk/__init__.py +187 -0
  75. iam_validator/sdk/arn_matching.py +382 -0
  76. iam_validator/sdk/context.py +222 -0
  77. iam_validator/sdk/exceptions.py +48 -0
  78. iam_validator/sdk/helpers.py +177 -0
  79. iam_validator/sdk/policy_utils.py +425 -0
  80. iam_validator/sdk/shortcuts.py +283 -0
  81. iam_validator/utils/__init__.py +31 -0
  82. iam_validator/utils/cache.py +105 -0
  83. iam_validator/utils/regex.py +206 -0
@@ -0,0 +1,421 @@
1
+ """
2
+ Principal condition requirement configurations for principal_validation check.
3
+
4
+ This module defines default condition requirements for principals in resource-based
5
+ policies, making it easy to manage complex principal condition enforcement rules
6
+ without deeply nested YAML/dict structures.
7
+
8
+ Using Python provides:
9
+ - Better readability and maintainability
10
+ - Type hints and IDE support
11
+ - Easy to add/modify requirements
12
+ - No parsing overhead
13
+ - Compiled to .pyc
14
+
15
+ Configuration Fields Reference:
16
+ - principals: List of principal patterns to match (supports wildcards)
17
+ - severity: Override default severity for this requirement
18
+ - required_conditions: Conditions that must be present (supports all_of/any_of/none_of)
19
+ - condition_key: The IAM condition key to validate
20
+ - expected_value: (Optional) Expected value for the condition key
21
+ - operator: (Optional) Specific operator to validate (e.g., "StringEquals", "IpAddress")
22
+ - description: Technical description of what the requirement does
23
+ - example: Concrete code example showing proper condition usage
24
+
25
+ Field Progression: detect (condition_key) → explain (description) → demonstrate (example)
26
+
27
+ For detailed explanation of these fields and how to customize requirements,
28
+ see: docs/configuration.md#principal-validation
29
+ """
30
+
31
+ from typing import Any, Final
32
+
33
+ # ============================================================================
34
+ # Principal Condition Requirement Definitions
35
+ # ============================================================================
36
+
37
+ # Public Access (*) - CRITICAL: Must have source restrictions
38
+ PUBLIC_ACCESS_REQUIREMENT: Final[dict[str, Any]] = {
39
+ "principals": ["*"],
40
+ "severity": "critical",
41
+ "required_conditions": {
42
+ "any_of": [
43
+ {
44
+ "condition_key": "aws:SourceArn",
45
+ "description": (
46
+ "Public access must be scoped to a specific source ARN "
47
+ "(e.g., SNS topic, EventBridge rule, CloudFront distribution)"
48
+ ),
49
+ "example": (
50
+ "# Example: S3 bucket policy allowing access from SNS topic\n"
51
+ '"Condition": {\n'
52
+ ' "StringEquals": {\n'
53
+ ' "aws:SourceArn": "arn:aws:sns:us-east-1:123456789012:my-topic"\n'
54
+ " }\n"
55
+ "}"
56
+ ),
57
+ },
58
+ {
59
+ "condition_key": "aws:SourceAccount",
60
+ "description": (
61
+ "Public access must be limited to a specific AWS account to prevent "
62
+ "unauthorized access from other accounts"
63
+ ),
64
+ "example": (
65
+ '"Condition": {\n'
66
+ ' "StringEquals": {\n'
67
+ ' "aws:SourceAccount": "123456789012"\n'
68
+ " }\n"
69
+ "}"
70
+ ),
71
+ },
72
+ {
73
+ "condition_key": "aws:SourceVpce",
74
+ "description": (
75
+ "Or limit public access to a specific VPC endpoint for network-level isolation"
76
+ ),
77
+ "example": (
78
+ '"Condition": {\n'
79
+ ' "StringEquals": {\n'
80
+ ' "aws:SourceVpce": "vpce-1a2b3c4d"\n'
81
+ " }\n"
82
+ "}"
83
+ ),
84
+ },
85
+ {
86
+ "condition_key": "aws:SourceIp",
87
+ "description": ("Or limit public access to specific IP addresses or CIDR ranges"),
88
+ "example": (
89
+ '"Condition": {\n'
90
+ ' "IpAddress": {\n'
91
+ ' "aws:SourceIp": ["10.0.0.0/8", "172.16.0.0/12"]\n'
92
+ " }\n"
93
+ "}"
94
+ ),
95
+ },
96
+ ]
97
+ },
98
+ }
99
+
100
+ # Cross-Account Root Access - HIGH: Must be from same organization
101
+ CROSS_ACCOUNT_ORG_REQUIREMENT: Final[dict[str, Any]] = {
102
+ "principals": ["arn:aws:iam::*:root"],
103
+ "severity": "high",
104
+ "required_conditions": {
105
+ "any_of": [
106
+ {
107
+ "condition_key": "aws:PrincipalOrgID",
108
+ "operator": "StringEquals",
109
+ "description": (
110
+ "Cross-account root access must be from principals in the same AWS Organization "
111
+ "to prevent unauthorized third-party access"
112
+ ),
113
+ "example": (
114
+ "# Replace with your organization ID\n"
115
+ '"Condition": {\n'
116
+ ' "StringEquals": {\n'
117
+ ' "aws:PrincipalOrgID": "o-123456789"\n'
118
+ " }\n"
119
+ "}"
120
+ ),
121
+ },
122
+ {
123
+ "condition_key": "aws:PrincipalOrgPaths",
124
+ "operator": "StringEquals",
125
+ "description": (
126
+ "Cross-account root access must be from principals in the same AWS Organization "
127
+ "to prevent unauthorized third-party access"
128
+ ),
129
+ "example": (
130
+ "# Replace with your organization ID\n"
131
+ '"Condition": {\n'
132
+ ' "StringEquals": {\n'
133
+ ' "aws:PrincipalOrgPaths": "o-123456789/*"\n'
134
+ " }\n"
135
+ "}"
136
+ ),
137
+ },
138
+ ],
139
+ },
140
+ }
141
+
142
+ # IAM Roles - HIGH: Must have MFA or VPC endpoint
143
+ IAM_ROLE_MFA_OR_VPC: Final[dict[str, Any]] = {
144
+ "principals": ["arn:aws:iam::*:role/*"],
145
+ "severity": "high",
146
+ "required_conditions": {
147
+ "any_of": [
148
+ {
149
+ "condition_key": "aws:MultiFactorAuthPresent",
150
+ "expected_value": True,
151
+ "description": "Require MFA authentication for IAM role access",
152
+ "example": (
153
+ '"Condition": {\n "Bool": {\n "aws:MultiFactorAuthPresent": "true"\n }\n}'
154
+ ),
155
+ },
156
+ {
157
+ "condition_key": "aws:SourceVpce",
158
+ "description": (
159
+ "Or require access from a specific VPC endpoint to ensure network-level isolation"
160
+ ),
161
+ "example": (
162
+ '"Condition": {\n'
163
+ ' "StringEquals": {\n'
164
+ ' "aws:SourceVpce": "vpce-12345678"\n'
165
+ " }\n"
166
+ "}"
167
+ ),
168
+ },
169
+ ]
170
+ },
171
+ }
172
+
173
+ # IAM Users - MEDIUM: Must have MFA and IP restrictions
174
+ IAM_USER_MFA_AND_IP: Final[dict[str, Any]] = {
175
+ "principals": ["arn:aws:iam::*:user/*"],
176
+ "severity": "medium",
177
+ "required_conditions": {
178
+ "all_of": [
179
+ {
180
+ "condition_key": "aws:MultiFactorAuthPresent",
181
+ "expected_value": True,
182
+ "severity": "high", # Override for this specific condition
183
+ "description": "IAM users must have MFA enabled for security",
184
+ "example": (
185
+ '"Condition": {\n "Bool": {\n "aws:MultiFactorAuthPresent": "true"\n }\n}'
186
+ ),
187
+ },
188
+ {
189
+ "condition_key": "aws:SourceIp",
190
+ "operator": "IpAddress",
191
+ "description": "IAM users must access from approved corporate IP ranges",
192
+ "example": (
193
+ '"Condition": {\n'
194
+ ' "IpAddress": {\n'
195
+ ' "aws:SourceIp": ["10.0.0.0/8", "172.16.0.0/12"]\n'
196
+ " }\n"
197
+ "}"
198
+ ),
199
+ },
200
+ ]
201
+ },
202
+ }
203
+
204
+ # Federated Users - HIGH: Tag-based access control (ABAC)
205
+ FEDERATED_USER_ABAC: Final[dict[str, Any]] = {
206
+ "principals": ["arn:aws:iam::*:federated-user/*"],
207
+ "severity": "high",
208
+ "required_conditions": {
209
+ "all_of": [
210
+ {
211
+ "condition_key": "aws:PrincipalTag/Department",
212
+ "description": "Federated users must have Department tag for access control",
213
+ "example": (
214
+ '"Condition": {\n'
215
+ ' "StringEquals": {\n'
216
+ ' "aws:PrincipalTag/Department": "Engineering"\n'
217
+ " }\n"
218
+ "}"
219
+ ),
220
+ },
221
+ {
222
+ "condition_key": "aws:RequestTag/Owner",
223
+ "operator": "StringEquals",
224
+ "expected_value": "${aws:PrincipalTag/Owner}",
225
+ "description": (
226
+ "Resource owner must match principal's owner tag (Attribute-Based Access Control)"
227
+ ),
228
+ "example": (
229
+ "# ABAC: Principal tag must match resource tag\n"
230
+ '"Condition": {\n'
231
+ ' "StringEquals": {\n'
232
+ ' "aws:RequestTag/Owner": "${aws:PrincipalTag/Owner}"\n'
233
+ " }\n"
234
+ "}"
235
+ ),
236
+ },
237
+ ]
238
+ },
239
+ }
240
+
241
+ # Prevent Insecure Transport - CRITICAL: Never allow HTTP
242
+ PREVENT_INSECURE_TRANSPORT: Final[dict[str, Any]] = {
243
+ "principals": ["*", "arn:aws:iam::*:*"],
244
+ "severity": "critical",
245
+ "required_conditions": {
246
+ "none_of": [
247
+ {
248
+ "condition_key": "aws:SecureTransport",
249
+ "expected_value": False,
250
+ "description": (
251
+ "Insecure transport (HTTP) must never be explicitly allowed. "
252
+ "This prevents man-in-the-middle attacks and data interception"
253
+ ),
254
+ "example": (
255
+ "# INCORRECT - Never allow this:\n"
256
+ '"Condition": {\n'
257
+ ' "Bool": {\n'
258
+ ' "aws:SecureTransport": "false" # ❌ This is forbidden\n'
259
+ " }\n"
260
+ "}\n\n"
261
+ "# CORRECT - Require HTTPS:\n"
262
+ '"Condition": {\n'
263
+ ' "Bool": {\n'
264
+ ' "aws:SecureTransport": "true" # ✅ Always use this\n'
265
+ " }\n"
266
+ "}"
267
+ ),
268
+ }
269
+ ]
270
+ },
271
+ }
272
+
273
+ # Assumed Role Sessions - MEDIUM: Require session tags
274
+ ASSUMED_ROLE_SESSION_TAGS: Final[dict[str, Any]] = {
275
+ "principals": ["arn:aws:sts::*:assumed-role/*"],
276
+ "severity": "medium",
277
+ "required_conditions": [
278
+ {
279
+ "condition_key": "aws:PrincipalTag/SessionName",
280
+ "description": (
281
+ "Assumed role sessions should be tagged with session name for audit trails "
282
+ "and access attribution"
283
+ ),
284
+ "example": (
285
+ '"Condition": {\n "StringLike": {\n "aws:PrincipalTag/SessionName": "*"\n }\n}'
286
+ ),
287
+ }
288
+ ],
289
+ }
290
+
291
+ # ============================================================================
292
+ # Registry and Helper Functions
293
+ # ============================================================================
294
+
295
+ # All available requirement definitions
296
+ ALL_PRINCIPAL_REQUIREMENTS: Final[dict[str, dict[str, Any]]] = {
297
+ "public_access": PUBLIC_ACCESS_REQUIREMENT,
298
+ "cross_account_org": CROSS_ACCOUNT_ORG_REQUIREMENT,
299
+ "iam_role_mfa_or_vpc": IAM_ROLE_MFA_OR_VPC,
300
+ "iam_user_mfa_and_ip": IAM_USER_MFA_AND_IP,
301
+ "federated_user_abac": FEDERATED_USER_ABAC,
302
+ "prevent_insecure_transport": PREVENT_INSECURE_TRANSPORT,
303
+ "assumed_role_session_tags": ASSUMED_ROLE_SESSION_TAGS,
304
+ }
305
+
306
+ # Default requirements enabled by default (most critical ones)
307
+ DEFAULT_ENABLED_REQUIREMENTS: Final[list[str]] = [
308
+ "public_access", # CRITICAL: Public access must have restrictions
309
+ "prevent_insecure_transport", # CRITICAL: Never allow insecure transport
310
+ ]
311
+
312
+
313
+ def get_default_principal_requirements() -> list[dict[str, Any]]:
314
+ """Get default principal condition requirements (most critical ones enabled by default).
315
+
316
+ Returns:
317
+ List of default principal condition requirements
318
+ """
319
+ return [ALL_PRINCIPAL_REQUIREMENTS[name] for name in DEFAULT_ENABLED_REQUIREMENTS]
320
+
321
+
322
+ def get_principal_requirement(name: str) -> dict[str, Any] | None:
323
+ """Get a single principal condition requirement by name.
324
+
325
+ Args:
326
+ name: The requirement name
327
+
328
+ Returns:
329
+ The principal condition requirement or None if not found
330
+ """
331
+ return ALL_PRINCIPAL_REQUIREMENTS.get(name)
332
+
333
+
334
+ def get_principal_requirements_by_names(names: list[str]) -> list[dict[str, Any]]:
335
+ """Get multiple principal condition requirements by name.
336
+
337
+ Args:
338
+ names: List of requirement names
339
+
340
+ Returns:
341
+ List of principal condition requirements
342
+ """
343
+ return [
344
+ ALL_PRINCIPAL_REQUIREMENTS[name] for name in names if name in ALL_PRINCIPAL_REQUIREMENTS
345
+ ]
346
+
347
+
348
+ def get_principal_requirements_by_severity(severity: str) -> list[dict[str, Any]]:
349
+ """Get principal condition requirements filtered by severity.
350
+
351
+ Args:
352
+ severity: The severity level (critical, high, medium, low)
353
+
354
+ Returns:
355
+ List of principal condition requirements with matching severity
356
+ """
357
+ return [req for req in ALL_PRINCIPAL_REQUIREMENTS.values() if req.get("severity") == severity]
358
+
359
+
360
+ def get_all_principal_requirement_names() -> list[str]:
361
+ """Get all available principal condition requirement names.
362
+
363
+ Returns:
364
+ List of all requirement names
365
+ """
366
+ return list(ALL_PRINCIPAL_REQUIREMENTS.keys())
367
+
368
+
369
+ # ============================================================================
370
+ # Usage Examples
371
+ # ============================================================================
372
+
373
+ """
374
+ # Example 1: Use default requirements (public_access + prevent_insecure_transport)
375
+ from iam_validator.core.config import get_default_principal_requirements
376
+
377
+ config = {
378
+ "principal_validation": {
379
+ "enabled": True,
380
+ "principal_condition_requirements": get_default_principal_requirements(),
381
+ }
382
+ }
383
+
384
+ # Example 2: Use specific requirements by name
385
+ from iam_validator.core.config import get_principal_requirements_by_names
386
+
387
+ config = {
388
+ "principal_validation": {
389
+ "enabled": True,
390
+ "principal_condition_requirements": get_principal_requirements_by_names([
391
+ "public_access",
392
+ "cross_account_org",
393
+ "iam_role_mfa_or_vpc",
394
+ ]),
395
+ }
396
+ }
397
+
398
+ # Example 3: Get all critical severity requirements
399
+ from iam_validator.core.config import get_principal_requirements_by_severity
400
+
401
+ config = {
402
+ "principal_validation": {
403
+ "enabled": True,
404
+ "principal_condition_requirements": get_principal_requirements_by_severity("critical"),
405
+ }
406
+ }
407
+
408
+ # Example 4: Get all available requirement names
409
+ from iam_validator.core.config import get_all_principal_requirement_names
410
+
411
+ print(get_all_principal_requirement_names())
412
+ # Output: ['public_access', 'cross_account_org', 'iam_role_mfa_or_vpc', ...]
413
+
414
+ # Example 5: Get a single requirement
415
+ from iam_validator.core.config import get_principal_requirement
416
+
417
+ req = get_principal_requirement("public_access")
418
+ if req:
419
+ print(req["principals"]) # ["*"]
420
+ print(req["severity"]) # "critical"
421
+ """