summaryrefslogtreecommitdiff
path: root/src/route53.rs
blob: e4379af96070f2814cd844c97ef6dddc34c0bcb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use std::collections::HashSet;
use std::str::FromStr;

use anyhow::{anyhow, Result};
use aws_sdk_route53 as route53;
use aws_sdk_route53::types::{HostedZone, ResourceRecordSet, RrType};
use trust_dns_proto::rr::Name;

use crate::dns::suffixes;
use crate::hashable::Hashable;

pub trait Route53 {
    fn route53(&self) -> &route53::Client;
}

pub async fn zone_for_domain<C>(aws_context: &C, name: &Name) -> Result<HostedZone>
where
    C: Route53,
{
    let names = suffixes(name.clone());

    let mut zone = None;
    let mut depth = None;
    let mut zones = aws_context
        .route53()
        .list_hosted_zones()
        .into_paginator()
        .items()
        .send();

    while let Some(candidate_zone) = zones.try_next().await? {
        let zone_name = Name::from_str(candidate_zone.name())?;
        let match_position = names.iter().position(|name| *name == zone_name);
        match (depth, match_position) {
            (None, Some(matched_depth)) => {
                zone = Some(candidate_zone.clone());
                depth = Some(matched_depth);
            }
            (Some(found_depth), Some(matched_depth)) => {
                if matched_depth < found_depth {
                    zone = Some(candidate_zone.clone());
                    depth = Some(matched_depth);
                }
            }
            (_, _) => {}
        }
    }

    zone.ok_or(anyhow!("No Route53 zone found for DNS suffix: {}", name))
}

pub async fn zone_actual_recordsets<C>(
    aws_context: &C,
    zone_id: &str,
    dns_name: &Name,
) -> Result<HashSet<Hashable<ResourceRecordSet>>>
where
    C: Route53,
{
    let mut suffix_records = HashSet::new();

    let mut next_record_name = Some(dns_name.to_ascii());
    let mut next_record_type = None;
    let mut next_record_identifier = None;

    loop {
        let records_resp = aws_context
            .route53()
            .list_resource_record_sets()
            .hosted_zone_id(zone_id)
            .set_start_record_name(next_record_name)
            .set_start_record_type(next_record_type)
            .set_start_record_identifier(next_record_identifier)
            .send()
            .await?;

        let recordsets = records_resp.resource_record_sets();
        for recordset in recordsets {
            let recordset_name = recordset.name();
            let recordset_name = Name::from_str(recordset_name)?;
            if &recordset_name != dns_name {
                break;
            }

            if [RrType::A, RrType::Aaaa].contains(recordset.r#type()) {
                suffix_records.insert(recordset.clone().into());
            }
        }

        if records_resp.is_truncated() {
            next_record_name = records_resp.next_record_name().map(String::from);
            next_record_type = records_resp.next_record_type().cloned();
            next_record_identifier = records_resp.next_record_identifier().map(String::from);
        } else {
            break;
        }
    }

    Ok(suffix_records)
}