Jarl keeps a BIND 9.20 server's whole configuration (views, ACLs, TSIG keys, zones of every type, records, named.conf options) in SQLite and renders named.conf, keys.conf and zone files from it. Every change goes through one changeset pipeline: whole-configuration validation, named-checkconf -z and named-checkzone in a staging directory, atomic install, rndc reconfig/reload, serial verification with rndc zonestatus, and rollback if named does not load the change. Changes apply one at a time by default or are bundled and applied together. Applied changesets can be reverted. - jarld: the daemon with the REST API and an embedded web UI, plus import, adopt, check, resync, backup and local user recovery commands - jarl: the CLI, including ACME DNS-01 hooks for certbot and acme.sh - users with argon2id passwords and CSRF-protected sessions; hashed API tokens with granular grants and roles; an audit log - systemd unit, sysusers.d/tmpfiles.d files, Makefile - unit tests, integration tests against a real named, scripts/e2e.sh - docs/ guides, README and CHANGELOG Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
6.0 KiB
Go
86 lines
6.0 KiB
Go
package rr
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// TypeInfo describes a record type for the web UI and CLI.
|
|
type TypeInfo struct {
|
|
Type string `json:"type"`
|
|
Code uint16 `json:"code"`
|
|
Common bool `json:"common"`
|
|
Description string `json:"description"`
|
|
Format string `json:"format,omitempty"`
|
|
Example string `json:"example,omitempty"`
|
|
}
|
|
|
|
var described = []TypeInfo{
|
|
{Type: "A", Common: true, Description: "IPv4 address", Format: "<IPv4 address>", Example: "192.0.2.10"},
|
|
{Type: "AAAA", Common: true, Description: "IPv6 address", Format: "<IPv6 address>", Example: "2001:db8::10"},
|
|
{Type: "CNAME", Common: true, Description: "Alias for another name (no other records may exist at the same name)", Format: "<target name>", Example: "www.example.com."},
|
|
{Type: "MX", Common: true, Description: "Mail exchanger", Format: "<preference> <mail server host name>", Example: "10 mail.example.com."},
|
|
{Type: "TXT", Common: true, Description: "Text. Plain text is quoted and split into 255-byte strings automatically; start with a quote to give presentation format.", Format: "<text> or \"string\" \"string\"", Example: "v=spf1 mx -all"},
|
|
{Type: "NS", Common: true, Description: "Name server for the zone (at the apex) or a delegation (below it)", Format: "<name server host name>", Example: "ns1.example.com."},
|
|
{Type: "PTR", Common: true, Description: "Reverse mapping (in in-addr.arpa / ip6.arpa zones)", Format: "<host name>", Example: "host.example.com."},
|
|
{Type: "SRV", Common: true, Description: "Service location (owner _service._proto.name)", Format: "<priority> <weight> <port> <target>", Example: "0 5 5060 sip.example.com."},
|
|
{Type: "CAA", Common: true, Description: "Certification authority authorization", Format: "<flags> <tag> <value>", Example: "0 issue \"letsencrypt.org\""},
|
|
{Type: "HTTPS", Common: true, Description: "HTTPS service binding (RFC 9460)", Format: "<priority> <target> [params...]", Example: "1 . alpn=\"h2,h3\""},
|
|
{Type: "SVCB", Description: "General service binding (RFC 9460)", Format: "<priority> <target> [params...]", Example: "1 svc.example.com. alpn=\"h2\" port=8443"},
|
|
{Type: "SSHFP", Description: "SSH host key fingerprint", Format: "<algorithm> <fp type> <fingerprint hex>", Example: "4 2 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},
|
|
{Type: "TLSA", Description: "TLS certificate association, DANE (owner _port._proto.name)", Format: "<usage> <selector> <matching type> <data hex>", Example: "3 1 1 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},
|
|
{Type: "DS", Description: "Delegation signer for a signed child zone (at a delegation point)", Format: "<key tag> <algorithm> <digest type> <digest hex>", Example: "12345 13 2 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},
|
|
{Type: "DNAME", Description: "Redirects a whole subtree to another name", Format: "<target name>", Example: "other.example.net."},
|
|
{Type: "NAPTR", Description: "Naming authority pointer", Format: "<order> <preference> \"<flags>\" \"<service>\" \"<regexp>\" <replacement>", Example: "100 10 \"u\" \"E2U+sip\" \"!^.*$!sip:info@example.com!\" ."},
|
|
{Type: "LOC", Description: "Geographical location", Format: "<lat> <lon> <alt>m [size [hp [vp]]]", Example: "52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000m 10m"},
|
|
{Type: "HINFO", Description: "Host information", Format: "\"<cpu>\" \"<os>\"", Example: "\"PC\" \"Linux\""},
|
|
{Type: "RP", Description: "Responsible person", Format: "<mailbox name> <TXT record name>", Example: "admin.example.com. ."},
|
|
{Type: "URI", Description: "Uniform resource identifier", Format: "<priority> <weight> \"<uri>\"", Example: "10 1 \"https://example.com/\""},
|
|
{Type: "OPENPGPKEY", Description: "OpenPGP public key (DANE)", Format: "<base64 key>"},
|
|
{Type: "SMIMEA", Description: "S/MIME certificate association", Format: "<usage> <selector> <matching type> <data hex>"},
|
|
{Type: "CERT", Description: "Certificate", Format: "<type> <key tag> <algorithm> <base64 certificate>"},
|
|
{Type: "DHCID", Description: "DHCP identifier", Format: "<base64 data>"},
|
|
{Type: "AFSDB", Description: "AFS database location", Format: "<subtype> <host name>", Example: "1 afs.example.com."},
|
|
{Type: "APL", Description: "Address prefix list", Format: "[!]<family>:<prefix>/<len> ...", Example: "1:192.168.32.0/21"},
|
|
{Type: "IPSECKEY", Description: "IPsec key", Format: "<precedence> <gateway type> <algorithm> <gateway> <base64 key>"},
|
|
{Type: "KX", Description: "Key exchanger", Format: "<preference> <host name>", Example: "10 kx.example.com."},
|
|
{Type: "KEY", Description: "Public key (SIG(0) and other legacy uses)", Format: "<flags> <protocol> <algorithm> <base64 key>"},
|
|
{Type: "SPF", Description: "Deprecated SPF record type; publish SPF policies as TXT instead", Format: "\"v=spf1 ...\""},
|
|
{Type: "CSYNC", Description: "Child-to-parent synchronization", Format: "<soa serial> <flags> <type list>", Example: "66 3 A NS AAAA"},
|
|
{Type: "EUI48", Description: "48-bit MAC address", Format: "xx-xx-xx-xx-xx-xx", Example: "00-00-5e-00-53-2a"},
|
|
{Type: "EUI64", Description: "64-bit EUI address", Format: "xx-xx-xx-xx-xx-xx-xx-xx", Example: "00-00-5e-ef-10-00-00-2a"},
|
|
{Type: "RESINFO", Description: "Resolver information (RFC 9606)", Format: "key=value ...", Example: "qnamemin exterr=15,16,17"},
|
|
}
|
|
|
|
// Types returns information about every record type that can be managed,
|
|
// common types first.
|
|
func Types() []TypeInfo {
|
|
seen := map[string]bool{}
|
|
var out []TypeInfo
|
|
for _, t := range described {
|
|
code, ok := dns.StringToType[t.Type]
|
|
if !ok {
|
|
continue
|
|
}
|
|
t.Code = code
|
|
out = append(out, t)
|
|
seen[t.Type] = true
|
|
}
|
|
var rest []TypeInfo
|
|
for code, name := range dns.TypeToString {
|
|
if seen[name] {
|
|
continue
|
|
}
|
|
if _, ok := dns.TypeToRR[code]; !ok {
|
|
continue
|
|
}
|
|
if _, bad := forbidden[code]; bad {
|
|
continue
|
|
}
|
|
rest = append(rest, TypeInfo{Type: name, Code: code, Description: name + " record (zone file format)"})
|
|
}
|
|
sort.Slice(rest, func(i, j int) bool { return rest[i].Type < rest[j].Type })
|
|
return append(out, rest...)
|
|
}
|