dnszones

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package dnszones manages whole DNS zones at Cloudflare and Route53: zones, their assigned name servers, record sets with routing policies, delegation checks, propagation checks, zone file import and export, and templates.

Index

Constants

View Source
const (
	DelegationNone      = "not_delegated"
	DelegationPartial   = "partially_delegated"
	DelegationDelegated = "delegated"
	DelegationElsewhere = "delegated_elsewhere"
)

Delegation states for a zone.

View Source
const (
	ProviderCloudflare = "cloudflare"
	ProviderRoute53    = "route53"
)

Provider names, matching the DNS-01 provider names used elsewhere.

View Source
const (
	RoutingSimple     = "simple"
	RoutingWeighted   = "weighted"
	RoutingFailover   = "failover"
	RoutingMultivalue = "multivalue"
)

Routing policies a record set can use. Only Route53 supports anything but simple.

View Source
const (
	FailoverPrimary   = "PRIMARY"
	FailoverSecondary = "SECONDARY"
)

Failover roles for RoutingFailover record sets.

View Source
const (
	ActionCreate    = "create"
	ActionUpdate    = "update"
	ActionDelete    = "delete"
	ActionUnchanged = "unchanged"
)

Plan actions.

View Source
const (
	SourceResolver      = "resolver"
	SourceAuthoritative = "authoritative"
)

Answer sources.

View Source
const (
	TTLAuto = 1
	TTLMin  = 30
	TTLMax  = 604800
)

TTL bounds. TTLAuto (1) is Cloudflare's "automatic".

View Source
const (
	SeverityError   = "error"
	SeverityWarning = "warning"
)

Issue severities.

View Source
const (
	IssueCNAMEExclusive = "cname_exclusive"
	IssueApexCNAME      = "apex_cname"
	IssueApexFlattened  = "apex_cname_flattened"
	IssueMixedRouting   = "mixed_routing"
	IssueExists         = "exists"
)

Issue codes, stable for the dashboard and CLI.

View Source
const (
	FormatJSON = "json"
	FormatBIND = "bind"
)

Export formats.

View Source
const Apex = "@"

Apex is the relative name of a zone's own apex.

View Source
const CloudflareBaseURL = "https://api.cloudflare.com/client/v4"

CloudflareBaseURL is Cloudflare's v4 API root.

View Source
const ServerSystem = "system"

ServerSystem names the host's own configured resolver.

Variables

View Source
var (
	// ErrZoneNotFound means no zone the credentials can see matches the reference.
	ErrZoneNotFound = errors.New("dnszones: zone not found")
	// ErrUnsupported means the provider has no equivalent for the requested feature.
	ErrUnsupported = errors.New("dnszones: not supported by this provider")
)
View Source
var CommonNames = []string{
	Apex, "www", "mail", "smtp", "imap", "pop", "webmail", "ftp", "api", "app", "blog", "shop",
	"cdn", "static", "admin", "dev", "staging", "status", "docs", "m", "autodiscover", "autoconfig",
	"_dmarc", "default._domainkey", "google._domainkey", "selector1._domainkey", "selector2._domainkey",
	"k1._domainkey", "s1._domainkey", "s2._domainkey",
}

CommonNames are probed when importing a zone from its current servers, since AXFR is almost always refused.

View Source
var CommonSRVNames = []string{"_sip._tls", "_sipfederationtls._tcp", "_autodiscover._tcp", "_submission._tcp", "_imaps._tcp", "_caldavs._tcp"}

CommonSRVNames are probed for SRV records only.

View Source
var DefaultCheckServers = []string{ServerSystem, "1.1.1.1:53", "8.8.8.8:53"}

DefaultCheckServers are asked for delegation and propagation checks.

View Source
var RecordTypes = []string{"A", "AAAA", "CNAME", "TXT", "MX", "CAA", "SRV", "NS"}

RecordTypes are the supported types, in the order the dashboard offers them.

View Source
var Templates = []Template{
	{
		ID: "email", Name: "Email (any provider)", Description: "MX, SPF and DMARC for a mail host you run or rent.",
		Params: append([]TemplateParam{
			{Key: "mx_host", Label: "Mail server host", Required: true, Placeholder: "mail.example.com"},
			{Key: "spf", Label: "SPF mechanisms", Default: "mx", Placeholder: "mx include:_spf.example.net"},
		}, dmarcParams...),
		// contains filtered or unexported fields
	},
	{
		ID: "dkim", Name: "DKIM key", Description: "Publish a DKIM public key under a selector.",
		Params: []TemplateParam{
			{Key: "selector", Label: "Selector", Required: true, Placeholder: "default"},
			{Key: "public_key", Label: "Public key (base64, without headers)", Required: true},
		},
		// contains filtered or unexported fields
	},
	{
		ID: "verification", Name: "Verification TXT", Description: "A TXT token a service asks you to publish to prove ownership.",
		Params: []TemplateParam{
			{Key: "name", Label: "Record name", Default: Apex},
			{Key: "value", Label: "Token", Required: true},
		},
		// contains filtered or unexported fields
	},
	{
		ID: "www-to-apex", Name: "www to apex", Description: "Point www at the zone apex with a CNAME.",
		// contains filtered or unexported fields
	},
	{
		ID: "google-workspace", Name: "Google Workspace", Description: "Google's MX and SPF, plus DMARC.",
		Params: dmarcParams,
		// contains filtered or unexported fields
	},
	{
		ID: "microsoft-365", Name: "Microsoft 365", Description: "Exchange Online MX, SPF and autodiscover, plus DMARC.",
		Params: append([]TemplateParam{
			{Key: "mx_prefix", Label: "MX prefix (your domain with dots as dashes)", Required: true, Placeholder: "example-com"},
		}, dmarcParams...),
		// contains filtered or unexported fields
	},
	{
		ID: "caa-letsencrypt", Name: "CAA for Let's Encrypt", Description: "Allow only Let's Encrypt to issue certificates, wildcards included.",
		// contains filtered or unexported fields
	},
}

Templates lists every built in template.

Functions

func ApplyPlan

func ApplyPlan(ctx context.Context, p Provider, zone Zone, plan Plan) (int, error)

ApplyPlan executes a plan: deletes first (so a CNAME can replace an A), then updates and creates. It refuses a blocked plan.

func CheckCapabilities

func CheckCapabilities(rs RecordSet, caps Capabilities) error

CheckCapabilities rejects features the provider does not support.

func ExportBIND

func ExportBIND(zone string, sets []RecordSet) string

ExportBIND renders sets as a zone file. Routed and alias sets have no zone file form and are listed as comments so nothing is silently dropped.

func ExportJSON

func ExportJSON(zone string, sets []RecordSet) ([]byte, error)

ExportJSON renders sets as an indented Document.

func FQDN

func FQDN(rel, zone string) string

FQDN returns the absolute name (no trailing dot) of a relative name.

func FormatRR

func FormatRR(rr dns.RR) (string, bool)

FormatRR renders an RR's data in this package's canonical value form.

func HasErrors

func HasErrors(issues []Issue) bool

HasErrors reports whether issues includes an error.

func IsManagedApexType

func IsManagedApexType(rs RecordSet) bool

IsManagedApexType reports whether a set is provider managed (apex NS and SOA).

func NormalizeDomain

func NormalizeDomain(d string) string

NormalizeDomain lowercases d and strips surrounding space and a trailing dot.

func QType

func QType(t string) (uint16, error)

QType maps a record type name to its wire value.

func RelativeName

func RelativeName(name, zone string) string

RelativeName turns an FQDN or relative name into a name relative to zone, with Apex for the zone itself.

func SameContent

func SameContent(a, b RecordSet) bool

SameContent reports whether a and b would serve identical answers.

func SortSets

func SortSets(sets []RecordSet)

SortSets orders sets by name (apex first), type, then set identifier.

func ValidateHealthCheck

func ValidateHealthCheck(hc HealthCheck) error

ValidateHealthCheck checks the fields CreateHealthCheck needs.

Types

type Alias

type Alias struct {
	DNSName              string `json:"dns_name"`
	HostedZoneID         string `json:"hosted_zone_id"`
	EvaluateTargetHealth bool   `json:"evaluate_target_health"`
}

Alias is a Route53 alias target. Alias sets are shown but edited only through the provider.

type Answer

type Answer struct {
	Server        string   `json:"server"`
	Values        []string `json:"values,omitempty"`
	TTL           uint32   `json:"ttl"`
	Rcode         string   `json:"rcode,omitempty"`
	Authoritative bool     `json:"authoritative,omitempty"`
	CNAME         string   `json:"cname,omitempty"`
	Error         string   `json:"error,omitempty"`
}

Answer is what one server returned for one question.

type Capabilities

type Capabilities struct {
	Proxied      bool `json:"proxied"`
	Routing      bool `json:"routing"`
	HealthChecks bool `json:"health_checks"`
	ApexCNAME    bool `json:"apex_cname"`
}

Capabilities says which optional features a provider supports, so callers can validate before sending anything upstream.

type Change

type Change struct {
	Action string     `json:"action"`
	Set    RecordSet  `json:"set"`
	Before *RecordSet `json:"before,omitempty"`
	Issues []Issue    `json:"issues,omitempty"`
}

Change is one planned record set change.

type Cloudflare

type Cloudflare struct {
	BaseURL   string
	HTTP      *http.Client
	AccountID string
	// contains filtered or unexported fields
}

Cloudflare implements Provider over Cloudflare's REST API with a scoped API token.

func NewCloudflare

func NewCloudflare(token string) *Cloudflare

NewCloudflare returns a client for token. The token is only ever sent as a bearer header.

func (*Cloudflare) Capabilities

func (c *Cloudflare) Capabilities() Capabilities

Capabilities implements Provider.

func (*Cloudflare) CreateZone

func (c *Cloudflare) CreateZone(ctx context.Context, name string) (Zone, error)

CreateZone implements Provider (POST /zones, type full).

func (*Cloudflare) DeleteRecordSet

func (c *Cloudflare) DeleteRecordSet(ctx context.Context, zone Zone, k Key) error

DeleteRecordSet implements Provider: deletes every record of name and type.

func (*Cloudflare) DeleteZone

func (c *Cloudflare) DeleteZone(ctx context.Context, id string) error

DeleteZone implements Provider (DELETE /zones/{id}).

func (*Cloudflare) GetZone

func (c *Cloudflare) GetZone(ctx context.Context, id string) (Zone, error)

GetZone implements Provider (GET /zones/{id}).

func (*Cloudflare) ListRecordSets

func (c *Cloudflare) ListRecordSets(ctx context.Context, zone Zone) ([]RecordSet, error)

ListRecordSets implements Provider (GET /zones/{id}/dns_records), grouping Cloudflare's individual records by name and type.

func (*Cloudflare) ListZones

func (c *Cloudflare) ListZones(ctx context.Context) ([]Zone, error)

ListZones implements Provider (GET /zones).

func (*Cloudflare) Name

func (c *Cloudflare) Name() string

Name implements Provider.

func (*Cloudflare) UpsertRecordSet

func (c *Cloudflare) UpsertRecordSet(ctx context.Context, zone Zone, rs RecordSet) error

UpsertRecordSet implements Provider: keeps records whose value is wanted (patching TTL or proxied when they differ), creates missing ones, then deletes the rest, so a live value is never briefly absent.

type CloudflareError

type CloudflareError struct {
	Status   int
	Messages []string
}

CloudflareError is a non-success API response.

func (*CloudflareError) Error

func (e *CloudflareError) Error() string

type DNSQuerier

type DNSQuerier struct {
	Timeout    time.Duration
	ResolvConf string
}

DNSQuerier is the real Querier, over UDP with a TCP retry on truncation.

func NewDNSQuerier

func NewDNSQuerier(timeout time.Duration) *DNSQuerier

NewDNSQuerier returns a querier with a per query timeout.

func (*DNSQuerier) Query

func (q *DNSQuerier) Query(ctx context.Context, server, name string, qtype uint16) Answer

Query implements Querier.

type Delegation

type Delegation struct {
	Domain    string               `json:"domain"`
	State     string               `json:"state"`
	Expected  []string             `json:"expected"`
	Resolvers []ResolverDelegation `json:"resolvers"`
	Elsewhere []string             `json:"elsewhere,omitempty"`
}

Delegation compares a zone's assigned name servers with what resolvers see.

func CheckDelegation

func CheckDelegation(ctx context.Context, q Querier, domain string, expected, servers []string) Delegation

CheckDelegation asks each server for the domain's NS set, in parallel.

func ComputeDelegation

func ComputeDelegation(expected []string, answers []Answer) Delegation

ComputeDelegation derives the overall state from per resolver answers. A resolver whose NS set is a subset of the assigned one counts as a match: a registrar given two of Route53's four servers still delegates correctly.

type Document

type Document struct {
	Zone    string      `json:"zone"`
	Records []RecordSet `json:"records"`
}

Document is the JSON import and export shape.

type HealthCheck

type HealthCheck struct {
	ID               string `json:"id"`
	Type             string `json:"type"`
	IPAddress        string `json:"ip_address,omitempty"`
	FQDN             string `json:"fqdn,omitempty"`
	Port             int32  `json:"port,omitempty"`
	ResourcePath     string `json:"resource_path,omitempty"`
	RequestInterval  int32  `json:"request_interval,omitempty"`
	FailureThreshold int32  `json:"failure_threshold,omitempty"`
}

HealthCheck is a Route53 health check usable by failover and multivalue sets.

type HealthChecker

type HealthChecker interface {
	ListHealthChecks(ctx context.Context) ([]HealthCheck, error)
	CreateHealthCheck(ctx context.Context, hc HealthCheck) (HealthCheck, error)
	DeleteHealthCheck(ctx context.Context, id string) error
}

HealthChecker is implemented by providers with health checks (Route53).

type Issue

type Issue struct {
	Severity string `json:"severity"`
	Code     string `json:"code"`
	Message  string `json:"message"`
}

Issue is one validation or conflict finding.

func Conflicts

func Conflicts(existing []RecordSet, rs RecordSet, caps Capabilities, ignore *Key) []Issue

Conflicts reports conflicts between rs and the other sets in the zone. ignore is the key being replaced by an update, if any.

type Key

type Key struct {
	Name          string `json:"name"`
	Type          string `json:"type"`
	SetIdentifier string `json:"set_identifier,omitempty"`
}

Key identifies a record set within a zone.

type Plan

type Plan struct {
	Changes []Change       `json:"changes"`
	Summary map[string]int `json:"summary"`
	Blocked bool           `json:"blocked"`
}

Plan is a diff between a zone and a desired set of records, shown before apply.

func BuildPlan

func BuildPlan(existing, desired []RecordSet, replace bool, caps Capabilities) Plan

BuildPlan diffs desired against existing. With replace, sets in existing but not in desired are deleted, except provider managed apex NS and SOA. Without replace nothing is ever deleted, so a foreign record is never lost.

type Propagation

type Propagation struct {
	Name     string              `json:"name"`
	Type     string              `json:"type"`
	Expected []string            `json:"expected,omitempty"`
	Answers  []PropagationAnswer `json:"answers"`
	Agree    bool                `json:"agree"`
}

Propagation is a record's answer across resolvers and the zone's own servers.

func CheckPropagation

func CheckPropagation(ctx context.Context, q Querier, name, typ string, resolvers, authoritative, expected []string) (Propagation, error)

CheckPropagation asks resolvers and, when known, the zone's authoritative servers directly. expected, when set, is what the zone says the answer is.

type PropagationAnswer

type PropagationAnswer struct {
	Answer
	Source  string `json:"source"`
	Matches *bool  `json:"matches,omitempty"`
}

PropagationAnswer is one server's answer plus how it compares.

type Provider

type Provider interface {
	Name() string
	Capabilities() Capabilities
	ListZones(ctx context.Context) ([]Zone, error)
	GetZone(ctx context.Context, id string) (Zone, error)
	CreateZone(ctx context.Context, name string) (Zone, error)
	DeleteZone(ctx context.Context, id string) error
	ListRecordSets(ctx context.Context, zone Zone) ([]RecordSet, error)
	UpsertRecordSet(ctx context.Context, zone Zone, rs RecordSet) error
	DeleteRecordSet(ctx context.Context, zone Zone, key Key) error
}

Provider is the zone-level surface both Cloudflare and Route53 implement.

type Querier

type Querier interface {
	Query(ctx context.Context, server, name string, qtype uint16) Answer
}

Querier asks one server one question. server is "host:port" or ServerSystem.

type RecordSet

type RecordSet struct {
	Name          string   `json:"name"`
	Type          string   `json:"type"`
	TTL           int      `json:"ttl"`
	Values        []string `json:"values"`
	Proxied       bool     `json:"proxied,omitempty"`
	Routing       string   `json:"routing,omitempty"`
	SetIdentifier string   `json:"set_identifier,omitempty"`
	Weight        *int64   `json:"weight,omitempty"`
	Failover      string   `json:"failover,omitempty"`
	HealthCheckID string   `json:"health_check_id,omitempty"`
	Alias         *Alias   `json:"alias,omitempty"`
}

RecordSet is every value of one name, type and set identifier. Values use zone file presentation syntax, except TXT which holds the raw text.

func Discover

func Discover(ctx context.Context, q Querier, zone string, extra, servers []string) []RecordSet

Discover resolves common names (plus extra) against servers and returns the record sets it found, for an operator to review before importing.

func MergeTemplate

func MergeTemplate(existing, sets []RecordSet) []RecordSet

MergeTemplate layers template sets over existing ones. TXT sets keep the other values already published at that name (verification tokens), except a second SPF record, which would make SPF fail.

func Normalize

func Normalize(rs RecordSet, zone string, defaultTTL int) (RecordSet, error)

Normalize validates rs and returns its canonical form: relative lower case name, upper case type, deduplicated canonical values and a defaulted TTL.

func ParseBIND

func ParseBIND(text, zone string, defaultTTL int) ([]RecordSet, []string, error)

ParseBIND parses zone file text into record sets relative to zone. SOA, apex NS and unsupported types are skipped with a warning.

func ParseJSON

func ParseJSON(data []byte) ([]RecordSet, error)

ParseJSON reads a Document (or a bare array of sets).

func (RecordSet) Key

func (rs RecordSet) Key() Key

Key returns rs's identity.

type ResolverDelegation

type ResolverDelegation struct {
	Server      string   `json:"server"`
	NameServers []string `json:"name_servers,omitempty"`
	Verdict     string   `json:"verdict"`
	Error       string   `json:"error,omitempty"`
}

ResolverDelegation is what one resolver says the domain's name servers are.

type Route53

type Route53 struct {
	API Route53API
	// HostedZoneID, when set, is used if the credentials cannot list zones.
	HostedZoneID string
}

Route53 implements Provider and HealthChecker over the AWS SDK.

func NewRoute53

func NewRoute53(accessKeyID, secretAccessKey, region, hostedZoneID string) *Route53

NewRoute53 builds a client from a static access key pair. Route53 is global, so region only picks the signing endpoint.

func (*Route53) Capabilities

func (r *Route53) Capabilities() Capabilities

Capabilities implements Provider.

func (*Route53) CreateHealthCheck

func (r *Route53) CreateHealthCheck(ctx context.Context, hc HealthCheck) (HealthCheck, error)

CreateHealthCheck implements HealthChecker (HTTP, HTTPS or TCP).

func (*Route53) CreateZone

func (r *Route53) CreateZone(ctx context.Context, name string) (Zone, error)

CreateZone implements Provider (CreateHostedZone, public).

func (*Route53) DeleteHealthCheck

func (r *Route53) DeleteHealthCheck(ctx context.Context, id string) error

DeleteHealthCheck implements HealthChecker.

func (*Route53) DeleteRecordSet

func (r *Route53) DeleteRecordSet(ctx context.Context, zone Zone, k Key) error

DeleteRecordSet implements Provider. Route53's DELETE needs the set's exact current content, so it is read back first; a missing set is not an error.

func (*Route53) DeleteZone

func (r *Route53) DeleteZone(ctx context.Context, id string) error

DeleteZone implements Provider (DeleteHostedZone). Route53 itself refuses while non NS/SOA sets remain, so callers delete those first.

func (*Route53) GetZone

func (r *Route53) GetZone(ctx context.Context, id string) (Zone, error)

GetZone implements Provider (GetHostedZone).

func (*Route53) ListHealthChecks

func (r *Route53) ListHealthChecks(ctx context.Context) ([]HealthCheck, error)

ListHealthChecks implements HealthChecker.

func (*Route53) ListRecordSets

func (r *Route53) ListRecordSets(ctx context.Context, zone Zone) ([]RecordSet, error)

ListRecordSets implements Provider (ListResourceRecordSets, paginated).

func (*Route53) ListZones

func (r *Route53) ListZones(ctx context.Context) ([]Zone, error)

ListZones implements Provider (ListHostedZones, then GetHostedZone per zone for name servers).

func (*Route53) Name

func (r *Route53) Name() string

Name implements Provider.

func (*Route53) UpsertRecordSet

func (r *Route53) UpsertRecordSet(ctx context.Context, zone Zone, rs RecordSet) error

UpsertRecordSet implements Provider (ChangeResourceRecordSets UPSERT).

type Route53API

Route53API is the subset of the AWS SDK client this package calls, so tests use a fake.

type Template

type Template struct {
	ID          string          `json:"id"`
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Params      []TemplateParam `json:"params"`
	// contains filtered or unexported fields
}

Template is a named set of records for a common setup. Data only.

func FindTemplate

func FindTemplate(id string) (Template, bool)

FindTemplate returns the template with id.

func (Template) Render

func (t Template) Render(params map[string]string) ([]RecordSet, error)

Render fills defaults, checks required params and returns the template's sets.

type TemplateParam

type TemplateParam struct {
	Key         string `json:"key"`
	Label       string `json:"label"`
	Required    bool   `json:"required"`
	Default     string `json:"default,omitempty"`
	Placeholder string `json:"placeholder,omitempty"`
}

TemplateParam is one input a template needs.

type ValidationError

type ValidationError struct{ Problems []string }

ValidationError carries every problem found in one record set.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type Zone

type Zone struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Provider    string   `json:"provider"`
	Status      string   `json:"status,omitempty"`
	RecordCount *int     `json:"record_count,omitempty"`
	NameServers []string `json:"name_servers"`
	Private     bool     `json:"private,omitempty"`
	ModifiedAt  string   `json:"modified_at,omitempty"`
}

Zone is one hosted zone. RecordCount is nil when the provider cannot report it cheaply.

func FindZone

func FindZone(zones []Zone, ref string) (Zone, error)

FindZone resolves ref (a zone id or a zone name) among zones.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL