///|
pub(all) enum FindingSeverity {
FindingInfo
FindingWarning
FindingError
} derive(Eq, Debug)
///|
pub struct ZonePolicy {
require_soa_value : Bool
require_apex_ns_value : Bool
enforce_cname_exclusive_value : Bool
max_records_value : Int?
max_ttl_value : Ttl?
} derive(Eq, Debug)
///|
pub struct ZoneFinding {
code_value : String
severity_value : FindingSeverity
message_value : String
owner_value : DomainName?
} derive(Eq, Debug)
///|
pub struct ZoneValidation {
finding_values : Array[ZoneFinding]
record_count_value : Int
} derive(Eq, Debug)
///|
pub fn ZonePolicy::new(
require_soa? : Bool = true,
require_apex_ns? : Bool = true,
enforce_cname_exclusive? : Bool = true,
max_records? : Int? = None,
max_ttl? : Ttl? = None,
) -> Result[ZonePolicy, ZoneError] {
match max_records {
Some(value) if value < 0 =>
return Err(
ZoneError::new(
IntegrityViolation,
"maximum record count cannot be negative",
SourceSpan::point(0, 0),
),
)
_ => ()
}
Ok({
require_soa_value: require_soa,
require_apex_ns_value: require_apex_ns,
enforce_cname_exclusive_value: enforce_cname_exclusive,
max_records_value: max_records,
max_ttl_value: max_ttl,
})
}
///|
pub fn ZonePolicy::standard() -> ZonePolicy {
ZonePolicy::new().unwrap()
}
///|
fn zone_finding(
code : String,
severity : FindingSeverity,
message : String,
owner? : DomainName? = None,
) -> ZoneFinding {
{
code_value: code,
severity_value: severity,
message_value: message,
owner_value: owner,
}
}
///|
fn owner_record_counts(
records : Array[ZoneRecord],
owner : DomainName,
) -> (Int, Int) {
let mut cname_count = 0
let mut other_count = 0
for record in records {
if record.owner() == owner {
if record.record_type() == "CNAME" {
cname_count = cname_count + 1
} else {
other_count = other_count + 1
}
}
}
(cname_count, other_count)
}
///|
fn owner_already_checked(
owners : Array[DomainName],
owner : DomainName,
) -> Bool {
for existing in owners {
if existing == owner {
return true
}
}
false
}
///|
pub fn validate_zone(
zone : ZoneDocument,
policy : ZonePolicy,
) -> Result[ZoneValidation, ZoneError] {
let records = zone.records()
let findings : Array[ZoneFinding] = []
let origin = zone.origin()
match policy.max_records_value {
Some(limit) if records.length() > limit =>
findings.push(
zone_finding(
"zone.record_limit",
FindingError,
"zone exceeds the configured record count limit",
),
)
_ => ()
}
let mut soa_count = 0
let mut apex_soa_count = 0
let mut apex_ns_count = 0
for record in records {
if record.record_type() == "SOA" {
soa_count = soa_count + 1
if origin == Some(record.owner()) {
apex_soa_count = apex_soa_count + 1
}
}
if record.record_type() == "NS" && origin == Some(record.owner()) {
apex_ns_count = apex_ns_count + 1
}
match policy.max_ttl_value {
Some(limit) if record.ttl().seconds() > limit.seconds() =>
findings.push(
zone_finding(
"zone.ttl_limit",
FindingError,
"record TTL exceeds the configured limit",
owner=Some(record.owner()),
),
)
_ => ()
}
}
if policy.require_soa_value && soa_count == 0 {
findings.push(
zone_finding(
MissingSoa.code(),
FindingError,
"zone contains no SOA record",
),
)
} else if soa_count > 1 {
findings.push(
zone_finding(
DuplicateSingletonRecord.code(),
FindingError,
"zone contains more than one SOA record",
),
)
} else if soa_count == 1 && apex_soa_count != 1 {
findings.push(
zone_finding(
"zone.soa_not_at_apex",
FindingError,
"SOA record owner is not the active zone origin",
),
)
}
if policy.require_apex_ns_value && apex_ns_count == 0 {
findings.push(
zone_finding(
MissingApexNs.code(),
FindingError,
"zone contains no NS record at its origin",
),
)
}
if policy.enforce_cname_exclusive_value {
let checked : Array[DomainName] = []
for record in records {
let owner = record.owner()
if !owner_already_checked(checked, owner) {
checked.push(owner)
let (cname_count, other_count) = owner_record_counts(records, owner)
if cname_count > 1 || (cname_count > 0 && other_count > 0) {
findings.push(
zone_finding(
ConflictingCname.code(),
FindingError,
"CNAME owner also has conflicting record data",
owner=Some(owner),
),
)
}
}
}
}
Ok({ finding_values: findings, record_count_value: records.length() })
}
///|
pub fn ZoneFinding::code(self : ZoneFinding) -> String {
self.code_value
}
///|
pub fn ZoneFinding::severity(self : ZoneFinding) -> FindingSeverity {
self.severity_value
}
///|
pub fn ZoneFinding::message(self : ZoneFinding) -> String {
self.message_value
}
///|
pub fn ZoneFinding::owner(self : ZoneFinding) -> DomainName? {
self.owner_value
}
///|
pub fn ZoneValidation::findings(self : ZoneValidation) -> Array[ZoneFinding] {
self.finding_values.copy()
}
///|
pub fn ZoneValidation::record_count(self : ZoneValidation) -> Int {
self.record_count_value
}
///|
pub fn ZoneValidation::error_count(self : ZoneValidation) -> Int {
let mut total = 0
for finding in self.finding_values {
if finding.severity() == FindingError {
total = total + 1
}
}
total
}
///|
pub fn ZoneValidation::warning_count(self : ZoneValidation) -> Int {
let mut total = 0
for finding in self.finding_values {
if finding.severity() == FindingWarning {
total = total + 1
}
}
total
}
///|
pub fn ZoneValidation::accepted(self : ZoneValidation) -> Bool {
self.error_count() == 0
}