iam-policy-validator 1.1.0__py3-none-any.whl → 1.1.2__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.
- {iam_policy_validator-1.1.0.dist-info → iam_policy_validator-1.1.2.dist-info}/METADATA +2 -2
- {iam_policy_validator-1.1.0.dist-info → iam_policy_validator-1.1.2.dist-info}/RECORD +25 -19
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/action_resource_constraint.py +151 -0
- iam_validator/checks/action_validation.py +18 -138
- iam_validator/checks/security_best_practices.py +152 -402
- iam_validator/checks/utils/__init__.py +1 -0
- iam_validator/checks/utils/policy_level_checks.py +143 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +252 -0
- iam_validator/checks/utils/wildcard_expansion.py +89 -0
- iam_validator/commands/__init__.py +3 -1
- iam_validator/commands/cache.py +402 -0
- iam_validator/core/access_analyzer_report.py +2 -1
- iam_validator/core/aws_fetcher.py +79 -19
- iam_validator/core/check_registry.py +3 -0
- iam_validator/core/cli.py +1 -1
- iam_validator/core/config_loader.py +1 -0
- iam_validator/core/defaults.py +103 -73
- iam_validator/core/formatters/enhanced.py +6 -1
- iam_validator/core/policy_checks.py +21 -2
- iam_validator/core/report.py +8 -1
- {iam_policy_validator-1.1.0.dist-info → iam_policy_validator-1.1.2.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.1.0.dist-info → iam_policy_validator-1.1.2.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.1.0.dist-info → iam_policy_validator-1.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iam-policy-validator
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Validate AWS IAM policies for correctness and security using AWS Service Reference API
|
|
5
5
|
Project-URL: Homepage, https://github.com/boogy/iam-policy-validator
|
|
6
6
|
Project-URL: Documentation, https://github.com/boogy/iam-policy-validator/tree/main/docs
|
|
@@ -197,7 +197,7 @@ jobs:
|
|
|
197
197
|
- name: Set up Python
|
|
198
198
|
uses: actions/setup-python@v5
|
|
199
199
|
with:
|
|
200
|
-
python-version: '3.
|
|
200
|
+
python-version: '3.12'
|
|
201
201
|
|
|
202
202
|
- name: Install uv
|
|
203
203
|
uses: astral-sh/setup-uv@v3
|
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
iam_validator/__init__.py,sha256=APnMR3Fu4fHhxfsHBvUM2dJIwazgvLKQbfOsSgFPidg,693
|
|
2
2
|
iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
|
|
3
|
-
iam_validator/__version__.py,sha256=
|
|
4
|
-
iam_validator/checks/__init__.py,sha256=
|
|
3
|
+
iam_validator/__version__.py,sha256=Gnq5VxEcBl7XBv8aQLOpUL1P1P6Gnsze4sf794FzMWs,206
|
|
4
|
+
iam_validator/checks/__init__.py,sha256=eKTPgiZ1i3zvyP6OdKgLx9s3u69onITMYifmJPJwZgM,968
|
|
5
5
|
iam_validator/checks/action_condition_enforcement.py,sha256=3M1Wj89Af6H-ywBTruZbJPzhCBBQVanVb5hwv-fkiDE,29721
|
|
6
|
-
iam_validator/checks/
|
|
6
|
+
iam_validator/checks/action_resource_constraint.py,sha256=p-gP7S9QYR6M7vffrnJY6LOlMUTn0kpEbrxQ8pTY5rs,6031
|
|
7
|
+
iam_validator/checks/action_validation.py,sha256=IpxtTsk58f2zEZ-xzAoyHw4QK8BCRV43OffP-8ydf9E,2578
|
|
7
8
|
iam_validator/checks/condition_key_validation.py,sha256=bc4LQ8IRKyt0RquaQvQvVjmeJnuOUAFRL8xdduLPa_U,2661
|
|
8
9
|
iam_validator/checks/policy_size.py,sha256=4cvZiWRJXGuvYo8PRcdD1Py_ZL8Xw0lOJfXTs6EX-_I,5753
|
|
9
10
|
iam_validator/checks/resource_validation.py,sha256=AEIoiR6AKYLuVaA8ne3QE5qy6NCMDe98_2JAiwE9-JU,4261
|
|
10
|
-
iam_validator/checks/security_best_practices.py,sha256
|
|
11
|
+
iam_validator/checks/security_best_practices.py,sha256=-gqxtcx_cUV1ZnyZ8Flydwan1vxb-RmnanWoIlU6YyY,21711
|
|
11
12
|
iam_validator/checks/sid_uniqueness.py,sha256=7S8RtVJgYPTKgr7gSEmxgT0oIGkSoXN6iu0ALHbcSfw,5015
|
|
12
|
-
iam_validator/
|
|
13
|
+
iam_validator/checks/utils/__init__.py,sha256=j0X4ibUB6RGx2a-kNoJnlVZwHfoEvzZsIeTmJIAoFzA,45
|
|
14
|
+
iam_validator/checks/utils/policy_level_checks.py,sha256=2V60C0zhKfsFPjQ-NMlD3EemtwA9S6-4no8nETgXdQE,5274
|
|
15
|
+
iam_validator/checks/utils/sensitive_action_matcher.py,sha256=VlTpgjMnympYa28kOdm6xRIUL2P87rOvm1O2NdnjtVI,8900
|
|
16
|
+
iam_validator/checks/utils/wildcard_expansion.py,sha256=L6AWrLRacqXqk9k-5ZmXv5HyoAyz98cg5AlTvzH2tTI,3158
|
|
17
|
+
iam_validator/commands/__init__.py,sha256=lF0fSUukLSxTAvhjg-0P79YMseYwihIr_tmQYbfNgcY,425
|
|
13
18
|
iam_validator/commands/analyze.py,sha256=TWlDaZ8gVOdNv6__KQQfzeLVW36qLiL5IzlhGYfvq_g,16501
|
|
14
19
|
iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gzs,1074
|
|
20
|
+
iam_validator/commands/cache.py,sha256=1E-irKF3e2CFUEG9s1z64hIKLVSYFQ-L92ld6L3za5g,14368
|
|
15
21
|
iam_validator/commands/post_to_pr.py,sha256=hl_K-XlELYN-ArjMdgQqysvIE-26yf9XdrMl4ToDwG0,2148
|
|
16
22
|
iam_validator/commands/validate.py,sha256=R295cOTly8n7zL1jfvbh9RuCgiM5edBqbf6YMn_4G9A,14013
|
|
17
23
|
iam_validator/core/__init__.py,sha256=1FvJPMrbzJfS9YbRUJCshJLd5gzWwR9Fd_slS0Aq9c8,416
|
|
18
24
|
iam_validator/core/access_analyzer.py,sha256=poeT1i74jXpKr1B3UmvqiTvCTbq82zffWgZHwiFUwoo,24337
|
|
19
|
-
iam_validator/core/access_analyzer_report.py,sha256=
|
|
20
|
-
iam_validator/core/aws_fetcher.py,sha256=
|
|
25
|
+
iam_validator/core/access_analyzer_report.py,sha256=IrQVszlhFfQ6WykYLpig7TU3hf8dnQTegPDsOvHjR5Q,24873
|
|
26
|
+
iam_validator/core/aws_fetcher.py,sha256=P7fYX1Q1TICuTOlGaqH97U3m8B0bqGE9jP7cxfmny8k,27418
|
|
21
27
|
iam_validator/core/aws_global_conditions.py,sha256=ADVcMEWhgvDZWdBmRUQN3HB7a9OycbTLecXFAy3LPbo,5837
|
|
22
|
-
iam_validator/core/check_registry.py,sha256=
|
|
23
|
-
iam_validator/core/cli.py,sha256=
|
|
24
|
-
iam_validator/core/config_loader.py,sha256=
|
|
25
|
-
iam_validator/core/defaults.py,sha256=
|
|
28
|
+
iam_validator/core/check_registry.py,sha256=wxqaF2t_3lWgT6x7_PnnZ8XGjHKUxUk72UlmdYBLFyo,15679
|
|
29
|
+
iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
|
|
30
|
+
iam_validator/core/config_loader.py,sha256=Pq2rd6LJtEZET0ZeW4hEZS2ZRLC5gNRsKbtLyIsT21I,16516
|
|
31
|
+
iam_validator/core/defaults.py,sha256=sPQJUMyjv4yalGCuyQhlY42rDc_-BZLq6qS0GgoP4mc,11893
|
|
26
32
|
iam_validator/core/models.py,sha256=rWIZnD-I81Sg4asgOhnB10FWJC5mxQ2JO9bdS0sHb4Q,10772
|
|
27
|
-
iam_validator/core/policy_checks.py,sha256=
|
|
33
|
+
iam_validator/core/policy_checks.py,sha256=vIzRkqf5k1BB0elry5a4E2SRBlp6Vz3jPqrav29k3PM,24842
|
|
28
34
|
iam_validator/core/policy_loader.py,sha256=TR7SpzlRG3TwH4HBGEFUuhNOmxIR8Cud2SQ-AmHWBpM,14040
|
|
29
35
|
iam_validator/core/pr_commenter.py,sha256=TOhVXKTFcRHQ9EVuShXQcKXn9aNjB1mU6FnR2jvltmw,10581
|
|
30
|
-
iam_validator/core/report.py,sha256=
|
|
36
|
+
iam_validator/core/report.py,sha256=wPkLvsIej-AaW5FlqntvUuHuEMvyi2iBI6NQF496gCM,33064
|
|
31
37
|
iam_validator/core/formatters/__init__.py,sha256=fnCKAEBXItnOf2m4rhVs7zwMaTxbG6ESh3CF8V5j5ec,868
|
|
32
38
|
iam_validator/core/formatters/base.py,sha256=SShDeDiy5mYQnS6BpA8xYg91N-KX1EObkOtlrVHqx1Q,4451
|
|
33
39
|
iam_validator/core/formatters/console.py,sha256=lX4Yp4bTW61fxe0fCiHuO6bCZtC_6cjCwqDNQ55nT_8,1937
|
|
34
40
|
iam_validator/core/formatters/csv.py,sha256=2FaN6Y_0TPMFOb3A3tNtj0-9bkEc5P-6eZ7eLROIqFE,5899
|
|
35
|
-
iam_validator/core/formatters/enhanced.py,sha256=
|
|
41
|
+
iam_validator/core/formatters/enhanced.py,sha256=k_DwzhGTARUKMv4bjkaCrpI6ypT10z9LcSk8gKlyDIM,16547
|
|
36
42
|
iam_validator/core/formatters/html.py,sha256=j4sQi-wXiD9kCHldW5JCzbJe0frhiP5uQI9KlH3Sj_g,22994
|
|
37
43
|
iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFVXG2bgnew,939
|
|
38
44
|
iam_validator/core/formatters/markdown.py,sha256=aPAY6FpZBHsVBDag3FAsB_X9CZzznFjX9dQr0ysDrTE,2251
|
|
@@ -40,8 +46,8 @@ iam_validator/core/formatters/sarif.py,sha256=tqp8g7RmUh0HRk-kKDaucx4sa-5I9ikgkS
|
|
|
40
46
|
iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
|
|
41
47
|
iam_validator/integrations/github_integration.py,sha256=bKs94vNT4PmcmUPUeuY2WJFhCYpUY2SWiBP1vj-andA,25673
|
|
42
48
|
iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
|
|
43
|
-
iam_policy_validator-1.1.
|
|
44
|
-
iam_policy_validator-1.1.
|
|
45
|
-
iam_policy_validator-1.1.
|
|
46
|
-
iam_policy_validator-1.1.
|
|
47
|
-
iam_policy_validator-1.1.
|
|
49
|
+
iam_policy_validator-1.1.2.dist-info/METADATA,sha256=mjrd4ODBydvBtCh_0Ztq0-FqQTNunBpEdIrDQ-5RXos,25144
|
|
50
|
+
iam_policy_validator-1.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
51
|
+
iam_policy_validator-1.1.2.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
52
|
+
iam_policy_validator-1.1.2.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
53
|
+
iam_policy_validator-1.1.2.dist-info/RECORD,,
|
iam_validator/__version__.py
CHANGED
iam_validator/checks/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ Built-in policy checks for IAM Policy Validator.
|
|
|
5
5
|
from iam_validator.checks.action_condition_enforcement import (
|
|
6
6
|
ActionConditionEnforcementCheck,
|
|
7
7
|
)
|
|
8
|
+
from iam_validator.checks.action_resource_constraint import ActionResourceConstraintCheck
|
|
8
9
|
from iam_validator.checks.action_validation import ActionValidationCheck
|
|
9
10
|
from iam_validator.checks.condition_key_validation import ConditionKeyValidationCheck
|
|
10
11
|
from iam_validator.checks.policy_size import PolicySizeCheck
|
|
@@ -14,6 +15,7 @@ from iam_validator.checks.sid_uniqueness import SidUniquenessCheck
|
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
"ActionConditionEnforcementCheck",
|
|
18
|
+
"ActionResourceConstraintCheck",
|
|
17
19
|
"ActionValidationCheck",
|
|
18
20
|
"ConditionKeyValidationCheck",
|
|
19
21
|
"PolicySizeCheck",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Action resource constraint check - validates resource constraints for actions.
|
|
2
|
+
|
|
3
|
+
This check ensures that:
|
|
4
|
+
- Actions WITHOUT required resource types (empty or missing Resources field in AWS API)
|
|
5
|
+
MUST use Resource: "*" because they are account-level operations
|
|
6
|
+
|
|
7
|
+
Examples of actions without required resources:
|
|
8
|
+
- s3:ListAllMyBuckets (lists all buckets in account)
|
|
9
|
+
- iam:ListRoles (lists all roles in account)
|
|
10
|
+
- ec2:DescribeInstances (describes instances across all regions)
|
|
11
|
+
|
|
12
|
+
These actions cannot target specific resources because they operate at the account level.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
16
|
+
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
17
|
+
from iam_validator.core.models import Statement, ValidationIssue
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ActionResourceConstraintCheck(PolicyCheck):
|
|
21
|
+
"""Validates resource constraints based on action requirements.
|
|
22
|
+
This check ensures that actions without required resource types use Resource: "*".
|
|
23
|
+
|
|
24
|
+
Examples of such actions include s3:ListAllMyBuckets, iam:ListRoles, etc.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def check_id(self) -> str:
|
|
29
|
+
return "action_resource_constraint"
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def description(self) -> str:
|
|
33
|
+
return "Validates that actions without required resource types use Resource: '*'"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def default_severity(self) -> str:
|
|
37
|
+
return "error"
|
|
38
|
+
|
|
39
|
+
async def execute(
|
|
40
|
+
self,
|
|
41
|
+
statement: Statement,
|
|
42
|
+
statement_idx: int,
|
|
43
|
+
fetcher: AWSServiceFetcher,
|
|
44
|
+
config: CheckConfig,
|
|
45
|
+
) -> list[ValidationIssue]:
|
|
46
|
+
"""Execute action resource constraint validation on a statement."""
|
|
47
|
+
issues = []
|
|
48
|
+
|
|
49
|
+
# Only check Allow statements
|
|
50
|
+
if statement.effect != "Allow":
|
|
51
|
+
return issues
|
|
52
|
+
|
|
53
|
+
# Get actions and resources from statement
|
|
54
|
+
actions = statement.get_actions()
|
|
55
|
+
resources = statement.get_resources()
|
|
56
|
+
statement_sid = statement.sid
|
|
57
|
+
line_number = statement.line_number
|
|
58
|
+
|
|
59
|
+
# Skip if no actions or wildcard action
|
|
60
|
+
if not actions or "*" in actions:
|
|
61
|
+
return issues
|
|
62
|
+
|
|
63
|
+
# Skip if already using wildcard resource (this is correct for these actions)
|
|
64
|
+
if "*" in resources:
|
|
65
|
+
return issues
|
|
66
|
+
|
|
67
|
+
# Check each action for resource requirements
|
|
68
|
+
actions_without_required_resources = []
|
|
69
|
+
|
|
70
|
+
for action in actions:
|
|
71
|
+
# Skip wildcard actions
|
|
72
|
+
if "*" in action:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
# Parse action to get service and action name
|
|
77
|
+
service_prefix, action_name = fetcher.parse_action(action)
|
|
78
|
+
|
|
79
|
+
# Fetch service detail to check resource requirements
|
|
80
|
+
service_detail = await fetcher.fetch_service_by_name(service_prefix)
|
|
81
|
+
|
|
82
|
+
# Check if action exists
|
|
83
|
+
if action_name not in service_detail.actions:
|
|
84
|
+
# Action doesn't exist - skip (will be caught by action_validation_check)
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
action_detail = service_detail.actions[action_name]
|
|
88
|
+
|
|
89
|
+
# Check if action has NO required resources (empty or missing Resources field)
|
|
90
|
+
has_no_required_resources = (
|
|
91
|
+
not action_detail.resources or len(action_detail.resources) == 0
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if has_no_required_resources:
|
|
95
|
+
actions_without_required_resources.append(action)
|
|
96
|
+
|
|
97
|
+
except ValueError:
|
|
98
|
+
# Invalid action format - skip (will be caught by action_validation_check)
|
|
99
|
+
continue
|
|
100
|
+
except Exception:
|
|
101
|
+
# Service not found or other error - skip
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# If we found actions without required resources, report the issue
|
|
105
|
+
if actions_without_required_resources:
|
|
106
|
+
# Get a sample of the resources to show in error message
|
|
107
|
+
resource_sample = resources[:3] if len(resources) > 3 else resources
|
|
108
|
+
resource_display = ", ".join(f'"{r}"' for r in resource_sample)
|
|
109
|
+
if len(resources) > 3:
|
|
110
|
+
resource_display += f", ... ({len(resources) - 3} more)"
|
|
111
|
+
|
|
112
|
+
# Format action list
|
|
113
|
+
action_list = ", ".join(f'"{a}"' for a in actions_without_required_resources)
|
|
114
|
+
|
|
115
|
+
# Determine message based on how many actions are affected
|
|
116
|
+
if len(actions_without_required_resources) == 1:
|
|
117
|
+
message = (
|
|
118
|
+
f"Action {action_list} does not operate on specific resources "
|
|
119
|
+
f'and requires Resource: "*"'
|
|
120
|
+
)
|
|
121
|
+
suggestion = (
|
|
122
|
+
f"Action {action_list} is an account-level operation that cannot target "
|
|
123
|
+
'specific resources. Move this action to a separate statement with Resource: "*", '
|
|
124
|
+
"and keep resource-specific actions in another statement with their specific ARNs"
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
message = (
|
|
128
|
+
f"Actions {action_list} do not operate on specific resources "
|
|
129
|
+
f'and require Resource: "*"'
|
|
130
|
+
)
|
|
131
|
+
suggestion = (
|
|
132
|
+
"These actions are account-level operations that cannot target "
|
|
133
|
+
'specific resources. Move these actions to a dedicated statement with Resource: "*", '
|
|
134
|
+
"and keep resource-specific actions in separate statements with their specific ARNs"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
issues.append(
|
|
138
|
+
ValidationIssue(
|
|
139
|
+
severity=self.get_severity(config),
|
|
140
|
+
statement_sid=statement_sid,
|
|
141
|
+
statement_index=statement_idx,
|
|
142
|
+
issue_type="invalid_resource_constraint",
|
|
143
|
+
message=message,
|
|
144
|
+
action=action_list,
|
|
145
|
+
resource=resource_display,
|
|
146
|
+
suggestion=suggestion,
|
|
147
|
+
line_number=line_number,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return issues
|
|
@@ -1,73 +1,22 @@
|
|
|
1
|
-
"""Action validation check - validates IAM actions against AWS service definitions.
|
|
1
|
+
"""Action validation check - validates IAM actions against AWS service definitions.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This check ensures that all actions specified in IAM policies are valid actions
|
|
4
|
+
defined by AWS services. It helps identify typos or deprecated actions that may
|
|
5
|
+
lead to unintended access permissions.
|
|
6
|
+
|
|
7
|
+
This check is not necessary when using Access Analyzer, as it performs similar
|
|
8
|
+
validations. However, it can be useful in environments where Access Analyzer is
|
|
9
|
+
not available or for pre-deployment policy validation to catch errors early.
|
|
10
|
+
"""
|
|
6
11
|
|
|
7
12
|
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
8
13
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
9
14
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
10
15
|
|
|
11
16
|
|
|
12
|
-
# Global cache for compiled wildcard patterns
|
|
13
|
-
@lru_cache(maxsize=512)
|
|
14
|
-
def _compile_wildcard_pattern(pattern: str) -> Pattern[str]:
|
|
15
|
-
"""Compile and cache wildcard patterns for O(1) reuse.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
pattern: Wildcard pattern (e.g., "s3:Get*")
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
Compiled regex pattern
|
|
22
|
-
|
|
23
|
-
Performance:
|
|
24
|
-
20-30x speedup by avoiding repeated pattern compilation
|
|
25
|
-
"""
|
|
26
|
-
regex_pattern = "^" + re.escape(pattern).replace(r"\*", ".*") + "$"
|
|
27
|
-
return re.compile(regex_pattern, re.IGNORECASE)
|
|
28
|
-
|
|
29
|
-
|
|
30
17
|
class ActionValidationCheck(PolicyCheck):
|
|
31
18
|
"""Validates that IAM actions exist in AWS services."""
|
|
32
19
|
|
|
33
|
-
# Default allowlist for safe wildcard patterns (read-only operations)
|
|
34
|
-
# Note: s3:Get* is intentionally excluded as reading S3 data can be sensitive
|
|
35
|
-
# Using frozenset for O(1) lookup performance
|
|
36
|
-
DEFAULT_ALLOWED_WILDCARDS = frozenset(
|
|
37
|
-
{
|
|
38
|
-
"s3:List*",
|
|
39
|
-
"s3:Describe*",
|
|
40
|
-
"ec2:Describe*",
|
|
41
|
-
"iam:Get*",
|
|
42
|
-
"iam:List*",
|
|
43
|
-
"rds:Describe*",
|
|
44
|
-
"lambda:Get*",
|
|
45
|
-
"lambda:List*",
|
|
46
|
-
"dynamodb:Describe*",
|
|
47
|
-
"cloudwatch:Describe*",
|
|
48
|
-
"cloudwatch:Get*",
|
|
49
|
-
"cloudwatch:List*",
|
|
50
|
-
"logs:Describe*",
|
|
51
|
-
"logs:Get*",
|
|
52
|
-
"logs:Filter*",
|
|
53
|
-
"kms:Describe*",
|
|
54
|
-
"kms:Get*",
|
|
55
|
-
"kms:List*",
|
|
56
|
-
"sns:Get*",
|
|
57
|
-
"sns:List*",
|
|
58
|
-
"sqs:Get*",
|
|
59
|
-
"sqs:List*",
|
|
60
|
-
"elasticloadbalancing:Describe*",
|
|
61
|
-
"autoscaling:Describe*",
|
|
62
|
-
"cloudformation:Describe*",
|
|
63
|
-
"cloudformation:Get*",
|
|
64
|
-
"cloudformation:List*",
|
|
65
|
-
"route53:Get*",
|
|
66
|
-
"route53:List*",
|
|
67
|
-
"apigateway:GET",
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
20
|
@property
|
|
72
21
|
def check_id(self) -> str:
|
|
73
22
|
return "action_validation"
|
|
@@ -80,41 +29,6 @@ class ActionValidationCheck(PolicyCheck):
|
|
|
80
29
|
def default_severity(self) -> str:
|
|
81
30
|
return "error"
|
|
82
31
|
|
|
83
|
-
def _is_allowed_wildcard(
|
|
84
|
-
self, action: str, allowed_wildcards: frozenset[str] | list[str] | set[str]
|
|
85
|
-
) -> bool:
|
|
86
|
-
"""Check if a wildcard action matches the allowlist.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
action: The action to check (e.g., "s3:Get*")
|
|
90
|
-
allowed_wildcards: Set or list of allowed wildcard patterns
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
True if the action matches any pattern in the allowlist
|
|
94
|
-
|
|
95
|
-
Note:
|
|
96
|
-
Exact matches use O(1) set lookup for performance.
|
|
97
|
-
Pattern matches (wildcards in allowlist) require O(n) iteration.
|
|
98
|
-
"""
|
|
99
|
-
# Fast O(1) exact match using set membership
|
|
100
|
-
if action in allowed_wildcards:
|
|
101
|
-
return True
|
|
102
|
-
|
|
103
|
-
# Pattern match - check if action matches any pattern in allowlist
|
|
104
|
-
# This is needed when allowlist contains wildcards like "s3:*"
|
|
105
|
-
# Uses cached compiled patterns for 20-30x speedup
|
|
106
|
-
for pattern in allowed_wildcards:
|
|
107
|
-
# Skip exact matches (already checked above)
|
|
108
|
-
if "*" not in pattern:
|
|
109
|
-
continue
|
|
110
|
-
|
|
111
|
-
# Use cached compiled pattern
|
|
112
|
-
compiled = _compile_wildcard_pattern(pattern)
|
|
113
|
-
if compiled.match(action):
|
|
114
|
-
return True
|
|
115
|
-
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
32
|
async def execute(
|
|
119
33
|
self,
|
|
120
34
|
statement: Statement,
|
|
@@ -122,7 +36,11 @@ class ActionValidationCheck(PolicyCheck):
|
|
|
122
36
|
fetcher: AWSServiceFetcher,
|
|
123
37
|
config: CheckConfig,
|
|
124
38
|
) -> list[ValidationIssue]:
|
|
125
|
-
"""Execute action validation on a statement.
|
|
39
|
+
"""Execute action validation on a statement.
|
|
40
|
+
|
|
41
|
+
This check ONLY validates that actions exist in AWS service definitions.
|
|
42
|
+
Wildcard security checks are handled by security_best_practices_check.
|
|
43
|
+
"""
|
|
126
44
|
issues = []
|
|
127
45
|
|
|
128
46
|
# Get actions from statement
|
|
@@ -130,28 +48,13 @@ class ActionValidationCheck(PolicyCheck):
|
|
|
130
48
|
statement_sid = statement.sid
|
|
131
49
|
line_number = statement.line_number
|
|
132
50
|
|
|
133
|
-
# Get allowed wildcards from config, or use defaults
|
|
134
|
-
allowed_wildcards_raw = config.config.get(
|
|
135
|
-
"allowed_wildcards", self.DEFAULT_ALLOWED_WILDCARDS
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# Convert list to frozenset for O(1) lookups (if from config)
|
|
139
|
-
# DEFAULT_ALLOWED_WILDCARDS is already a frozenset
|
|
140
|
-
if isinstance(allowed_wildcards_raw, list):
|
|
141
|
-
allowed_wildcards = frozenset(allowed_wildcards_raw)
|
|
142
|
-
else:
|
|
143
|
-
allowed_wildcards = allowed_wildcards_raw
|
|
144
|
-
|
|
145
|
-
# Check if wildcard warnings are disabled entirely
|
|
146
|
-
disable_wildcard_warnings = config.config.get("disable_wildcard_warnings", False)
|
|
147
|
-
|
|
148
51
|
for action in actions:
|
|
149
|
-
#
|
|
150
|
-
if action == "*":
|
|
52
|
+
# Skip wildcard actions - they're handled by security_best_practices_check
|
|
53
|
+
if action == "*" or "*" in action:
|
|
151
54
|
continue
|
|
152
55
|
|
|
153
|
-
# Validate the action
|
|
154
|
-
is_valid, error_msg,
|
|
56
|
+
# Validate the action exists in AWS
|
|
57
|
+
is_valid, error_msg, _is_wildcard = await fetcher.validate_action(action)
|
|
155
58
|
|
|
156
59
|
if not is_valid:
|
|
157
60
|
issues.append(
|
|
@@ -165,28 +68,5 @@ class ActionValidationCheck(PolicyCheck):
|
|
|
165
68
|
line_number=line_number,
|
|
166
69
|
)
|
|
167
70
|
)
|
|
168
|
-
elif is_wildcard:
|
|
169
|
-
# Check if this wildcard is in the allowlist
|
|
170
|
-
if self._is_allowed_wildcard(action, allowed_wildcards):
|
|
171
|
-
# Wildcard is allowed, skip warning
|
|
172
|
-
continue
|
|
173
|
-
|
|
174
|
-
# Wildcard actions are security concerns (unless disabled)
|
|
175
|
-
# Note: This uses "info" severity by default because the security_best_practices
|
|
176
|
-
# check provides more comprehensive wildcard analysis with proper security severities.
|
|
177
|
-
# This is mainly informational - the action IS valid, just uses a wildcard.
|
|
178
|
-
if not disable_wildcard_warnings:
|
|
179
|
-
issues.append(
|
|
180
|
-
ValidationIssue(
|
|
181
|
-
severity="info", # Changed from "warning" - this is just informational
|
|
182
|
-
statement_sid=statement_sid,
|
|
183
|
-
statement_index=statement_idx,
|
|
184
|
-
issue_type="wildcard_action",
|
|
185
|
-
message=f"Action uses wildcard: {action}",
|
|
186
|
-
action=action,
|
|
187
|
-
suggestion="Consider using specific actions instead of wildcards for better security",
|
|
188
|
-
line_number=line_number,
|
|
189
|
-
)
|
|
190
|
-
)
|
|
191
71
|
|
|
192
72
|
return issues
|