iam-policy-validator 1.7.2__py3-none-any.whl → 1.8.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.
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -6
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/RECORD +38 -35
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +61 -23
- iam_validator/checks/action_resource_matching.py +6 -2
- iam_validator/checks/action_validation.py +1 -1
- iam_validator/checks/condition_key_validation.py +1 -1
- iam_validator/checks/condition_type_mismatch.py +6 -6
- iam_validator/checks/policy_structure.py +577 -0
- iam_validator/checks/policy_type_validation.py +48 -32
- iam_validator/checks/principal_validation.py +65 -133
- iam_validator/checks/resource_validation.py +8 -8
- iam_validator/checks/sensitive_action.py +7 -3
- iam_validator/checks/service_wildcard.py +2 -2
- iam_validator/checks/set_operator_validation.py +11 -11
- iam_validator/checks/sid_uniqueness.py +8 -4
- iam_validator/checks/trust_policy_validation.py +512 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
- iam_validator/checks/utils/wildcard_expansion.py +1 -1
- iam_validator/checks/wildcard_action.py +3 -1
- iam_validator/checks/wildcard_resource.py +3 -1
- iam_validator/commands/validate.py +6 -12
- iam_validator/core/__init__.py +1 -2
- iam_validator/core/access_analyzer.py +1 -1
- iam_validator/core/access_analyzer_report.py +2 -2
- iam_validator/core/aws_fetcher.py +45 -43
- iam_validator/core/check_registry.py +83 -79
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/defaults.py +58 -52
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +15 -5
- iam_validator/core/policy_checks.py +31 -472
- iam_validator/core/policy_loader.py +27 -4
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,6 +15,7 @@ from dataclasses import dataclass, field
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any
|
|
16
16
|
|
|
17
17
|
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
18
|
+
from iam_validator.core.ignore_patterns import IgnorePatternMatcher
|
|
18
19
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
@@ -36,107 +37,64 @@ class CheckConfig:
|
|
|
36
37
|
List of patterns to ignore findings.
|
|
37
38
|
|
|
38
39
|
Each pattern is a dict with optional fields:
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
40
|
+
- filepath: Regex to match file path
|
|
41
|
+
- action: Regex to match action name
|
|
42
|
+
- resource: Regex to match resource
|
|
43
|
+
- sid: Exact SID to match (or regex if ends with .*)
|
|
44
|
+
- condition_key: Regex to match condition key
|
|
44
45
|
|
|
45
46
|
Multiple fields in one pattern = AND logic
|
|
46
47
|
Multiple patterns = OR logic (any pattern matches → ignore)
|
|
47
48
|
|
|
48
49
|
Example:
|
|
49
50
|
ignore_patterns:
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
-
|
|
51
|
+
- filepath: "test/.*|examples/.*"
|
|
52
|
+
- filepath: "policies/readonly-.*"
|
|
53
|
+
action: ".*:(Get|List|Describe).*"
|
|
54
|
+
- sid: "AllowReadOnlyAccess"
|
|
54
55
|
"""
|
|
55
56
|
|
|
56
57
|
def should_ignore(self, issue: ValidationIssue, filepath: str = "") -> bool:
|
|
57
58
|
"""
|
|
58
59
|
Check if issue should be ignored based on ignore patterns.
|
|
59
60
|
|
|
61
|
+
Uses centralized IgnorePatternMatcher for high-performance filtering
|
|
62
|
+
with cached compiled regex patterns.
|
|
63
|
+
|
|
60
64
|
Args:
|
|
61
65
|
issue: The validation issue to check
|
|
62
66
|
filepath: Path to the policy file
|
|
63
67
|
|
|
64
68
|
Returns:
|
|
65
69
|
True if the issue should be ignored
|
|
66
|
-
"""
|
|
67
|
-
if not self.ignore_patterns:
|
|
68
|
-
return False
|
|
69
|
-
|
|
70
|
-
import re
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
71
|
+
Performance:
|
|
72
|
+
- Cached regex compilation (LRU cache)
|
|
73
|
+
- Early exit optimization
|
|
74
|
+
"""
|
|
75
|
+
return IgnorePatternMatcher.should_ignore_issue(issue, filepath, self.ignore_patterns)
|
|
77
76
|
|
|
78
|
-
def
|
|
79
|
-
self,
|
|
80
|
-
pattern: dict[str, Any],
|
|
81
|
-
issue: ValidationIssue,
|
|
82
|
-
filepath: str,
|
|
83
|
-
re_module: Any,
|
|
84
|
-
) -> bool:
|
|
77
|
+
def filter_actions(self, actions: frozenset[str]) -> frozenset[str]:
|
|
85
78
|
"""
|
|
86
|
-
|
|
79
|
+
Filter actions based on action ignore patterns.
|
|
87
80
|
|
|
88
|
-
|
|
81
|
+
Uses centralized IgnorePatternMatcher for high-performance filtering
|
|
82
|
+
with cached compiled regex patterns.
|
|
83
|
+
|
|
84
|
+
This is useful for checks that need to filter a set of actions before
|
|
85
|
+
creating ValidationIssues (e.g., sensitive_action check).
|
|
89
86
|
|
|
90
87
|
Args:
|
|
91
|
-
|
|
92
|
-
issue: ValidationIssue to check
|
|
93
|
-
filepath: Path to policy file
|
|
94
|
-
re_module: re module for regex matching
|
|
88
|
+
actions: Set of actions to filter
|
|
95
89
|
|
|
96
90
|
Returns:
|
|
97
|
-
|
|
91
|
+
Filtered set of actions (actions matching ignore patterns removed)
|
|
92
|
+
|
|
93
|
+
Performance:
|
|
94
|
+
- Cached regex compilation (LRU cache)
|
|
95
|
+
- Early exit per action on first match
|
|
98
96
|
"""
|
|
99
|
-
|
|
100
|
-
actual_value = None
|
|
101
|
-
|
|
102
|
-
if field_name == "filepath_regex":
|
|
103
|
-
actual_value = filepath
|
|
104
|
-
elif field_name == "action_matches":
|
|
105
|
-
actual_value = issue.action or ""
|
|
106
|
-
elif field_name == "resource_matches":
|
|
107
|
-
actual_value = issue.resource or ""
|
|
108
|
-
elif field_name == "statement_sid":
|
|
109
|
-
# For SID, support both exact match and regex
|
|
110
|
-
if isinstance(regex_pattern, str) and "*" in regex_pattern:
|
|
111
|
-
# Treat as regex if contains wildcard
|
|
112
|
-
actual_value = issue.statement_sid or ""
|
|
113
|
-
else:
|
|
114
|
-
# Exact match
|
|
115
|
-
if issue.statement_sid != regex_pattern:
|
|
116
|
-
return False
|
|
117
|
-
continue
|
|
118
|
-
elif field_name == "condition_key_matches":
|
|
119
|
-
actual_value = issue.condition_key or ""
|
|
120
|
-
else:
|
|
121
|
-
# Unknown field, skip
|
|
122
|
-
continue
|
|
123
|
-
|
|
124
|
-
# Check regex match (case-insensitive)
|
|
125
|
-
if actual_value is None:
|
|
126
|
-
return False
|
|
127
|
-
|
|
128
|
-
try:
|
|
129
|
-
if not re_module.search(
|
|
130
|
-
str(regex_pattern),
|
|
131
|
-
str(actual_value),
|
|
132
|
-
re_module.IGNORECASE,
|
|
133
|
-
):
|
|
134
|
-
return False
|
|
135
|
-
except re_module.error:
|
|
136
|
-
# Invalid regex, don't match
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
return True # All fields matched
|
|
97
|
+
return IgnorePatternMatcher.filter_actions(actions, self.ignore_patterns)
|
|
140
98
|
|
|
141
99
|
|
|
142
100
|
class PolicyCheck(ABC):
|
|
@@ -399,6 +357,10 @@ class CheckRegistry:
|
|
|
399
357
|
config = self.get_config(check.check_id)
|
|
400
358
|
if config:
|
|
401
359
|
issues = await check.execute(statement, statement_idx, fetcher, config)
|
|
360
|
+
# Inject check_id into each issue
|
|
361
|
+
for issue in issues:
|
|
362
|
+
if issue.check_id is None:
|
|
363
|
+
issue.check_id = check.check_id
|
|
402
364
|
# Filter issues based on ignore_patterns
|
|
403
365
|
filtered_issues = [
|
|
404
366
|
issue for issue in issues if not config.should_ignore(issue, filepath)
|
|
@@ -427,7 +389,12 @@ class CheckRegistry:
|
|
|
427
389
|
check = enabled_checks[idx]
|
|
428
390
|
print(f"Warning: Check '{check.check_id}' failed: {result}")
|
|
429
391
|
elif isinstance(result, list):
|
|
392
|
+
check = enabled_checks[idx]
|
|
430
393
|
config = configs[idx]
|
|
394
|
+
# Inject check_id into each issue
|
|
395
|
+
for issue in result:
|
|
396
|
+
if issue.check_id is None:
|
|
397
|
+
issue.check_id = check.check_id
|
|
431
398
|
# Filter issues based on ignore_patterns
|
|
432
399
|
filtered_issues = [
|
|
433
400
|
issue for issue in result if not config.should_ignore(issue, filepath)
|
|
@@ -475,6 +442,7 @@ class CheckRegistry:
|
|
|
475
442
|
policy_file: str,
|
|
476
443
|
fetcher: AWSServiceFetcher,
|
|
477
444
|
policy_type: str = "IDENTITY_POLICY",
|
|
445
|
+
**kwargs,
|
|
478
446
|
) -> list[ValidationIssue]:
|
|
479
447
|
"""
|
|
480
448
|
Execute all enabled policy-level checks.
|
|
@@ -487,6 +455,7 @@ class CheckRegistry:
|
|
|
487
455
|
policy_file: Path to the policy file (for context/reporting)
|
|
488
456
|
fetcher: AWS service fetcher for API calls
|
|
489
457
|
policy_type: Type of policy (IDENTITY_POLICY, RESOURCE_POLICY, SERVICE_CONTROL_POLICY)
|
|
458
|
+
**kwargs: Additional arguments to pass to checks (e.g., raw_policy_dict)
|
|
490
459
|
|
|
491
460
|
Returns:
|
|
492
461
|
List of all ValidationIssue objects from all policy-level checks
|
|
@@ -507,34 +476,61 @@ class CheckRegistry:
|
|
|
507
476
|
if config:
|
|
508
477
|
try:
|
|
509
478
|
issues = await check.execute_policy(
|
|
510
|
-
policy,
|
|
479
|
+
policy,
|
|
480
|
+
policy_file,
|
|
481
|
+
fetcher,
|
|
482
|
+
config,
|
|
483
|
+
policy_type=policy_type,
|
|
484
|
+
**kwargs,
|
|
511
485
|
)
|
|
512
|
-
|
|
486
|
+
# Inject check_id into each issue
|
|
487
|
+
for issue in issues:
|
|
488
|
+
if issue.check_id is None:
|
|
489
|
+
issue.check_id = check.check_id
|
|
490
|
+
# Filter issues based on ignore_patterns
|
|
491
|
+
filtered_issues = [
|
|
492
|
+
issue
|
|
493
|
+
for issue in issues
|
|
494
|
+
if not config.should_ignore(issue, policy_file)
|
|
495
|
+
]
|
|
496
|
+
all_issues.extend(filtered_issues)
|
|
513
497
|
except Exception as e:
|
|
514
498
|
print(f"Warning: Check '{check.check_id}' failed: {e}")
|
|
515
499
|
return all_issues
|
|
516
500
|
|
|
517
501
|
# Execute all policy-level checks in parallel
|
|
518
502
|
tasks = []
|
|
503
|
+
configs = []
|
|
519
504
|
for check in policy_level_checks:
|
|
520
505
|
config = self.get_config(check.check_id)
|
|
521
506
|
if config:
|
|
522
507
|
task = check.execute_policy(
|
|
523
|
-
policy, policy_file, fetcher, config, policy_type=policy_type
|
|
508
|
+
policy, policy_file, fetcher, config, policy_type=policy_type, **kwargs
|
|
524
509
|
)
|
|
525
510
|
tasks.append(task)
|
|
511
|
+
configs.append(config)
|
|
526
512
|
|
|
527
513
|
# Wait for all checks to complete
|
|
528
514
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
529
515
|
|
|
530
|
-
# Collect all issues, handling any exceptions
|
|
516
|
+
# Collect all issues, handling any exceptions and applying ignore_patterns
|
|
531
517
|
for idx, result in enumerate(results):
|
|
532
518
|
if isinstance(result, Exception):
|
|
533
519
|
# Log error but continue with other checks
|
|
534
520
|
check = policy_level_checks[idx]
|
|
535
521
|
print(f"Warning: Check '{check.check_id}' failed: {result}")
|
|
536
522
|
elif isinstance(result, list):
|
|
537
|
-
|
|
523
|
+
check = policy_level_checks[idx]
|
|
524
|
+
config = configs[idx]
|
|
525
|
+
# Inject check_id into each issue
|
|
526
|
+
for issue in result:
|
|
527
|
+
if issue.check_id is None:
|
|
528
|
+
issue.check_id = check.check_id
|
|
529
|
+
# Filter issues based on ignore_patterns
|
|
530
|
+
filtered_issues = [
|
|
531
|
+
issue for issue in result if not config.should_ignore(issue, policy_file)
|
|
532
|
+
]
|
|
533
|
+
all_issues.extend(filtered_issues)
|
|
538
534
|
|
|
539
535
|
return all_issues
|
|
540
536
|
|
|
@@ -561,6 +557,11 @@ def create_default_registry(
|
|
|
561
557
|
# Import and register built-in checks
|
|
562
558
|
from iam_validator import checks
|
|
563
559
|
|
|
560
|
+
# 0. FUNDAMENTAL STRUCTURE (Must run FIRST - validates basic policy structure)
|
|
561
|
+
registry.register(
|
|
562
|
+
checks.PolicyStructureCheck()
|
|
563
|
+
) # Policy-level: Validates required fields, conflicts, valid values
|
|
564
|
+
|
|
564
565
|
# 1. POLICY STRUCTURE (Checks that examine the entire policy, not individual statements)
|
|
565
566
|
registry.register(
|
|
566
567
|
checks.SidUniquenessCheck()
|
|
@@ -600,6 +601,9 @@ def create_default_registry(
|
|
|
600
601
|
registry.register(
|
|
601
602
|
checks.PrincipalValidationCheck()
|
|
602
603
|
) # Principal validation (resource policies)
|
|
604
|
+
registry.register(
|
|
605
|
+
checks.TrustPolicyValidationCheck()
|
|
606
|
+
) # Trust policy validation (role assumption policies)
|
|
603
607
|
|
|
604
608
|
# Note: policy_type_validation is a standalone function (not a class-based check)
|
|
605
609
|
# and is called separately in the validation flow
|
|
@@ -54,23 +54,75 @@ IAM_PASS_ROLE_REQUIREMENT: Final[dict[str, Any]] = {
|
|
|
54
54
|
S3_WRITE_ORG_ID: Final[dict[str, Any]] = {
|
|
55
55
|
"actions": ["s3:PutObject"],
|
|
56
56
|
"severity": "medium",
|
|
57
|
-
"required_conditions":
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
"required_conditions": {
|
|
58
|
+
"any_of": [
|
|
59
|
+
# Option 1: Use organization-level control with ResourceOrgID
|
|
60
|
+
{
|
|
61
|
+
"all_of": [
|
|
62
|
+
{
|
|
63
|
+
"condition_key": "aws:ResourceOrgID",
|
|
64
|
+
"description": "Restrict S3 write actions to resources within your AWS Organization",
|
|
65
|
+
"expected_value": "${aws:PrincipalOrgID}",
|
|
66
|
+
"example": (
|
|
67
|
+
"{\n"
|
|
68
|
+
' "Condition": {\n'
|
|
69
|
+
' "StringEquals": {\n'
|
|
70
|
+
' "aws:ResourceOrgID": "${aws:PrincipalOrgID}",\n'
|
|
71
|
+
' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
|
|
72
|
+
" }\n"
|
|
73
|
+
" }\n"
|
|
74
|
+
"}"
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"condition_key": "aws:ResourceAccount",
|
|
79
|
+
"description": "Ensure the S3 resource belongs to the same AWS account as the principal",
|
|
80
|
+
"expected_value": "${aws:PrincipalAccount}",
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
# Option 2: Use organization path-based control
|
|
85
|
+
{
|
|
86
|
+
"all_of": [
|
|
87
|
+
{
|
|
88
|
+
"condition_key": "aws:ResourceOrgPaths",
|
|
89
|
+
"description": "Restrict S3 write actions to resources within your AWS Organization path",
|
|
90
|
+
"expected_value": "${aws:PrincipalOrgPaths}",
|
|
91
|
+
"example": (
|
|
92
|
+
"{\n"
|
|
93
|
+
' "Condition": {\n'
|
|
94
|
+
' "StringEquals": {\n'
|
|
95
|
+
' "aws:ResourceOrgPaths": "${aws:PrincipalOrgPaths}",\n'
|
|
96
|
+
' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
|
|
97
|
+
" }\n"
|
|
98
|
+
" }\n"
|
|
99
|
+
"}"
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"condition_key": "aws:ResourceAccount",
|
|
104
|
+
"description": "Ensure the S3 resource belongs to the same AWS account as the principal",
|
|
105
|
+
"expected_value": "${aws:PrincipalAccount}",
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
# Option 3: Account-only control (less restrictive, but still secure)
|
|
110
|
+
{
|
|
111
|
+
"condition_key": "aws:ResourceAccount",
|
|
112
|
+
"description": "Restrict S3 write actions to resources within the same AWS account",
|
|
113
|
+
"expected_value": "${aws:PrincipalAccount}",
|
|
114
|
+
"example": (
|
|
115
|
+
"{\n"
|
|
116
|
+
' "Condition": {\n'
|
|
117
|
+
' "StringEquals": {\n'
|
|
118
|
+
' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
|
|
119
|
+
" }\n"
|
|
120
|
+
" }\n"
|
|
121
|
+
"}"
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
74
126
|
}
|
|
75
127
|
|
|
76
128
|
# IP Restrictions - Source IP requirements
|
|
@@ -22,7 +22,6 @@ from iam_validator.core.config.condition_requirements import CONDITION_REQUIREME
|
|
|
22
22
|
from iam_validator.core.config.principal_requirements import (
|
|
23
23
|
get_default_principal_requirements,
|
|
24
24
|
)
|
|
25
|
-
from iam_validator.core.config.service_principals import DEFAULT_SERVICE_PRINCIPALS
|
|
26
25
|
from iam_validator.core.config.wildcards import (
|
|
27
26
|
DEFAULT_ALLOWED_WILDCARDS,
|
|
28
27
|
DEFAULT_SERVICE_WILDCARDS,
|
|
@@ -195,61 +194,68 @@ DEFAULT_CONFIG = {
|
|
|
195
194
|
# 9. PRINCIPAL VALIDATION
|
|
196
195
|
# ========================================================================
|
|
197
196
|
# Validates Principal elements in resource-based policies
|
|
198
|
-
#
|
|
199
|
-
# Only runs when --policy-type RESOURCE_POLICY
|
|
197
|
+
# Applies to: S3 buckets, SNS topics, SQS queues, Lambda functions, etc.
|
|
198
|
+
# Only runs when: --policy-type RESOURCE_POLICY
|
|
200
199
|
#
|
|
201
|
-
#
|
|
200
|
+
# Three control mechanisms:
|
|
201
|
+
# 1. blocked_principals - Block specific principals (deny list)
|
|
202
|
+
# 2. allowed_principals - Allow only specific principals (whitelist mode)
|
|
203
|
+
# 3. principal_condition_requirements - Require conditions for principals
|
|
204
|
+
# 4. allowed_service_principals - Always allow AWS service principals
|
|
202
205
|
"principal_validation": {
|
|
203
206
|
"enabled": True,
|
|
204
207
|
"severity": "high", # Security issue, not IAM validity error
|
|
205
208
|
"description": "Validates Principal elements in resource policies for security best practices",
|
|
206
|
-
# blocked_principals:
|
|
207
|
-
# Default: ["*"] blocks public access
|
|
208
|
-
# Examples:
|
|
209
|
-
# ["*"] - Block public access
|
|
210
|
-
# ["*", "arn:aws:iam::*:root"] - Block public + all AWS accounts
|
|
209
|
+
# blocked_principals: Deny list - these principals are never allowed
|
|
210
|
+
# Default: ["*"] blocks public access
|
|
211
211
|
"blocked_principals": ["*"],
|
|
212
|
-
# allowed_principals:
|
|
213
|
-
#
|
|
214
|
-
# Examples:
|
|
215
|
-
# [] - Allow all (except blocked)
|
|
216
|
-
# ["arn:aws:iam::123456789012:root"] - Only allow specific account
|
|
217
|
-
# ["arn:aws:iam::*:role/OrgAccessRole"] - Allow specific role in any account
|
|
212
|
+
# allowed_principals: Whitelist mode - when set, ONLY these are allowed
|
|
213
|
+
# Default: [] allows all (except blocked)
|
|
218
214
|
"allowed_principals": [],
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
# Default:
|
|
222
|
-
# Examples:
|
|
223
|
-
# "*": ["aws:SourceArn"] - Public access must specify source ARN
|
|
224
|
-
# "arn:aws:iam::*:root": ["aws:PrincipalOrgID"] - Cross-account must be from org
|
|
225
|
-
"require_conditions_for": {
|
|
226
|
-
"*": [
|
|
227
|
-
"aws:SourceArn",
|
|
228
|
-
"aws:SourceAccount",
|
|
229
|
-
"aws:SourceVpce",
|
|
230
|
-
"aws:SourceIp",
|
|
231
|
-
"aws:SourceOrgID",
|
|
232
|
-
"aws:SourceOrgPaths",
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
# principal_condition_requirements: Advanced condition requirements for principals
|
|
236
|
-
# Similar to action_condition_enforcement but for principals
|
|
237
|
-
# Supports all_of/any_of/none_of logic with rich metadata
|
|
238
|
-
# Default: 2 critical requirements enabled (public_access, prevent_insecure_transport)
|
|
215
|
+
# principal_condition_requirements: Require conditions for specific principals
|
|
216
|
+
# Supports all_of/any_of/none_of logic like action_condition_enforcement
|
|
217
|
+
# Default: 2 enabled (public_access, prevent_insecure_transport)
|
|
239
218
|
# See: iam_validator/core/config/principal_requirements.py
|
|
240
|
-
# To customize requirements, use Python API:
|
|
241
|
-
# from iam_validator.core.config import get_principal_requirements_by_names
|
|
242
|
-
# requirements = get_principal_requirements_by_names(['public_access', 'cross_account_org'])
|
|
243
|
-
# To disable: set to empty list []
|
|
244
219
|
"principal_condition_requirements": get_default_principal_requirements(),
|
|
245
|
-
# allowed_service_principals: AWS service principals
|
|
246
|
-
# Default:
|
|
247
|
-
#
|
|
248
|
-
|
|
249
|
-
|
|
220
|
+
# allowed_service_principals: AWS service principals (*.amazonaws.com)
|
|
221
|
+
# Default: ["aws:*"] allows ALL AWS service principals
|
|
222
|
+
# Note: "aws:*" is different from "*" (public access)
|
|
223
|
+
"allowed_service_principals": ["aws:*"],
|
|
224
|
+
},
|
|
225
|
+
# ========================================================================
|
|
226
|
+
# 10. TRUST POLICY VALIDATION
|
|
227
|
+
# ========================================================================
|
|
228
|
+
# Validate trust policies (role assumption policies) for security best practices
|
|
229
|
+
# Ensures assume role actions have appropriate principals and conditions
|
|
230
|
+
#
|
|
231
|
+
# Key validations:
|
|
232
|
+
# - Action-Principal type matching (e.g., AssumeRoleWithSAML needs Federated)
|
|
233
|
+
# - Provider ARN format validation (SAML vs OIDC provider patterns)
|
|
234
|
+
# - Required conditions per assume method
|
|
235
|
+
#
|
|
236
|
+
# Complements principal_validation check (which validates principal allowlists/blocklists)
|
|
237
|
+
# This check focuses on action-principal coupling specific to trust policies
|
|
238
|
+
#
|
|
239
|
+
# Auto-detection: Only runs on statements with assume role actions
|
|
240
|
+
"trust_policy_validation": {
|
|
241
|
+
"enabled": True, # Enabled by default (auto-detects trust policies)
|
|
242
|
+
"severity": "high", # Security issue
|
|
243
|
+
"description": "Validates trust policies for role assumption security and action-principal coupling",
|
|
244
|
+
# validation_rules: Custom rules override defaults
|
|
245
|
+
# Default rules validate:
|
|
246
|
+
# - sts:AssumeRole → AWS or Service principals
|
|
247
|
+
# - sts:AssumeRoleWithSAML → Federated (SAML provider) with SAML:aud
|
|
248
|
+
# - sts:AssumeRoleWithWebIdentity → Federated (OIDC provider)
|
|
249
|
+
# Example custom rules:
|
|
250
|
+
# "validation_rules": {
|
|
251
|
+
# "sts:AssumeRole": {
|
|
252
|
+
# "allowed_principal_types": ["AWS"], # Only AWS, not Service
|
|
253
|
+
# "required_conditions": ["sts:ExternalId"], # Always require ExternalId
|
|
254
|
+
# }
|
|
255
|
+
# }
|
|
250
256
|
},
|
|
251
257
|
# ========================================================================
|
|
252
|
-
#
|
|
258
|
+
# 11. POLICY TYPE VALIDATION
|
|
253
259
|
# ========================================================================
|
|
254
260
|
# Validate policy type requirements (new in v1.3.0)
|
|
255
261
|
# Ensures policies conform to the declared type (IDENTITY vs RESOURCE_POLICY)
|
|
@@ -264,7 +270,7 @@ DEFAULT_CONFIG = {
|
|
|
264
270
|
"description": "Validates policies match declared type and enforces RCP requirements",
|
|
265
271
|
},
|
|
266
272
|
# ========================================================================
|
|
267
|
-
#
|
|
273
|
+
# 12. ACTION-RESOURCE MATCHING
|
|
268
274
|
# ========================================================================
|
|
269
275
|
# Validate action-resource matching
|
|
270
276
|
# Ensures resources match the required resource types for actions
|
|
@@ -304,7 +310,7 @@ DEFAULT_CONFIG = {
|
|
|
304
310
|
# See: iam_validator/core/config/sensitive_actions.py for sensitive actions
|
|
305
311
|
# ========================================================================
|
|
306
312
|
# ========================================================================
|
|
307
|
-
#
|
|
313
|
+
# 13. WILDCARD ACTION
|
|
308
314
|
# ========================================================================
|
|
309
315
|
# Check for wildcard actions (Action: "*")
|
|
310
316
|
# Flags statements that allow all actions
|
|
@@ -323,7 +329,7 @@ DEFAULT_CONFIG = {
|
|
|
323
329
|
),
|
|
324
330
|
},
|
|
325
331
|
# ========================================================================
|
|
326
|
-
#
|
|
332
|
+
# 14. WILDCARD RESOURCE
|
|
327
333
|
# ========================================================================
|
|
328
334
|
# Check for wildcard resources (Resource: "*")
|
|
329
335
|
# Flags statements that apply to all resources
|
|
@@ -350,7 +356,7 @@ DEFAULT_CONFIG = {
|
|
|
350
356
|
),
|
|
351
357
|
},
|
|
352
358
|
# ========================================================================
|
|
353
|
-
#
|
|
359
|
+
# 15. FULL WILDCARD (CRITICAL)
|
|
354
360
|
# ========================================================================
|
|
355
361
|
# Check for BOTH Action: "*" AND Resource: "*" (CRITICAL)
|
|
356
362
|
# This grants full administrative access (AdministratorAccess equivalent)
|
|
@@ -374,7 +380,7 @@ DEFAULT_CONFIG = {
|
|
|
374
380
|
),
|
|
375
381
|
},
|
|
376
382
|
# ========================================================================
|
|
377
|
-
#
|
|
383
|
+
# 16. SERVICE WILDCARD
|
|
378
384
|
# ========================================================================
|
|
379
385
|
# Check for service-level wildcards (e.g., "iam:*", "s3:*", "ec2:*")
|
|
380
386
|
# These grant ALL permissions for a service (often too permissive)
|
|
@@ -404,7 +410,7 @@ DEFAULT_CONFIG = {
|
|
|
404
410
|
),
|
|
405
411
|
},
|
|
406
412
|
# ========================================================================
|
|
407
|
-
#
|
|
413
|
+
# 17. SENSITIVE ACTION
|
|
408
414
|
# ========================================================================
|
|
409
415
|
# Check for sensitive actions without IAM conditions
|
|
410
416
|
# Sensitive actions: IAM changes, secrets access, destructive operations
|
|
@@ -479,7 +485,7 @@ DEFAULT_CONFIG = {
|
|
|
479
485
|
],
|
|
480
486
|
},
|
|
481
487
|
# ========================================================================
|
|
482
|
-
#
|
|
488
|
+
# 18. ACTION CONDITION ENFORCEMENT
|
|
483
489
|
# ========================================================================
|
|
484
490
|
# Enforce specific IAM condition requirements for actions
|
|
485
491
|
# Examples: iam:PassRole must specify iam:PassedToService,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Service principals utilities for resource policy validation.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
This module provides:
|
|
5
|
+
- Default list of common AWS service principals
|
|
6
|
+
- Utility to check if a principal is any AWS service principal
|
|
7
|
+
- Functions to categorize service principals by type
|
|
8
|
+
|
|
9
|
+
Configuration:
|
|
10
|
+
- Use "*" in allowed_service_principals to allow ALL AWS service principals
|
|
11
|
+
- Use explicit list to restrict to specific services only
|
|
12
|
+
- AWS service principals end with .amazonaws.com or .amazonaws.com.cn
|
|
6
13
|
"""
|
|
7
14
|
|
|
8
15
|
from typing import Final
|
|
@@ -58,6 +65,36 @@ def is_allowed_service_principal(principal: str) -> bool:
|
|
|
58
65
|
return principal in DEFAULT_SERVICE_PRINCIPALS
|
|
59
66
|
|
|
60
67
|
|
|
68
|
+
def is_aws_service_principal(principal: str) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Check if a principal is an AWS service principal (any AWS service).
|
|
71
|
+
|
|
72
|
+
This checks if the principal matches the AWS service principal pattern.
|
|
73
|
+
AWS service principals typically end with ".amazonaws.com" or ".amazonaws.com.cn"
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
principal: Principal to check (e.g., "lambda.amazonaws.com", "s3.amazonaws.com.cn")
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if principal matches AWS service principal pattern
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
>>> is_aws_service_principal("lambda.amazonaws.com")
|
|
83
|
+
True
|
|
84
|
+
>>> is_aws_service_principal("s3.amazonaws.com.cn")
|
|
85
|
+
True
|
|
86
|
+
>>> is_aws_service_principal("arn:aws:iam::123456789012:root")
|
|
87
|
+
False
|
|
88
|
+
>>> is_aws_service_principal("*")
|
|
89
|
+
False
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(principal, str):
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# AWS service principals end with .amazonaws.com or .amazonaws.com.cn
|
|
95
|
+
return principal.endswith(".amazonaws.com") or principal.endswith(".amazonaws.com.cn")
|
|
96
|
+
|
|
97
|
+
|
|
61
98
|
def get_service_principals_by_category() -> dict[str, tuple[str, ...]]:
|
|
62
99
|
"""
|
|
63
100
|
Get service principals organized by service category.
|