iam-policy-validator 1.0.4__py3-none-any.whl → 1.1.1__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.0.4.dist-info → iam_policy_validator-1.1.1.dist-info}/METADATA +88 -10
- iam_policy_validator-1.1.1.dist-info/RECORD +53 -0
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/action_condition_enforcement.py +112 -28
- iam_validator/checks/action_resource_constraint.py +151 -0
- iam_validator/checks/action_validation.py +18 -138
- iam_validator/checks/security_best_practices.py +241 -400
- 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/commands/validate.py +7 -5
- 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 +40 -3
- iam_validator/core/defaults.py +334 -0
- iam_validator/core/formatters/__init__.py +2 -0
- iam_validator/core/formatters/console.py +44 -7
- iam_validator/core/formatters/csv.py +7 -2
- iam_validator/core/formatters/enhanced.py +433 -0
- iam_validator/core/formatters/html.py +127 -37
- iam_validator/core/formatters/markdown.py +10 -2
- iam_validator/core/models.py +30 -6
- iam_validator/core/policy_checks.py +21 -2
- iam_validator/core/report.py +112 -26
- iam_policy_validator-1.0.4.dist-info/RECORD +0 -45
- {iam_policy_validator-1.0.4.dist-info → iam_policy_validator-1.1.1.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.0.4.dist-info → iam_policy_validator-1.1.1.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.0.4.dist-info → iam_policy_validator-1.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,21 +15,57 @@ from typing import Any
|
|
|
15
15
|
import yaml
|
|
16
16
|
|
|
17
17
|
from iam_validator.core.check_registry import CheckConfig, CheckRegistry, PolicyCheck
|
|
18
|
+
from iam_validator.core.defaults import get_default_config
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def deep_merge(base: dict, override: dict) -> dict:
|
|
24
|
+
"""
|
|
25
|
+
Deep merge two dictionaries, with override taking precedence.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base: Base dictionary with default values
|
|
29
|
+
override: Dictionary with override values
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Merged dictionary where override values take precedence
|
|
33
|
+
"""
|
|
34
|
+
result = base.copy()
|
|
35
|
+
for key, value in override.items():
|
|
36
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
37
|
+
result[key] = deep_merge(result[key], value)
|
|
38
|
+
else:
|
|
39
|
+
result[key] = value
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
22
43
|
class ValidatorConfig:
|
|
23
44
|
"""Main configuration object for the validator."""
|
|
24
45
|
|
|
25
|
-
def __init__(self, config_dict: dict[str, Any] | None = None):
|
|
46
|
+
def __init__(self, config_dict: dict[str, Any] | None = None, use_defaults: bool = True):
|
|
26
47
|
"""
|
|
27
48
|
Initialize configuration from a dictionary.
|
|
28
49
|
|
|
29
50
|
Args:
|
|
30
|
-
config_dict: Dictionary loaded from YAML config file
|
|
51
|
+
config_dict: Dictionary loaded from YAML config file.
|
|
52
|
+
If None, either uses default configuration (if use_defaults=True)
|
|
53
|
+
or creates an empty configuration (if use_defaults=False).
|
|
54
|
+
If provided, merges with defaults (user config takes precedence).
|
|
55
|
+
use_defaults: Whether to load default configuration. Set to False for testing
|
|
56
|
+
or when you want an empty configuration.
|
|
31
57
|
"""
|
|
32
|
-
|
|
58
|
+
# Start with default configuration if requested
|
|
59
|
+
if use_defaults:
|
|
60
|
+
default_config = get_default_config()
|
|
61
|
+
# Merge user config with defaults if provided
|
|
62
|
+
if config_dict:
|
|
63
|
+
self.config_dict = deep_merge(default_config, config_dict)
|
|
64
|
+
else:
|
|
65
|
+
self.config_dict = default_config
|
|
66
|
+
else:
|
|
67
|
+
# No defaults - use provided config or empty dict
|
|
68
|
+
self.config_dict = config_dict or {}
|
|
33
69
|
|
|
34
70
|
# Support both nested and flat structure
|
|
35
71
|
# New flat structure: each check is a top-level key ending with "_check"
|
|
@@ -210,6 +246,7 @@ class ConfigLoader:
|
|
|
210
246
|
severity=check_config_dict.get("severity"),
|
|
211
247
|
config=check_config_dict,
|
|
212
248
|
description=check_config_dict.get("description", check.description),
|
|
249
|
+
root_config=config.config_dict, # Pass full config for cross-check access
|
|
213
250
|
)
|
|
214
251
|
|
|
215
252
|
registry.configure_check(check_id, check_config)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default configuration for IAM Policy Validator.
|
|
3
|
+
|
|
4
|
+
This module contains the default configuration that is used when no user
|
|
5
|
+
configuration file is provided. User configuration files will override
|
|
6
|
+
these defaults.
|
|
7
|
+
|
|
8
|
+
This configuration is synced with the default-config.yaml file.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# SEVERITY LEVELS
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# The validator uses two types of severity levels:
|
|
15
|
+
#
|
|
16
|
+
# 1. IAM VALIDITY SEVERITIES (for AWS IAM policy correctness):
|
|
17
|
+
# - error: Policy violates AWS IAM rules (invalid actions, ARNs, etc.)
|
|
18
|
+
# - warning: Policy may have IAM-related issues but is technically valid
|
|
19
|
+
# - info: Informational messages about the policy structure
|
|
20
|
+
#
|
|
21
|
+
# 2. SECURITY SEVERITIES (for security best practices):
|
|
22
|
+
# - critical: Critical security risk (e.g., wildcard action + resource)
|
|
23
|
+
# - high: High security risk (e.g., missing required conditions)
|
|
24
|
+
# - medium: Medium security risk (e.g., overly permissive wildcards)
|
|
25
|
+
# - low: Low security risk (e.g., minor best practice violations)
|
|
26
|
+
#
|
|
27
|
+
# Use 'error' for policy validity issues, and 'critical/high/medium/low' for
|
|
28
|
+
# security best practices. This distinction helps separate "broken policies"
|
|
29
|
+
# from "insecure but valid policies".
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
# Default configuration dictionary
|
|
33
|
+
DEFAULT_CONFIG = {
|
|
34
|
+
"settings": {
|
|
35
|
+
"fail_fast": False,
|
|
36
|
+
"max_concurrent": 10,
|
|
37
|
+
"enable_builtin_checks": True,
|
|
38
|
+
"parallel_execution": True,
|
|
39
|
+
"cache_enabled": True,
|
|
40
|
+
"cache_ttl_hours": 168,
|
|
41
|
+
"fail_on_severity": ["error", "critical"],
|
|
42
|
+
},
|
|
43
|
+
"sid_uniqueness_check": {
|
|
44
|
+
"enabled": True,
|
|
45
|
+
"severity": "error",
|
|
46
|
+
"description": "Validates that Statement IDs (Sids) are unique within the policy",
|
|
47
|
+
},
|
|
48
|
+
"policy_size_check": {
|
|
49
|
+
"enabled": True,
|
|
50
|
+
"severity": "error",
|
|
51
|
+
"description": "Validates that IAM policies don't exceed AWS size limits",
|
|
52
|
+
"policy_type": "managed",
|
|
53
|
+
},
|
|
54
|
+
"action_validation_check": {
|
|
55
|
+
"enabled": True,
|
|
56
|
+
"severity": "error",
|
|
57
|
+
"description": "Validates that actions exist in AWS services",
|
|
58
|
+
},
|
|
59
|
+
"condition_key_validation_check": {
|
|
60
|
+
"enabled": True,
|
|
61
|
+
"severity": "error",
|
|
62
|
+
"description": "Validates condition keys against AWS service definitions for specified actions",
|
|
63
|
+
"validate_aws_global_keys": True,
|
|
64
|
+
},
|
|
65
|
+
"resource_validation_check": {
|
|
66
|
+
"enabled": True,
|
|
67
|
+
"severity": "error",
|
|
68
|
+
"description": "Validates ARN format for resources",
|
|
69
|
+
"arn_pattern": "^arn:(aws|aws-cn|aws-us-gov|aws-eusc|aws-iso|aws-iso-b|aws-iso-e|aws-iso-f):[a-z0-9\\-]+:[a-z0-9\\-*]*:[0-9*]*:.+$",
|
|
70
|
+
},
|
|
71
|
+
"action_resource_constraint_check": {
|
|
72
|
+
"enabled": True,
|
|
73
|
+
"severity": "error",
|
|
74
|
+
"description": "Validates that actions without required resource types use Resource: '*'",
|
|
75
|
+
},
|
|
76
|
+
"security_best_practices_check": {
|
|
77
|
+
"enabled": True,
|
|
78
|
+
"description": "Checks for common security anti-patterns",
|
|
79
|
+
"allowed_wildcards": [
|
|
80
|
+
"autoscaling:Describe*",
|
|
81
|
+
"cloudwatch:Describe*",
|
|
82
|
+
"cloudwatch:Get*",
|
|
83
|
+
"cloudwatch:List*",
|
|
84
|
+
"dynamodb:Describe*",
|
|
85
|
+
"ec2:Describe*",
|
|
86
|
+
"elasticloadbalancing:Describe*",
|
|
87
|
+
"iam:Get*",
|
|
88
|
+
"iam:List*",
|
|
89
|
+
"kms:Describe*",
|
|
90
|
+
"lambda:Get*",
|
|
91
|
+
"lambda:List*",
|
|
92
|
+
"logs:Describe*",
|
|
93
|
+
"logs:Filter*",
|
|
94
|
+
"logs:Get*",
|
|
95
|
+
"rds:Describe*",
|
|
96
|
+
"route53:Get*",
|
|
97
|
+
"route53:List*",
|
|
98
|
+
"s3:Describe*",
|
|
99
|
+
"s3:GetBucket*",
|
|
100
|
+
"s3:GetM*",
|
|
101
|
+
"s3:List*",
|
|
102
|
+
"sqs:Get*",
|
|
103
|
+
"sqs:List*",
|
|
104
|
+
"apigateway:GET",
|
|
105
|
+
],
|
|
106
|
+
"wildcard_action_check": {
|
|
107
|
+
"enabled": True,
|
|
108
|
+
"severity": "medium",
|
|
109
|
+
"message": "Statement allows all actions (*)",
|
|
110
|
+
"suggestion": "Replace wildcard with specific actions needed for your use case",
|
|
111
|
+
"example": """Replace:
|
|
112
|
+
"Action": ["*"]
|
|
113
|
+
|
|
114
|
+
With specific actions:
|
|
115
|
+
"Action": [
|
|
116
|
+
"s3:GetObject",
|
|
117
|
+
"s3:PutObject",
|
|
118
|
+
"s3:ListBucket"
|
|
119
|
+
]
|
|
120
|
+
""",
|
|
121
|
+
},
|
|
122
|
+
"wildcard_resource_check": {
|
|
123
|
+
"enabled": True,
|
|
124
|
+
"severity": "medium",
|
|
125
|
+
"message": "Statement applies to all resources (*)",
|
|
126
|
+
"suggestion": "Replace wildcard with specific resource ARNs",
|
|
127
|
+
"example": """Replace:
|
|
128
|
+
"Resource": "*"
|
|
129
|
+
|
|
130
|
+
With specific ARNs:
|
|
131
|
+
"Resource": [
|
|
132
|
+
"arn:aws:service:region:account-id:resource-type/resource-id",
|
|
133
|
+
"arn:aws:service:region:account-id:resource-type/*"
|
|
134
|
+
]
|
|
135
|
+
""",
|
|
136
|
+
},
|
|
137
|
+
"full_wildcard_check": {
|
|
138
|
+
"enabled": True,
|
|
139
|
+
"severity": "critical",
|
|
140
|
+
"message": "Statement allows all actions on all resources - CRITICAL SECURITY RISK",
|
|
141
|
+
"suggestion": "This grants full administrative access. Replace both wildcards with specific actions and resources to follow least-privilege principle",
|
|
142
|
+
"example": """Replace:
|
|
143
|
+
"Action": "*",
|
|
144
|
+
"Resource": "*"
|
|
145
|
+
|
|
146
|
+
With specific values:
|
|
147
|
+
"Action": [
|
|
148
|
+
"s3:GetObject",
|
|
149
|
+
"s3:PutObject"
|
|
150
|
+
],
|
|
151
|
+
"Resource": [
|
|
152
|
+
"arn:aws:s3:::my-bucket/*"
|
|
153
|
+
]
|
|
154
|
+
""",
|
|
155
|
+
},
|
|
156
|
+
"service_wildcard_check": {
|
|
157
|
+
"enabled": True,
|
|
158
|
+
"severity": "high",
|
|
159
|
+
"allowed_services": ["logs", "cloudwatch", "xray"],
|
|
160
|
+
},
|
|
161
|
+
"sensitive_action_check": {
|
|
162
|
+
"enabled": True,
|
|
163
|
+
"severity": "medium",
|
|
164
|
+
"sensitive_actions": [
|
|
165
|
+
"iam:AddClientIDToOpenIDConnectProvider",
|
|
166
|
+
"iam:AttachRolePolicy",
|
|
167
|
+
"iam:AttachUserPolicy",
|
|
168
|
+
"iam:CreateAccessKey",
|
|
169
|
+
"iam:CreateOpenIDConnectProvider",
|
|
170
|
+
"iam:CreatePolicyVersion",
|
|
171
|
+
"iam:CreateRole",
|
|
172
|
+
"iam:CreateSAMLProvider",
|
|
173
|
+
"iam:CreateUser",
|
|
174
|
+
"iam:DeleteAccessKey",
|
|
175
|
+
"iam:DeleteLoginProfile",
|
|
176
|
+
"iam:DeleteOpenIDConnectProvider",
|
|
177
|
+
"iam:DeleteRole",
|
|
178
|
+
"iam:DeleteRolePolicy",
|
|
179
|
+
"iam:DeleteSAMLProvider",
|
|
180
|
+
"iam:DeleteUser",
|
|
181
|
+
"iam:DeleteUserPolicy",
|
|
182
|
+
"iam:DetachRolePolicy",
|
|
183
|
+
"iam:DetachUserPolicy",
|
|
184
|
+
"iam:PutRolePolicy",
|
|
185
|
+
"iam:PutUserPolicy",
|
|
186
|
+
"iam:SetDefaultPolicyVersion",
|
|
187
|
+
"iam:UpdateAccessKey",
|
|
188
|
+
"iam:UpdateAssumeRolePolicy",
|
|
189
|
+
"kms:DisableKey",
|
|
190
|
+
"kms:PutKeyPolicy",
|
|
191
|
+
"kms:ScheduleKeyDeletion",
|
|
192
|
+
"secretsmanager:DeleteSecret",
|
|
193
|
+
"secretsmanager:GetSecretValue",
|
|
194
|
+
"secretsmanager:PutSecretValue",
|
|
195
|
+
"ssm:DeleteParameter",
|
|
196
|
+
"ssm:PutParameter",
|
|
197
|
+
"ec2:DeleteSnapshot",
|
|
198
|
+
"ec2:DeleteVolume",
|
|
199
|
+
"ec2:DeleteVpc",
|
|
200
|
+
"ec2:ModifyInstanceAttribute",
|
|
201
|
+
"ec2:TerminateInstances",
|
|
202
|
+
"ecr:DeleteRepository",
|
|
203
|
+
"ecs:DeleteCluster",
|
|
204
|
+
"ecs:DeleteService",
|
|
205
|
+
"eks:DeleteCluster",
|
|
206
|
+
"lambda:DeleteFunction",
|
|
207
|
+
"lambda:DeleteFunctionConcurrency",
|
|
208
|
+
"lambda:PutFunctionConcurrency",
|
|
209
|
+
"dynamodb:DeleteTable",
|
|
210
|
+
"efs:DeleteFileSystem",
|
|
211
|
+
"elasticache:DeleteCacheCluster",
|
|
212
|
+
"fsx:DeleteFileSystem",
|
|
213
|
+
"rds:DeleteDBCluster",
|
|
214
|
+
"rds:DeleteDBInstance",
|
|
215
|
+
"redshift:DeleteCluster",
|
|
216
|
+
"backup:DeleteBackupVault",
|
|
217
|
+
"glacier:DeleteArchive",
|
|
218
|
+
"s3:DeleteBucket",
|
|
219
|
+
"s3:DeleteBucketPolicy",
|
|
220
|
+
"s3:DeleteObject",
|
|
221
|
+
"s3:PutBucketPolicy",
|
|
222
|
+
"s3:PutLifecycleConfiguration",
|
|
223
|
+
"ec2:AuthorizeSecurityGroupIngress",
|
|
224
|
+
"ec2:DeleteSecurityGroup",
|
|
225
|
+
"ec2:DisassociateRouteTable",
|
|
226
|
+
"ec2:RevokeSecurityGroupEgress",
|
|
227
|
+
"cloudtrail:DeleteTrail",
|
|
228
|
+
"cloudtrail:StopLogging",
|
|
229
|
+
"cloudwatch:DeleteLogGroup",
|
|
230
|
+
"config:DeleteConfigurationRecorder",
|
|
231
|
+
"guardduty:DeleteDetector",
|
|
232
|
+
"account:CloseAccount",
|
|
233
|
+
"account:CreateAccount",
|
|
234
|
+
"organizations:LeaveOrganization",
|
|
235
|
+
"organizations:RemoveAccountFromOrganization",
|
|
236
|
+
],
|
|
237
|
+
"sensitive_action_patterns": ["^iam:Delete.*"],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
"action_condition_enforcement_check": {
|
|
241
|
+
"enabled": True,
|
|
242
|
+
"severity": "high",
|
|
243
|
+
"description": "Enforce specific IAM condition requirements (unified: MFA, IP, tags, etc.)",
|
|
244
|
+
"action_condition_requirements": [
|
|
245
|
+
{
|
|
246
|
+
"actions": ["iam:PassRole"],
|
|
247
|
+
"action_patterns": ["^iam:Pas?.*$"],
|
|
248
|
+
"severity": "high",
|
|
249
|
+
"required_conditions": [
|
|
250
|
+
{
|
|
251
|
+
"condition_key": "iam:PassedToService",
|
|
252
|
+
"description": "Specify which AWS services are allowed to use the passed role to prevent privilege escalation",
|
|
253
|
+
"example": """"Condition": {
|
|
254
|
+
"StringEquals": {
|
|
255
|
+
"iam:PassedToService": [
|
|
256
|
+
"lambda.amazonaws.com",
|
|
257
|
+
"ecs-tasks.amazonaws.com",
|
|
258
|
+
"ec2.amazonaws.com",
|
|
259
|
+
"glue.amazonaws.com",
|
|
260
|
+
"lambda.amazonaws.com"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
""",
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"actions": [
|
|
270
|
+
"iam:Create*",
|
|
271
|
+
"iam:CreateRole",
|
|
272
|
+
"iam:Put*Policy*",
|
|
273
|
+
"iam:PutUserPolicy",
|
|
274
|
+
"iam:PutRolePolicy",
|
|
275
|
+
"iam:Attach*Policy*",
|
|
276
|
+
"iam:AttachUserPolicy",
|
|
277
|
+
"iam:AttachRolePolicy",
|
|
278
|
+
],
|
|
279
|
+
"action_patterns": ["^iam:Create", "^iam:Put.*Policy", "^iam:Attach.*Policy"],
|
|
280
|
+
"severity": "high",
|
|
281
|
+
"required_conditions": [
|
|
282
|
+
{
|
|
283
|
+
"condition_key": "iam:PermissionsBoundary",
|
|
284
|
+
"description": "Require permissions boundary for sensitive IAM operations to prevent privilege escalation",
|
|
285
|
+
"expected_value": "arn:aws:iam::*:policy/DeveloperBoundary",
|
|
286
|
+
"example": """# See: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
|
|
287
|
+
"Condition": {
|
|
288
|
+
"StringEquals": {
|
|
289
|
+
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/XCompanyBoundaries"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
""",
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"action_patterns": [
|
|
298
|
+
"^ssm:StartSession$",
|
|
299
|
+
"^ssm:Run.*$",
|
|
300
|
+
"^s3:GetObject$",
|
|
301
|
+
"^rds-db:Connect$",
|
|
302
|
+
],
|
|
303
|
+
"severity": "low",
|
|
304
|
+
"required_conditions": [
|
|
305
|
+
{
|
|
306
|
+
"condition_key": "aws:SourceIp",
|
|
307
|
+
"description": "Restrict access to corporate IP ranges",
|
|
308
|
+
"example": """"Condition": {
|
|
309
|
+
"IpAddress": {
|
|
310
|
+
"aws:SourceIp": [
|
|
311
|
+
"10.0.0.0/8",
|
|
312
|
+
"172.16.0.0/12"
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
""",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_default_config() -> dict:
|
|
326
|
+
"""
|
|
327
|
+
Get a deep copy of the default configuration.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
A deep copy of the default configuration dictionary
|
|
331
|
+
"""
|
|
332
|
+
import copy
|
|
333
|
+
|
|
334
|
+
return copy.deepcopy(DEFAULT_CONFIG)
|
|
@@ -7,6 +7,7 @@ from iam_validator.core.formatters.base import (
|
|
|
7
7
|
)
|
|
8
8
|
from iam_validator.core.formatters.console import ConsoleFormatter
|
|
9
9
|
from iam_validator.core.formatters.csv import CSVFormatter
|
|
10
|
+
from iam_validator.core.formatters.enhanced import EnhancedFormatter
|
|
10
11
|
from iam_validator.core.formatters.html import HTMLFormatter
|
|
11
12
|
from iam_validator.core.formatters.json import JSONFormatter
|
|
12
13
|
from iam_validator.core.formatters.markdown import MarkdownFormatter
|
|
@@ -17,6 +18,7 @@ __all__ = [
|
|
|
17
18
|
"FormatterRegistry",
|
|
18
19
|
"get_global_registry",
|
|
19
20
|
"ConsoleFormatter",
|
|
21
|
+
"EnhancedFormatter",
|
|
20
22
|
"JSONFormatter",
|
|
21
23
|
"MarkdownFormatter",
|
|
22
24
|
"SARIFFormatter",
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
"""Console formatter -
|
|
1
|
+
"""Console formatter - Classic console output using report.py."""
|
|
2
|
+
|
|
3
|
+
from io import StringIO
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
2
6
|
|
|
3
7
|
from iam_validator.core.formatters.base import OutputFormatter
|
|
4
8
|
from iam_validator.core.models import ValidationReport
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class ConsoleFormatter(OutputFormatter):
|
|
8
|
-
"""
|
|
12
|
+
"""Classic console formatter - uses the standard report.py print_console_report output."""
|
|
9
13
|
|
|
10
14
|
@property
|
|
11
15
|
def format_id(self) -> str:
|
|
@@ -13,10 +17,43 @@ class ConsoleFormatter(OutputFormatter):
|
|
|
13
17
|
|
|
14
18
|
@property
|
|
15
19
|
def description(self) -> str:
|
|
16
|
-
return "
|
|
20
|
+
return "Classic console output with colors and tables"
|
|
17
21
|
|
|
18
22
|
def format(self, report: ValidationReport, **kwargs) -> str:
|
|
19
|
-
"""Format
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
"""Format validation report using the classic console output from report.py.
|
|
24
|
+
|
|
25
|
+
This delegates to the ReportGenerator.print_console_report method,
|
|
26
|
+
capturing its output to return as a string.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
report: Validation report to format
|
|
30
|
+
**kwargs: Additional options (color: bool = True)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Formatted string with classic console output
|
|
34
|
+
"""
|
|
35
|
+
# Import here to avoid circular dependency
|
|
36
|
+
from iam_validator.core.report import ReportGenerator
|
|
37
|
+
|
|
38
|
+
# Allow disabling color for plain text output
|
|
39
|
+
color = kwargs.get("color", True)
|
|
40
|
+
|
|
41
|
+
# Capture the output from print_console_report
|
|
42
|
+
string_buffer = StringIO()
|
|
43
|
+
console = Console(file=string_buffer, force_terminal=color, width=120)
|
|
44
|
+
|
|
45
|
+
# Create a generator instance with our custom console
|
|
46
|
+
generator = ReportGenerator()
|
|
47
|
+
|
|
48
|
+
# Replace the console temporarily to capture output
|
|
49
|
+
original_console = generator.console
|
|
50
|
+
generator.console = console
|
|
51
|
+
|
|
52
|
+
# Call the actual print_console_report method
|
|
53
|
+
generator.print_console_report(report)
|
|
54
|
+
|
|
55
|
+
# Restore original console
|
|
56
|
+
generator.console = original_console
|
|
57
|
+
|
|
58
|
+
# Return captured output
|
|
59
|
+
return string_buffer.getvalue()
|
|
@@ -68,9 +68,14 @@ class CSVFormatter(OutputFormatter):
|
|
|
68
68
|
writer.writerow(["Summary Statistics"])
|
|
69
69
|
writer.writerow(["Metric", "Value"])
|
|
70
70
|
writer.writerow(["Total Policies", report.total_policies])
|
|
71
|
-
writer.writerow(["Valid Policies", report.valid_policies])
|
|
72
|
-
writer.writerow(["Invalid Policies", report.invalid_policies])
|
|
71
|
+
writer.writerow(["Valid Policies (IAM)", report.valid_policies])
|
|
72
|
+
writer.writerow(["Invalid Policies (IAM)", report.invalid_policies])
|
|
73
|
+
writer.writerow(["Policies with Security Findings", report.policies_with_security_issues])
|
|
73
74
|
writer.writerow(["Total Issues", report.total_issues])
|
|
75
|
+
writer.writerow(["Validity Issues", report.validity_issues])
|
|
76
|
+
writer.writerow(["Security Issues", report.security_issues])
|
|
77
|
+
writer.writerow([""])
|
|
78
|
+
writer.writerow(["Legacy Severity Breakdown"])
|
|
74
79
|
writer.writerow(["Errors", errors])
|
|
75
80
|
writer.writerow(["Warnings", warnings])
|
|
76
81
|
writer.writerow(["Info", infos])
|