iam-policy-validator 1.4.0__py3-none-any.whl → 1.5.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.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/METADATA +18 -19
- {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/RECORD +31 -20
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +13 -3
- iam_validator/checks/action_condition_enforcement.py +1 -6
- iam_validator/checks/condition_key_validation.py +21 -1
- iam_validator/checks/full_wildcard.py +67 -0
- iam_validator/checks/principal_validation.py +497 -3
- iam_validator/checks/sensitive_action.py +178 -0
- iam_validator/checks/service_wildcard.py +105 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +39 -31
- iam_validator/checks/wildcard_action.py +62 -0
- iam_validator/checks/wildcard_resource.py +131 -0
- iam_validator/commands/download_services.py +3 -8
- iam_validator/commands/validate.py +28 -2
- iam_validator/core/aws_fetcher.py +25 -12
- iam_validator/core/check_registry.py +15 -21
- iam_validator/core/config/__init__.py +83 -0
- iam_validator/core/config/aws_api.py +35 -0
- iam_validator/core/config/condition_requirements.py +535 -0
- iam_validator/core/config/defaults.py +390 -0
- iam_validator/core/config/principal_requirements.py +421 -0
- iam_validator/core/config/sensitive_actions.py +133 -0
- iam_validator/core/config/service_principals.py +95 -0
- iam_validator/core/config/wildcards.py +124 -0
- iam_validator/core/config_loader.py +29 -9
- iam_validator/core/formatters/enhanced.py +11 -5
- iam_validator/core/formatters/sarif.py +78 -14
- iam_validator/checks/security_best_practices.py +0 -536
- iam_validator/core/defaults.py +0 -393
- {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/licenses/LICENSE +0 -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
|
+
"""
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sensitive actions catalog for IAM Policy Validator.
|
|
3
|
+
|
|
4
|
+
This module defines a curated list of sensitive AWS actions that should
|
|
5
|
+
typically have IAM conditions to limit when they can be used.
|
|
6
|
+
|
|
7
|
+
Using Python instead of YAML/JSON provides:
|
|
8
|
+
- Zero parsing overhead (compiled to .pyc)
|
|
9
|
+
- Better PyPI packaging (no data files)
|
|
10
|
+
- Type hints and IDE autocomplete
|
|
11
|
+
- Easy to extend and maintain
|
|
12
|
+
|
|
13
|
+
The list is a frozenset for O(1) lookup performance.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Final
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# Sensitive Actions List
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# These actions are considered sensitive and should typically have IAM
|
|
22
|
+
# conditions to limit when they can be used. Examples include:
|
|
23
|
+
# - IAM identity and access management changes
|
|
24
|
+
# - Secrets and credential operations
|
|
25
|
+
# - Destructive operations (deletions)
|
|
26
|
+
# - Security configuration changes
|
|
27
|
+
# - Network and firewall modifications
|
|
28
|
+
# - Logging and audit trail changes
|
|
29
|
+
# ============================================================================
|
|
30
|
+
|
|
31
|
+
DEFAULT_SENSITIVE_ACTIONS: Final[frozenset[str]] = frozenset(
|
|
32
|
+
{
|
|
33
|
+
# IAM & Identity Management
|
|
34
|
+
"iam:AddClientIDToOpenIDConnectProvider",
|
|
35
|
+
"iam:AttachRolePolicy",
|
|
36
|
+
"iam:AttachUserPolicy",
|
|
37
|
+
"iam:CreateAccessKey",
|
|
38
|
+
"iam:CreateOpenIDConnectProvider",
|
|
39
|
+
"iam:CreatePolicyVersion",
|
|
40
|
+
"iam:CreateRole",
|
|
41
|
+
"iam:CreateSAMLProvider",
|
|
42
|
+
"iam:CreateUser",
|
|
43
|
+
"iam:DeleteAccessKey",
|
|
44
|
+
"iam:DeleteLoginProfile",
|
|
45
|
+
"iam:DeleteOpenIDConnectProvider",
|
|
46
|
+
"iam:DeleteRole",
|
|
47
|
+
"iam:DeleteRolePolicy",
|
|
48
|
+
"iam:DeleteSAMLProvider",
|
|
49
|
+
"iam:DeleteUser",
|
|
50
|
+
"iam:DeleteUserPolicy",
|
|
51
|
+
"iam:DetachRolePolicy",
|
|
52
|
+
"iam:DetachUserPolicy",
|
|
53
|
+
"iam:PutRolePolicy",
|
|
54
|
+
"iam:PutUserPolicy",
|
|
55
|
+
"iam:SetDefaultPolicyVersion",
|
|
56
|
+
"iam:UpdateAccessKey",
|
|
57
|
+
"iam:UpdateAssumeRolePolicy",
|
|
58
|
+
# Secrets & Credentials
|
|
59
|
+
"kms:DisableKey",
|
|
60
|
+
"kms:PutKeyPolicy",
|
|
61
|
+
"kms:ScheduleKeyDeletion",
|
|
62
|
+
"secretsmanager:DeleteSecret",
|
|
63
|
+
"secretsmanager:GetSecretValue",
|
|
64
|
+
"secretsmanager:PutSecretValue",
|
|
65
|
+
"ssm:DeleteParameter",
|
|
66
|
+
"ssm:PutParameter",
|
|
67
|
+
# Compute & Containers
|
|
68
|
+
"ec2:DeleteSnapshot",
|
|
69
|
+
"ec2:DeleteVolume",
|
|
70
|
+
"ec2:DeleteVpc",
|
|
71
|
+
"ec2:ModifyInstanceAttribute",
|
|
72
|
+
"ec2:TerminateInstances",
|
|
73
|
+
"ecr:DeleteRepository",
|
|
74
|
+
"ecs:DeleteCluster",
|
|
75
|
+
"ecs:DeleteService",
|
|
76
|
+
"eks:DeleteCluster",
|
|
77
|
+
"lambda:DeleteFunction",
|
|
78
|
+
"lambda:DeleteFunctionConcurrency",
|
|
79
|
+
"lambda:PutFunctionConcurrency",
|
|
80
|
+
# Database & Storage
|
|
81
|
+
"dynamodb:DeleteTable",
|
|
82
|
+
"efs:DeleteFileSystem",
|
|
83
|
+
"elasticache:DeleteCacheCluster",
|
|
84
|
+
"fsx:DeleteFileSystem",
|
|
85
|
+
"rds:DeleteDBCluster",
|
|
86
|
+
"rds:DeleteDBInstance",
|
|
87
|
+
"redshift:DeleteCluster",
|
|
88
|
+
# S3 & Backup
|
|
89
|
+
"backup:DeleteBackupVault",
|
|
90
|
+
"glacier:DeleteArchive",
|
|
91
|
+
"s3:DeleteBucket",
|
|
92
|
+
"s3:DeleteBucketPolicy",
|
|
93
|
+
"s3:DeleteObject",
|
|
94
|
+
"s3:PutBucketPolicy",
|
|
95
|
+
"s3:PutLifecycleConfiguration",
|
|
96
|
+
# Network & Security
|
|
97
|
+
"ec2:ApplySecurityGroupsToClientVpnTargetNetwork",
|
|
98
|
+
"ec2:AssociateClientVpnTargetNetwork",
|
|
99
|
+
"ec2:AssociateSecurityGroupVpc",
|
|
100
|
+
"ec2:AttachVpnGateway",
|
|
101
|
+
"ec2:AuthorizeClientVpnIngress",
|
|
102
|
+
"ec2:AuthorizeSecurityGroupIngress",
|
|
103
|
+
"ec2:CreateClientVpnRoute",
|
|
104
|
+
"ec2:CreateVpnConnection",
|
|
105
|
+
"ec2:DeleteSecurityGroup",
|
|
106
|
+
"ec2:DeleteVpnConnection",
|
|
107
|
+
"ec2:DisassociateRouteTable",
|
|
108
|
+
"ec2:RevokeSecurityGroupEgress",
|
|
109
|
+
# Access & Logging
|
|
110
|
+
"cloudtrail:DeleteTrail",
|
|
111
|
+
"cloudtrail:StopLogging",
|
|
112
|
+
"cloudwatch:DeleteLogGroup",
|
|
113
|
+
"config:DeleteConfigurationRecorder",
|
|
114
|
+
"guardduty:DeleteDetector",
|
|
115
|
+
# Account & Organization
|
|
116
|
+
"account:CloseAccount",
|
|
117
|
+
"account:CreateAccount",
|
|
118
|
+
"account:DeleteAccount",
|
|
119
|
+
"organizations:DeleteOrganization",
|
|
120
|
+
"organizations:LeaveOrganization",
|
|
121
|
+
"organizations:RemoveAccountFromOrganization",
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_sensitive_actions() -> frozenset[str]:
|
|
127
|
+
"""
|
|
128
|
+
Get all sensitive actions.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Frozenset of all sensitive actions for O(1) membership testing
|
|
132
|
+
"""
|
|
133
|
+
return DEFAULT_SENSITIVE_ACTIONS
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default service principals for resource policy validation.
|
|
3
|
+
|
|
4
|
+
These are AWS service principals that are commonly used and considered safe
|
|
5
|
+
in resource-based policies (S3 bucket policies, SNS topic policies, etc.).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# Allowed Service Principals
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# These AWS service principals are commonly used in resource policies
|
|
14
|
+
# and are generally considered safe to allow
|
|
15
|
+
|
|
16
|
+
DEFAULT_SERVICE_PRINCIPALS: Final[tuple[str, ...]] = (
|
|
17
|
+
"cloudfront.amazonaws.com",
|
|
18
|
+
"s3.amazonaws.com",
|
|
19
|
+
"sns.amazonaws.com",
|
|
20
|
+
"lambda.amazonaws.com",
|
|
21
|
+
"logs.amazonaws.com",
|
|
22
|
+
"events.amazonaws.com",
|
|
23
|
+
"elasticloadbalancing.amazonaws.com",
|
|
24
|
+
"cloudtrail.amazonaws.com",
|
|
25
|
+
"config.amazonaws.com",
|
|
26
|
+
"backup.amazonaws.com",
|
|
27
|
+
"cloudwatch.amazonaws.com",
|
|
28
|
+
"monitoring.amazonaws.com",
|
|
29
|
+
"ec2.amazonaws.com",
|
|
30
|
+
"ecs-tasks.amazonaws.com",
|
|
31
|
+
"eks.amazonaws.com",
|
|
32
|
+
"apigateway.amazonaws.com",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_service_principals() -> tuple[str, ...]:
|
|
37
|
+
"""
|
|
38
|
+
Get tuple of allowed service principals.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple of AWS service principal names
|
|
42
|
+
"""
|
|
43
|
+
return DEFAULT_SERVICE_PRINCIPALS
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_allowed_service_principal(principal: str) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
Check if a principal is an allowed service principal.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
principal: Principal to check (e.g., "lambda.amazonaws.com")
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if principal is in allowed list
|
|
55
|
+
|
|
56
|
+
Performance: O(n) but small list (~16 items)
|
|
57
|
+
"""
|
|
58
|
+
return principal in DEFAULT_SERVICE_PRINCIPALS
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_service_principals_by_category() -> dict[str, tuple[str, ...]]:
|
|
62
|
+
"""
|
|
63
|
+
Get service principals organized by service category.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dictionary mapping categories to service principal tuples
|
|
67
|
+
"""
|
|
68
|
+
return {
|
|
69
|
+
"storage": (
|
|
70
|
+
"s3.amazonaws.com",
|
|
71
|
+
"backup.amazonaws.com",
|
|
72
|
+
),
|
|
73
|
+
"compute": (
|
|
74
|
+
"lambda.amazonaws.com",
|
|
75
|
+
"ec2.amazonaws.com",
|
|
76
|
+
"ecs-tasks.amazonaws.com",
|
|
77
|
+
"eks.amazonaws.com",
|
|
78
|
+
),
|
|
79
|
+
"networking": (
|
|
80
|
+
"cloudfront.amazonaws.com",
|
|
81
|
+
"elasticloadbalancing.amazonaws.com",
|
|
82
|
+
"apigateway.amazonaws.com",
|
|
83
|
+
),
|
|
84
|
+
"monitoring": (
|
|
85
|
+
"logs.amazonaws.com",
|
|
86
|
+
"cloudwatch.amazonaws.com",
|
|
87
|
+
"monitoring.amazonaws.com",
|
|
88
|
+
"cloudtrail.amazonaws.com",
|
|
89
|
+
),
|
|
90
|
+
"messaging": (
|
|
91
|
+
"sns.amazonaws.com",
|
|
92
|
+
"events.amazonaws.com",
|
|
93
|
+
),
|
|
94
|
+
"management": ("config.amazonaws.com",),
|
|
95
|
+
}
|