iam-policy-validator 1.7.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.
Potentially problematic release.
This version of iam-policy-validator might be problematic. Click here for more details.
- iam_policy_validator-1.7.0.dist-info/METADATA +1057 -0
- iam_policy_validator-1.7.0.dist-info/RECORD +83 -0
- iam_policy_validator-1.7.0.dist-info/WHEEL +4 -0
- iam_policy_validator-1.7.0.dist-info/entry_points.txt +2 -0
- iam_policy_validator-1.7.0.dist-info/licenses/LICENSE +21 -0
- iam_validator/__init__.py +27 -0
- iam_validator/__main__.py +11 -0
- iam_validator/__version__.py +7 -0
- iam_validator/checks/__init__.py +43 -0
- iam_validator/checks/action_condition_enforcement.py +884 -0
- iam_validator/checks/action_resource_matching.py +441 -0
- iam_validator/checks/action_validation.py +72 -0
- iam_validator/checks/condition_key_validation.py +92 -0
- iam_validator/checks/condition_type_mismatch.py +259 -0
- iam_validator/checks/full_wildcard.py +71 -0
- iam_validator/checks/mfa_condition_check.py +112 -0
- iam_validator/checks/policy_size.py +147 -0
- iam_validator/checks/policy_type_validation.py +305 -0
- iam_validator/checks/principal_validation.py +776 -0
- iam_validator/checks/resource_validation.py +138 -0
- iam_validator/checks/sensitive_action.py +254 -0
- iam_validator/checks/service_wildcard.py +107 -0
- iam_validator/checks/set_operator_validation.py +157 -0
- iam_validator/checks/sid_uniqueness.py +170 -0
- 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 +294 -0
- iam_validator/checks/utils/wildcard_expansion.py +87 -0
- iam_validator/checks/wildcard_action.py +67 -0
- iam_validator/checks/wildcard_resource.py +135 -0
- iam_validator/commands/__init__.py +25 -0
- iam_validator/commands/analyze.py +531 -0
- iam_validator/commands/base.py +48 -0
- iam_validator/commands/cache.py +392 -0
- iam_validator/commands/download_services.py +255 -0
- iam_validator/commands/post_to_pr.py +86 -0
- iam_validator/commands/validate.py +600 -0
- iam_validator/core/__init__.py +14 -0
- iam_validator/core/access_analyzer.py +671 -0
- iam_validator/core/access_analyzer_report.py +640 -0
- iam_validator/core/aws_fetcher.py +940 -0
- iam_validator/core/check_registry.py +607 -0
- iam_validator/core/cli.py +134 -0
- iam_validator/core/condition_validators.py +626 -0
- iam_validator/core/config/__init__.py +81 -0
- iam_validator/core/config/aws_api.py +35 -0
- iam_validator/core/config/aws_global_conditions.py +160 -0
- iam_validator/core/config/category_suggestions.py +104 -0
- iam_validator/core/config/condition_requirements.py +155 -0
- iam_validator/core/config/config_loader.py +472 -0
- iam_validator/core/config/defaults.py +523 -0
- iam_validator/core/config/principal_requirements.py +421 -0
- iam_validator/core/config/sensitive_actions.py +672 -0
- iam_validator/core/config/service_principals.py +95 -0
- iam_validator/core/config/wildcards.py +124 -0
- iam_validator/core/constants.py +74 -0
- iam_validator/core/formatters/__init__.py +27 -0
- iam_validator/core/formatters/base.py +147 -0
- iam_validator/core/formatters/console.py +59 -0
- iam_validator/core/formatters/csv.py +170 -0
- iam_validator/core/formatters/enhanced.py +440 -0
- iam_validator/core/formatters/html.py +672 -0
- iam_validator/core/formatters/json.py +33 -0
- iam_validator/core/formatters/markdown.py +63 -0
- iam_validator/core/formatters/sarif.py +251 -0
- iam_validator/core/models.py +327 -0
- iam_validator/core/policy_checks.py +656 -0
- iam_validator/core/policy_loader.py +396 -0
- iam_validator/core/pr_commenter.py +424 -0
- iam_validator/core/report.py +872 -0
- iam_validator/integrations/__init__.py +28 -0
- iam_validator/integrations/github_integration.py +815 -0
- iam_validator/integrations/ms_teams.py +442 -0
- iam_validator/sdk/__init__.py +187 -0
- iam_validator/sdk/arn_matching.py +382 -0
- iam_validator/sdk/context.py +222 -0
- iam_validator/sdk/exceptions.py +48 -0
- iam_validator/sdk/helpers.py +177 -0
- iam_validator/sdk/policy_utils.py +425 -0
- iam_validator/sdk/shortcuts.py +283 -0
- iam_validator/utils/__init__.py +31 -0
- iam_validator/utils/cache.py +105 -0
- iam_validator/utils/regex.py +206 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core configuration modules for IAM Policy Validator.
|
|
3
|
+
|
|
4
|
+
This package contains default configuration data used by validators, organized into
|
|
5
|
+
logical modules for better maintainability and performance.
|
|
6
|
+
|
|
7
|
+
All configuration is defined as Python code (not YAML/JSON) for:
|
|
8
|
+
- Faster loading (no parsing overhead)
|
|
9
|
+
- Better PyPI packaging (no data files to manage)
|
|
10
|
+
- Type hints and IDE support
|
|
11
|
+
- Compiled to .pyc for even faster imports
|
|
12
|
+
|
|
13
|
+
Performance benefits:
|
|
14
|
+
- 5-10x faster than YAML parsing
|
|
15
|
+
- Zero runtime parsing overhead
|
|
16
|
+
- Lazy loading support
|
|
17
|
+
- O(1) frozenset lookups
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from iam_validator.core.config.aws_api import (
|
|
21
|
+
AWS_SERVICE_REFERENCE_BASE_URL,
|
|
22
|
+
get_service_reference_url,
|
|
23
|
+
)
|
|
24
|
+
from iam_validator.core.config.aws_global_conditions import (
|
|
25
|
+
AWS_GLOBAL_CONDITION_KEYS,
|
|
26
|
+
AWSGlobalConditions,
|
|
27
|
+
get_global_conditions,
|
|
28
|
+
)
|
|
29
|
+
from iam_validator.core.config.condition_requirements import CONDITION_REQUIREMENTS
|
|
30
|
+
from iam_validator.core.config.defaults import DEFAULT_CONFIG
|
|
31
|
+
from iam_validator.core.config.principal_requirements import (
|
|
32
|
+
ALL_PRINCIPAL_REQUIREMENTS,
|
|
33
|
+
DEFAULT_ENABLED_REQUIREMENTS,
|
|
34
|
+
get_all_principal_requirement_names,
|
|
35
|
+
get_default_principal_requirements,
|
|
36
|
+
get_principal_requirement,
|
|
37
|
+
get_principal_requirements_by_names,
|
|
38
|
+
get_principal_requirements_by_severity,
|
|
39
|
+
)
|
|
40
|
+
from iam_validator.core.config.sensitive_actions import (
|
|
41
|
+
DEFAULT_SENSITIVE_ACTIONS,
|
|
42
|
+
get_sensitive_actions,
|
|
43
|
+
)
|
|
44
|
+
from iam_validator.core.config.service_principals import DEFAULT_SERVICE_PRINCIPALS
|
|
45
|
+
from iam_validator.core.config.wildcards import (
|
|
46
|
+
DEFAULT_ALLOWED_WILDCARDS,
|
|
47
|
+
DEFAULT_SERVICE_WILDCARDS,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# NOTE: ConfigLoader is NOT imported here to avoid circular imports
|
|
51
|
+
# Import it directly from iam_validator.core.config.config_loader when needed
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Default configuration
|
|
55
|
+
"DEFAULT_CONFIG",
|
|
56
|
+
# AWS API endpoints
|
|
57
|
+
"AWS_SERVICE_REFERENCE_BASE_URL",
|
|
58
|
+
"get_service_reference_url",
|
|
59
|
+
# AWS Global Conditions
|
|
60
|
+
"AWS_GLOBAL_CONDITION_KEYS",
|
|
61
|
+
"AWSGlobalConditions",
|
|
62
|
+
"get_global_conditions",
|
|
63
|
+
# Sensitive actions
|
|
64
|
+
"DEFAULT_SENSITIVE_ACTIONS",
|
|
65
|
+
"get_sensitive_actions",
|
|
66
|
+
# Condition requirements (for actions)
|
|
67
|
+
"CONDITION_REQUIREMENTS",
|
|
68
|
+
# Principal requirements (for principals)
|
|
69
|
+
"ALL_PRINCIPAL_REQUIREMENTS",
|
|
70
|
+
"DEFAULT_ENABLED_REQUIREMENTS",
|
|
71
|
+
"get_default_principal_requirements",
|
|
72
|
+
"get_principal_requirement",
|
|
73
|
+
"get_all_principal_requirement_names",
|
|
74
|
+
"get_principal_requirements_by_names",
|
|
75
|
+
"get_principal_requirements_by_severity",
|
|
76
|
+
# Wildcards
|
|
77
|
+
"DEFAULT_ALLOWED_WILDCARDS",
|
|
78
|
+
"DEFAULT_SERVICE_WILDCARDS",
|
|
79
|
+
# Service principals
|
|
80
|
+
"DEFAULT_SERVICE_PRINCIPALS",
|
|
81
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS API configuration constants.
|
|
3
|
+
|
|
4
|
+
This module centralizes AWS API endpoints and related configuration
|
|
5
|
+
used throughout the IAM Policy Validator.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# AWS Service Reference API base URL
|
|
9
|
+
# This is the official AWS service reference that provides action, resource, and condition key metadata
|
|
10
|
+
AWS_SERVICE_REFERENCE_BASE_URL = "https://servicereference.us-east-1.amazonaws.com/"
|
|
11
|
+
|
|
12
|
+
# Alternative endpoints for different regions (currently not used, but available for future expansion)
|
|
13
|
+
AWS_SERVICE_REFERENCE_ENDPOINTS = {
|
|
14
|
+
"us-east-1": "https://servicereference.us-east-1.amazonaws.com/",
|
|
15
|
+
# Add other regional endpoints if they become available
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_service_reference_url(region: str = "us-east-1") -> str:
|
|
20
|
+
"""
|
|
21
|
+
Get the AWS Service Reference API URL for a specific region.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
region: AWS region (default: us-east-1)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The service reference base URL for the specified region
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> get_service_reference_url()
|
|
31
|
+
'https://servicereference.us-east-1.amazonaws.com/'
|
|
32
|
+
>>> get_service_reference_url("us-east-1")
|
|
33
|
+
'https://servicereference.us-east-1.amazonaws.com/'
|
|
34
|
+
"""
|
|
35
|
+
return AWS_SERVICE_REFERENCE_ENDPOINTS.get(region, AWS_SERVICE_REFERENCE_BASE_URL)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Global Condition Keys Management.
|
|
3
|
+
|
|
4
|
+
Provides access to the list of valid AWS global condition keys
|
|
5
|
+
that can be used across all AWS services.
|
|
6
|
+
|
|
7
|
+
Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
|
|
8
|
+
Last updated: 2025-01-17
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
# AWS Global Condition Keys with Type Information
|
|
15
|
+
# These condition keys are available for use in IAM policies across all AWS services
|
|
16
|
+
# Format: {key: type} where type is one of: String, ARN, Bool, Date, IPAddress, Numeric
|
|
17
|
+
AWS_GLOBAL_CONDITION_KEYS = {
|
|
18
|
+
# Properties of the Principal
|
|
19
|
+
"aws:PrincipalArn": "ARN", # ARN of the principal making the request
|
|
20
|
+
"aws:PrincipalAccount": "String", # Account to which the requesting principal belongs
|
|
21
|
+
"aws:PrincipalOrgPaths": "String", # AWS Organizations path for the principal
|
|
22
|
+
"aws:PrincipalOrgID": "String", # Organization identifier of the principal
|
|
23
|
+
"aws:PrincipalIsAWSService": "Bool", # Checks if call is made directly by AWS service principal
|
|
24
|
+
"aws:PrincipalServiceName": "String", # Service principal name making the request
|
|
25
|
+
"aws:PrincipalServiceNamesList": "String", # List of all service principal names
|
|
26
|
+
"aws:PrincipalType": "String", # Type of principal making the request
|
|
27
|
+
"aws:userid": "String", # Principal identifier of the requester
|
|
28
|
+
"aws:username": "String", # User name of the requester
|
|
29
|
+
# Properties of a Role Session
|
|
30
|
+
"aws:AssumedRoot": "Bool", # Checks if request used AssumeRoot for privileged access
|
|
31
|
+
"aws:FederatedProvider": "String", # Principal's issuing identity provider
|
|
32
|
+
"aws:TokenIssueTime": "Date", # When temporary security credentials were issued
|
|
33
|
+
"aws:MultiFactorAuthAge": "Numeric", # Seconds since MFA authorization
|
|
34
|
+
"aws:MultiFactorAuthPresent": "Bool", # Whether MFA was used for temporary credentials
|
|
35
|
+
"aws:ChatbotSourceArn": "ARN", # Source chat configuration ARN
|
|
36
|
+
"aws:Ec2InstanceSourceVpc": "String", # VPC where EC2 IAM role credentials were delivered
|
|
37
|
+
"aws:Ec2InstanceSourcePrivateIPv4": "IPAddress", # Private IPv4 of EC2 instance
|
|
38
|
+
"aws:SourceIdentity": "String", # Source identity set when assuming a role
|
|
39
|
+
"ec2:RoleDelivery": "Numeric", # Instance metadata service version
|
|
40
|
+
# Network Properties
|
|
41
|
+
"aws:SourceIp": "IPAddress", # Requester's IP address (IPv4/IPv6)
|
|
42
|
+
"aws:SourceVpc": "String", # VPC through which request travels
|
|
43
|
+
"aws:SourceVpce": "String", # VPC endpoint identifier
|
|
44
|
+
"aws:VpceAccount": "String", # AWS account owning the VPC endpoint
|
|
45
|
+
"aws:VpceOrgID": "String", # Organization ID of VPC endpoint owner
|
|
46
|
+
"aws:VpceOrgPaths": "String", # AWS Organizations path of VPC endpoint
|
|
47
|
+
"aws:VpcSourceIp": "IPAddress", # IP address from VPC endpoint request
|
|
48
|
+
# Resource Properties
|
|
49
|
+
"aws:ResourceAccount": "String", # Resource owner's AWS account ID
|
|
50
|
+
"aws:ResourceOrgID": "String", # Organization ID of resource owner
|
|
51
|
+
"aws:ResourceOrgPaths": "String", # AWS Organizations path of resource
|
|
52
|
+
# Request Properties
|
|
53
|
+
"aws:CurrentTime": "Date", # Current date and time
|
|
54
|
+
"aws:EpochTime": "Date", # Request timestamp in epoch format (also accepts Numeric)
|
|
55
|
+
"aws:referer": "String", # HTTP referer header value (note: lowercase 'r')
|
|
56
|
+
"aws:Referer": "String", # HTTP referer header value (alternate capitalization)
|
|
57
|
+
"aws:RequestedRegion": "String", # AWS Region for the request
|
|
58
|
+
"aws:TagKeys": "String", # Tag keys present in request
|
|
59
|
+
"aws:SecureTransport": "Bool", # Whether HTTPS was used
|
|
60
|
+
"aws:SourceAccount": "String", # Account making the request
|
|
61
|
+
"aws:SourceArn": "ARN", # ARN of request source
|
|
62
|
+
"aws:SourceOrgID": "String", # Organization ID of request source
|
|
63
|
+
"aws:SourceOrgPaths": "String", # Organization paths of request source
|
|
64
|
+
"aws:UserAgent": "String", # HTTP user agent string
|
|
65
|
+
# Cross-Service Keys
|
|
66
|
+
"aws:CalledVia": "String", # Services called in request chain
|
|
67
|
+
"aws:CalledViaFirst": "String", # First service in call chain
|
|
68
|
+
"aws:CalledViaLast": "String", # Last service in call chain
|
|
69
|
+
"aws:ViaAWSService": "Bool", # Whether AWS service made the request
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Patterns that should be recognized (wildcards and tag-based keys)
|
|
73
|
+
# These allow things like aws:RequestTag/Department or aws:PrincipalTag/Environment
|
|
74
|
+
AWS_CONDITION_KEY_PATTERNS = [
|
|
75
|
+
{
|
|
76
|
+
"pattern": r"^aws:RequestTag/[a-zA-Z0-9+\-=._:/@]+$",
|
|
77
|
+
"description": "Tag keys in the request (for tag-based access control)",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"pattern": r"^aws:ResourceTag/[a-zA-Z0-9+\-=._:/@]+$",
|
|
81
|
+
"description": "Tags on the resource being accessed",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"pattern": r"^aws:PrincipalTag/[a-zA-Z0-9+\-=._:/@]+$",
|
|
85
|
+
"description": "Tags attached to the principal making the request",
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AWSGlobalConditions:
|
|
91
|
+
"""Manages AWS global condition keys."""
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
"""Initialize with global condition keys."""
|
|
95
|
+
self._global_keys: dict[str, str] = AWS_GLOBAL_CONDITION_KEYS.copy()
|
|
96
|
+
self._patterns: list[dict[str, Any]] = AWS_CONDITION_KEY_PATTERNS.copy()
|
|
97
|
+
|
|
98
|
+
def is_valid_global_key(self, condition_key: str) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Check if a condition key is a valid AWS global condition key.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
condition_key: The condition key to validate (e.g., "aws:SourceIp")
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if valid global condition key, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
# Check exact matches first
|
|
109
|
+
if condition_key in self._global_keys:
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
# Check patterns (for tags and wildcards)
|
|
113
|
+
for pattern_config in self._patterns:
|
|
114
|
+
pattern = pattern_config["pattern"]
|
|
115
|
+
if re.match(pattern, condition_key):
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
def get_key_type(self, condition_key: str) -> str | None:
|
|
121
|
+
"""
|
|
122
|
+
Get the expected type for a global condition key.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
condition_key: The condition key (e.g., "aws:SourceIp")
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Type string (String, ARN, Bool, Date, IPAddress, Numeric) or None if not found
|
|
129
|
+
"""
|
|
130
|
+
# Check exact matches
|
|
131
|
+
if condition_key in self._global_keys:
|
|
132
|
+
return self._global_keys[condition_key]
|
|
133
|
+
|
|
134
|
+
# Check patterns - all tag-based keys are String type
|
|
135
|
+
for pattern_config in self._patterns:
|
|
136
|
+
pattern = pattern_config["pattern"]
|
|
137
|
+
if re.match(pattern, condition_key):
|
|
138
|
+
return "String"
|
|
139
|
+
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def get_all_keys(self) -> dict[str, str]:
|
|
143
|
+
"""Get all explicit global condition keys with their types."""
|
|
144
|
+
return self._global_keys.copy()
|
|
145
|
+
|
|
146
|
+
def get_patterns(self) -> list[dict[str, Any]]:
|
|
147
|
+
"""Get all condition key patterns."""
|
|
148
|
+
return self._patterns.copy()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# Singleton instance
|
|
152
|
+
_global_conditions_instance = None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_global_conditions() -> AWSGlobalConditions:
|
|
156
|
+
"""Get singleton instance of AWSGlobalConditions."""
|
|
157
|
+
global _global_conditions_instance
|
|
158
|
+
if _global_conditions_instance is None:
|
|
159
|
+
_global_conditions_instance = AWSGlobalConditions()
|
|
160
|
+
return _global_conditions_instance
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Category-specific suggestions for sensitive actions.
|
|
3
|
+
|
|
4
|
+
This module defines ABAC-focused (Attribute-Based Access Control) suggestions
|
|
5
|
+
and examples for each sensitive action category. These provide actionable
|
|
6
|
+
guidance for securing sensitive AWS actions.
|
|
7
|
+
|
|
8
|
+
ABAC is the recommended approach as it:
|
|
9
|
+
- Scales across all AWS services
|
|
10
|
+
- Reduces policy maintenance overhead
|
|
11
|
+
- Provides fine-grained access control
|
|
12
|
+
- Enables self-service resource management
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any, Final
|
|
16
|
+
|
|
17
|
+
# ============================================================================
|
|
18
|
+
# ABAC-Focused Category Suggestions
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# Each category provides tailored guidance based on the security risk profile
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
DEFAULT_CATEGORY_SUGGESTIONS: Final[dict[str, dict[str, Any]]] = {
|
|
24
|
+
"credential_exposure": {
|
|
25
|
+
"suggestion": (
|
|
26
|
+
"This action can expose credentials or secrets. Use ABAC to restrict access:\n"
|
|
27
|
+
"• Match principal tags to resource tags (aws:PrincipalTag/team = aws:ResourceTag/team)\n"
|
|
28
|
+
"• Require MFA (aws:MultiFactorAuthPresent = true)\n"
|
|
29
|
+
"• Restrict to trusted networks (aws:SourceIp)\n"
|
|
30
|
+
"• Limit to business hours (aws:CurrentTime)"
|
|
31
|
+
),
|
|
32
|
+
"example": (
|
|
33
|
+
'"Condition": {\n'
|
|
34
|
+
' "StringEquals": {\n'
|
|
35
|
+
' "aws:PrincipalTag/owner": "${aws:ResourceTag/owner}"\n'
|
|
36
|
+
" },\n"
|
|
37
|
+
' "Bool": {"aws:MultiFactorAuthPresent": "true"}\n'
|
|
38
|
+
"}"
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
"data_access": {
|
|
42
|
+
"suggestion": (
|
|
43
|
+
"This action retrieves sensitive data. Use ABAC to control data access:\n"
|
|
44
|
+
"• Match principal tags to resource tags (aws:PrincipalTag/data-access = aws:ResourceTag/data-classification)\n"
|
|
45
|
+
"• Limit by department/team (aws:PrincipalTag/department = aws:ResourceTag/owner)\n"
|
|
46
|
+
"• Restrict data exfiltration (aws:SourceIp or aws:SourceVpc)\n"
|
|
47
|
+
"• Consider data classification levels"
|
|
48
|
+
),
|
|
49
|
+
"example": (
|
|
50
|
+
'"Condition": {\n'
|
|
51
|
+
' "StringEquals": {\n'
|
|
52
|
+
' "aws:PrincipalTag/owner": "${aws:ResourceTag/owner}",\n'
|
|
53
|
+
' "aws:ResourceTag/data-classification": ["public", "internal"]\n'
|
|
54
|
+
" }\n"
|
|
55
|
+
"}"
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
"priv_esc": {
|
|
59
|
+
"suggestion": (
|
|
60
|
+
"This action enables privilege escalation. Use ABAC + strong controls:\n"
|
|
61
|
+
"• Require specific role tags (aws:PrincipalTag/role = admin)\n"
|
|
62
|
+
"• Enforce permissions boundary (iam:PermissionsBoundary)\n"
|
|
63
|
+
"• Require MFA (aws:MultiFactorAuthPresent = true) - CRITICAL\n"
|
|
64
|
+
"• Limit request tags (aws:RequestTag/environment != production)"
|
|
65
|
+
),
|
|
66
|
+
"example": (
|
|
67
|
+
'"Condition": {\n'
|
|
68
|
+
' "StringEquals": {\n'
|
|
69
|
+
' "aws:PrincipalTag/role": "security-admin",\n'
|
|
70
|
+
' "iam:PermissionsBoundary": "arn:aws:iam::*:policy/MaxPermissions"\n'
|
|
71
|
+
" },\n"
|
|
72
|
+
' "Bool": {"aws:MultiFactorAuthPresent": "true"}\n'
|
|
73
|
+
"}"
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
"resource_exposure": {
|
|
77
|
+
"suggestion": (
|
|
78
|
+
"This action modifies resource policies. Use ABAC to prevent unauthorized changes:\n"
|
|
79
|
+
"• Match principal tags to resource tags (aws:PrincipalTag/team = aws:ResourceTag/managed-by)\n"
|
|
80
|
+
"• Restrict by environment (aws:ResourceTag/environment = development)\n"
|
|
81
|
+
"• Prevent external access (aws:PrincipalOrgID)\n"
|
|
82
|
+
"• Require approval tags (aws:RequestTag/change-approved = true)"
|
|
83
|
+
),
|
|
84
|
+
"example": (
|
|
85
|
+
'"Condition": {\n'
|
|
86
|
+
' "StringEquals": {\n'
|
|
87
|
+
' "aws:PrincipalTag/owner": "${aws:ResourceTag/managed-by}",\n'
|
|
88
|
+
' "aws:ResourceTag/environment": "${aws:PrincipalTag/environment}",\n'
|
|
89
|
+
' "aws:PrincipalOrgID": "o-xxxxxxxxxx"\n'
|
|
90
|
+
" }\n"
|
|
91
|
+
"}"
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_category_suggestions() -> dict[str, dict[str, Any]]:
|
|
98
|
+
"""
|
|
99
|
+
Get default category suggestions.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary mapping category IDs to suggestion/example dictionaries
|
|
103
|
+
"""
|
|
104
|
+
return DEFAULT_CATEGORY_SUGGESTIONS.copy()
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Condition requirement configurations for action_condition_enforcement check.
|
|
3
|
+
|
|
4
|
+
This module defines default condition requirements for sensitive actions,
|
|
5
|
+
making it easy to manage complex condition enforcement rules without
|
|
6
|
+
deeply nested YAML/dict structures.
|
|
7
|
+
|
|
8
|
+
Configuration Fields Reference:
|
|
9
|
+
- description: Technical description of what the requirement does (shown in output)
|
|
10
|
+
- example: Concrete code example showing proper condition usage
|
|
11
|
+
- condition_key: The IAM condition key to validate
|
|
12
|
+
- expected_value: (Optional) Expected value for the condition key
|
|
13
|
+
- severity: (Optional) Override default severity for this requirement
|
|
14
|
+
|
|
15
|
+
Field Progression: detect (condition_key) → explain (description) → demonstrate (example)
|
|
16
|
+
|
|
17
|
+
For detailed explanation of these fields and how to customize requirements,
|
|
18
|
+
see: docs/condition-requirements.md and docs/configuration.md#customizing-messages
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Final
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
# Condition Requirement Definitions
|
|
25
|
+
# ============================================================================
|
|
26
|
+
|
|
27
|
+
# IAM PassRole - CRITICAL: Prevent privilege escalation
|
|
28
|
+
IAM_PASS_ROLE_REQUIREMENT: Final[dict[str, Any]] = {
|
|
29
|
+
"actions": ["iam:PassRole"],
|
|
30
|
+
"severity": "high",
|
|
31
|
+
"required_conditions": [
|
|
32
|
+
{
|
|
33
|
+
"condition_key": "iam:PassedToService",
|
|
34
|
+
"description": (
|
|
35
|
+
"Restrict which AWS services can assume the passed role to prevent privilege escalation"
|
|
36
|
+
),
|
|
37
|
+
"example": (
|
|
38
|
+
'"Condition": {\n'
|
|
39
|
+
' "StringEquals": {\n'
|
|
40
|
+
' "iam:PassedToService": [\n'
|
|
41
|
+
' "lambda.amazonaws.com",\n'
|
|
42
|
+
' "ecs-tasks.amazonaws.com",\n'
|
|
43
|
+
' "ec2.amazonaws.com",\n'
|
|
44
|
+
' "glue.amazonaws.com"\n'
|
|
45
|
+
" ]\n"
|
|
46
|
+
" }\n"
|
|
47
|
+
"}"
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# S3 Write Operations - Require organization ID
|
|
54
|
+
S3_WRITE_ORG_ID: Final[dict[str, Any]] = {
|
|
55
|
+
"actions": ["s3:PutObject"],
|
|
56
|
+
"severity": "medium",
|
|
57
|
+
"required_conditions": [
|
|
58
|
+
{
|
|
59
|
+
"condition_key": "aws:ResourceOrgId",
|
|
60
|
+
"description": (
|
|
61
|
+
"Require aws:ResourceAccount, aws:ResourceOrgID or aws:ResourceOrgPaths condition(s) for S3 write actions to enforce organization-level access control"
|
|
62
|
+
),
|
|
63
|
+
"example": (
|
|
64
|
+
"{\n"
|
|
65
|
+
' "Condition": {\n'
|
|
66
|
+
' "StringEquals": {\n'
|
|
67
|
+
' "aws:ResourceOrgId": "${aws:PrincipalOrgID}"\n'
|
|
68
|
+
" }\n"
|
|
69
|
+
" }\n"
|
|
70
|
+
"}"
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# IP Restrictions - Source IP requirements
|
|
77
|
+
SOURCE_IP_RESTRICTIONS: Final[dict[str, Any]] = {
|
|
78
|
+
"action_patterns": [
|
|
79
|
+
"^ssm:StartSession$",
|
|
80
|
+
"^ssm:Run.*$",
|
|
81
|
+
"^s3:GetObject$",
|
|
82
|
+
"^rds-db:Connect$",
|
|
83
|
+
],
|
|
84
|
+
"severity": "low",
|
|
85
|
+
"required_conditions": [
|
|
86
|
+
{
|
|
87
|
+
"condition_key": "aws:SourceIp",
|
|
88
|
+
"description": "Restrict access to corporate IP ranges",
|
|
89
|
+
"example": (
|
|
90
|
+
"{\n"
|
|
91
|
+
' "Condition": {\n'
|
|
92
|
+
' "IpAddress": {\n'
|
|
93
|
+
' "aws:SourceIp": [\n'
|
|
94
|
+
' "10.0.0.0/8",\n'
|
|
95
|
+
' "172.16.0.0/12"\n'
|
|
96
|
+
" ]\n"
|
|
97
|
+
" }\n"
|
|
98
|
+
" }\n"
|
|
99
|
+
"}"
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# S3 Secure Transport - Never allow insecure transport
|
|
106
|
+
S3_SECURE_TRANSPORT: Final[dict[str, Any]] = {
|
|
107
|
+
"actions": ["s3:GetObject", "s3:PutObject"],
|
|
108
|
+
"severity": "critical",
|
|
109
|
+
"required_conditions": {
|
|
110
|
+
"none_of": [
|
|
111
|
+
{
|
|
112
|
+
"condition_key": "aws:SecureTransport",
|
|
113
|
+
"expected_value": False,
|
|
114
|
+
"description": "Never allow insecure transport to be explicitly permitted",
|
|
115
|
+
"example": (
|
|
116
|
+
"# Set this condition to true to enforce secure transport or remove it entirely\n"
|
|
117
|
+
"{\n"
|
|
118
|
+
' "Condition": {\n'
|
|
119
|
+
' "Bool": {\n'
|
|
120
|
+
' "aws:SecureTransport": "true"\n'
|
|
121
|
+
" }\n"
|
|
122
|
+
" }\n"
|
|
123
|
+
"}"
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Prevent overly permissive IP ranges
|
|
131
|
+
PREVENT_PUBLIC_IP: Final[dict[str, Any]] = {
|
|
132
|
+
"action_patterns": ["^s3:.*"],
|
|
133
|
+
"severity": "high",
|
|
134
|
+
"required_conditions": {
|
|
135
|
+
"none_of": [
|
|
136
|
+
{
|
|
137
|
+
"condition_key": "aws:SourceIp",
|
|
138
|
+
"expected_value": "0.0.0.0/0",
|
|
139
|
+
"description": "Do not allow access from any IP address",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ============================================================================
|
|
146
|
+
# Condition Requirements
|
|
147
|
+
# ============================================================================
|
|
148
|
+
|
|
149
|
+
CONDITION_REQUIREMENTS: Final[list[dict[str, Any]]] = [
|
|
150
|
+
IAM_PASS_ROLE_REQUIREMENT,
|
|
151
|
+
S3_WRITE_ORG_ID,
|
|
152
|
+
SOURCE_IP_RESTRICTIONS,
|
|
153
|
+
S3_SECURE_TRANSPORT,
|
|
154
|
+
PREVENT_PUBLIC_IP,
|
|
155
|
+
]
|