iam-policy-validator 1.0.3__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.
- {iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/METADATA +210 -436
- {iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/RECORD +20 -18
- iam_validator/__version__.py +1 -1
- iam_validator/checks/action_condition_enforcement.py +112 -28
- iam_validator/checks/security_best_practices.py +103 -12
- iam_validator/commands/validate.py +7 -5
- iam_validator/core/cli.py +26 -9
- iam_validator/core/config_loader.py +39 -3
- iam_validator/core/defaults.py +304 -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 +428 -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/report.py +104 -25
- {iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -43,10 +43,18 @@ class MarkdownFormatter(OutputFormatter):
|
|
|
43
43
|
|
|
44
44
|
output = [
|
|
45
45
|
"# IAM Policy Validation Report\n",
|
|
46
|
+
"## Summary",
|
|
46
47
|
f"**Total Policies:** {report.total_policies}",
|
|
47
|
-
f"**Valid
|
|
48
|
-
f"**Invalid
|
|
48
|
+
f"**Valid (IAM):** {report.valid_policies} ✅",
|
|
49
|
+
f"**Invalid (IAM):** {report.invalid_policies} ❌",
|
|
50
|
+
f"**Security Findings:** {report.policies_with_security_issues} ⚠️",
|
|
51
|
+
"",
|
|
52
|
+
"## Issue Breakdown",
|
|
49
53
|
f"**Total Issues:** {report.total_issues}",
|
|
54
|
+
f"**Validity Issues:** {report.validity_issues} (error/warning/info)",
|
|
55
|
+
f"**Security Issues:** {report.security_issues} (critical/high/medium/low)",
|
|
56
|
+
"",
|
|
57
|
+
"## Legacy Severity Counts",
|
|
50
58
|
f"**Errors:** {errors}",
|
|
51
59
|
f"**Warnings:** {warnings}",
|
|
52
60
|
f"**Info:** {infos}\n",
|
iam_validator/core/models.py
CHANGED
|
@@ -251,14 +251,38 @@ class ValidationReport(BaseModel):
|
|
|
251
251
|
|
|
252
252
|
total_policies: int
|
|
253
253
|
valid_policies: int
|
|
254
|
-
invalid_policies: int
|
|
254
|
+
invalid_policies: int # Policies with IAM validity issues (error/warning)
|
|
255
|
+
policies_with_security_issues: int = (
|
|
256
|
+
0 # Policies with security findings (critical/high/medium/low)
|
|
257
|
+
)
|
|
255
258
|
total_issues: int
|
|
259
|
+
validity_issues: int = 0 # Count of IAM validity issues (error/warning/info)
|
|
260
|
+
security_issues: int = 0 # Count of security issues (critical/high/medium/low)
|
|
256
261
|
results: list[PolicyValidationResult] = Field(default_factory=list)
|
|
257
262
|
|
|
258
263
|
def get_summary(self) -> str:
|
|
259
264
|
"""Generate a human-readable summary."""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
)
|
|
265
|
+
parts = []
|
|
266
|
+
parts.append(f"Validated {self.total_policies} policies:")
|
|
267
|
+
|
|
268
|
+
# Always show valid/invalid counts
|
|
269
|
+
parts.append(f"{self.valid_policies} valid")
|
|
270
|
+
|
|
271
|
+
if self.invalid_policies > 0:
|
|
272
|
+
parts.append(f"{self.invalid_policies} invalid (IAM validity)")
|
|
273
|
+
|
|
274
|
+
if self.policies_with_security_issues > 0:
|
|
275
|
+
parts.append(f"{self.policies_with_security_issues} with security findings")
|
|
276
|
+
|
|
277
|
+
parts.append(f"{self.total_issues} total issues")
|
|
278
|
+
|
|
279
|
+
# Show breakdown if there are issues
|
|
280
|
+
if self.total_issues > 0 and (self.validity_issues > 0 or self.security_issues > 0):
|
|
281
|
+
breakdown_parts = []
|
|
282
|
+
if self.validity_issues > 0:
|
|
283
|
+
breakdown_parts.append(f"{self.validity_issues} validity")
|
|
284
|
+
if self.security_issues > 0:
|
|
285
|
+
breakdown_parts.append(f"{self.security_issues} security")
|
|
286
|
+
parts.append(f"({', '.join(breakdown_parts)})")
|
|
287
|
+
|
|
288
|
+
return " ".join(parts)
|
iam_validator/core/report.py
CHANGED
|
@@ -14,6 +14,7 @@ from rich.text import Text
|
|
|
14
14
|
from iam_validator.core.formatters import (
|
|
15
15
|
ConsoleFormatter,
|
|
16
16
|
CSVFormatter,
|
|
17
|
+
EnhancedFormatter,
|
|
17
18
|
HTMLFormatter,
|
|
18
19
|
JSONFormatter,
|
|
19
20
|
MarkdownFormatter,
|
|
@@ -43,6 +44,8 @@ class ReportGenerator:
|
|
|
43
44
|
# Register all built-in formatters
|
|
44
45
|
if not self.formatter_registry.get_formatter("console"):
|
|
45
46
|
self.formatter_registry.register(ConsoleFormatter())
|
|
47
|
+
if not self.formatter_registry.get_formatter("enhanced"):
|
|
48
|
+
self.formatter_registry.register(EnhancedFormatter())
|
|
46
49
|
if not self.formatter_registry.get_formatter("json"):
|
|
47
50
|
self.formatter_registry.register(JSONFormatter())
|
|
48
51
|
if not self.formatter_registry.get_formatter("markdown"):
|
|
@@ -80,11 +83,27 @@ class ReportGenerator:
|
|
|
80
83
|
invalid_count = len(results) - valid_count
|
|
81
84
|
total_issues = sum(len(r.issues) for r in results)
|
|
82
85
|
|
|
86
|
+
# Count policies with security issues (separate from validity issues)
|
|
87
|
+
policies_with_security_issues = sum(
|
|
88
|
+
1 for r in results if any(issue.is_security_severity() for issue in r.issues)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Count validity vs security issues
|
|
92
|
+
validity_issues = sum(
|
|
93
|
+
sum(1 for issue in r.issues if issue.is_validity_severity()) for r in results
|
|
94
|
+
)
|
|
95
|
+
security_issues = sum(
|
|
96
|
+
sum(1 for issue in r.issues if issue.is_security_severity()) for r in results
|
|
97
|
+
)
|
|
98
|
+
|
|
83
99
|
return ValidationReport(
|
|
84
100
|
total_policies=len(results),
|
|
85
101
|
valid_policies=valid_count,
|
|
86
102
|
invalid_policies=invalid_count,
|
|
103
|
+
policies_with_security_issues=policies_with_security_issues,
|
|
87
104
|
total_issues=total_issues,
|
|
105
|
+
validity_issues=validity_issues,
|
|
106
|
+
security_issues=security_issues,
|
|
88
107
|
results=results,
|
|
89
108
|
)
|
|
90
109
|
|
|
@@ -98,8 +117,31 @@ class ReportGenerator:
|
|
|
98
117
|
summary_text = Text()
|
|
99
118
|
summary_text.append(f"Total Policies: {report.total_policies}\n")
|
|
100
119
|
summary_text.append(f"Valid: {report.valid_policies} ", style="green")
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
|
|
121
|
+
# Show invalid policies (IAM validity issues)
|
|
122
|
+
if report.invalid_policies > 0:
|
|
123
|
+
summary_text.append(f"Invalid: {report.invalid_policies} ", style="red")
|
|
124
|
+
|
|
125
|
+
# Show policies with security findings (separate from validity)
|
|
126
|
+
if report.policies_with_security_issues > 0:
|
|
127
|
+
summary_text.append(
|
|
128
|
+
f"Security Findings: {report.policies_with_security_issues} ", style="yellow"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
summary_text.append("\n")
|
|
132
|
+
|
|
133
|
+
# Breakdown of issue types
|
|
134
|
+
summary_text.append(f"Total Issues: {report.total_issues}")
|
|
135
|
+
if report.validity_issues > 0 or report.security_issues > 0:
|
|
136
|
+
summary_text.append(" (")
|
|
137
|
+
if report.validity_issues > 0:
|
|
138
|
+
summary_text.append(f"{report.validity_issues} validity", style="red")
|
|
139
|
+
if report.validity_issues > 0 and report.security_issues > 0:
|
|
140
|
+
summary_text.append(", ")
|
|
141
|
+
if report.security_issues > 0:
|
|
142
|
+
summary_text.append(f"{report.security_issues} security", style="yellow")
|
|
143
|
+
summary_text.append(")")
|
|
144
|
+
summary_text.append("\n")
|
|
103
145
|
|
|
104
146
|
self.console.print(Panel(summary_text, title="Validation Summary", border_style="blue"))
|
|
105
147
|
|
|
@@ -127,11 +169,11 @@ class ReportGenerator:
|
|
|
127
169
|
self.console.print(" [dim]No issues found[/dim]")
|
|
128
170
|
return
|
|
129
171
|
|
|
130
|
-
# Create issues table
|
|
172
|
+
# Create issues table with adjusted column widths for better readability
|
|
131
173
|
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 1))
|
|
132
|
-
table.add_column("Severity", style="cyan", width=
|
|
133
|
-
table.add_column("Type", style="magenta", width=
|
|
134
|
-
table.add_column("Message", style="white")
|
|
174
|
+
table.add_column("Severity", style="cyan", width=12, no_wrap=False)
|
|
175
|
+
table.add_column("Type", style="magenta", width=25, no_wrap=False)
|
|
176
|
+
table.add_column("Message", style="white", no_wrap=False)
|
|
135
177
|
|
|
136
178
|
for issue in result.issues:
|
|
137
179
|
severity_style = {
|
|
@@ -149,6 +191,8 @@ class ReportGenerator:
|
|
|
149
191
|
location = f"Statement {issue.statement_index}"
|
|
150
192
|
if issue.statement_sid:
|
|
151
193
|
location += f" ({issue.statement_sid})"
|
|
194
|
+
if issue.line_number is not None:
|
|
195
|
+
location += f" @L{issue.line_number}"
|
|
152
196
|
|
|
153
197
|
message = f"{location}: {issue.message}"
|
|
154
198
|
if issue.suggestion:
|
|
@@ -580,22 +624,35 @@ class ReportGenerator:
|
|
|
580
624
|
continue
|
|
581
625
|
|
|
582
626
|
policy_lines = []
|
|
583
|
-
policy_lines.append("<details open>")
|
|
584
|
-
policy_lines.append(
|
|
585
|
-
f"<summary><b>{idx}. <code>{result.policy_file}</code></b> - {len(result.issues)} issue(s) found</summary>"
|
|
586
|
-
)
|
|
587
|
-
policy_lines.append("")
|
|
588
627
|
|
|
589
628
|
# Group issues by severity - support both IAM validity and security severities
|
|
590
629
|
errors = [i for i in result.issues if i.severity in ("error", "critical", "high")]
|
|
591
630
|
warnings = [i for i in result.issues if i.severity in ("warning", "medium")]
|
|
592
631
|
infos = [i for i in result.issues if i.severity in ("info", "low")]
|
|
593
632
|
|
|
633
|
+
# Build severity summary for header
|
|
634
|
+
severity_parts = []
|
|
635
|
+
if errors:
|
|
636
|
+
severity_parts.append(f"🔴 {len(errors)}")
|
|
637
|
+
if warnings:
|
|
638
|
+
severity_parts.append(f"🟡 {len(warnings)}")
|
|
639
|
+
if infos:
|
|
640
|
+
severity_parts.append(f"🔵 {len(infos)}")
|
|
641
|
+
severity_summary = " · ".join(severity_parts)
|
|
642
|
+
|
|
643
|
+
# Only open first 3 policy details by default to avoid wall of text
|
|
644
|
+
is_open = " open" if policies_shown < 3 else ""
|
|
645
|
+
policy_lines.append(f"<details{is_open}>")
|
|
646
|
+
policy_lines.append(
|
|
647
|
+
f"<summary><b>{idx}. <code>{result.policy_file}</code></b> - {severity_summary}</summary>"
|
|
648
|
+
)
|
|
649
|
+
policy_lines.append("")
|
|
650
|
+
|
|
594
651
|
# Add errors (prioritized)
|
|
595
652
|
if errors:
|
|
596
653
|
policy_lines.append("### 🔴 Errors")
|
|
597
654
|
policy_lines.append("")
|
|
598
|
-
for issue in errors:
|
|
655
|
+
for i, issue in enumerate(errors):
|
|
599
656
|
issue_content = self._format_issue_markdown(issue)
|
|
600
657
|
test_length = len("\n".join(details_lines + policy_lines)) + len(
|
|
601
658
|
issue_content
|
|
@@ -605,6 +662,10 @@ class ReportGenerator:
|
|
|
605
662
|
break
|
|
606
663
|
policy_lines.append(issue_content)
|
|
607
664
|
issues_shown += 1
|
|
665
|
+
# Add separator between issues within same severity
|
|
666
|
+
if i < len(errors) - 1:
|
|
667
|
+
policy_lines.append("---")
|
|
668
|
+
policy_lines.append("")
|
|
608
669
|
policy_lines.append("")
|
|
609
670
|
|
|
610
671
|
if truncated:
|
|
@@ -614,7 +675,7 @@ class ReportGenerator:
|
|
|
614
675
|
if warnings:
|
|
615
676
|
policy_lines.append("### 🟡 Warnings")
|
|
616
677
|
policy_lines.append("")
|
|
617
|
-
for issue in warnings:
|
|
678
|
+
for i, issue in enumerate(warnings):
|
|
618
679
|
issue_content = self._format_issue_markdown(issue)
|
|
619
680
|
test_length = len("\n".join(details_lines + policy_lines)) + len(
|
|
620
681
|
issue_content
|
|
@@ -624,6 +685,10 @@ class ReportGenerator:
|
|
|
624
685
|
break
|
|
625
686
|
policy_lines.append(issue_content)
|
|
626
687
|
issues_shown += 1
|
|
688
|
+
# Add separator between issues within same severity
|
|
689
|
+
if i < len(warnings) - 1:
|
|
690
|
+
policy_lines.append("---")
|
|
691
|
+
policy_lines.append("")
|
|
627
692
|
policy_lines.append("")
|
|
628
693
|
|
|
629
694
|
if truncated:
|
|
@@ -633,7 +698,7 @@ class ReportGenerator:
|
|
|
633
698
|
if infos:
|
|
634
699
|
policy_lines.append("### 🔵 Info")
|
|
635
700
|
policy_lines.append("")
|
|
636
|
-
for issue in infos:
|
|
701
|
+
for i, issue in enumerate(infos):
|
|
637
702
|
issue_content = self._format_issue_markdown(issue)
|
|
638
703
|
test_length = len("\n".join(details_lines + policy_lines)) + len(
|
|
639
704
|
issue_content
|
|
@@ -643,6 +708,10 @@ class ReportGenerator:
|
|
|
643
708
|
break
|
|
644
709
|
policy_lines.append(issue_content)
|
|
645
710
|
issues_shown += 1
|
|
711
|
+
# Add separator between issues within same severity
|
|
712
|
+
if i < len(infos) - 1:
|
|
713
|
+
policy_lines.append("---")
|
|
714
|
+
policy_lines.append("")
|
|
646
715
|
policy_lines.append("")
|
|
647
716
|
|
|
648
717
|
if truncated:
|
|
@@ -650,6 +719,8 @@ class ReportGenerator:
|
|
|
650
719
|
|
|
651
720
|
policy_lines.append("</details>")
|
|
652
721
|
policy_lines.append("")
|
|
722
|
+
policy_lines.append("---")
|
|
723
|
+
policy_lines.append("")
|
|
653
724
|
|
|
654
725
|
# Check if adding this policy would exceed limit
|
|
655
726
|
test_length = len("\n".join(details_lines + policy_lines))
|
|
@@ -712,7 +783,7 @@ class ReportGenerator:
|
|
|
712
783
|
parts.append(f"> {issue.message}")
|
|
713
784
|
parts.append("")
|
|
714
785
|
|
|
715
|
-
# Details section
|
|
786
|
+
# Details section - inline format
|
|
716
787
|
details = []
|
|
717
788
|
if issue.action:
|
|
718
789
|
details.append(f"**Action:** `{issue.action}`")
|
|
@@ -722,19 +793,27 @@ class ReportGenerator:
|
|
|
722
793
|
details.append(f"**Condition Key:** `{issue.condition_key}`")
|
|
723
794
|
|
|
724
795
|
if details:
|
|
725
|
-
parts.append("
|
|
726
|
-
parts.append("<tr><td>")
|
|
727
|
-
parts.append("")
|
|
728
|
-
parts.extend(details)
|
|
729
|
-
parts.append("")
|
|
730
|
-
parts.append("</td></tr>")
|
|
731
|
-
parts.append("</table>")
|
|
796
|
+
parts.append(" · ".join(details))
|
|
732
797
|
parts.append("")
|
|
733
798
|
|
|
734
|
-
# Suggestion in highlighted box
|
|
799
|
+
# Suggestion in highlighted box with code examples
|
|
735
800
|
if issue.suggestion:
|
|
736
|
-
|
|
737
|
-
|
|
801
|
+
# Check if suggestion contains "Example:" section
|
|
802
|
+
if "\nExample:\n" in issue.suggestion:
|
|
803
|
+
text_part, code_part = issue.suggestion.split("\nExample:\n", 1)
|
|
804
|
+
parts.append(f"> 💡 **Suggestion:** {text_part}")
|
|
805
|
+
parts.append("")
|
|
806
|
+
parts.append("<details>")
|
|
807
|
+
parts.append("<summary>📖 View Example</summary>")
|
|
808
|
+
parts.append("")
|
|
809
|
+
parts.append("```json")
|
|
810
|
+
parts.append(code_part)
|
|
811
|
+
parts.append("```")
|
|
812
|
+
parts.append("</details>")
|
|
813
|
+
parts.append("")
|
|
814
|
+
else:
|
|
815
|
+
parts.append(f"> 💡 **Suggestion:** {issue.suggestion}")
|
|
816
|
+
parts.append("")
|
|
738
817
|
|
|
739
818
|
return "\n".join(parts)
|
|
740
819
|
|
|
File without changes
|
{iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{iam_policy_validator-1.0.3.dist-info → iam_policy_validator-1.1.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|