Skip to content

Commit 3e68262

Browse files
authored
DNS servers should have NS and SOA records (#8047)
this is probably the more _exciting_ part of the issues outlined in #6944. the changes here get us to the point that for both internal and external DNS, we have: * A/AAAA records for the DNS servers in the internal/external group (named `ns1.<zone>`, `ns2.<zone>`, ...) * NS records for those servers at the zone apex, one for each of the `ns*.<zone>` described above * an SOA record synthesized on-demand for the zone apex for each of `oxide.internal` (for internal DNS) and `$delegated_domain` (for external DNS) * the SOA's serial is updated whenever the zone is changed. serial numbers are effectively the DNS config generation, so they start from 1 and tick upward with each change. this is different from most SOA serial schemes (in particular the ones that would use YYYYMMDDNN numbering schemes) but so far as i can tell this is consistent with RFC 1035 requirements. we do _not_ support zone transfers here. i believe the SOA record here would be reasonable to guide zone transfers if we did, but obviously that's not something i've tested. ### SOA fields the SOA record's `RNAME` is hardcoded to `admin@<zone_name>`. this is out of expediency to provide *something*, but it's probably wrong most of the time. there's no way to get an MX record installed for `<zone_name>` in the rack's external DNS servers, so barring DNS hijinks in the deployed environment, this will be a dead address. problems here are: * we would want to take in an administrative email at rack setup time, so that would be minor plumbing * more importantly, what to backfill this with for deployed systems? it seems like the best answer here is to allow configuration of the rack's delegated domain and zone after initial setup, and being able to update an administrative email would fit in pretty naturally there. but we don't have that right now, so `admin@` it is. configuration of external DNS is probably more important in the context of zone transfers and permitting a list of remote addresses to whom we're willing to permit zone transfers. so it feels like this is in the API's future at some point. ## bonus one minorly interesting observation along the way is that external DNS servers in particular are reachable at a few addresses - whichever public address they get in the rack's internal address range, and whichever address they get in the external address range. the public address is what's used for A/AAAA records. so, if you're looking around from inside a DNS zone you can get odd-looking answers like: ``` # 172.30.1.5 is the internal address that an external DNS server is bound to. # oxide.test is the delegated domain for this local Omicron deployment. root@oxz_external_dns_68c5e255:~# dig +short ns2.oxide.test @172.30.1.5 192.168.0.161 root@oxz_external_dns_68c5e255:~# dig +short soa oxide.test @172.30.1.5 ns1.oxide.test. admin.oxide.test. 2 3600 600 18000 150 root@oxz_external_dns_68c5e255:~# dig +short ns oxide.test @172.30.1.5 ns1.oxide.test. ns2.oxide.test. # 192.168.0.160 is an external address for this same server. # there are no records referencing 172.30.1.5 here. root@oxz_external_dns_68c5e255:~# dig +short ns oxide.test @192.168.0.160 ns1.oxide.test. ns2.oxide.test. root@oxz_external_dns_68c5e255:~# dig +short ns1.oxide.test @192.168.0.160 192.168.0.160 ```
1 parent 7547bfa commit 3e68262

File tree

48 files changed

+6798
-1290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+6798
-1290
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/dns-service-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub type DnsError = crate::Error<crate::types::Error>;
3131
pub const ERROR_CODE_UPDATE_IN_PROGRESS: &'static str = "UpdateInProgress";
3232
pub const ERROR_CODE_BAD_UPDATE_GENERATION: &'static str =
3333
"BadUpdateGeneration";
34+
pub const ERROR_CODE_INCOMPATIBLE_RECORD: &'static str = "IncompatibleRecord";
3435

3536
/// Returns whether an error from this client should be retried
3637
pub fn is_retryable(error: &DnsError) -> bool {

common/src/api/external/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,10 @@ impl Generation {
748748
);
749749
Generation(next_gen)
750750
}
751+
752+
pub const fn as_u64(self) -> u64 {
753+
self.0
754+
}
751755
}
752756

753757
impl<'de> Deserialize<'de> for Generation {

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6645,7 +6645,7 @@ fn print_name(
66456645
if records.len() == 1 {
66466646
match &records[0] {
66476647
DnsRecord::Srv(_) => (),
6648-
DnsRecord::Aaaa(_) | DnsRecord::A(_) => {
6648+
DnsRecord::Aaaa(_) | DnsRecord::A(_) | DnsRecord::Ns(_) => {
66496649
println!(
66506650
"{} {:50} {}",
66516651
prefix,
@@ -6670,6 +6670,7 @@ fn format_record(record: &DnsRecord) -> impl Display {
66706670
DnsRecord::Srv(Srv { port, target, .. }) => {
66716671
format!("SRV port {:5} {}", port, target)
66726672
}
6673+
DnsRecord::Ns(ns) => format!("NS {}", ns),
66736674
}
66746675
}
66756676

dev-tools/omdb/tests/successes.out

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ DNS zone: oxide-dev.test (External)
2828
requested version: 2 (created at <REDACTED_TIMESTAMP>)
2929
version created by Nexus: ..........<REDACTED_UUID>...........
3030
version created because: create silo: "test-suite-silo"
31-
changes: names added: 1, names removed: 0
31+
changes: names added: 3, names removed: 0
3232

33+
+ @ NS ns1.oxide-dev.test
34+
+ ns1 AAAA ::1
3335
+ test-suite-silo.sys A 127.0.0.1
3436
---------------------------------------------
3537
stderr:
@@ -42,6 +44,8 @@ termination: Exited(0)
4244
stdout:
4345
External zone: oxide-dev.test
4446
NAME RECORDS
47+
@ NS ns1.oxide-dev.test
48+
ns1 AAAA ::1
4549
test-suite-silo.sys A 127.0.0.1
4650
---------------------------------------------
4751
stderr:

dev-tools/reconfigurator-cli/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ enum BlueprintEditCommands {
441441
},
442442
/// expunge a zone
443443
ExpungeZone { zone_id: OmicronZoneUuid },
444+
/// mark an expunged zone ready for cleanup
445+
MarkForCleanup { zone_id: OmicronZoneUuid },
444446
/// configure an SP update
445447
SetSpUpdate {
446448
/// serial number to update
@@ -1123,6 +1125,13 @@ fn cmd_blueprint_edit(
11231125
.context("failed to expunge zone")?;
11241126
format!("expunged zone {zone_id} from sled {sled_id}")
11251127
}
1128+
BlueprintEditCommands::MarkForCleanup { zone_id } => {
1129+
let sled_id = sled_with_zone(&builder, &zone_id)?;
1130+
builder
1131+
.sled_mark_expunged_zone_ready_for_cleanup(sled_id, zone_id)
1132+
.context("failed to mark zone ready for cleanup")?;
1133+
format!("marked zone {zone_id} ready for cleanup")
1134+
}
11261135
BlueprintEditCommands::SetSpUpdate {
11271136
serial,
11281137
artifact_hash,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This is a legacy test; new tests shouldn't need to set a seed
2+
3+
load-example --seed test_expunge_newly_added_external_dns
4+
5+
blueprint-show 3f00b694-1b16-4aaa-8f78-e6b3a527b434
6+
blueprint-edit 3f00b694-1b16-4aaa-8f78-e6b3a527b434 expunge-zone 9995de32-dd52-4eb1-b0eb-141eb84bc739
7+
8+
# Diff DNS to see that the expunged zone is no longer has DNS records.
9+
blueprint-diff 3f00b694-1b16-4aaa-8f78-e6b3a527b434 366b0b68-d80e-4bc1-abd3-dc69837847e0
10+
11+
blueprint-show 366b0b68-d80e-4bc1-abd3-dc69837847e0
12+
# blueprint-plan will place a new external DNS zone, diff DNS to see the new zone has `ns<N>` and NS records.
13+
blueprint-plan 366b0b68-d80e-4bc1-abd3-dc69837847e0
14+
blueprint-diff 366b0b68-d80e-4bc1-abd3-dc69837847e0 9c998c1d-1a7b-440a-ae0c-40f781dea6e2
15+
16+
blueprint-show 9c998c1d-1a7b-440a-ae0c-40f781dea6e2
17+
# expunging the new zone should work, then diff again to see the new zone also have its DNS records removed.
18+
blueprint-edit 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 expunge-zone d786ef4a-5acb-4f5d-a732-a00addf986b5
19+
blueprint-diff 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 2ac8c740-444d-42ff-8d66-9812a7e51288
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load-example
2+
3+
blueprint-show dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21
4+
# Expunge an internal DNS zone
5+
blueprint-edit dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 expunge-zone 5a526763-1d2b-42a5-b2ef-42f58aa8cbfa
6+
# Diff against the new blueprint; the zone has been expunged so its records should be removed.
7+
blueprint-diff dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1
8+
9+
# Mark the internal DNS zone ready for cleanup.
10+
# This approximates sled-agent performing an inventory collection and seeing the DNS zone has gone away.
11+
# This zone's records were removed in the expunge before, so there are no further DNS changes.
12+
blueprint-edit 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 mark-for-cleanup 5a526763-1d2b-42a5-b2ef-42f58aa8cbfa
13+
blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 58d5e830-0884-47d8-a7cd-b2b3751adeb4
14+
15+
# Planning a new blueprint will now replace the expunged zone, with new records for its replacement.
16+
blueprint-plan 58d5e830-0884-47d8-a7cd-b2b3751adeb4
17+
blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1

dev-tools/reconfigurator-cli/tests/input/cmds-expunge-newly-added.txt

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)