Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLOUDFLAREAPI: Enable CanUseLOC #2799

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions commands/types/dnscontrol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ declare function AZURE_ALIAS(name: string, type: "A" | "AAAA" | "CNAME", target:
* );
* ```
*
* DNSControl contains a [`CAA_BUILDER`](../record/CAA_BUILDER.md) which can be used to simply create `CAA()` records for your domains. Instead of creating each CAA record individually, you can simply configure your report mail address, the authorized certificate authorities and the builder cares about the rest.
* DNSControl contains a [`CAA_BUILDER`](CAA_BUILDER.md) which can be used to simply create `CAA()` records for your domains. Instead of creating each CAA record individually, you can simply configure your report mail address, the authorized certificate authorities and the builder cares about the rest.
*
* @see https://docs.dnscontrol.org/language-reference/domain-modifiers/caa
*/
Expand Down Expand Up @@ -2230,9 +2230,9 @@ declare function PANIC(message: string): never;
* of `REV("1.2.3.4")` is `4.3.2.1.in-addr.arpa.`, which means the following
* are all equivalent:
*
* * `PTR(REV("1.2.3.4"), `
* * `PTR("4.3.2.1.in-addr.arpa."), `
* * `PTR("4.3",` // Assuming the domain is `2.1.in-addr.arpa`
* * `PTR(REV("1.2.3.4", ...`
* * `PTR("4.3.2.1.in-addr.arpa.", ...`
* * `PTR("4.3", ...` // Assuming the domain is `2.1.in-addr.arpa`
*
* All magic is RFC2317-aware. We use the first format listed in the
* RFC for both [`REV()`](../global/REV.md) and `PTR()`. The format is
Expand Down
2 changes: 1 addition & 1 deletion documentation/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`AZURE_PRIVATE_DNS`](providers/azure_private_dns.md) | ✅ | ✅ | ❌ | ❌ | ❌ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ❌ | ❌ | ❔ | ❔ | ✅ | ✅ | ✅ | ✅ |
| [`BIND`](providers/bind.md) | ✅ | ✅ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| [`BUNNY_DNS`](providers/bunny_dns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| [`CLOUDFLAREAPI`](providers/cloudflareapi.md) | ✅ | ✅ | ❌ | ✅ | ✅ | ❔ | | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❌ | ✅ | ✅ | ✅ |
| [`CLOUDFLAREAPI`](providers/cloudflareapi.md) | ✅ | ✅ | ❌ | ✅ | ✅ | ❔ | | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❌ | ✅ | ✅ | ✅ |
| [`CLOUDNS`](providers/cloudns.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`CSCGLOBAL`](providers/cscglobal.md) | ✅ | ✅ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ✅ |
| [`DESEC`](providers/desec.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ |
Expand Down
50 changes: 50 additions & 0 deletions models/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,40 +95,65 @@ type RecordConfig struct {

// If you add a field to this struct, also add it to the list in the UnmarshalJSON function.
MxPreference uint16 `json:"mxpreference,omitempty"`

SrvPriority uint16 `json:"srvpriority,omitempty"`
SrvWeight uint16 `json:"srvweight,omitempty"`
SrvPort uint16 `json:"srvport,omitempty"`

CaaTag string `json:"caatag,omitempty"`
CaaFlag uint8 `json:"caaflag,omitempty"`

DsKeyTag uint16 `json:"dskeytag,omitempty"`
DsAlgorithm uint8 `json:"dsalgorithm,omitempty"`
DsDigestType uint8 `json:"dsdigesttype,omitempty"`
DsDigest string `json:"dsdigest,omitempty"`

LocVersion uint8 `json:"locversion,omitempty"`
LocSize uint8 `json:"locsize,omitempty"`
LocHorizPre uint8 `json:"lochorizpre,omitempty"`
LocVertPre uint8 `json:"locvertpre,omitempty"`
LocLatitude uint32 `json:"loclatitude,omitempty"`
LocLongitude uint32 `json:"loclongitude,omitempty"`
LocAltitude uint32 `json:"localtitude,omitempty"`

LocLatDegrees uint8 `json:"loclatdegrees,omitempty"`
LocLatMinutes uint8 `json:"lotlatminutes,omitempty"`
LocLatSeconds float32 `json:"loclatseconds,omitempty"`
LocLatDirection string `json:"loclatdirection,omitempty"`
LocLongDegrees uint8 `json:"loclongdegrees,omitempty"`
LocLongMinutes uint8 `json:"lotlongminutes,omitempty"`
LocLongSeconds float32 `json:"loclongseconds,omitempty"`
LocLongDirection string `json:"loclongdirection,omitempty"`

LocOrigAltitude int32 `json:"locorigaltitude,omitempty"`
LocOrigSize float32 `json:"locorigsize,omitempty"`
LocOrigHorizPre float32 `json:"locorighorizpre,omitempty"`
LocOrigVertPre float32 `json:"locorigvertpre,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this provider need new fields when multiple providers already support LOC without needing the extra fields?

Copy link
Author

@ConnorMcF ConnorMcF Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Cloudflare API requires the original degrees, minutes, seconds values whereas to my knowledge the LOC model converts to decimal degrees, which doesn't fit into the request data Cloudflare asks for.
I'm trying to pass through both the DD and the original DMS values to the provider to allow either to be used.

https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-create-dns-record (Body -> LOC Record)

I may be wrong here, or there may be a better way about this - but this is just what I've come to so far.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! So if I understand it correctly: Some of the fields are what the user input. Those inputs are then massaged and stored as another set of fields.

If that's the situation, then please be clear in the comments which are the user-input fields and which are the derived fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to have RecordConfig contain a field called LOC which is a pointer to a LOCConfig. All those fields would be moved to LOCConfig. That would conserve memory. (You don't have to make this change right now. First let's see if the extra RAM causes problems. If we really want to save memory, we should come up with a solution that works for Naptr, Loc, and other fields too.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Tim. I recall implementing LOC for what was necessary in the final result. I referenced the original RFC. There are several ways to write coordinates, with a few which are commonly accepted everywhere. I tried handling the common ones in the js import, so there might need to be a bit of work there also. So be mindful of the import process done via the Javascript files (and test coverage) if those routes are necessary.

Might not be a bad idea to pass through components from the original js config.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to my original commit or PR to find the js bits. Good to see Cloud Flare filling out their portfolio with LOC. 🥳


NaptrOrder uint16 `json:"naptrorder,omitempty"`
NaptrPreference uint16 `json:"naptrpreference,omitempty"`
NaptrFlags string `json:"naptrflags,omitempty"`
NaptrService string `json:"naptrservice,omitempty"`
NaptrRegexp string `json:"naptrregexp,omitempty"`

SshfpAlgorithm uint8 `json:"sshfpalgorithm,omitempty"`
SshfpFingerprint uint8 `json:"sshfpfingerprint,omitempty"`

SoaMbox string `json:"soambox,omitempty"`
SoaSerial uint32 `json:"soaserial,omitempty"`
SoaRefresh uint32 `json:"soarefresh,omitempty"`
SoaRetry uint32 `json:"soaretry,omitempty"`
SoaExpire uint32 `json:"soaexpire,omitempty"`
SoaMinttl uint32 `json:"soaminttl,omitempty"`

TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`

R53Alias map[string]string `json:"r53_alias,omitempty"`

AzureAlias map[string]string `json:"azure_alias,omitempty"`

UnknownTypeName string `json:"unknown_type_name,omitempty"`
}

Expand Down Expand Up @@ -163,40 +188,65 @@ func (rc *RecordConfig) UnmarshalJSON(b []byte) error {
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.

MxPreference uint16 `json:"mxpreference,omitempty"`

SrvPriority uint16 `json:"srvpriority,omitempty"`
SrvWeight uint16 `json:"srvweight,omitempty"`
SrvPort uint16 `json:"srvport,omitempty"`

CaaTag string `json:"caatag,omitempty"`
CaaFlag uint8 `json:"caaflag,omitempty"`

DsKeyTag uint16 `json:"dskeytag,omitempty"`
DsAlgorithm uint8 `json:"dsalgorithm,omitempty"`
DsDigestType uint8 `json:"dsdigesttype,omitempty"`
DsDigest string `json:"dsdigest,omitempty"`

LocVersion uint8 `json:"locversion,omitempty"`
LocSize uint8 `json:"locsize,omitempty"`
LocHorizPre uint8 `json:"lochorizpre,omitempty"`
LocVertPre uint8 `json:"locvertpre,omitempty"`
LocLatitude int `json:"loclatitude,omitempty"`
LocLongitude int `json:"loclongitude,omitempty"`
LocAltitude uint32 `json:"localtitude,omitempty"`

LocLatDegrees uint8 `json:"loclatdegrees,omitempty"`
LocLatMinutes uint8 `json:"lotlatminutes,omitempty"`
LocLatSeconds float32 `json:"loclatseconds,omitempty"`
LocLatDirection string `json:"loclatdirection,omitempty"`
LocLongDegrees uint8 `json:"loclongdegrees,omitempty"`
LocLongMinutes uint8 `json:"lotlongminutes,omitempty"`
LocLongSeconds float32 `json:"loclongseconds,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't see the LOC records at cloud flare link. So we assume only seconds will take decimal fractions ie floats. Degrees and minutes are whole integers.

LocLongDirection string `json:"loclongdirection,omitempty"`

LocOrigAltitude int32 `json:"locorigaltitude,omitempty"`
LocOrigSize float32 `json:"locorigsize,omitempty"`
LocOrigHorizPre float32 `json:"locorighorizpre,omitempty"`
LocOrigVertPre float32 `json:"locorigvertpre,omitempty"`

NaptrOrder uint16 `json:"naptrorder,omitempty"`
NaptrPreference uint16 `json:"naptrpreference,omitempty"`
NaptrFlags string `json:"naptrflags,omitempty"`
NaptrService string `json:"naptrservice,omitempty"`
NaptrRegexp string `json:"naptrregexp,omitempty"`

SshfpAlgorithm uint8 `json:"sshfpalgorithm,omitempty"`
SshfpFingerprint uint8 `json:"sshfpfingerprint,omitempty"`

SoaMbox string `json:"soambox,omitempty"`
SoaSerial uint32 `json:"soaserial,omitempty"`
SoaRefresh uint32 `json:"soarefresh,omitempty"`
SoaRetry uint32 `json:"soaretry,omitempty"`
SoaExpire uint32 `json:"soaexpire,omitempty"`
SoaMinttl uint32 `json:"soaminttl,omitempty"`

TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`

R53Alias map[string]string `json:"r53_alias,omitempty"`

AzureAlias map[string]string `json:"azure_alias,omitempty"`

UnknownTypeName string `json:"unknown_type_name,omitempty"`

EnsureAbsent bool `json:"ensure_absent,omitempty"` // Override NO_PURGE and delete this record
Expand Down
16 changes: 16 additions & 0 deletions models/t_loc.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ func (rc *RecordConfig) calculateLOCFields(d1 uint8, m1 uint8, s1 float32, ns st
const LOCDegrees = 60 * LOCHours
const LOCAltitudeBase int32 = 100000

// Some providers want the original values, so we should keep them around
rc.LocLatDegrees = d1
rc.LocLatMinutes = m1
rc.LocLatSeconds = s1
rc.LocLatDirection = ns

rc.LocLongDegrees = d2
rc.LocLongMinutes = m2
rc.LocLongSeconds = s2
rc.LocLongDirection = ew

rc.LocOrigAltitude = al
rc.LocOrigSize = sz
rc.LocOrigHorizPre = hp
rc.LocOrigVertPre = vp

lat := uint64((uint32(d1) * LOCDegrees) + (uint32(m1) * LOCHours) + uint32(s1*1000))
lon := uint64((uint32(d2) * LOCDegrees) + (uint32(m2) * LOCHours) + uint32(s2*1000))
if strings.ToUpper(ns) == "N" {
Expand Down
54 changes: 33 additions & 21 deletions providers/cloudflare/cloudflareProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var features = providers.DocumentationNotes{
providers.CanUseAlias: providers.Can("CF automatically flattens CNAME records into A records dynamically"),
providers.CanUseCAA: providers.Can(),
providers.CanUseDSForChildren: providers.Can(),
providers.CanUseLOC: providers.Cannot(),
providers.CanUseLOC: providers.Can(),
providers.CanUseNAPTR: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
Expand Down Expand Up @@ -628,26 +628,38 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS

// Used on the "existing" records.
type cfRecData struct {
Name string `json:"name"`
Target cfTarget `json:"target"`
Service string `json:"service"` // SRV
Proto string `json:"proto"` // SRV
Priority uint16 `json:"priority"` // SRV
Weight uint16 `json:"weight"` // SRV
Port uint16 `json:"port"` // SRV
Tag string `json:"tag"` // CAA
Flags uint8 `json:"flags"` // CAA
Value string `json:"value"` // CAA
Usage uint8 `json:"usage"` // TLSA
Selector uint8 `json:"selector"` // TLSA
MatchingType uint8 `json:"matching_type"` // TLSA
Certificate string `json:"certificate"` // TLSA
Algorithm uint8 `json:"algorithm"` // SSHFP/DS
HashType uint8 `json:"type"` // SSHFP
Fingerprint string `json:"fingerprint"` // SSHFP
KeyTag uint16 `json:"key_tag"` // DS
DigestType uint8 `json:"digest_type"` // DS
Digest string `json:"digest"` // DS
Name string `json:"name"`
Target cfTarget `json:"target"`
Service string `json:"service"` // SRV
Proto string `json:"proto"` // SRV
Priority uint16 `json:"priority"` // SRV
Weight uint16 `json:"weight"` // SRV
Port uint16 `json:"port"` // SRV
Tag string `json:"tag"` // CAA
Flags uint8 `json:"flags"` // CAA
Value string `json:"value"` // CAA
Usage uint8 `json:"usage"` // TLSA
Selector uint8 `json:"selector"` // TLSA
MatchingType uint8 `json:"matching_type"` // TLSA
Certificate string `json:"certificate"` // TLSA
Algorithm uint8 `json:"algorithm"` // SSHFP/DS
HashType uint8 `json:"type"` // SSHFP
Fingerprint string `json:"fingerprint"` // SSHFP
KeyTag uint16 `json:"key_tag"` // DS
DigestType uint8 `json:"digest_type"` // DS
Digest string `json:"digest"` // DS
LatDegrees uint8 `json:"lat_degrees"` // LOC
LatMinutes uint8 `json:"lat_minutes"` // LOC
LatSeconds float32 `json:"lat_seconds"` // LOC
LatDirection string `json:"lat_direction"` // LOC
LongDegrees uint8 `json:"long_degrees"` // LOC
LongMinutes uint8 `json:"long_minutes"` // LOC
LongSeconds float32 `json:"long_seconds"` // LOC
LongDirection string `json:"long_direction"` // LOC
PrecisionHorz float32 `json:"precision_horz"` // LOC
PrecisionVert float32 `json:"precision_vert"` // LOC
Altitude int32 `json:"altitude"` // LOC
Size float32 `json:"size"` // LOC
}

// cfTarget is a SRV target. A null target is represented by an empty string, but
Expand Down
21 changes: 21 additions & 0 deletions providers/cloudflare/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ func cfDSData(rec *models.RecordConfig) *cfRecData {
}
}

func cfLocData(rec *models.RecordConfig) *cfRecData {
return &cfRecData{
LatDegrees: rec.LocLatDegrees,
LatMinutes: rec.LocLatMinutes,
LatSeconds: rec.LocLatSeconds,
LatDirection: rec.LocLatDirection,

LongDegrees: rec.LocLongDegrees,
LongMinutes: rec.LocLongMinutes,
LongSeconds: rec.LocLongSeconds,
LongDirection: rec.LocLongDirection,

PrecisionHorz: rec.LocOrigHorizPre,
PrecisionVert: rec.LocOrigVertPre,
Altitude: rec.LocOrigAltitude,
Size: rec.LocOrigSize,
}
}

func cfSrvData(rec *models.RecordConfig) *cfRecData {
serverParts := strings.Split(rec.GetLabelFQDN(), ".")
c := &cfRecData{
Expand Down Expand Up @@ -168,6 +187,8 @@ func (c *cloudflareProvider) createRecDiff2(rec *models.RecordConfig, domainID s
} else if rec.Type == "NAPTR" {
cf.Data = cfNaptrData(rec)
cf.Name = rec.GetLabelFQDN()
} else if rec.Type == "LOC" {
cf.Data = cfLocData(rec)
}
resp, err := c.cfClient.CreateDNSRecord(context.Background(), cloudflare.ZoneIdentifier(domainID), cf)
if err != nil {
Expand Down