summaryrefslogtreecommitdiff
path: root/src/route53.rs
blob: b9d4c34b084a1b744391ffd4fbe098ac7edd4cd7 (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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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());

    // Outer pagination loop needs to be pulled out to a trait - this is some hot nonsense.
    let mut zone = None;
    let mut depth = None;
    let mut zones_marker = None;
    loop {
        let zones_resp = aws_context
            .route53()
            .list_hosted_zones()
            .set_marker(zones_marker)
            .send()
            .await?;

        let zones = zones_resp.hosted_zones().unwrap_or(&[]);
        for candidate_zone in zones.iter() {
            let zone_name = match candidate_zone.name() {
                None => continue,
                Some(name) => name,
            };
            let zone_name = Name::from_str(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);
                    }
                }
                (_, _) => {}
            }
        }

        if zones_resp.is_truncated() {
            zones_marker = zones_resp.next_marker().map(String::from);
        } else {
            break;
        }
    }

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

pub async fn zone_suffix_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().unwrap_or(&[]);
        for recordset in recordsets {
            let recordset_name = recordset.name().ok_or(anyhow!(
                "Record set with no name found in zone: {}",
                zone_id
            ))?;
            let recordset_name = Name::from_str(recordset_name)?;
            if &recordset_name != dns_name {
                break;
            }

            if let Some(rr_type) = recordset.r#type() {
                if [RrType::A, RrType::Aaaa].contains(rr_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)
}