netbox2dns

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Dec 25, 2023 License: Apache-2.0 Imports: 20 Imported by: 0

README

netbox2dns

netbox2dns is a tool for publishing DNS records from Netbox data.

Netbox provides a reasonable interface for managing and documenting IP addresses and network devices, but out of the box there's no good way to publish Netbox's data into DNS. This tool is designed to publish A, AAAA, and PTR records from Netbox into Google Cloud DNS. It should be possible to add other DNS providers without too much work, as long as they're able to handle incremental record additions and removals.

Compiling

Check out a copy of the netbox2dns code from GitHub using git clone https://github.com/scottlaird/netbox2dns.git. Then, run go build cmd/netbox2dns/netbox2dns.go, and it should generate a netbox2dns binary. This can be copied to other directories or other systems as needed.

Configuration

Edit netbox2dns.yaml. Here is an example config:

config:
  netbox: 
    host:  "netbox.example.com"
    token: "01234567890abcdef"

  defaults:
    project: "google-cloud-dns-project-name-123456"
    ttl: 300
  
  zones: 
    - name: "internal.example.com"
      zonetype: "clouddns"
      zonename: "internal-example-com"
    - name: "example.com"
      zonetype: "zonefile"
      filename: "/etc/dns/example.com.zone"
    - name: "10.in-addr.arpa"
      zonetype: "clouddns"
      zonename: "reverse-v4-10"
      delete_entries: true
    - name: "0.0.0.0.ip6.arpa"
      zonetype: "clouddns"
      zonename: "reverse-v6-0000"
      delete_entries: true

Each zone needs to specify a name and a zonetype. Currently supported zonetypes are clouddns for Google Cloud DNS and zonefile for text zone files. See config.cue for an authoratative list of parameters per zone.

To talk to Netbox, you'll need to provide your Netbox host, a Netbox API token with (at a minimum) read access to Netbox's IP Address data.

To talk to Google Cloud DNS, you'll need to specify a project ID. This should match the Google Cloud project name that hosts your DNS records on console.cloud.google.com. For now, netbox2dns uses Application Default Credentials. See Google's documentation for how to set these up using the gcloud CLI.

Finally, list your zones. When adding new records, netbox2dns will add records to the longest matching zone name. For the example above, with internal.example.com and example.com, if Netbox has a record for router1.internal.example.com, then it will be added to internal.example.com. Any records that don't fix into a listed zone will be ignored.

By default, netbox2dns will search in /etc/netbox2dns/, /usr/local/etc/netbox2dns/, and the correct directory for its config file. Config files can be in YAML (shown above), JSON, or CUE format. Examples in all 3 formats are available.

Use

Short version: create a configuration file (see previous section), then run netbox2dns diff, followed by netbox2dns push if the diff looks acceptable.

Upon startup, netbox2dns will fetch all IP Address records from Netbox and all A/AAAA/PTR records from the listed zones. netbox2dns ignores other record types, including SOA, NS, and CNAME.

For each active IP address in Netbox that has a DNS name, netbox2dns will try to add both forward and reverse DNS records. Both IPv4 and IPv6 should be handled automatically.

This tool has 2 operating modes, diff and push. diff shows significant differences between DNS zones and Netbox, and push makes changes to DNS.

By default, netbox2dns will only add records from Netbox, and will not remove DNS records for IP addresses that are not in Netbox. In cases where Netbox is authoritative for zone information, you can add the delete_entries: true setting for each zone in the config file. This will make netbox2dns remove unknown A, AAAA, or PTR records from Google Cloud DNS. This makes the most sense for reverse DNS, when Netbox is the source of truth for all IP address assignement.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CompareRecordSets

func CompareRecordSets(older []*Record, newer []*Record, zd *ZoneDelta)

CompareRecordSets compares sets of records and updates a ZoneDelta with results.

func FindConfig

func FindConfig(basename string) (string, error)

FindConfig looks in several locations for a config file named "$basename.yml", "$basename.yaml", "$basename.json", or "$basename.cue".

func GetNetboxIPAddresses

func GetNetboxIPAddresses(host, token string) (netbox.IPAddrs, error)

GetNetboxIPAddresses fetches a list of IP Addresses from a Netbox server.

func IncrementSerial

func IncrementSerial(cz *ConfigZone, serial uint32) (uint32, error)

IncrementSerial increments the serial number on a DNS zone. This recognizes 2 basic serial number patterns:

1. Simple incrememnting integers (1 -> 2 -> 3, etc) 2. Date-based serial numbers (2022123004 -> 2022123005 -> 2022123100)

It assumes that any serial number greather than 2000010100 (the first date-based serial number in 2000 AD) is date-based and updates it accordingly. If the date portion of the serial matches todays date, then it increments the serial number by one. If the date-based portion does *not* match today's date, then the new serial number is today, with two trailing 0s.

Finally, there is a check that the new serial is greater than the old serial. This will break after 100 updates happen on a single day (2022123099 will get incremented to 2022123100, which is fine, but the following update will try to use 2022123000, which will fail). This is a fundimental problem with date-based serial formats, and will clear up on its own once the calendar rolls over to the next day.

func ReverseName

func ReverseName(addr netip.Addr) string

ReverseName takes an IP address and returns the correct reverse DNS name for that IP. It maps IPv4 addresses into `in-addr.arpa` and IPv6 addresses into `ip6.arpa`.

Types

type ByLength

type ByLength []*Zone

ByLength is a wrapper for []string for sorting the string slice by length, from longest to shortest.

func (ByLength) Len

func (a ByLength) Len() int

func (ByLength) Less

func (a ByLength) Less(i, j int) bool

func (ByLength) Swap

func (a ByLength) Swap(i, j int)

type CloudDNS

type CloudDNS struct {
	// contains filtered or unexported fields
}

CloudDNS implements talking to Google Cloud DNS, and provides methods for fetching existing DNS entries, adding new entries, or deleting old entries.

func NewCloudDNS

func NewCloudDNS(ctx context.Context, cz *ConfigZone) (*CloudDNS, error)

NewCloudDNS creates a new CloudDNS.

func (*CloudDNS) ImportZone

func (cd *CloudDNS) ImportZone(cfg *ConfigZone) (*Zone, error)

ImportZone imports all entries from the specified Google Cloud DNS zone.

func (*CloudDNS) RemoveRecord

func (cd *CloudDNS) RemoveRecord(cz *ConfigZone, r *Record) error

RemoveRecord removes a DNS entry from Google Cloud DNS.

TODO: implement

func (*CloudDNS) Save

func (cd *CloudDNS) Save(cz *ConfigZone) error

Save flushes changes to Cloud DNS. This is a no-op at the moment, but we'll eventually batch queries together for performance.

func (*CloudDNS) WriteRecord

func (cd *CloudDNS) WriteRecord(cz *ConfigZone, r *Record) error

WriteRecord adds a record to Google Cloud DNS.

type Config

type Config struct {
	Netbox struct {
		Host  string `json:"host,omitempty"`
		Token string `json:"token,omitempty"`
	} `json:"netbox,omitempty"`
	Defaults struct {
		Zonetype string `json:"zonetype,omitempty"`
		TTL      int64  `json:"ttl,omitempty"`
		Project  string `json:"project,omitempty"`
	} `json:"defaults,omitempty"`
	ZoneMap map[string]*ConfigZone `json:"zonemap,omitempty"`
	Zones   []*ConfigZone          `json:"zones,omitempty"`
}

Config matches the `config` item in the schema defined in `config.cue`. Each item must be marked as `notempty` and must have a JSON tag that matches the name in the CUE file.

func ParseConfig

func ParseConfig(filename string) (*Config, error)

ParseConfig parses a config file and returns a Config object or an error. When used with FindConfig, it can hunt down a config file in several formats and then parse and validate it automatically.

type ConfigRoot

type ConfigRoot struct {
	Config Config `json:"config,omitempty"`
}

ConfigRoot matches the root of the schema defined in `config.cue`.

type ConfigZone

type ConfigZone struct {
	ZoneType      string `json:"zonetype,omitempty"`
	Name          string `json:"name,omitempty"`
	ZoneName      string `json:"zonename,omitempty"`
	Filename      string `json:"filename,omitempty"`
	Project       string `json:"project,omitempty"`
	TTL           int64  `json:"ttl,omitempty"`
	DeleteEntries bool   `json:"delete_entries,omitempty"`
}

ConfigZone matches `Zone` in `config.cue`. This needs to be the union of all defined zone types. At the moment, this is only CloudDNSZone, but other types are possible. They're switched based on the `ZoneType` field. Then, code in `dns.go` uses that to dispatch to the correct back-end handler.

type DNSProvider

type DNSProvider interface {
	ImportZone(cz *ConfigZone) (*Zone, error)
	WriteRecord(cz *ConfigZone, r *Record) error
	RemoveRecord(cz *ConfigZone, r *Record) error
	Save(cz *ConfigZone) error
}

DNSProvider is an interface to a DNS provider backend, such a CloudDNS or ZoneFile.

func NewDNSProvider

func NewDNSProvider(ctx context.Context, cz *ConfigZone) (DNSProvider, error)

NewDNSProvider creates a provider of the correct type for the described zone.

type Record

type Record struct {
	Name    string
	Type    string
	TTL     int64
	Rrdatas []string
}

Record describes a DNS record, like 'foo.example.com IN AAAA 1:2::3:4'.

func (*Record) NameNoDot

func (r *Record) NameNoDot() string

NameNoDot returns the name of a record with no trailing dot.

func (*Record) RrdataNoDot

func (r *Record) RrdataNoDot() string

RrdataNoDot returns the Rrdata for a record, with no trailing dor.

type Zone

type Zone struct {
	Name          string
	ZoneName      string
	Project       string
	Filename      string
	DeleteEntries bool
	TTL           int64
	Records       map[string][]*Record
}

Zone represents a single DNS zone on a single provider (Google Cloud DNS, fixed zone files, etc).

func (*Zone) AddRecord

func (z *Zone) AddRecord(r *Record)

AddRecord adds a single record to this zone. It does not check that this is the correct zone for the record.

func (*Zone) Compare

func (z *Zone) Compare(newer *Zone, zd *ZoneDelta)

Compare compares two Zone structures and updates a ZoneDelta with changes.

func (*Zone) NewZoneDelta

func (z *Zone) NewZoneDelta() *ZoneDelta

NewZoneDelta creates a new ZoneDelta. This is used to track changes between versions of a DNS zone.

type ZoneDelta

type ZoneDelta struct {
	Name          string
	ZoneName      string
	Project       string
	Filename      string
	AddRecords    map[string][]*Record
	RemoveRecords map[string][]*Record
}

ZoneDelta describes the difference between two versions of the same zone. It shows added and removed records.

type ZoneFileDNS

type ZoneFileDNS struct {
	// contains filtered or unexported fields
}

ZoneFileDNS provides an implementation of DNS using traditional BIND-style zone files.

func NewZoneFileDNS

func NewZoneFileDNS(ctx context.Context, cz *ConfigZone) (*ZoneFileDNS, error)

NewZoneFileDNS creates a new ZoneFileDNS object.

func (*ZoneFileDNS) ImportZone

func (zfd *ZoneFileDNS) ImportZone(cz *ConfigZone) (*Zone, error)

ImportZone reads DNS entries from a zone file on disk (as specified as part of the zone config in the netbox2dns config file) and populates the ZoneFileDNS with them.

func (*ZoneFileDNS) RemoveRecord

func (zfd *ZoneFileDNS) RemoveRecord(cz *ConfigZone, r *Record) error

RemoveRecord removes a Record from the zonefile behind the ZoneFileDNS. Note that this won't actually be written until 'Save()' is called.

func (*ZoneFileDNS) Save

func (zfd *ZoneFileDNS) Save(cz *ConfigZone) error

Save flushes the current zonefile to disk. Without this, no changes will be written out.

func (*ZoneFileDNS) WriteRecord

func (zfd *ZoneFileDNS) WriteRecord(cz *ConfigZone, r *Record) error

WriteRecord writes a Record to the zonefile behind the ZoneFileDNS. Note that this won't actually be written until 'Save()' is called.

type Zones

type Zones struct {
	Zones map[string]*Zone
	// contains filtered or unexported fields
}

Zones represents the set of all DNS zones known to netbox2dns.

func ImportZones

func ImportZones(ctx context.Context, cfg *Config) (*Zones, error)

ImportZones creates new DNS providers for each zone and imports all existing records for each zone.

func NewZones

func NewZones() *Zones

NewZones creates a new Zones structure and initializes it.

func (*Zones) AddAddrs

func (z *Zones) AddAddrs(addrs netbox.IPAddrs) error

AddAddrs adds multiple addresses to a set of Zones. This creates both forward and reverse DNS entries.

func (*Zones) AddRecord

func (z *Zones) AddRecord(r *Record) error

AddRecord adds a record to the appropriate zone. It finds the longest suffix match among all known zones and adds the new record there. If no zones match, then an error is returned.

func (*Zones) AddZone

func (z *Zones) AddZone(zone *Zone)

AddZone adds a new Zone to Zones.

func (*Zones) Compare

func (z *Zones) Compare(newer *Zones) []*ZoneDelta

Compare compares two Zones structures and returns a slice of ZoneDeltas showing what has changed.

func (*Zones) NewZone

func (z *Zones) NewZone(cz *ConfigZone)

NewZone creates a new Zone in Zones using the settings in the provided ConfigZone. The resulting Zone is added to Zones automatically.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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