iam-policy-validator 1.0.4__py3-none-any.whl → 1.1.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.

@@ -59,9 +59,9 @@ Examples:
59
59
  parser.add_argument(
60
60
  "--format",
61
61
  "-f",
62
- choices=["console", "json", "markdown", "html", "csv", "sarif"],
62
+ choices=["console", "enhanced", "json", "markdown", "html", "csv", "sarif"],
63
63
  default="console",
64
- help="Output format (default: console)",
64
+ help="Output format (default: console). Use 'enhanced' for modern visual output with Rich library",
65
65
  )
66
66
 
67
67
  parser.add_argument(
@@ -177,7 +177,8 @@ Examples:
177
177
  report = generator.generate_report(results)
178
178
 
179
179
  # Output results
180
- if args.format == "console":
180
+ if args.format is None:
181
+ # Default: use classic console output (direct Rich printing)
181
182
  generator.print_console_report(report)
182
183
  elif args.format == "json":
183
184
  if args.output:
@@ -190,7 +191,7 @@ Examples:
190
191
  else:
191
192
  print(generator.generate_github_comment(report))
192
193
  else:
193
- # Use formatter registry for other formats (html, csv, sarif)
194
+ # Use formatter registry for other formats (enhanced, html, csv, sarif)
194
195
  output_content = generator.format_report(report, args.format)
195
196
  if args.output:
196
197
  with open(args.output, "w", encoding="utf-8") as f:
@@ -285,6 +286,7 @@ Examples:
285
286
 
286
287
  # Output final results
287
288
  if args.format == "console":
289
+ # Classic console output (direct Rich printing from report.py)
288
290
  generator.print_console_report(report)
289
291
  elif args.format == "json":
290
292
  if args.output:
@@ -297,7 +299,7 @@ Examples:
297
299
  else:
298
300
  print(generator.generate_github_comment(report))
299
301
  else:
300
- # Use formatter registry for other formats (html, csv, sarif)
302
+ # Use formatter registry for other formats (enhanced, html, csv, sarif)
301
303
  output_content = generator.format_report(report, args.format)
302
304
  if args.output:
303
305
  with open(args.output, "w", encoding="utf-8") as f:
@@ -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
- self.config_dict = config_dict or {}
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"
@@ -0,0 +1,304 @@
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_directory": ".cache/aws_services",
41
+ "cache_ttl_hours": 24,
42
+ "fail_on_severity": ["error", "critical"],
43
+ },
44
+ "sid_uniqueness_check": {
45
+ "enabled": True,
46
+ "severity": "error",
47
+ "description": "Validates that Statement IDs (Sids) are unique within the policy",
48
+ },
49
+ "policy_size_check": {
50
+ "enabled": True,
51
+ "severity": "error",
52
+ "description": "Validates that IAM policies don't exceed AWS size limits",
53
+ "policy_type": "managed",
54
+ },
55
+ "action_validation_check": {
56
+ "enabled": True,
57
+ "severity": "error",
58
+ "description": "Validates that actions exist in AWS services",
59
+ "disable_wildcard_warnings": True,
60
+ },
61
+ "condition_key_validation_check": {
62
+ "enabled": True,
63
+ "severity": "error",
64
+ "description": "Validates condition keys against AWS service definitions",
65
+ "validate_aws_global_keys": True,
66
+ },
67
+ "resource_validation_check": {
68
+ "enabled": True,
69
+ "severity": "error",
70
+ "description": "Validates ARN format for resources",
71
+ "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*]*:.+$",
72
+ },
73
+ "security_best_practices_check": {
74
+ "enabled": True,
75
+ "description": "Checks for common security anti-patterns",
76
+ "wildcard_action_check": {
77
+ "enabled": True,
78
+ "severity": "medium",
79
+ "message": "Statement allows all actions (*)",
80
+ "suggestion": "Replace wildcard with specific actions needed for your use case",
81
+ "example": """Replace:
82
+ "Action": ["*"]
83
+
84
+ With specific actions:
85
+ "Action": [
86
+ "s3:GetObject",
87
+ "s3:PutObject",
88
+ "s3:ListBucket"
89
+ ]
90
+ """,
91
+ },
92
+ "wildcard_resource_check": {
93
+ "enabled": True,
94
+ "severity": "medium",
95
+ "message": "Statement applies to all resources (*)",
96
+ "suggestion": "Replace wildcard with specific resource ARNs",
97
+ "example": """Replace:
98
+ "Resource": "*"
99
+
100
+ With specific ARNs:
101
+ "Resource": [
102
+ "arn:aws:service:region:account-id:resource-type/resource-id",
103
+ "arn:aws:service:region:account-id:resource-type/*"
104
+ ]
105
+ """,
106
+ },
107
+ "full_wildcard_check": {
108
+ "enabled": True,
109
+ "severity": "critical",
110
+ "message": "Statement allows all actions on all resources - CRITICAL SECURITY RISK",
111
+ "suggestion": "This grants full administrative access. Replace both wildcards with specific actions and resources to follow least-privilege principle",
112
+ "example": """Replace:
113
+ "Action": "*",
114
+ "Resource": "*"
115
+
116
+ With specific values:
117
+ "Action": [
118
+ "s3:GetObject",
119
+ "s3:PutObject"
120
+ ],
121
+ "Resource": [
122
+ "arn:aws:s3:::my-bucket/*"
123
+ ]
124
+ """,
125
+ },
126
+ "service_wildcard_check": {
127
+ "enabled": True,
128
+ "severity": "high",
129
+ "allowed_services": ["logs", "cloudwatch"],
130
+ },
131
+ "sensitive_action_check": {
132
+ "enabled": True,
133
+ "severity": "medium",
134
+ "sensitive_actions": [
135
+ "iam:CreateUser",
136
+ "iam:CreateRole",
137
+ "iam:PutUserPolicy",
138
+ "iam:PutRolePolicy",
139
+ "iam:AttachUserPolicy",
140
+ "iam:AttachRolePolicy",
141
+ "iam:CreateAccessKey",
142
+ "iam:DeleteUser",
143
+ "iam:DeleteRole",
144
+ "s3:DeleteBucket",
145
+ "s3:PutBucketPolicy",
146
+ "s3:DeleteBucketPolicy",
147
+ "ec2:TerminateInstances",
148
+ "ec2:DeleteVolume",
149
+ "rds:DeleteDBInstance",
150
+ "lambda:DeleteFunction",
151
+ "eks:DeleteCluster",
152
+ ],
153
+ "sensitive_action_patterns": ["^iam:Delete.*"],
154
+ },
155
+ },
156
+ "action_condition_enforcement_check": {
157
+ "enabled": True,
158
+ "severity": "high",
159
+ "description": "Enforce specific IAM condition requirements (unified: MFA, IP, tags, etc.)",
160
+ "action_condition_requirements": [
161
+ {
162
+ "actions": ["iam:PassRole"],
163
+ "action_patterns": ["^iam:Pas?.*$"],
164
+ "severity": "high",
165
+ "required_conditions": [
166
+ {
167
+ "condition_key": "iam:PassedToService",
168
+ "description": "Specify which AWS services are allowed to use the passed role to prevent privilege escalation",
169
+ "example": """"Condition": {
170
+ "StringEquals": {
171
+ "iam:PassedToService": [
172
+ "lambda.amazonaws.com",
173
+ "ecs-tasks.amazonaws.com",
174
+ "ec2.amazonaws.com",
175
+ "glue.amazonaws.com",
176
+ "lambda.amazonaws.com"
177
+ ]
178
+ }
179
+ }
180
+ """,
181
+ },
182
+ ],
183
+ },
184
+ {
185
+ "actions": [
186
+ "iam:Create*",
187
+ "iam:CreateRole",
188
+ "iam:Put*Policy*",
189
+ "iam:PutUserPolicy",
190
+ "iam:PutRolePolicy",
191
+ "iam:Attach*Policy*",
192
+ "iam:AttachUserPolicy",
193
+ "iam:AttachRolePolicy",
194
+ ],
195
+ "action_patterns": ["^iam:Create", "^iam:Put.*Policy", "^iam:Attach.*Policy"],
196
+ "severity": "high",
197
+ "required_conditions": [
198
+ {
199
+ "condition_key": "iam:PermissionsBoundary",
200
+ "description": "Require permissions boundary for sensitive IAM operations to prevent privilege escalation",
201
+ "expected_value": "arn:aws:iam::*:policy/DeveloperBoundary",
202
+ "example": """# See: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
203
+ "Condition": {
204
+ "StringEquals": {
205
+ "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/XCompanyBoundaries"
206
+ }
207
+ }
208
+ """,
209
+ },
210
+ ],
211
+ },
212
+ {
213
+ "actions": ["s3:DeleteBucket", "s3:DeleteBucketPolicy", "s3:PutBucketPolicy"],
214
+ "severity": "high",
215
+ "required_conditions": [
216
+ {
217
+ "condition_key": "aws:MultiFactorAuthPresent",
218
+ "description": "Require MFA for S3 destructive operations",
219
+ "expected_value": True,
220
+ },
221
+ ],
222
+ },
223
+ {
224
+ "action_patterns": [
225
+ "^ssm:StartSession$",
226
+ "^ssm:Run.*$",
227
+ "^s3:GetObject$",
228
+ "^rds:.*$",
229
+ ],
230
+ "severity": "medium",
231
+ "required_conditions": [
232
+ {
233
+ "condition_key": "aws:SourceIp",
234
+ "description": "Restrict access to corporate IP ranges",
235
+ "example": """"Condition": {
236
+ "IpAddress": {
237
+ "aws:SourceIp": [
238
+ "10.0.0.0/8",
239
+ "172.16.0.0/12"
240
+ ]
241
+ }
242
+ }
243
+ """,
244
+ },
245
+ ],
246
+ },
247
+ {
248
+ "actions": ["ec2:RunInstances"],
249
+ "required_conditions": {
250
+ "all_of": [
251
+ {
252
+ "condition_key": "aws:ResourceTag/owner",
253
+ "operator": "StringEquals",
254
+ "expected_value": "${aws:PrincipalTag/owner}",
255
+ "description": "Resource owner must match the principal's owner tag",
256
+ },
257
+ {
258
+ "condition_key": "aws:RequestTag/env",
259
+ "operator": "StringEquals",
260
+ "expected_value": [
261
+ "prod",
262
+ "pre",
263
+ "dev",
264
+ "sandbox",
265
+ ],
266
+ "description": "Must specify a valid Environment tag",
267
+ },
268
+ ],
269
+ },
270
+ },
271
+ {
272
+ "action_patterns": ["^rds:Create.*", "^rds:Modify.*"],
273
+ "required_conditions": {
274
+ "all_of": [
275
+ {
276
+ "condition_key": "aws:RequestTag/DataClassification",
277
+ "description": "Must specify data classification",
278
+ },
279
+ {
280
+ "condition_key": "aws:RequestTag/BackupPolicy",
281
+ "description": "Must specify backup policy",
282
+ },
283
+ {
284
+ "condition_key": "aws:RequestTag/Owner",
285
+ "description": "Must specify resource owner",
286
+ },
287
+ ],
288
+ },
289
+ },
290
+ ],
291
+ },
292
+ }
293
+
294
+
295
+ def get_default_config() -> dict:
296
+ """
297
+ Get a deep copy of the default configuration.
298
+
299
+ Returns:
300
+ A deep copy of the default configuration dictionary
301
+ """
302
+ import copy
303
+
304
+ 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 - placeholder for existing functionality."""
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
- """Console formatter using Rich library."""
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 "Rich console output with colors and tables"
20
+ return "Classic console output with colors and tables"
17
21
 
18
22
  def format(self, report: ValidationReport, **kwargs) -> str:
19
- """Format for console output."""
20
- # This would integrate with existing Rich console output
21
- # from iam_validator.core.report module
22
- return "Console output (uses Rich library for terminal display)"
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])