///|
/// Category of remediation hint produced from audit findings.
pub(all) enum HintKind {
MoveSpecificRule
TightenCidr
ConfirmDefaultPolicy
CanonicalizeInput
RemoveDuplicate
ReviewSpecialScope
FixSyntax
ReviewOverlap
} derive(Eq, Debug)
///|
pub fn HintKind::label(self : HintKind) -> String {
match self {
MoveSpecificRule => "move_specific_rule"
TightenCidr => "tighten_cidr"
ConfirmDefaultPolicy => "confirm_default_policy"
CanonicalizeInput => "canonicalize_input"
RemoveDuplicate => "remove_duplicate"
ReviewSpecialScope => "review_special_scope"
FixSyntax => "fix_syntax"
ReviewOverlap => "review_overlap"
}
}
///|
pub struct FixHint {
kind : HintKind
severity : Severity
rule_id : String
text : String
} derive(Eq, Debug)
///|
pub fn FixHint::new(
kind : HintKind,
severity : Severity,
rule_id : String,
text : String,
) -> FixHint {
{ kind, severity, rule_id, text }
}
///|
pub fn FixHint::kind(self : FixHint) -> HintKind {
self.kind
}
///|
pub fn FixHint::severity(self : FixHint) -> Severity {
self.severity
}
///|
pub fn FixHint::rule_id(self : FixHint) -> String {
self.rule_id
}
///|
pub fn FixHint::text(self : FixHint) -> String {
self.text
}
///|
pub fn FixHint::summary(self : FixHint) -> String {
"[" +
self.severity.label() +
"] " +
self.kind.label() +
" " +
self.rule_id +
": " +
self.text
}
///|
pub fn AuditReport::fix_hints(self : AuditReport) -> Array[FixHint] {
let hints : Array[FixHint] = []
for finding in self.findings() {
hints.push(hint_from_finding(finding))
}
hints
}
///|
pub fn AuditReport::hint_report(self : AuditReport) -> String {
let hints = self.fix_hints()
let mut output = "MoonCIDR remediation hints\n"
output = output + "hints: " + hints.length().to_string() + "\n"
if hints.length() == 0 {
output + "\nNo hints."
} else {
output = output + "\nHints:\n"
for hint in hints {
output = output + "- " + hint.summary() + "\n"
}
output
}
}
///|
pub fn AuditReport::count_hint_kind(self : AuditReport, kind : HintKind) -> Int {
let mut count = 0
for hint in self.fix_hints() {
if hint.kind() == kind {
count = count + 1
}
}
count
}
///|
fn hint_from_finding(finding : Finding) -> FixHint {
match finding.kind() {
ShadowedRule =>
FixHint::new(
MoveSpecificRule,
finding.severity(),
finding.rule_id(),
"move the more specific rule before the broader rule, or remove it if the shadowing is intentional",
)
TooWideAllow =>
FixHint::new(
TightenCidr,
finding.severity(),
finding.rule_id(),
"replace the broad allow range with the smallest CIDR blocks that are actually required",
)
GlobalDeny =>
FixHint::new(
ConfirmDefaultPolicy,
finding.severity(),
finding.rule_id(),
"confirm this is the intended default policy and place it after all specific allow or deny rules",
)
NonCanonicalCidr =>
FixHint::new(
CanonicalizeInput,
finding.severity(),
finding.rule_id(),
"rewrite the CIDR block using its normalized network address",
)
DuplicateRule =>
FixHint::new(
RemoveDuplicate,
finding.severity(),
finding.rule_id(),
"remove the duplicated rule or merge its note into the original rule",
)
PrivateRange | LoopbackRange | LinkLocalRange | MulticastRange =>
FixHint::new(
ReviewSpecialScope,
finding.severity(),
finding.rule_id(),
"review whether this address scope belongs in the current gateway or public-facing policy",
)
ParseError =>
FixHint::new(
FixSyntax,
finding.severity(),
finding.rule_id(),
"fix the rule syntax before relying on audit results",
)
ConflictingOverlap | RedundantOverlap =>
FixHint::new(
ReviewOverlap,
finding.severity(),
finding.rule_id(),
"review overlapping ranges and split or reorder rules to make intent explicit",
)
}
}