iam-policy-validator 1.14.2__py3-none-any.whl → 1.14.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iam-policy-validator
3
- Version: 1.14.2
3
+ Version: 1.14.3
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
@@ -1,11 +1,11 @@
1
1
  iam_validator/__init__.py,sha256=xHdUASOxFHwEXfT_GSr_KrkLlnxZ-pAAr1wW1PwAGko,693
2
2
  iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
3
- iam_validator/__version__.py,sha256=aKdF7mPOKj0H8xIHMqcgUIdAMplJ350c2A8EWCObeRY,374
3
+ iam_validator/__version__.py,sha256=Gm8njL8lXkcG-nPd19SPPxnjvafHZUsjluEm-8FCYvo,374
4
4
  iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
5
5
  iam_validator/checks/action_condition_enforcement.py,sha256=2-XUMbof9tQ7SHZNmAHMkR1DgbOIzY2eFWlp9S9dwLk,60625
6
6
  iam_validator/checks/action_resource_matching.py,sha256=qND0hfDgNoxFEdLWwrxOPVDfdj3k50nzedT2qF7nK7o,19428
7
7
  iam_validator/checks/action_validation.py,sha256=glA2F3iQBU-d8rruiyB9IdzVOESC2Kb-91SnwRCdQF0,2562
8
- iam_validator/checks/condition_key_validation.py,sha256=u9eCxI_OjkWqK_KcgPPxu4ZO16E9pqjK8CDiW7QbvZ4,4031
8
+ iam_validator/checks/condition_key_validation.py,sha256=5i8LqqV78SjWK6pLrbttWmeMAD4pDC12_FjTjx5dFSU,4024
9
9
  iam_validator/checks/condition_type_mismatch.py,sha256=KJp7zQHDd8VeTcfjcD-ur3S4070cXEDTWkFtxfp7CuE,10652
10
10
  iam_validator/checks/full_wildcard.py,sha256=0TkkHtV0MZ6nZtJRtGdn3wwOMM96TRyGO7l7mmdHNUo,2325
11
11
  iam_validator/checks/mfa_condition_check.py,sha256=y1LbqcvQ_fL2BPTNaKRQoBYM5hM7JET9cDPUOWKFEVs,4814
@@ -43,7 +43,7 @@ iam_validator/core/check_registry.py,sha256=oRCdWoCGQ8VZERVYd821u9r5NdKQ9FMC54e6
43
43
  iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
44
44
  iam_validator/core/codeowners.py,sha256=dfRjYTpcTVmc-h95i4EoPXCXlcblD8yryeJBaTKQfjM,7530
45
45
  iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
46
- iam_validator/core/constants.py,sha256=tHAZwINyB1wj_l5ZjnhFZ_LIA8S2C5lF-9CZtKijFac,6192
46
+ iam_validator/core/constants.py,sha256=YRT_uOUs1Dnt9J1hE-zG6Ja0numsH4Mf7RNNXNJWZIY,7447
47
47
  iam_validator/core/diff_parser.py,sha256=5Jxa6WvQZtG5grblZeUH2OQ2R46tFLK-h8tvkHOSfLk,12110
48
48
  iam_validator/core/finding_fingerprint.py,sha256=NJIlu8NhdenWbLS7ww8LyWFasJgpKWN63-DprrNW7Zs,4353
49
49
  iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
@@ -62,10 +62,10 @@ iam_validator/core/aws_service/fetcher.py,sha256=TD9qQ4tQF4xdEGhVVADGgF8QlXe15R3
62
62
  iam_validator/core/aws_service/parsers.py,sha256=gJzR7HCD8ItCWCCbguTQIZpPEdj2rdMwC7LPhu7ve14,5174
63
63
  iam_validator/core/aws_service/patterns.py,sha256=gGc55Tn-EJ3cmcWtmYAZROUajKYz7DaMchYWGEhHpC0,1726
64
64
  iam_validator/core/aws_service/storage.py,sha256=PrfKdvF60IL7E_8xYs_XwFoAJPRcVYw57FVLHCoqwVk,10429
65
- iam_validator/core/aws_service/validators.py,sha256=L9XRJdGmR-vZ1r0bj5SCznULyKEY_G1OAjij7-kOZPM,16463
65
+ iam_validator/core/aws_service/validators.py,sha256=7izAvL92TsWpQePU7g9qvegT_F2xz8CqpbrnSgE40Xg,19890
66
66
  iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H2dHg61rU,2671
67
67
  iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
68
- iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
68
+ iam_validator/core/config/aws_global_conditions.py,sha256=PO3zMdzM_QWZduxL3lV2nrmpcMEEwKASCUYHcqX0LBg,7363
69
69
  iam_validator/core/config/category_suggestions.py,sha256=fopaZ9kXDrsLgi_r0pERrLwgdPPJl5VIiKvXtQK9tj0,8583
70
70
  iam_validator/core/config/check_documentation.py,sha256=Adyt3Q4JSRlVNPWulXVlRIEorxXG_nt0mkPz_ySa8y4,15931
71
71
  iam_validator/core/config/condition_requirements.py,sha256=1CeQJfWV-Y2ImW0Mq9YdrgvH-hj9IXe0gVOm3B36Rc8,10655
@@ -85,7 +85,7 @@ iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFV
85
85
  iam_validator/core/formatters/markdown.py,sha256=dk4STeY-tOEZsVrlmolIEqZvWYP9JhRtygxxNA49DEE,2293
86
86
  iam_validator/core/formatters/sarif.py,sha256=03MHSyuZm9FlzaPeWg7wH-UTzzCDhSy6vMPrFpFNkS8,18884
87
87
  iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
88
- iam_validator/integrations/github_integration.py,sha256=2pOjTVsLMymx-wU31Ly7JqODgNXf5U7lvteVqBpaRgE,67913
88
+ iam_validator/integrations/github_integration.py,sha256=OZjVFkeEK0PYerqHFOuc0tFtTMmo78JhbqZgFduzq-8,67949
89
89
  iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
90
90
  iam_validator/sdk/__init__.py,sha256=AZLnfdn3A9AWb0pMhsbu3GAOAzt6rV7Fi3E3d9_3ZdI,6388
91
91
  iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urchEFDA,12778
@@ -99,8 +99,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
99
99
  iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
100
100
  iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
101
101
  iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
102
- iam_policy_validator-1.14.2.dist-info/METADATA,sha256=63ruVMh-1wI_vzVi2Elo6UC6LlmTbyZ7q1vr_-9n_rg,34456
103
- iam_policy_validator-1.14.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
104
- iam_policy_validator-1.14.2.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
105
- iam_policy_validator-1.14.2.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
106
- iam_policy_validator-1.14.2.dist-info/RECORD,,
102
+ iam_policy_validator-1.14.3.dist-info/METADATA,sha256=aH2RAojXqwEay7CKD89eRTVCHphTpvyXON4XUdMcRfg,34456
103
+ iam_policy_validator-1.14.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
104
+ iam_policy_validator-1.14.3.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
105
+ iam_policy_validator-1.14.3.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
106
+ iam_policy_validator-1.14.3.dist-info/RECORD,,
@@ -3,7 +3,7 @@
3
3
  This file is the single source of truth for the package version.
4
4
  """
5
5
 
6
- __version__ = "1.14.2"
6
+ __version__ = "1.14.3"
7
7
  # Parse version, handling pre-release suffixes like -rc, -alpha, -beta
8
8
  _version_base = __version__.split("-", maxsplit=1)[0] # Remove pre-release suffix if present
9
9
  __version_info__ = tuple(int(part) for part in _version_base.split("."))
@@ -37,7 +37,7 @@ class ConditionKeyValidationCheck(PolicyCheck):
37
37
  resources = statement.get_resources()
38
38
 
39
39
  # Extract all condition keys from all condition operators
40
- for operator, conditions in statement.condition.items():
40
+ for _, conditions in statement.condition.items():
41
41
  for condition_key in conditions.keys():
42
42
  # Validate this condition key against each action in the statement
43
43
  for action in actions:
@@ -5,14 +5,104 @@ including actions, condition keys, and ARN formats.
5
5
  """
6
6
 
7
7
  import logging
8
+ import re
8
9
  from dataclasses import dataclass
9
10
  from typing import Any
10
11
 
11
12
  from iam_validator.core.aws_service.parsers import ServiceParser
13
+ from iam_validator.core.constants import (
14
+ AWS_TAG_KEY_ALLOWED_CHARS,
15
+ AWS_TAG_KEY_MAX_LENGTH,
16
+ AWS_TAG_KEY_PLACEHOLDERS,
17
+ )
12
18
  from iam_validator.core.models import ServiceDetail
13
19
 
14
20
  logger = logging.getLogger(__name__)
15
21
 
22
+ # Pre-compiled regex for AWS tag key validation
23
+ # Uses centralized constants from iam_validator.core.constants
24
+ _TAG_KEY_PATTERN = re.compile(rf"^[{AWS_TAG_KEY_ALLOWED_CHARS}]{{1,{AWS_TAG_KEY_MAX_LENGTH}}}$")
25
+
26
+
27
+ def _is_valid_tag_key(tag_key: str) -> bool:
28
+ """Validate an AWS tag key format.
29
+
30
+ AWS tag keys must:
31
+ - Be 1-128 characters long
32
+ - Contain only: letters, numbers, spaces, and + - = . _ : / @
33
+ - Not be empty
34
+
35
+ Note: The 'aws:' prefix check is not done here as it's for the condition key prefix,
36
+ not the tag key portion (e.g., in 'ssm:resourceTag/owner', 'owner' is the tag key).
37
+
38
+ Args:
39
+ tag_key: The tag key portion to validate
40
+
41
+ Returns:
42
+ True if valid AWS tag key format
43
+ """
44
+ if not tag_key or len(tag_key) > AWS_TAG_KEY_MAX_LENGTH:
45
+ return False
46
+ return bool(_TAG_KEY_PATTERN.match(tag_key))
47
+
48
+
49
+ def _matches_condition_key_pattern(condition_key: str, pattern: str) -> bool:
50
+ """Check if a condition key matches a pattern with tag-key placeholders.
51
+
52
+ AWS service definitions use patterns like:
53
+ - `ssm:resourceTag/tag-key` or `ssm:resourceTag/${TagKey}` to match `ssm:resourceTag/owner`
54
+ - `aws:ResourceTag/${TagKey}` to match `aws:ResourceTag/Environment`
55
+
56
+ Args:
57
+ condition_key: The actual condition key from the policy (e.g., "ssm:resourceTag/owner")
58
+ pattern: The pattern from AWS service definition (e.g., "ssm:resourceTag/tag-key")
59
+
60
+ Returns:
61
+ True if condition_key matches the pattern
62
+ """
63
+ # Exact match (fast path)
64
+ if condition_key == pattern:
65
+ return True
66
+
67
+ # Check for tag-key placeholder patterns
68
+ for tag_placeholder in AWS_TAG_KEY_PLACEHOLDERS:
69
+ if tag_placeholder in pattern:
70
+ # Extract the prefix before the placeholder
71
+ prefix = pattern.split(tag_placeholder, 1)[0]
72
+ prefix_with_slash = prefix + "/"
73
+ # Check if condition_key starts with prefix and has a tag key after it
74
+ if condition_key.startswith(prefix_with_slash):
75
+ # Validate tag key format per AWS constraints
76
+ tag_key = condition_key[len(prefix_with_slash) :]
77
+ if _is_valid_tag_key(tag_key):
78
+ return True
79
+
80
+ return False
81
+
82
+
83
+ def _condition_key_in_list(condition_key: str, condition_keys: list[str]) -> bool:
84
+ """Check if a condition key matches any key in the list, supporting patterns.
85
+
86
+ Args:
87
+ condition_key: The condition key to check
88
+ condition_keys: List of condition keys (may include patterns)
89
+
90
+ Returns:
91
+ True if condition_key matches any entry in the list
92
+ """
93
+ # Fast path: check for exact match first (most common case)
94
+ if condition_key in condition_keys:
95
+ return True
96
+
97
+ # Slower path: check patterns only if no exact match
98
+ for pattern in condition_keys:
99
+ # Skip exact matches (already checked above)
100
+ if pattern == condition_key:
101
+ continue
102
+ if _matches_condition_key_pattern(condition_key, pattern):
103
+ return True
104
+ return False
105
+
16
106
 
17
107
  @dataclass
18
108
  class ConditionKeyValidationResult:
@@ -134,7 +224,7 @@ class ServiceValidator:
134
224
  action: str,
135
225
  condition_key: str,
136
226
  service_detail: ServiceDetail,
137
- resources: list[str] | None = None,
227
+ resources: list[str] | None = None, # pylint: disable=unused-argument - kept for API compatibility
138
228
  ) -> ConditionKeyValidationResult:
139
229
  """Validate condition key against action and optionally resource types.
140
230
 
@@ -173,22 +263,23 @@ class ServiceValidator:
173
263
  error_message=f"Invalid AWS global condition key: `{condition_key}`.",
174
264
  )
175
265
 
176
- # Check service-specific condition keys
177
- if condition_key in service_detail.condition_keys:
266
+ # Check service-specific condition keys (with pattern matching for tag keys)
267
+ if service_detail.condition_keys and _condition_key_in_list(
268
+ condition_key, list(service_detail.condition_keys.keys())
269
+ ):
178
270
  return ConditionKeyValidationResult(is_valid=True)
179
271
 
180
272
  # Check action-specific condition keys
181
273
  if action_name in service_detail.actions:
182
274
  action_detail = service_detail.actions[action_name]
183
- if (
184
- action_detail.action_condition_keys
185
- and condition_key in action_detail.action_condition_keys
275
+ if action_detail.action_condition_keys and _condition_key_in_list(
276
+ condition_key, action_detail.action_condition_keys
186
277
  ):
187
278
  return ConditionKeyValidationResult(is_valid=True)
188
279
 
189
280
  # Check resource-specific condition keys
190
281
  # Get resource types required by this action
191
- if resources and action_detail.resources:
282
+ if action_detail.resources:
192
283
  for res_req in action_detail.resources:
193
284
  resource_name = res_req.get("Name", "")
194
285
  if not resource_name:
@@ -197,7 +288,7 @@ class ServiceValidator:
197
288
  # Look up resource type definition
198
289
  resource_type = service_detail.resources.get(resource_name)
199
290
  if resource_type and resource_type.condition_keys:
200
- if condition_key in resource_type.condition_keys:
291
+ if _condition_key_in_list(condition_key, resource_type.condition_keys):
201
292
  return ConditionKeyValidationResult(is_valid=True)
202
293
 
203
294
  # If it's a global key but the action has specific condition keys defined,
@@ -11,6 +11,8 @@ Last updated: 2025-01-17
11
11
  import re
12
12
  from typing import Any
13
13
 
14
+ from iam_validator.core.constants import AWS_TAG_KEY_ALLOWED_CHARS
15
+
14
16
  # AWS Global Condition Keys with Type Information
15
17
  # These condition keys are available for use in IAM policies across all AWS services
16
18
  # Format: {key: type} where type is one of: String, ARN, Bool, Date, IPAddress, Numeric
@@ -71,17 +73,18 @@ AWS_GLOBAL_CONDITION_KEYS = {
71
73
 
72
74
  # Patterns that should be recognized (wildcards and tag-based keys)
73
75
  # These allow things like aws:RequestTag/Department or aws:PrincipalTag/Environment
76
+ # Uses centralized tag key character class from constants
74
77
  AWS_CONDITION_KEY_PATTERNS = [
75
78
  {
76
- "pattern": r"^aws:RequestTag/[a-zA-Z0-9+\-=._:/@]+$",
79
+ "pattern": rf"^aws:RequestTag/[{AWS_TAG_KEY_ALLOWED_CHARS}]+$",
77
80
  "description": "Tag keys in the request (for tag-based access control)",
78
81
  },
79
82
  {
80
- "pattern": r"^aws:ResourceTag/[a-zA-Z0-9+\-=._:/@]+$",
83
+ "pattern": rf"^aws:ResourceTag/[{AWS_TAG_KEY_ALLOWED_CHARS}]+$",
81
84
  "description": "Tags on the resource being accessed",
82
85
  },
83
86
  {
84
- "pattern": r"^aws:PrincipalTag/[a-zA-Z0-9+\-=._:/@]+$",
87
+ "pattern": rf"^aws:PrincipalTag/[{AWS_TAG_KEY_ALLOWED_CHARS}]+$",
85
88
  "description": "Tags attached to the principal making the request",
86
89
  },
87
90
  ]
@@ -154,7 +157,8 @@ _global_conditions_instance = None
154
157
 
155
158
  def get_global_conditions() -> AWSGlobalConditions:
156
159
  """Get singleton instance of AWSGlobalConditions."""
157
- global _global_conditions_instance
160
+ global _global_conditions_instance # pylint: disable=global-statement
161
+
158
162
  if _global_conditions_instance is None:
159
163
  _global_conditions_instance = AWSGlobalConditions()
160
164
  return _global_conditions_instance
@@ -147,3 +147,32 @@ RCP_SUPPORTED_SERVICES = frozenset(
147
147
 
148
148
  # AWS Service Authorization Reference (for finding valid actions, resources, and condition keys)
149
149
  AWS_SERVICE_AUTH_REF_URL = "https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html"
150
+
151
+ # ============================================================================
152
+ # AWS Tag Constraints
153
+ # ============================================================================
154
+ # Reference: https://docs.aws.amazon.com/tag-editor/latest/userguide/best-practices-and-strats.html
155
+
156
+ # --- Tag Key Constraints ---
157
+ # Allowed characters in AWS tag keys: letters, numbers, spaces, and + - = . _ : / @
158
+ # This is the character class for use in regex patterns
159
+ AWS_TAG_KEY_ALLOWED_CHARS = r"a-zA-Z0-9 +\-=._:/@"
160
+
161
+ # Maximum length for AWS tag keys (per AWS documentation)
162
+ AWS_TAG_KEY_MAX_LENGTH = 128
163
+
164
+ # Tag-key placeholder patterns used in AWS service definitions
165
+ # These patterns indicate where a tag key should be substituted
166
+ AWS_TAG_KEY_PLACEHOLDERS = ("/tag-key", "/${TagKey}", "/${tag-key}")
167
+
168
+ # --- Tag Value Constraints ---
169
+ # Allowed characters in AWS tag values: letters, numbers, spaces, and + - = . _ : / @
170
+ # Same character set as tag keys
171
+ AWS_TAG_VALUE_ALLOWED_CHARS = r"a-zA-Z0-9 +\-=._:/@"
172
+
173
+ # Maximum length for AWS tag values (per AWS documentation)
174
+ # Note: Tag values can be empty (minimum 0), unlike keys which must have at least 1 char
175
+ AWS_TAG_VALUE_MAX_LENGTH = 256
176
+
177
+ # Minimum length for AWS tag values (can be empty)
178
+ AWS_TAG_VALUE_MIN_LENGTH = 0
@@ -34,7 +34,7 @@ class GitHubRateLimitError(Exception):
34
34
  class GitHubRetryableError(Exception):
35
35
  """Raised for transient GitHub API errors that should be retried."""
36
36
 
37
- pass
37
+ pass # pylint: disable=unnecessary-pass
38
38
 
39
39
 
40
40
  # Retry configuration