///|
/// Type of change between two rule sets.
pub(all) enum RuleChangeKind {
AddedRule
RemovedRule
ActionChanged
NoteChanged
UnchangedRule
} derive(Eq, Debug)
///|
pub fn RuleChangeKind::label(self : RuleChangeKind) -> String {
match self {
AddedRule => "added_rule"
RemovedRule => "removed_rule"
ActionChanged => "action_changed"
NoteChanged => "note_changed"
UnchangedRule => "unchanged_rule"
}
}
///|
pub fn RuleChangeKind::is_risky(self : RuleChangeKind) -> Bool {
match self {
AddedRule | RemovedRule | ActionChanged => true
NoteChanged | UnchangedRule => false
}
}
///|
/// A change item matched by CIDR range.
pub struct RuleChange {
kind : RuleChangeKind
before_id : String
after_id : String
block : String
message : String
} derive(Eq, Debug)
///|
pub fn RuleChange::new(
kind : RuleChangeKind,
before_id : String,
after_id : String,
block : String,
message : String,
) -> RuleChange {
{ kind, before_id, after_id, block, message }
}
///|
pub fn RuleChange::kind(self : RuleChange) -> RuleChangeKind {
self.kind
}
///|
pub fn RuleChange::before_id(self : RuleChange) -> String {
self.before_id
}
///|
pub fn RuleChange::after_id(self : RuleChange) -> String {
self.after_id
}
///|
pub fn RuleChange::block(self : RuleChange) -> String {
self.block
}
///|
pub fn RuleChange::message(self : RuleChange) -> String {
self.message
}
///|
pub fn RuleChange::summary(self : RuleChange) -> String {
self.kind.label() + " " + self.block + ": " + self.message
}
///|
pub struct DiffReport {
changes : Array[RuleChange]
} derive(Debug)
///|
pub fn DiffReport::changes(self : DiffReport) -> Array[RuleChange] {
self.changes
}
///|
pub fn DiffReport::change_count(self : DiffReport) -> Int {
self.changes.length()
}
///|
pub fn DiffReport::count_kind(self : DiffReport, kind : RuleChangeKind) -> Int {
let mut count = 0
for change in self.changes {
if change.kind() == kind {
count = count + 1
}
}
count
}
///|
pub fn DiffReport::risky_count(self : DiffReport) -> Int {
let mut count = 0
for change in self.changes {
if change.kind().is_risky() {
count = count + 1
}
}
count
}
///|
pub fn DiffReport::is_empty(self : DiffReport) -> Bool {
self.changes.length() == 0
}
///|
pub fn DiffReport::risk_level(self : DiffReport) -> String {
let risky = self.risky_count()
if risky >= 3 {
"high"
} else if risky > 0 {
"medium"
} else if self.change_count() > 0 {
"low"
} else {
"clean"
}
}
///|
pub fn DiffReport::text_report(self : DiffReport) -> String {
let mut output = "MoonCIDR diff report\n"
output = output + "changes: " + self.change_count().to_string() + "\n"
output = output + "risky_changes: " + self.risky_count().to_string() + "\n"
output = output + "risk: " + self.risk_level() + "\n"
if self.changes.length() == 0 {
output + "\nNo rule changes."
} else {
output = output + "\nChanges:\n"
for change in self.changes {
output = output + "- " + change.summary() + "\n"
}
output
}
}
///|
pub fn RuleSet::diff_from(self : RuleSet, previous : RuleSet) -> DiffReport {
let changes : Array[RuleChange] = []
for before in previous.rules() {
match find_same_block(self.rules(), before.block()) {
None =>
changes.push(
RuleChange::new(
RemovedRule,
before.id(),
"",
before.block().to_string(),
"rule was removed: " + before.summary(),
),
)
Some(after) =>
if before.action() != after.action() {
changes.push(
RuleChange::new(
ActionChanged,
before.id(),
after.id(),
before.block().to_string(),
"action changed from " +
before.action().label() +
" to " +
after.action().label(),
),
)
} else if before.note() != after.note() {
changes.push(
RuleChange::new(
NoteChanged,
before.id(),
after.id(),
before.block().to_string(),
"note changed",
),
)
} else {
changes.push(
RuleChange::new(
UnchangedRule,
before.id(),
after.id(),
before.block().to_string(),
"rule is unchanged",
),
)
}
}
}
for after in self.rules() {
match find_same_block(previous.rules(), after.block()) {
None =>
changes.push(
RuleChange::new(
AddedRule,
"",
after.id(),
after.block().to_string(),
"rule was added: " + after.summary(),
),
)
Some(_) => ()
}
}
{ changes, }
}
///|
pub fn diff_rule_sets(current : RuleSet, previous : RuleSet) -> DiffReport {
current.diff_from(previous)
}
///|
fn find_same_block(rules : Array[Rule], block : CidrBlock) -> Rule? {
for rule in rules {
if rule.block().equal_range(block) {
return Some(rule)
}
}
None
}