fix(dns): reconciliar reescrevia os mesmos registos a cada passagem
Duas coisas apanhadas ao correr o reconcile contra a zona real depois de a
rota passar a funcionar.
1) Nao era idempotente: 0 criados, 4 corrigidos, 11 iguais -- e outra vez 4
corrigidos na passagem seguinte, para sempre, com o temporizador a fazer o
mesmo em ciclo. Os 4 eram os SRV. A Cloudflare devolve o content de um SRV
SEM a prioridade ("1 993 host" para um registo guardado como "0 1 993
host") e guarda-a em data. O adaptador so recorria a data quando o content
vinha vazio, portanto lia sempre um valor diferente do da spec.
Para SRV e TLSA a data passa a ser a autoridade.
2) A spec de cada dominio incluia o A e o AAAA do mail host. Um segundo
dominio servido pelo mesmo servidor aponta o MX para um host que nao lhe
pertence, por isso reconciliar o cienciaembarcada.com.br tentava escrever
mail.lucasrguerra.dev.br -- zona alheia. A Cloudflare respondia "an
identical record already exists" e a consola mostrava dois erros para um
dominio que estava correcto. O mesmo valia para o TLSA, que tambem tem o
nome do mail host.
Os registos de endereco passam a ser publicados so pela zona que contem o
host; o MX e os SRV, que apontam para ele, continuam a ser de cada dominio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ce5fcafaa
commit
47c3700380
7 files changed
+153
-20
No files matched your search
@@ -130,12 +130,15 @@ func (a *CloudflareAdapter) ListRecords(ctx context.Context, zoneID string) ([]e
|
||||
if r.Proxied != nil {
|
||||
rec.Proxied = *r.Proxied
|
||||
}
|
||||
// Cloudflare returns SRV and TLSA with content already rendered,
|
||||
// but older zones and some responses only populate data; fall back
|
||||
// to it so a structured record is not seen as an empty value and
|
||||
// rewritten on every reconcile.
|
||||
if rec.Value == "" && len(r.Data) > 0 {
|
||||
rec.Value = renderStructuredValue(r.Type, r.Data)
|
||||
// For SRV and TLSA the structured data is the authority, not the
|
||||
// rendered content. Cloudflare renders SRV content WITHOUT the
|
||||
// priority - a record stored as "0 1 993 host" reads back as
|
||||
// "1 993 host" - so trusting content made every SRV differ from
|
||||
// the spec and be rewritten on every single reconcile.
|
||||
if len(r.Data) > 0 {
|
||||
if rendered := renderStructuredValue(r.Type, r.Data); rendered != "" {
|
||||
rec.Value = rendered
|
||||
}
|
||||
}
|
||||
out = append(out, rec)
|
||||
}
|
||||
|
||||
@@ -235,3 +235,46 @@ func TestCloudflareAdapter_ListRecords_RendersStructuredValues(t *testing.T) {
|
||||
t.Errorf("value = %q, want the rendered presentation form", records[0].Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Cloudflare renders an SRV record's content WITHOUT the priority: the zone
|
||||
// holding "0 1 993 mail.example.test" answers content "1 993 mail.example.test"
|
||||
// and keeps the priority in data. Taking content at face value made the record
|
||||
// differ from the spec on every comparison, so every reconcile rewrote all four
|
||||
// SRV records - against the live zone, on a timer, forever.
|
||||
func TestCloudflareAdapter_ListRecords_KeepsSrvPriority(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"result": []map[string]interface{}{{
|
||||
"id": "rec_1", "type": "SRV", "name": "_imaps._tcp.example.test",
|
||||
// Exactly what the API returns: the priority is missing here
|
||||
// and present in data.
|
||||
"content": "1 993 mail.example.test",
|
||||
"data": map[string]interface{}{
|
||||
"priority": float64(0), "weight": float64(1),
|
||||
"port": float64(993), "target": "mail.example.test",
|
||||
},
|
||||
"ttl": float64(1),
|
||||
}},
|
||||
"result_info": map[string]interface{}{"total_pages": float64(1)},
|
||||
})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
adapter := NewCloudflareAdapter("token")
|
||||
adapter.baseURL = ts.URL
|
||||
|
||||
records, err := adapter.ListRecords(context.Background(), "zone1")
|
||||
if err != nil {
|
||||
t.Fatalf("ListRecords: %v", err)
|
||||
}
|
||||
if len(records) != 1 {
|
||||
t.Fatalf("got %d records, want 1", len(records))
|
||||
}
|
||||
|
||||
const want = "0 1 993 mail.example.test"
|
||||
if records[0].Value != want {
|
||||
t.Errorf("read SRV value %q, want %q: the record will be rewritten on every reconcile",
|
||||
records[0].Value, want)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user