iam-policy-validator 1.5.0__py3-none-any.whl → 1.6.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 (42) hide show
  1. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/METADATA +89 -60
  2. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/RECORD +40 -25
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +9 -3
  5. iam_validator/checks/action_condition_enforcement.py +164 -2
  6. iam_validator/checks/action_resource_matching.py +424 -0
  7. iam_validator/checks/condition_key_validation.py +3 -1
  8. iam_validator/checks/condition_type_mismatch.py +259 -0
  9. iam_validator/checks/mfa_condition_check.py +112 -0
  10. iam_validator/checks/sensitive_action.py +78 -6
  11. iam_validator/checks/set_operator_validation.py +157 -0
  12. iam_validator/checks/utils/sensitive_action_matcher.py +35 -1
  13. iam_validator/commands/cache.py +1 -1
  14. iam_validator/commands/validate.py +44 -11
  15. iam_validator/core/aws_fetcher.py +89 -52
  16. iam_validator/core/check_registry.py +165 -21
  17. iam_validator/core/condition_validators.py +626 -0
  18. iam_validator/core/config/__init__.py +13 -15
  19. iam_validator/core/config/aws_global_conditions.py +160 -0
  20. iam_validator/core/config/category_suggestions.py +104 -0
  21. iam_validator/core/config/condition_requirements.py +5 -385
  22. iam_validator/core/{config_loader.py → config/config_loader.py} +3 -0
  23. iam_validator/core/config/defaults.py +187 -54
  24. iam_validator/core/config/sensitive_actions.py +620 -81
  25. iam_validator/core/models.py +14 -1
  26. iam_validator/core/policy_checks.py +4 -4
  27. iam_validator/core/pr_commenter.py +1 -1
  28. iam_validator/sdk/__init__.py +187 -0
  29. iam_validator/sdk/arn_matching.py +274 -0
  30. iam_validator/sdk/context.py +222 -0
  31. iam_validator/sdk/exceptions.py +48 -0
  32. iam_validator/sdk/helpers.py +177 -0
  33. iam_validator/sdk/policy_utils.py +425 -0
  34. iam_validator/sdk/shortcuts.py +283 -0
  35. iam_validator/utils/__init__.py +31 -0
  36. iam_validator/utils/cache.py +105 -0
  37. iam_validator/utils/regex.py +206 -0
  38. iam_validator/checks/action_resource_constraint.py +0 -151
  39. iam_validator/core/aws_global_conditions.py +0 -137
  40. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/WHEEL +0 -0
  41. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/entry_points.txt +0 -0
  42. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,13 +5,6 @@ This module defines default condition requirements for sensitive actions,
5
5
  making it easy to manage complex condition enforcement rules without
6
6
  deeply nested YAML/dict structures.
7
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
8
  Configuration Fields Reference:
16
9
  - description: Technical description of what the requirement does (shown in output)
17
10
  - example: Concrete code example showing proper condition usage
@@ -57,39 +50,6 @@ IAM_PASS_ROLE_REQUIREMENT: Final[dict[str, Any]] = {
57
50
  ],
58
51
  }
59
52
 
60
- # IAM Write Operations - Require permissions boundary
61
- IAM_WRITE_PERMISSIONS_BOUNDARY: Final[dict[str, Any]] = {
62
- "actions": [
63
- "iam:CreateRole",
64
- "iam:PutRolePolicy*",
65
- "iam:PutUserPolicy",
66
- "iam:PutRolePolicy",
67
- "iam:Attach*Policy*",
68
- "iam:AttachUserPolicy",
69
- "iam:AttachRolePolicy",
70
- ],
71
- "severity": "high",
72
- "required_conditions": [
73
- {
74
- "condition_key": "iam:PermissionsBoundary",
75
- "description": (
76
- "Require permissions boundary for sensitive IAM operations to prevent privilege escalation"
77
- ),
78
- "expected_value": "arn:aws:iam::*:policy/DeveloperBoundary",
79
- "example": (
80
- "# See: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html\n"
81
- "{\n"
82
- ' "Condition": {\n'
83
- ' "StringEquals": {\n'
84
- ' "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/XCompanyBoundaries"\n'
85
- " }\n"
86
- " }\n"
87
- "}"
88
- ),
89
- },
90
- ],
91
- }
92
-
93
53
  # S3 Write Operations - Require organization ID
94
54
  S3_WRITE_ORG_ID: Final[dict[str, Any]] = {
95
55
  "actions": ["s3:PutObject"],
@@ -98,7 +58,7 @@ S3_WRITE_ORG_ID: Final[dict[str, Any]] = {
98
58
  {
99
59
  "condition_key": "aws:ResourceOrgId",
100
60
  "description": (
101
- "Require aws:ResourceOrgId condition for S3 write actions to enforce organization-level access control"
61
+ "Require aws:ResourceAccount, aws:ResourceOrgID or aws:ResourceOrgPaths condition(s) for S3 write actions to enforce organization-level access control"
102
62
  ),
103
63
  "example": (
104
64
  "{\n"
@@ -145,6 +105,7 @@ SOURCE_IP_RESTRICTIONS: Final[dict[str, Any]] = {
145
105
  # S3 Secure Transport - Never allow insecure transport
146
106
  S3_SECURE_TRANSPORT: Final[dict[str, Any]] = {
147
107
  "actions": ["s3:GetObject", "s3:PutObject"],
108
+ "severity": "critical",
148
109
  "required_conditions": {
149
110
  "none_of": [
150
111
  {
@@ -166,171 +127,6 @@ S3_SECURE_TRANSPORT: Final[dict[str, Any]] = {
166
127
  },
167
128
  }
168
129
 
169
- # ============================================================================
170
- # Optional Requirements (Commented Examples for Users)
171
- # ============================================================================
172
- # These are disabled by default but can be enabled by users
173
-
174
- # S3 Destructive Operations - Require MFA
175
- S3_DESTRUCTIVE_MFA: Final[dict[str, Any]] = {
176
- "actions": [
177
- "s3:DeleteBucket",
178
- "s3:DeleteBucketPolicy",
179
- "s3:PutBucketPolicy",
180
- ],
181
- "severity": "high",
182
- "required_conditions": [
183
- {
184
- "condition_key": "aws:MultiFactorAuthPresent",
185
- "description": "Require MFA for S3 destructive operations",
186
- "expected_value": "true",
187
- "example": (
188
- "{\n"
189
- ' "Condition": {\n'
190
- ' "Bool": {\n'
191
- ' "aws:MultiFactorAuthPresent": "true"\n'
192
- " }\n"
193
- " }\n"
194
- "}"
195
- ),
196
- },
197
- ],
198
- }
199
-
200
- # All S3 Operations - Require HTTPS
201
- S3_REQUIRE_HTTPS: Final[dict[str, Any]] = {
202
- "action_patterns": ["^s3:.*"],
203
- "severity": "medium",
204
- "required_conditions": [
205
- {
206
- "condition_key": "aws:SecureTransport",
207
- "description": "Require HTTPS for all S3 operations",
208
- "expected_value": True,
209
- "example": (
210
- "{\n"
211
- ' "Condition": {\n'
212
- ' "Bool": {\n'
213
- ' "aws:SecureTransport": "true"\n'
214
- " }\n"
215
- " }\n"
216
- "}"
217
- ),
218
- },
219
- ],
220
- }
221
-
222
- # EC2 Instances - Must be in specific VPCs
223
- EC2_VPC_RESTRICTION: Final[dict[str, Any]] = {
224
- "actions": ["ec2:RunInstances"],
225
- "severity": "high",
226
- "required_conditions": [
227
- {
228
- "condition_key": "ec2:Vpc",
229
- "description": "EC2 instances must be launched in approved VPCs",
230
- "example": (
231
- "{\n"
232
- ' "Condition": {\n'
233
- ' "StringEquals": {\n'
234
- ' "ec2:Vpc": "arn:aws:ec2:us-east-1:123456789012:vpc/vpc-12345678"\n'
235
- " }\n"
236
- " }\n"
237
- "}"
238
- ),
239
- },
240
- ],
241
- }
242
-
243
- # EC2 Instances - Tag requirements (ABAC)
244
- EC2_TAG_REQUIREMENTS: Final[dict[str, Any]] = {
245
- "actions": ["ec2:RunInstances"],
246
- "severity": "high",
247
- "required_conditions": {
248
- "all_of": [
249
- {
250
- "condition_key": "aws:RequestTag/env",
251
- "operator": "StringEquals",
252
- "expected_value": ["prod", "pre", "dev", "sandbox"],
253
- "description": "Must specify a valid Environment tag",
254
- },
255
- ],
256
- "any_of": [
257
- {
258
- "condition_key": "aws:ResourceTag/owner",
259
- "operator": "StringEquals",
260
- "expected_value": "${aws:PrincipalTag/owner}",
261
- "description": "Resource owner must match the principal's owner tag",
262
- },
263
- {
264
- "condition_key": "aws:RequestTag/owner",
265
- "description": "Must specify resource owner",
266
- "expected_value": "${aws:PrincipalTag/owner}",
267
- },
268
- ],
269
- },
270
- }
271
-
272
- # RDS - Database tag requirements
273
- RDS_TAG_REQUIREMENTS: Final[dict[str, Any]] = {
274
- "action_patterns": [
275
- "^rds:Create.*",
276
- "^rds:Modify.*",
277
- ],
278
- "severity": "medium",
279
- "required_conditions": {
280
- "all_of": [
281
- {
282
- "condition_key": "aws:RequestTag/DataClassification",
283
- "description": "Must specify data classification",
284
- },
285
- {
286
- "condition_key": "aws:RequestTag/BackupPolicy",
287
- "description": "Must specify backup policy",
288
- },
289
- {
290
- "condition_key": "aws:RequestTag/Owner",
291
- "description": "Must specify resource owner",
292
- },
293
- ],
294
- },
295
- }
296
-
297
- # S3 Bucket Operations - Data classification matching
298
- S3_BUCKET_TAG_REQUIREMENTS: Final[dict[str, Any]] = {
299
- "actions": ["s3:CreateBucket", "s3:PutObject"],
300
- "severity": "medium",
301
- "required_conditions": {
302
- "all_of": [
303
- {
304
- "condition_key": "aws:ResourceTag/DataClassification",
305
- "operator": "StringEquals",
306
- "expected_value": "${aws:PrincipalTag/DataClassification}",
307
- "description": "Data classification must match principal's tag",
308
- },
309
- {
310
- "condition_key": "aws:RequestTag/Owner",
311
- "description": "Must specify owner",
312
- },
313
- {
314
- "condition_key": "aws:RequestTag/CostCenter",
315
- "description": "Must specify cost center",
316
- },
317
- ],
318
- },
319
- }
320
-
321
- # Forbidden Actions - Flag if these dangerous actions appear
322
- FORBIDDEN_ACTIONS: Final[dict[str, Any]] = {
323
- "actions": {
324
- "none_of": [
325
- "iam:*",
326
- "s3:DeleteBucket",
327
- "s3:DeleteBucketPolicy",
328
- ],
329
- },
330
- "severity": "critical",
331
- "description": "These highly sensitive actions are forbidden in this policy",
332
- }
333
-
334
130
  # Prevent overly permissive IP ranges
335
131
  PREVENT_PUBLIC_IP: Final[dict[str, Any]] = {
336
132
  "action_patterns": ["^s3:.*"],
@@ -347,189 +143,13 @@ PREVENT_PUBLIC_IP: Final[dict[str, Any]] = {
347
143
  }
348
144
 
349
145
  # ============================================================================
350
- # Default Requirements List
146
+ # Condition Requirements
351
147
  # ============================================================================
352
148
 
353
- # Requirements enabled by default
354
- DEFAULT_CONDITION_REQUIREMENTS: Final[list[dict[str, Any]]] = [
149
+ CONDITION_REQUIREMENTS: Final[list[dict[str, Any]]] = [
355
150
  IAM_PASS_ROLE_REQUIREMENT,
356
- IAM_WRITE_PERMISSIONS_BOUNDARY,
357
151
  S3_WRITE_ORG_ID,
358
152
  SOURCE_IP_RESTRICTIONS,
359
153
  S3_SECURE_TRANSPORT,
154
+ PREVENT_PUBLIC_IP,
360
155
  ]
361
-
362
- # All available requirements (including optional ones)
363
- ALL_CONDITION_REQUIREMENTS: Final[dict[str, dict[str, Any]]] = {
364
- # Default (enabled)
365
- "iam_pass_role": IAM_PASS_ROLE_REQUIREMENT,
366
- "iam_permissions_boundary": IAM_WRITE_PERMISSIONS_BOUNDARY,
367
- "s3_org_id": S3_WRITE_ORG_ID,
368
- "source_ip_restrictions": SOURCE_IP_RESTRICTIONS,
369
- "s3_secure_transport": S3_SECURE_TRANSPORT,
370
- # Optional (disabled by default)
371
- "s3_destructive_mfa": S3_DESTRUCTIVE_MFA,
372
- "s3_require_https": S3_REQUIRE_HTTPS,
373
- "ec2_vpc_restriction": EC2_VPC_RESTRICTION,
374
- "ec2_tag_requirements": EC2_TAG_REQUIREMENTS,
375
- "rds_tag_requirements": RDS_TAG_REQUIREMENTS,
376
- "s3_bucket_tag_requirements": S3_BUCKET_TAG_REQUIREMENTS,
377
- "forbidden_actions": FORBIDDEN_ACTIONS,
378
- "prevent_public_ip": PREVENT_PUBLIC_IP,
379
- }
380
-
381
-
382
- # ============================================================================
383
- # Helper Functions
384
- # ============================================================================
385
-
386
-
387
- def get_default_requirements() -> list[dict[str, Any]]:
388
- """
389
- Get the default condition requirements.
390
-
391
- Returns:
392
- List of default condition requirement configurations
393
- """
394
- # Return a copy to prevent modification
395
- import copy
396
-
397
- return copy.deepcopy(DEFAULT_CONDITION_REQUIREMENTS)
398
-
399
-
400
- def get_requirement(name: str) -> dict[str, Any] | None:
401
- """
402
- Get a specific requirement by name.
403
-
404
- Args:
405
- name: Requirement name (e.g., "iam_pass_role", "s3_destructive_mfa")
406
-
407
- Returns:
408
- Requirement configuration dict, or None if not found
409
- """
410
- import copy
411
-
412
- req = ALL_CONDITION_REQUIREMENTS.get(name)
413
- return copy.deepcopy(req) if req else None
414
-
415
-
416
- def get_all_requirement_names() -> list[str]:
417
- """
418
- Get list of all available requirement names.
419
-
420
- Returns:
421
- List of requirement names
422
- """
423
- return list(ALL_CONDITION_REQUIREMENTS.keys())
424
-
425
-
426
- def get_requirements_by_names(names: list[str]) -> list[dict[str, Any]]:
427
- """
428
- Get multiple requirements by name.
429
-
430
- Args:
431
- names: List of requirement names
432
-
433
- Returns:
434
- List of requirement configurations
435
- """
436
- import copy
437
-
438
- requirements = []
439
- for name in names:
440
- req = ALL_CONDITION_REQUIREMENTS.get(name)
441
- if req:
442
- requirements.append(copy.deepcopy(req))
443
- return requirements
444
-
445
-
446
- def get_requirements_by_severity(
447
- min_severity: str = "low",
448
- ) -> list[dict[str, Any]]:
449
- """
450
- Get requirements filtered by minimum severity.
451
-
452
- Args:
453
- min_severity: Minimum severity level (low, medium, high, critical)
454
-
455
- Returns:
456
- List of requirements matching severity criteria
457
- """
458
- import copy
459
-
460
- severity_order = {"low": 0, "medium": 1, "high": 2, "critical": 3}
461
- min_level = severity_order.get(min_severity, 0)
462
-
463
- requirements = []
464
- for req in ALL_CONDITION_REQUIREMENTS.values():
465
- req_severity = req.get("severity", "low")
466
- req_level = severity_order.get(req_severity, 0)
467
- if req_level >= min_level:
468
- requirements.append(copy.deepcopy(req))
469
-
470
- return requirements
471
-
472
-
473
- def describe_requirement(name: str) -> dict[str, Any]:
474
- """
475
- Get description and metadata for a requirement.
476
-
477
- Args:
478
- name: Requirement name
479
-
480
- Returns:
481
- Dictionary with requirement metadata
482
- """
483
- descriptions = {
484
- "iam_pass_role": {
485
- "name": "IAM PassRole Restriction",
486
- "category": "privilege_escalation",
487
- "severity": "high",
488
- "description": "Prevents privilege escalation by requiring iam:PassedToService condition",
489
- "required": True,
490
- },
491
- "iam_permissions_boundary": {
492
- "name": "IAM Permissions Boundary",
493
- "category": "privilege_escalation",
494
- "severity": "high",
495
- "description": "Requires permissions boundary for IAM write operations",
496
- "required": True,
497
- },
498
- "s3_org_id": {
499
- "name": "S3 Organization ID",
500
- "category": "data_exfiltration",
501
- "severity": "medium",
502
- "description": "Ensures S3 operations stay within organization",
503
- "required": True,
504
- },
505
- "source_ip_restrictions": {
506
- "name": "Source IP Restrictions",
507
- "category": "network_security",
508
- "severity": "low",
509
- "description": "Restricts access to corporate IP ranges",
510
- "required": False,
511
- },
512
- "s3_secure_transport": {
513
- "name": "S3 Secure Transport",
514
- "category": "encryption",
515
- "severity": "medium",
516
- "description": "Prevents explicitly allowing insecure transport",
517
- "required": True,
518
- },
519
- "s3_destructive_mfa": {
520
- "name": "S3 Destructive MFA",
521
- "category": "data_protection",
522
- "severity": "high",
523
- "description": "Requires MFA for destructive S3 operations",
524
- "required": False,
525
- },
526
- "ec2_tag_requirements": {
527
- "name": "EC2 Tag Requirements (ABAC)",
528
- "category": "abac",
529
- "severity": "high",
530
- "description": "Enforces tag-based access control for EC2 instances",
531
- "required": False,
532
- },
533
- }
534
-
535
- return descriptions.get(name, {"name": "Unknown", "description": "Unknown requirement"})
@@ -267,6 +267,9 @@ class ConfigLoader:
267
267
  config=check_config_dict,
268
268
  description=check_config_dict.get("description", check.description),
269
269
  root_config=config.config_dict, # Pass full config for cross-check access
270
+ ignore_patterns=check_config_dict.get(
271
+ "ignore_patterns", []
272
+ ), # NEW: Ignore patterns
270
273
  )
271
274
 
272
275
  registry.configure_check(check_id, check_config)