dnscontrol/providers/fortigate/converter.go
Klett IT a815bdcf45
FORTIGATE: Add NS and MX record support (#3671)
Co-authored-by: Klett IT <git@klett-it.net>
2025-07-18 10:22:37 -04:00

184 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fortigate
import (
"fmt"
"golang.org/x/net/idna"
"net"
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
)
// nativeToRecord convert an fgDNSRecord coming from FortiGate into a *models.RecordConfig that dnscontrol understands
func nativeToRecord(domain string, n fgDNSRecord) (*models.RecordConfig, error) {
rc := &models.RecordConfig{}
rc.Type = strings.ToUpper(n.Type)
rc.Original = n
// Label / Name
label := strings.TrimSuffix(n.Hostname, ".")
if label == "@" {
label = ""
}
rc.SetLabel(label, domain)
// TTL
if n.TTL == 0 {
rc.TTL = 0 // inherit
} else {
rc.TTL = n.TTL
}
// Status → Metadata
if strings.ToLower(n.Status) != "enable" {
if rc.Metadata == nil {
rc.Metadata = map[string]string{}
}
rc.Metadata["fortigate_status"] = "disable"
}
// Type-specific fields
switch rc.Type {
case "A":
ip := net.ParseIP(n.IP)
if ip == nil || ip.To4() == nil {
return nil, fmt.Errorf("[FORTIGATE] Invalid IPv4 address %q in %+v", n.IP, n)
}
rc.SetTargetIP(ip)
case "AAAA":
ip := net.ParseIP(n.IPv6)
if ip == nil || ip.To16() == nil || ip.To4() != nil {
return nil, fmt.Errorf("[FORTIGATE] Invalid IPv6 address %q in %+v", n.IPv6, n)
}
rc.SetTargetIP(ip)
case "CNAME":
if n.CanonicalName == "" {
return nil, fmt.Errorf("[FORTIGATE] CNAME record without canonical-name (id=%d)", n.ID)
}
if err := rc.SetTarget(n.CanonicalName); err != nil {
return nil, err
}
case "NS":
if n.Hostname == "" {
return nil, fmt.Errorf("[FORTIGATE] NS record missing hostname (id=%d)", n.ID)
}
rc.SetLabel("@", domain)
if err := rc.SetTarget(n.Hostname); err != nil {
return nil, err
}
case "MX":
if n.Hostname == "" {
return nil, fmt.Errorf("[FORTIGATE] MX record missing hostname (id=%d)", n.ID)
}
rc.SetLabel("@", domain)
rc.MxPreference = n.Preference
if err := rc.SetTarget(n.Hostname); err != nil {
return nil, err
}
default:
// Not supported due to FortiGate limitations
return nil, fmt.Errorf("[FORTIGATE] Record type %q is not supported by fortigate provider", rc.Type)
}
return rc, nil
}
func recordsToNative(recs models.Records) ([]*fgDNSRecord, []error) {
var resourceRecords []*fgDNSRecord
var errors []error
id := 1
for _, record := range recs {
n := &fgDNSRecord{
Status: "enable",
Type: strings.ToUpper(record.Type),
}
// TTL
if ttl := record.TTL; ttl > 0 {
n.TTL = ttl
}
// Wildcard support
if strings.Contains(record.GetLabelFQDN(), "*") {
errors = append(errors, fmt.Errorf("[FORTIGATE] Wildcard records are not supported: %s", record.GetLabelFQDN()))
continue
}
// Status from Metadata
if v, ok := record.Metadata["fortigate_status"]; ok && strings.ToLower(v) == "disable" {
n.Status = "disable"
}
// Hostname (Label)
label := record.GetLabel()
if label == "" {
label = "@"
}
n.Hostname = label
// Type-specific fields
switch n.Type {
case "A":
ip := record.GetTargetIP()
if ip == nil || ip.To4() == nil {
errors = append(errors, fmt.Errorf("[FORTIGATE] A record is missing a valid IPv4 address: %s", record.GetLabelFQDN()))
continue
}
n.IP = ip.String()
case "AAAA":
ip := record.GetTargetIP()
if ip == nil || ip.To16() == nil || ip.To4() != nil {
errors = append(errors, fmt.Errorf("[FORTIGATE] AAAA record is missing a valid IPv6 address: %s", record.GetLabelFQDN()))
continue
}
n.IPv6 = ip.String()
case "CNAME":
target := record.GetTargetField()
if ascii, err := idna.ToASCII(target); err == nil {
target = ascii
}
n.CanonicalName = target
case "NS":
target := record.GetTargetField()
if ascii, err := idna.ToASCII(target); err == nil {
target = ascii
}
n.Hostname = target
n.CanonicalName = ""
case "MX":
target := record.GetTargetField()
if ascii, err := idna.ToASCII(target); err == nil {
target = ascii
}
n.Hostname = target
n.Preference = record.MxPreference
n.CanonicalName = ""
default:
errors = append(errors, fmt.Errorf("[FORTIGATE] Record type %q is not supported: %s", n.Type, record.GetLabelFQDN()))
continue
}
n.ID = id
id++
resourceRecords = append(resourceRecords, n)
}
return resourceRecords, errors
}